diff --git a/components/SlidesGenerator.tsx b/components/SlidesGenerator.tsx index df87d1e..a384df7 100644 --- a/components/SlidesGenerator.tsx +++ b/components/SlidesGenerator.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Textarea } from "@/components/ui/textarea"; @@ -25,7 +25,15 @@ import { Hash, Play, Pause, - RotateCcw, + Upload, + X, + FileText, + Image as ImageIcon, + File, + Sparkles, + BarChart3, + TrendingUp, + Zap, } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -51,24 +59,53 @@ const LANGUAGES = [ ]; const THEMES = [ - { id: "corporate", name: "Corporate", colors: ["#1e3a5f", "#2563eb", "#ffffff"], icon: "🏢" }, - { id: "modern", name: "Modern", colors: ["#0f172a", "#6366f1", "#f8fafc"], icon: "✨" }, - { id: "minimal", name: "Minimal", colors: ["#ffffff", "#374151", "#f3f4f6"], icon: "◻️" }, - { id: "dark", name: "Dark Mode", colors: ["#0a0a0a", "#a855f7", "#fafafa"], icon: "🌙" }, - { id: "vibrant", name: "Vibrant", colors: ["#7c3aed", "#ec4899", "#fef3c7"], icon: "🎨" }, - { id: "gradient", name: "Gradient", colors: ["#667eea", "#764ba2", "#ffffff"], icon: "🌈" }, + { id: "corporate-blue", name: "Corporate Blue", colors: ["#0f172a", "#3b82f6", "#60a5fa", "#ffffff"], icon: "🏢", gradient: "linear-gradient(135deg, #0f172a 0%, #1e3a5f 50%, #1e40af 100%)" }, + { id: "executive-dark", name: "Executive Dark", colors: ["#09090b", "#6366f1", "#a855f7", "#fafafa"], icon: "👔", gradient: "linear-gradient(135deg, #09090b 0%, #18181b 50%, #27272a 100%)" }, + { id: "modern-gradient", name: "Modern Gradient", colors: ["#0c0a09", "#f97316", "#eab308", "#fafaf9"], icon: "✨", gradient: "linear-gradient(135deg, #0c0a09 0%, #1c1917 50%, #292524 100%)" }, + { id: "tech-neon", name: "Tech Neon", colors: ["#020617", "#22d3ee", "#a3e635", "#f8fafc"], icon: "⚡", gradient: "linear-gradient(135deg, #020617 0%, #0c1929 50%, #172554 100%)" }, + { id: "minimal-light", name: "Minimal Light", colors: ["#ffffff", "#18181b", "#71717a", "#f4f4f5"], icon: "◻️", gradient: "linear-gradient(135deg, #ffffff 0%, #f4f4f5 50%, #e4e4e7 100%)" }, + { id: "premium-gold", name: "Premium Gold", colors: ["#1a1a2e", "#d4af37", "#ffd700", "#f5f5dc"], icon: "👑", gradient: "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)" }, + { id: "nature-green", name: "Nature Green", colors: ["#14532d", "#22c55e", "#86efac", "#f0fdf4"], icon: "🌿", gradient: "linear-gradient(135deg, #14532d 0%, #166534 50%, #15803d 100%)" }, + { id: "sunset-warm", name: "Sunset Warm", colors: ["#1f1f1f", "#f43f5e", "#fb923c", "#fef2f2"], icon: "🌅", gradient: "linear-gradient(135deg, #1f1f1f 0%, #2d1b1b 50%, #3d2424 100%)" }, ]; const AUDIENCES = [ - { id: "executives", name: "Executives & C-Suite", icon: "👔" }, - { id: "investors", name: "Investors & Stakeholders", icon: "💼" }, - { id: "technical", name: "Technical Team", icon: "💻" }, - { id: "marketing", name: "Marketing & Sales", icon: "📈" }, - { id: "general", name: "General Audience", icon: "👥" }, - { id: "students", name: "Students & Educators", icon: "🎓" }, - { id: "customers", name: "Customers & Clients", icon: "🤝" }, + { id: "executives", name: "Executives & C-Suite", icon: "👔", style: "Sophisticated, data-driven, strategic focus" }, + { id: "investors", name: "Investors & Board", icon: "💼", style: "ROI-focused, metrics-heavy, growth narrative" }, + { id: "technical", name: "Technical Team", icon: "💻", style: "Detailed, architecture diagrams, code snippets" }, + { id: "marketing", name: "Marketing & Sales", icon: "📈", style: "Persuasive, visual storytelling, emotional appeal" }, + { id: "general", name: "General Audience", icon: "👥", style: "Clear, engaging, accessible language" }, + { id: "stakeholders", name: "Stakeholders", icon: "🤝", style: "Project updates, milestones, risk mitigation" }, + { id: "clients", name: "Clients & Customers", icon: "⭐", style: "Benefits-focused, testimonials, case studies" }, ]; +const ANIMATION_STYLES = [ + { id: "professional", name: "Professional", description: "Subtle fade & slide transitions" }, + { id: "dynamic", name: "Dynamic", description: "Engaging animations with emphasis effects" }, + { id: "minimal", name: "Minimal", description: "Clean, simple transitions only" }, + { id: "impressive", name: "Impressive", description: "Bold animations, parallax, morphing effects" }, +]; + +interface AttachedFile { + id: string; + name: string; + type: string; + size: number; + content?: string; + preview?: string; + colors?: string[]; +} + +const ACCEPTED_FILE_TYPES = { + documents: [".pdf", ".doc", ".docx", ".txt", ".rtf", ".md"], + presentations: [".pptx", ".ppt", ".key", ".odp"], + images: [".png", ".jpg", ".jpeg", ".svg", ".webp", ".gif"], + data: [".json", ".csv", ".xlsx", ".xls"], + design: [".ase", ".aco", ".gpl", ".css"], // Color palette formats +}; + +const ALL_ACCEPTED = Object.values(ACCEPTED_FILE_TYPES).flat().join(","); + export default function SlidesGenerator() { const { selectedProvider, @@ -88,17 +125,22 @@ export default function SlidesGenerator() { const [topic, setTopic] = useState(""); const [language, setLanguage] = useState("en"); - const [theme, setTheme] = useState("modern"); - const [audience, setAudience] = useState("general"); + const [theme, setTheme] = useState("executive-dark"); + const [audience, setAudience] = useState("executives"); const [organization, setOrganization] = useState(""); - const [slideCount, setSlideCount] = useState(8); + const [slideCount, setSlideCount] = useState(10); + const [animationStyle, setAnimationStyle] = useState("professional"); const [copied, setCopied] = useState(false); const [currentSlide, setCurrentSlide] = useState(0); const [isFullscreen, setIsFullscreen] = useState(false); const [isAutoPlaying, setIsAutoPlaying] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false); + const [attachedFiles, setAttachedFiles] = useState([]); + const [isDragOver, setIsDragOver] = useState(false); + const [uploadProgress, setUploadProgress] = useState(null); const slideContainerRef = useRef(null); const autoPlayRef = useRef(null); + const fileInputRef = useRef(null); const selectedModel = selectedModels[selectedProvider]; const models = availableModels[selectedProvider] || modelAdapter.getAvailableModels(selectedProvider); @@ -149,12 +191,171 @@ export default function SlidesGenerator() { } }; + // Extract colors from image + const extractColorsFromImage = (file: File): Promise => { + return new Promise((resolve) => { + const img = document.createElement("img"); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + img.onload = () => { + canvas.width = 50; + canvas.height = 50; + ctx?.drawImage(img, 0, 0, 50, 50); + + const imageData = ctx?.getImageData(0, 0, 50, 50); + if (!imageData) { + resolve([]); + return; + } + + const colorCounts: Record = {}; + for (let i = 0; i < imageData.data.length; i += 4) { + const r = Math.round(imageData.data[i] / 32) * 32; + const g = Math.round(imageData.data[i + 1] / 32) * 32; + const b = Math.round(imageData.data[i + 2] / 32) * 32; + const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; + colorCounts[hex] = (colorCounts[hex] || 0) + 1; + } + + const sortedColors = Object.entries(colorCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) + .map(([color]) => color); + + resolve(sortedColors); + URL.revokeObjectURL(img.src); + }; + + img.src = URL.createObjectURL(file); + }); + }; + + // Process uploaded file + const processFile = async (file: File): Promise => { + const id = Math.random().toString(36).substr(2, 9); + const ext = file.name.split(".").pop()?.toLowerCase() || ""; + + const attachedFile: AttachedFile = { + id, + name: file.name, + type: file.type || ext, + size: file.size, + }; + + try { + // Handle text-based files + if ([...ACCEPTED_FILE_TYPES.documents, ".json", ".csv", ".css", ".md"].some(e => file.name.endsWith(e))) { + const text = await file.text(); + attachedFile.content = text.slice(0, 50000); // Limit to 50KB of text + + // Extract colors from CSS files + if (file.name.endsWith(".css")) { + const colorMatches = text.match(/#[0-9a-fA-F]{3,8}|rgb\([^)]+\)|hsl\([^)]+\)/g); + if (colorMatches) { + attachedFile.colors = [...new Set(colorMatches)].slice(0, 10); + } + } + + // Parse JSON color palettes + if (file.name.endsWith(".json")) { + try { + const json = JSON.parse(text); + if (json.colors || json.palette) { + attachedFile.colors = (json.colors || json.palette).slice(0, 10); + } + } catch { } + } + } + + // Handle images + if (ACCEPTED_FILE_TYPES.images.some(e => file.name.toLowerCase().endsWith(e))) { + attachedFile.preview = URL.createObjectURL(file); + attachedFile.colors = await extractColorsFromImage(file); + } + + // Handle presentations (extract text content if possible) + if (ACCEPTED_FILE_TYPES.presentations.some(e => file.name.toLowerCase().endsWith(e))) { + attachedFile.content = `[Presentation file: ${file.name}] - Analyze structure and content for redesign.`; + } + + return attachedFile; + } catch (err) { + console.error("Error processing file:", err); + return attachedFile; + } + }; + + const handleFileDrop = useCallback(async (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + const files = Array.from(e.dataTransfer.files); + await handleFileUpload(files); + }, []); + + const handleFileUpload = async (files: File[]) => { + setUploadProgress("Processing files..."); + + const newFiles: AttachedFile[] = []; + for (const file of files) { + setUploadProgress(`Processing ${file.name}...`); + const processed = await processFile(file); + if (processed) { + newFiles.push(processed); + } + } + + setAttachedFiles(prev => [...prev, ...newFiles]); + setUploadProgress(null); + }; + + const removeFile = (id: string) => { + setAttachedFiles(prev => { + const file = prev.find(f => f.id === id); + if (file?.preview) { + URL.revokeObjectURL(file.preview); + } + return prev.filter(f => f.id !== id); + }); + }; + + const getFileIcon = (type: string, name: string) => { + if (name.match(/\.(png|jpg|jpeg|svg|webp|gif)$/i)) return ; + if (name.match(/\.(pdf|doc|docx|txt|md)$/i)) return ; + if (name.match(/\.(pptx|ppt|key)$/i)) return ; + return ; + }; + + const buildFileContext = (): string => { + if (attachedFiles.length === 0) return ""; + + let context = "\n\n## ATTACHED FILES CONTEXT:\n"; + + for (const file of attachedFiles) { + context += `\n### File: ${file.name}\n`; + + if (file.colors && file.colors.length > 0) { + context += `Brand Colors Extracted: ${file.colors.join(", ")}\n`; + context += "USE THESE EXACT COLORS in the presentation design.\n"; + } + + if (file.content) { + context += `Content:\n\`\`\`\n${file.content.slice(0, 10000)}\n\`\`\`\n`; + } + + if (file.name.match(/\.(pptx|ppt|key)$/i)) { + context += "This is an existing presentation - analyze its structure and REDESIGN with modern aesthetics while preserving the content flow.\n"; + } + } + + return context; + }; + const parseSlides = (content: string): SlidesPresentation | null => { try { - // Try to extract JSON from markdown code blocks const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/); const jsonStr = jsonMatch ? jsonMatch[1].trim() : content.trim(); - const parsed = JSON.parse(jsonStr); if (parsed.slides && Array.isArray(parsed.slides)) { @@ -170,7 +371,7 @@ export default function SlidesGenerator() { id: slide.id || `slide-${index + 1}`, title: slide.title || `Slide ${index + 1}`, content: slide.content || "", - htmlContent: slide.htmlContent || generateDefaultHtml(slide, index), + htmlContent: slide.htmlContent || generateAnimatedHtml(slide, index), notes: slide.notes || "", layout: slide.layout || "content", order: slide.order || index + 1, @@ -186,33 +387,101 @@ export default function SlidesGenerator() { return null; }; - const generateDefaultHtml = (slide: any, index: number): string => { + const generateAnimatedHtml = (slide: any, index: number): string => { const themeConfig = THEMES.find(t => t.id === theme) || THEMES[1]; - const [bg, accent, text] = themeConfig.colors; + const [bg, accent, secondary, text] = themeConfig.colors; + const gradient = themeConfig.gradient; + + // Get brand colors from attached files if available + const brandColors = attachedFiles.flatMap(f => f.colors || []).slice(0, 3); + const primaryColor = brandColors[0] || accent; + const secondaryColor = brandColors[1] || secondary; return ` -
-

${slide.title || `Slide ${index + 1}`}

-
- ${slide.content || "Content goes here..."} + +
+
+ + +
+

${slide.title || `Slide ${index + 1}`}

+ +
+ ${slide.content || "Content goes here..."} +
+ + +
+
+
+
+
+ +
`; }; @@ -235,26 +504,38 @@ export default function SlidesGenerator() { setError(null); setCurrentSlide(0); - console.log("[SlidesGenerator] Starting slides generation...", { + console.log("[SlidesGenerator] Starting animated slides generation...", { selectedProvider, selectedModel, topic, language, - theme + theme, + animationStyle, + attachedFilesCount: attachedFiles.length }); try { const languageName = LANGUAGES.find(l => l.code === language)?.name || "English"; - const audienceName = AUDIENCES.find(a => a.id === audience)?.name || "General Audience"; + const audienceConfig = AUDIENCES.find(a => a.id === audience); + const animConfig = ANIMATION_STYLES.find(a => a.id === animationStyle); + const themeConfig = THEMES.find(t => t.id === theme); + const fileContext = buildFileContext(); + + // Build enhanced topic with file context + const enhancedTopic = `${topic}${fileContext}`; const result = await modelAdapter.generateSlides( - topic, + enhancedTopic, { language: languageName, theme, slideCount, - audience: audienceName, + audience: audienceConfig?.name || "General Audience", organization, + animationStyle: animConfig?.name, + audienceStyle: audienceConfig?.style, + themeColors: themeConfig?.colors, + brandColors: attachedFiles.flatMap(f => f.colors || []).slice(0, 5), }, selectedProvider, selectedModel @@ -267,7 +548,6 @@ export default function SlidesGenerator() { if (presentation) { setSlidesPresentation(presentation); } else { - // Fallback: create a simple presentation with the raw content setSlidesPresentation({ id: Math.random().toString(36).substr(2, 9), title: topic.slice(0, 50), @@ -280,8 +560,8 @@ export default function SlidesGenerator() { title: "Generated Content", content: result.data, htmlContent: ` -
-
${result.data}
+
+
${result.data}
`, layout: "content", @@ -316,7 +596,10 @@ export default function SlidesGenerator() { if (!slidesPresentation) return; const themeConfig = THEMES.find(t => t.id === slidesPresentation.theme) || THEMES[1]; - const [bg, accent, text] = themeConfig.colors; + const [bg, accent, secondary, text] = themeConfig.colors; + const brandColors = attachedFiles.flatMap(f => f.colors || []); + const primaryColor = brandColors[0] || accent; + const secondaryColor = brandColors[1] || secondary; const html = ` @@ -324,24 +607,118 @@ export default function SlidesGenerator() { ${slidesPresentation.title} - + +
+ ${organization ? `` : ''}
${slidesPresentation.slides.map((slide, i) => `
@@ -350,29 +727,62 @@ export default function SlidesGenerator() { `).join('')}
- + +
1 / ${slidesPresentation.slides.length}
`; @@ -381,7 +791,7 @@ export default function SlidesGenerator() { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; - a.download = `${slidesPresentation.title.replace(/[^a-z0-9]/gi, '_')}_presentation.html`; + a.download = `${slidesPresentation.title.replace(/[^a-z0-9]/gi, '_')}_animated_presentation.html`; a.click(); URL.revokeObjectURL(url); }; @@ -404,19 +814,28 @@ export default function SlidesGenerator() { } }; + const formatFileSize = (bytes: number) => { + if (bytes < 1024) return bytes + " B"; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"; + return (bytes / (1024 * 1024)).toFixed(1) + " MB"; + }; + return (
{/* Input Panel */} -
- +
+
- Slides Generator + Slides Generator + + PRO + - Generate stunning HTML5 presentation slides with multi-language support + Generate stunning, animated HTML5 presentations with charts, graphics & corporate-ready design @@ -458,13 +877,112 @@ export default function SlidesGenerator() {