feat: Enhanced Slides Generator with animated presentations and file attachments
Major improvements: - MODERN ANIMATED SLIDES: CSS3 animations (fadeIn, slideIn, scaleIn, pulse) SVG charts and data visualizations, glassmorphism effects, gradient backgrounds Professional typography, staggered reveal animations - FILE ATTACHMENT SUPPORT: Upload PowerPoint, PDFs, Docs, Images, Color Palettes Auto-extract colors from images for brand consistency Parse CSS/JSON files for color palettes Context-aware slide generation from attached documents - ENHANCED THEMING: 8 premium themes (Corporate Blue, Executive Dark, Tech Neon, etc.) 4 animation styles (Professional, Dynamic, Minimal, Impressive) 7 audience presets with style descriptions - IMPROVED UX: Drag-and-drop file upload zone Progress bar during presentation playback Enhanced HTML export with autoplay and keyboard navigation File size display and color palette preview
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef, useCallback } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
@@ -25,7 +25,15 @@ import {
|
|||||||
Hash,
|
Hash,
|
||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
RotateCcw,
|
Upload,
|
||||||
|
X,
|
||||||
|
FileText,
|
||||||
|
Image as ImageIcon,
|
||||||
|
File,
|
||||||
|
Sparkles,
|
||||||
|
BarChart3,
|
||||||
|
TrendingUp,
|
||||||
|
Zap,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -51,24 +59,53 @@ const LANGUAGES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const THEMES = [
|
const THEMES = [
|
||||||
{ id: "corporate", name: "Corporate", colors: ["#1e3a5f", "#2563eb", "#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: "modern", name: "Modern", colors: ["#0f172a", "#6366f1", "#f8fafc"], icon: "✨" },
|
{ id: "executive-dark", name: "Executive Dark", colors: ["#09090b", "#6366f1", "#a855f7", "#fafafa"], icon: "👔", gradient: "linear-gradient(135deg, #09090b 0%, #18181b 50%, #27272a 100%)" },
|
||||||
{ id: "minimal", name: "Minimal", colors: ["#ffffff", "#374151", "#f3f4f6"], icon: "◻️" },
|
{ id: "modern-gradient", name: "Modern Gradient", colors: ["#0c0a09", "#f97316", "#eab308", "#fafaf9"], icon: "✨", gradient: "linear-gradient(135deg, #0c0a09 0%, #1c1917 50%, #292524 100%)" },
|
||||||
{ id: "dark", name: "Dark Mode", colors: ["#0a0a0a", "#a855f7", "#fafafa"], icon: "🌙" },
|
{ id: "tech-neon", name: "Tech Neon", colors: ["#020617", "#22d3ee", "#a3e635", "#f8fafc"], icon: "⚡", gradient: "linear-gradient(135deg, #020617 0%, #0c1929 50%, #172554 100%)" },
|
||||||
{ id: "vibrant", name: "Vibrant", colors: ["#7c3aed", "#ec4899", "#fef3c7"], icon: "🎨" },
|
{ id: "minimal-light", name: "Minimal Light", colors: ["#ffffff", "#18181b", "#71717a", "#f4f4f5"], icon: "◻️", gradient: "linear-gradient(135deg, #ffffff 0%, #f4f4f5 50%, #e4e4e7 100%)" },
|
||||||
{ id: "gradient", name: "Gradient", colors: ["#667eea", "#764ba2", "#ffffff"], icon: "🌈" },
|
{ 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 = [
|
const AUDIENCES = [
|
||||||
{ id: "executives", name: "Executives & C-Suite", icon: "👔" },
|
{ id: "executives", name: "Executives & C-Suite", icon: "👔", style: "Sophisticated, data-driven, strategic focus" },
|
||||||
{ id: "investors", name: "Investors & Stakeholders", icon: "💼" },
|
{ id: "investors", name: "Investors & Board", icon: "💼", style: "ROI-focused, metrics-heavy, growth narrative" },
|
||||||
{ id: "technical", name: "Technical Team", icon: "💻" },
|
{ id: "technical", name: "Technical Team", icon: "💻", style: "Detailed, architecture diagrams, code snippets" },
|
||||||
{ id: "marketing", name: "Marketing & Sales", icon: "📈" },
|
{ id: "marketing", name: "Marketing & Sales", icon: "📈", style: "Persuasive, visual storytelling, emotional appeal" },
|
||||||
{ id: "general", name: "General Audience", icon: "👥" },
|
{ id: "general", name: "General Audience", icon: "👥", style: "Clear, engaging, accessible language" },
|
||||||
{ id: "students", name: "Students & Educators", icon: "🎓" },
|
{ id: "stakeholders", name: "Stakeholders", icon: "🤝", style: "Project updates, milestones, risk mitigation" },
|
||||||
{ id: "customers", name: "Customers & Clients", icon: "🤝" },
|
{ 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() {
|
export default function SlidesGenerator() {
|
||||||
const {
|
const {
|
||||||
selectedProvider,
|
selectedProvider,
|
||||||
@@ -88,17 +125,22 @@ export default function SlidesGenerator() {
|
|||||||
|
|
||||||
const [topic, setTopic] = useState("");
|
const [topic, setTopic] = useState("");
|
||||||
const [language, setLanguage] = useState("en");
|
const [language, setLanguage] = useState("en");
|
||||||
const [theme, setTheme] = useState("modern");
|
const [theme, setTheme] = useState("executive-dark");
|
||||||
const [audience, setAudience] = useState("general");
|
const [audience, setAudience] = useState("executives");
|
||||||
const [organization, setOrganization] = useState("");
|
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 [copied, setCopied] = useState(false);
|
||||||
const [currentSlide, setCurrentSlide] = useState(0);
|
const [currentSlide, setCurrentSlide] = useState(0);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
const [isAutoPlaying, setIsAutoPlaying] = useState(false);
|
const [isAutoPlaying, setIsAutoPlaying] = useState(false);
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
|
const [attachedFiles, setAttachedFiles] = useState<AttachedFile[]>([]);
|
||||||
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
|
const [uploadProgress, setUploadProgress] = useState<string | null>(null);
|
||||||
const slideContainerRef = useRef<HTMLDivElement>(null);
|
const slideContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const autoPlayRef = useRef<NodeJS.Timeout | null>(null);
|
const autoPlayRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const selectedModel = selectedModels[selectedProvider];
|
const selectedModel = selectedModels[selectedProvider];
|
||||||
const models = availableModels[selectedProvider] || modelAdapter.getAvailableModels(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<string[]> => {
|
||||||
|
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<string, number> = {};
|
||||||
|
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<AttachedFile | null> => {
|
||||||
|
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 <ImageIcon className="h-4 w-4" />;
|
||||||
|
if (name.match(/\.(pdf|doc|docx|txt|md)$/i)) return <FileText className="h-4 w-4" />;
|
||||||
|
if (name.match(/\.(pptx|ppt|key)$/i)) return <Presentation className="h-4 w-4" />;
|
||||||
|
return <File className="h-4 w-4" />;
|
||||||
|
};
|
||||||
|
|
||||||
|
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 => {
|
const parseSlides = (content: string): SlidesPresentation | null => {
|
||||||
try {
|
try {
|
||||||
// Try to extract JSON from markdown code blocks
|
|
||||||
const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/);
|
const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||||
const jsonStr = jsonMatch ? jsonMatch[1].trim() : content.trim();
|
const jsonStr = jsonMatch ? jsonMatch[1].trim() : content.trim();
|
||||||
|
|
||||||
const parsed = JSON.parse(jsonStr);
|
const parsed = JSON.parse(jsonStr);
|
||||||
|
|
||||||
if (parsed.slides && Array.isArray(parsed.slides)) {
|
if (parsed.slides && Array.isArray(parsed.slides)) {
|
||||||
@@ -170,7 +371,7 @@ export default function SlidesGenerator() {
|
|||||||
id: slide.id || `slide-${index + 1}`,
|
id: slide.id || `slide-${index + 1}`,
|
||||||
title: slide.title || `Slide ${index + 1}`,
|
title: slide.title || `Slide ${index + 1}`,
|
||||||
content: slide.content || "",
|
content: slide.content || "",
|
||||||
htmlContent: slide.htmlContent || generateDefaultHtml(slide, index),
|
htmlContent: slide.htmlContent || generateAnimatedHtml(slide, index),
|
||||||
notes: slide.notes || "",
|
notes: slide.notes || "",
|
||||||
layout: slide.layout || "content",
|
layout: slide.layout || "content",
|
||||||
order: slide.order || index + 1,
|
order: slide.order || index + 1,
|
||||||
@@ -186,33 +387,101 @@ export default function SlidesGenerator() {
|
|||||||
return null;
|
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 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 `
|
return `
|
||||||
<div style="
|
<div class="slide-container" style="
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding: 3rem;
|
padding: 4rem;
|
||||||
background: linear-gradient(135deg, ${bg} 0%, ${accent}22 100%);
|
background: ${gradient};
|
||||||
color: ${theme === 'minimal' ? '#1f2937' : text};
|
color: ${text};
|
||||||
font-family: 'Inter', 'Segoe UI', system-ui, sans-serif;
|
font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
">
|
">
|
||||||
<h2 style="
|
<!-- Animated Background Elements -->
|
||||||
font-size: 2.5rem;
|
<div style="
|
||||||
font-weight: 700;
|
position: absolute;
|
||||||
margin-bottom: 1.5rem;
|
top: -50%;
|
||||||
background: linear-gradient(90deg, ${accent}, ${accent}cc);
|
right: -20%;
|
||||||
-webkit-background-clip: text;
|
width: 80%;
|
||||||
-webkit-text-fill-color: transparent;
|
height: 150%;
|
||||||
background-clip: text;
|
background: radial-gradient(ellipse at center, ${primaryColor}15 0%, transparent 70%);
|
||||||
">${slide.title || `Slide ${index + 1}`}</h2>
|
animation: pulse 8s ease-in-out infinite;
|
||||||
<div style="font-size: 1.25rem; line-height: 1.8; opacity: 0.9;">
|
"></div>
|
||||||
${slide.content || "Content goes here..."}
|
<div style="
|
||||||
|
position: absolute;
|
||||||
|
bottom: -30%;
|
||||||
|
left: -10%;
|
||||||
|
width: 60%;
|
||||||
|
height: 100%;
|
||||||
|
background: radial-gradient(ellipse at center, ${secondaryColor}10 0%, transparent 70%);
|
||||||
|
animation: pulse 10s ease-in-out infinite reverse;
|
||||||
|
"></div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div style="position: relative; z-index: 2;">
|
||||||
|
<h2 style="
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
background: linear-gradient(135deg, ${primaryColor} 0%, ${secondaryColor} 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
animation: slideIn 0.8s ease-out;
|
||||||
|
">${slide.title || `Slide ${index + 1}`}</h2>
|
||||||
|
|
||||||
|
<div style="
|
||||||
|
font-size: 1.35rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
opacity: 0.95;
|
||||||
|
max-width: 90%;
|
||||||
|
animation: fadeIn 1s ease-out 0.3s both;
|
||||||
|
">
|
||||||
|
${slide.content || "Content goes here..."}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Decorative Elements -->
|
||||||
|
<div style="
|
||||||
|
position: absolute;
|
||||||
|
bottom: 3rem;
|
||||||
|
right: 4rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
">
|
||||||
|
<div style="width: 8px; height: 8px; border-radius: 50%; background: ${primaryColor}; opacity: 0.6;"></div>
|
||||||
|
<div style="width: 8px; height: 8px; border-radius: 50%; background: ${secondaryColor}; opacity: 0.4;"></div>
|
||||||
|
<div style="width: 8px; height: 8px; border-radius: 50%; background: ${text}; opacity: 0.2;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: 0.7; transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
@keyframes slideIn {
|
||||||
|
from { opacity: 0; transform: translateX(-30px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
to { opacity: 0.95; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
@@ -235,26 +504,38 @@ export default function SlidesGenerator() {
|
|||||||
setError(null);
|
setError(null);
|
||||||
setCurrentSlide(0);
|
setCurrentSlide(0);
|
||||||
|
|
||||||
console.log("[SlidesGenerator] Starting slides generation...", {
|
console.log("[SlidesGenerator] Starting animated slides generation...", {
|
||||||
selectedProvider,
|
selectedProvider,
|
||||||
selectedModel,
|
selectedModel,
|
||||||
topic,
|
topic,
|
||||||
language,
|
language,
|
||||||
theme
|
theme,
|
||||||
|
animationStyle,
|
||||||
|
attachedFilesCount: attachedFiles.length
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const languageName = LANGUAGES.find(l => l.code === language)?.name || "English";
|
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(
|
const result = await modelAdapter.generateSlides(
|
||||||
topic,
|
enhancedTopic,
|
||||||
{
|
{
|
||||||
language: languageName,
|
language: languageName,
|
||||||
theme,
|
theme,
|
||||||
slideCount,
|
slideCount,
|
||||||
audience: audienceName,
|
audience: audienceConfig?.name || "General Audience",
|
||||||
organization,
|
organization,
|
||||||
|
animationStyle: animConfig?.name,
|
||||||
|
audienceStyle: audienceConfig?.style,
|
||||||
|
themeColors: themeConfig?.colors,
|
||||||
|
brandColors: attachedFiles.flatMap(f => f.colors || []).slice(0, 5),
|
||||||
},
|
},
|
||||||
selectedProvider,
|
selectedProvider,
|
||||||
selectedModel
|
selectedModel
|
||||||
@@ -267,7 +548,6 @@ export default function SlidesGenerator() {
|
|||||||
if (presentation) {
|
if (presentation) {
|
||||||
setSlidesPresentation(presentation);
|
setSlidesPresentation(presentation);
|
||||||
} else {
|
} else {
|
||||||
// Fallback: create a simple presentation with the raw content
|
|
||||||
setSlidesPresentation({
|
setSlidesPresentation({
|
||||||
id: Math.random().toString(36).substr(2, 9),
|
id: Math.random().toString(36).substr(2, 9),
|
||||||
title: topic.slice(0, 50),
|
title: topic.slice(0, 50),
|
||||||
@@ -280,8 +560,8 @@ export default function SlidesGenerator() {
|
|||||||
title: "Generated Content",
|
title: "Generated Content",
|
||||||
content: result.data,
|
content: result.data,
|
||||||
htmlContent: `
|
htmlContent: `
|
||||||
<div style="padding: 2rem; font-family: system-ui;">
|
<div style="padding: 2rem; font-family: system-ui; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); min-height: 100%; color: #f8fafc;">
|
||||||
<pre style="white-space: pre-wrap; font-size: 0.875rem;">${result.data}</pre>
|
<pre style="white-space: pre-wrap; font-size: 0.875rem; opacity: 0.9;">${result.data}</pre>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
layout: "content",
|
layout: "content",
|
||||||
@@ -316,7 +596,10 @@ export default function SlidesGenerator() {
|
|||||||
if (!slidesPresentation) return;
|
if (!slidesPresentation) return;
|
||||||
|
|
||||||
const themeConfig = THEMES.find(t => t.id === slidesPresentation.theme) || THEMES[1];
|
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 = `<!DOCTYPE html>
|
const html = `<!DOCTYPE html>
|
||||||
<html lang="${language}">
|
<html lang="${language}">
|
||||||
@@ -324,24 +607,118 @@ export default function SlidesGenerator() {
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>${slidesPresentation.title}</title>
|
<title>${slidesPresentation.title}</title>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body { font-family: 'Inter', system-ui, sans-serif; background: ${bg}; color: ${text}; }
|
body {
|
||||||
|
font-family: 'Inter', system-ui, sans-serif;
|
||||||
|
background: ${bg};
|
||||||
|
color: ${text};
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
.slides-container { width: 100vw; height: 100vh; overflow: hidden; position: relative; }
|
.slides-container { width: 100vw; height: 100vh; overflow: hidden; position: relative; }
|
||||||
.slide { width: 100%; height: 100%; display: none; animation: fadeIn 0.5s ease; }
|
.slide {
|
||||||
.slide.active { display: block; }
|
width: 100%;
|
||||||
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
|
height: 100%;
|
||||||
.controls { position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%);
|
display: none;
|
||||||
display: flex; gap: 1rem; background: rgba(0,0,0,0.8); padding: 0.75rem 1.5rem; border-radius: 2rem; }
|
position: absolute;
|
||||||
.controls button { background: ${accent}; color: white; border: none; padding: 0.5rem 1rem;
|
top: 0;
|
||||||
border-radius: 0.5rem; cursor: pointer; font-weight: 500; transition: all 0.2s; }
|
left: 0;
|
||||||
.controls button:hover { transform: scale(1.05); }
|
}
|
||||||
.slide-counter { position: fixed; bottom: 2rem; right: 2rem; background: rgba(0,0,0,0.6);
|
.slide.active {
|
||||||
padding: 0.5rem 1rem; border-radius: 1rem; font-size: 0.875rem; }
|
display: block;
|
||||||
|
animation: slideEnter 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
.slide.exit {
|
||||||
|
animation: slideExit 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||||
|
}
|
||||||
|
@keyframes slideEnter {
|
||||||
|
from { opacity: 0; transform: translateX(40px); }
|
||||||
|
to { opacity: 1; transform: translateX(0); }
|
||||||
|
}
|
||||||
|
@keyframes slideExit {
|
||||||
|
from { opacity: 1; transform: translateX(0); }
|
||||||
|
to { opacity: 0; transform: translateX(-40px); }
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: 0.7; transform: scale(1.05); }
|
||||||
|
}
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0); }
|
||||||
|
50% { transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
background: rgba(0,0,0,0.85);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
padding: 0.875rem 1.75rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.controls button {
|
||||||
|
background: linear-gradient(135deg, ${primaryColor} 0%, ${secondaryColor} 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
border-radius: 0.625rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 4px 15px ${primaryColor}40;
|
||||||
|
}
|
||||||
|
.controls button:hover {
|
||||||
|
transform: translateY(-2px) scale(1.02);
|
||||||
|
box-shadow: 0 8px 25px ${primaryColor}50;
|
||||||
|
}
|
||||||
|
.controls button:active {
|
||||||
|
transform: translateY(0) scale(0.98);
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, ${primaryColor}, ${secondaryColor});
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.slide-counter {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
background: rgba(0,0,0,0.7);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
padding: 0.625rem 1.25rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
${organization ? `
|
||||||
|
.org-logo {
|
||||||
|
position: fixed;
|
||||||
|
top: 2rem;
|
||||||
|
left: 2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
z-index: 100;
|
||||||
|
}` : ''}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="progress-bar" id="progress"></div>
|
||||||
|
${organization ? `<div class="org-logo">${organization}</div>` : ''}
|
||||||
<div class="slides-container">
|
<div class="slides-container">
|
||||||
${slidesPresentation.slides.map((slide, i) => `
|
${slidesPresentation.slides.map((slide, i) => `
|
||||||
<div class="slide${i === 0 ? ' active' : ''}" data-slide="${i}">
|
<div class="slide${i === 0 ? ' active' : ''}" data-slide="${i}">
|
||||||
@@ -350,29 +727,62 @@ export default function SlidesGenerator() {
|
|||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<button onclick="prevSlide()">← Previous</button>
|
<button onclick="prevSlide()">← Prev</button>
|
||||||
|
<button onclick="toggleAutoplay()" id="autoplayBtn">▶ Auto</button>
|
||||||
<button onclick="nextSlide()">Next →</button>
|
<button onclick="nextSlide()">Next →</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="slide-counter"><span id="current">1</span> / ${slidesPresentation.slides.length}</div>
|
<div class="slide-counter"><span id="current">1</span> / ${slidesPresentation.slides.length}</div>
|
||||||
<script>
|
<script>
|
||||||
let current = 0;
|
let current = 0;
|
||||||
|
let autoplay = null;
|
||||||
const slides = document.querySelectorAll('.slide');
|
const slides = document.querySelectorAll('.slide');
|
||||||
const counter = document.getElementById('current');
|
const counter = document.getElementById('current');
|
||||||
|
const progress = document.getElementById('progress');
|
||||||
|
const autoplayBtn = document.getElementById('autoplayBtn');
|
||||||
|
const total = slides.length;
|
||||||
|
|
||||||
function showSlide(n) {
|
function updateProgress() {
|
||||||
slides.forEach(s => s.classList.remove('active'));
|
progress.style.width = ((current + 1) / total * 100) + '%';
|
||||||
current = (n + slides.length) % slides.length;
|
|
||||||
slides[current].classList.add('active');
|
|
||||||
counter.textContent = current + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextSlide() { showSlide(current + 1); }
|
function showSlide(n, direction = 1) {
|
||||||
function prevSlide() { showSlide(current - 1); }
|
const prev = current;
|
||||||
|
slides[prev].classList.add('exit');
|
||||||
|
slides[prev].classList.remove('active');
|
||||||
|
|
||||||
|
current = (n + total) % total;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
slides[prev].classList.remove('exit');
|
||||||
|
slides[current].classList.add('active');
|
||||||
|
}, 400);
|
||||||
|
|
||||||
|
counter.textContent = current + 1;
|
||||||
|
updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() { showSlide(current + 1, 1); }
|
||||||
|
function prevSlide() { showSlide(current - 1, -1); }
|
||||||
|
|
||||||
|
function toggleAutoplay() {
|
||||||
|
if (autoplay) {
|
||||||
|
clearInterval(autoplay);
|
||||||
|
autoplay = null;
|
||||||
|
autoplayBtn.textContent = '▶ Auto';
|
||||||
|
} else {
|
||||||
|
autoplay = setInterval(nextSlide, 5000);
|
||||||
|
autoplayBtn.textContent = '⏸ Stop';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', e => {
|
document.addEventListener('keydown', e => {
|
||||||
if (e.key === 'ArrowRight' || e.key === ' ') nextSlide();
|
if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); nextSlide(); }
|
||||||
if (e.key === 'ArrowLeft') prevSlide();
|
if (e.key === 'ArrowLeft') { e.preventDefault(); prevSlide(); }
|
||||||
|
if (e.key === 'f' || e.key === 'F') document.documentElement.requestFullscreen?.();
|
||||||
|
if (e.key === 'Escape' && document.fullscreenElement) document.exitFullscreen?.();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateProgress();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
@@ -381,7 +791,7 @@ export default function SlidesGenerator() {
|
|||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = url;
|
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();
|
a.click();
|
||||||
URL.revokeObjectURL(url);
|
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 (
|
return (
|
||||||
<div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 xl:grid-cols-2">
|
<div className="mx-auto grid max-w-7xl gap-4 lg:gap-6 grid-cols-1 xl:grid-cols-2">
|
||||||
{/* Input Panel */}
|
{/* Input Panel */}
|
||||||
<Card className="h-fit">
|
<Card className="h-fit">
|
||||||
<CardHeader className="p-4 lg:p-6">
|
<CardHeader className="p-4 lg:p-6">
|
||||||
<CardTitle className="flex items-center gap-2 text-base lg:text-lg">
|
<CardTitle className="flex items-center gap-2 text-base lg:text-lg">
|
||||||
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500 to-purple-600 text-white">
|
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-violet-500 via-purple-500 to-fuchsia-500 text-white shadow-lg shadow-violet-500/25">
|
||||||
<Presentation className="h-4 w-4" />
|
<Sparkles className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
Slides Generator
|
<span>Slides Generator</span>
|
||||||
|
<span className="ml-auto text-[10px] font-normal px-2 py-0.5 rounded-full bg-gradient-to-r from-amber-500/10 to-orange-500/10 text-amber-600 border border-amber-200/50">
|
||||||
|
PRO
|
||||||
|
</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs lg:text-sm">
|
<CardDescription className="text-xs lg:text-sm">
|
||||||
Generate stunning HTML5 presentation slides with multi-language support
|
Generate stunning, animated HTML5 presentations with charts, graphics & corporate-ready design
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4 lg:space-y-5 p-4 lg:p-6 pt-0 lg:pt-0">
|
<CardContent className="space-y-4 lg:space-y-5 p-4 lg:p-6 pt-0 lg:pt-0">
|
||||||
@@ -458,13 +877,112 @@ export default function SlidesGenerator() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs lg:text-sm font-medium">Presentation Topic</label>
|
<label className="text-xs lg:text-sm font-medium">Presentation Topic</label>
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="e.g., Q4 2024 Company Performance Review, AI in Healthcare: Transforming Patient Care, Product Launch Strategy for Global Markets..."
|
placeholder="e.g., Q4 2024 Revenue Analysis with YoY Growth Comparison, AI Integration Roadmap for Enterprise, Product Launch Strategy with Market Positioning..."
|
||||||
value={topic}
|
value={topic}
|
||||||
onChange={(e) => setTopic(e.target.value)}
|
onChange={(e) => setTopic(e.target.value)}
|
||||||
className="min-h-[100px] lg:min-h-[120px] resize-y text-sm"
|
className="min-h-[100px] lg:min-h-[120px] resize-y text-sm"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* File Upload Zone */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs lg:text-sm font-medium flex items-center gap-1.5">
|
||||||
|
<Upload className="h-3.5 w-3.5 text-blue-500" />
|
||||||
|
Attach Files for Context
|
||||||
|
<span className="text-[10px] text-muted-foreground font-normal">(Optional)</span>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"relative border-2 border-dashed rounded-lg p-4 transition-all text-center",
|
||||||
|
isDragOver
|
||||||
|
? "border-violet-500 bg-violet-500/5"
|
||||||
|
: "border-muted-foreground/25 hover:border-muted-foreground/50"
|
||||||
|
)}
|
||||||
|
onDragOver={(e) => { e.preventDefault(); setIsDragOver(true); }}
|
||||||
|
onDragLeave={() => setIsDragOver(false)}
|
||||||
|
onDrop={handleFileDrop}
|
||||||
|
onClick={() => fileInputRef.current?.click()}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
multiple
|
||||||
|
accept={ALL_ACCEPTED}
|
||||||
|
className="hidden"
|
||||||
|
onChange={(e) => e.target.files && handleFileUpload(Array.from(e.target.files))}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col items-center gap-2 cursor-pointer">
|
||||||
|
<div className="p-3 rounded-full bg-muted/50">
|
||||||
|
<Upload className="h-5 w-5 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-xs font-medium">
|
||||||
|
{isDragOver ? "Drop files here" : "Drag & drop or click to upload"}
|
||||||
|
</p>
|
||||||
|
<p className="text-[10px] text-muted-foreground mt-0.5">
|
||||||
|
PowerPoint, PDFs, Docs, Images, Color Palettes
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{uploadProgress && (
|
||||||
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
|
{uploadProgress}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Attached Files List */}
|
||||||
|
{attachedFiles.length > 0 && (
|
||||||
|
<div className="space-y-1.5 mt-2">
|
||||||
|
{attachedFiles.map((file) => (
|
||||||
|
<div
|
||||||
|
key={file.id}
|
||||||
|
className="flex items-center gap-2 p-2 rounded-md bg-muted/30 border text-xs group"
|
||||||
|
>
|
||||||
|
{file.preview ? (
|
||||||
|
<img src={file.preview} alt="" className="w-8 h-8 rounded object-cover" />
|
||||||
|
) : (
|
||||||
|
<div className="w-8 h-8 rounded bg-muted flex items-center justify-center">
|
||||||
|
{getFileIcon(file.type, file.name)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="font-medium truncate">{file.name}</p>
|
||||||
|
<p className="text-[10px] text-muted-foreground">
|
||||||
|
{formatFileSize(file.size)}
|
||||||
|
{file.colors && file.colors.length > 0 && (
|
||||||
|
<span className="ml-2">
|
||||||
|
• {file.colors.length} colors extracted
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{file.colors && file.colors.length > 0 && (
|
||||||
|
<div className="flex gap-0.5">
|
||||||
|
{file.colors.slice(0, 4).map((color, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="w-4 h-4 rounded-sm border border-white/20"
|
||||||
|
style={{ backgroundColor: color }}
|
||||||
|
title={color}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); removeFile(file.id); }}
|
||||||
|
className="p-1 rounded hover:bg-destructive/10 text-muted-foreground hover:text-destructive transition-colors"
|
||||||
|
>
|
||||||
|
<X className="h-3.5 w-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Language & Theme Row */}
|
{/* Language & Theme Row */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -504,6 +1022,31 @@ export default function SlidesGenerator() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Animation Style */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs lg:text-sm font-medium flex items-center gap-1.5">
|
||||||
|
<Zap className="h-3.5 w-3.5 text-amber-500" />
|
||||||
|
Animation Style
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
{ANIMATION_STYLES.map((style) => (
|
||||||
|
<button
|
||||||
|
key={style.id}
|
||||||
|
onClick={() => setAnimationStyle(style.id)}
|
||||||
|
className={cn(
|
||||||
|
"p-2.5 rounded-lg border text-left transition-all",
|
||||||
|
animationStyle === style.id
|
||||||
|
? "border-violet-500 bg-violet-500/10"
|
||||||
|
: "border-muted hover:border-violet-300"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p className="text-xs font-medium">{style.name}</p>
|
||||||
|
<p className="text-[10px] text-muted-foreground">{style.description}</p>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Advanced Options Toggle */}
|
{/* Advanced Options Toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAdvanced(!showAdvanced)}
|
onClick={() => setShowAdvanced(!showAdvanced)}
|
||||||
@@ -545,7 +1088,7 @@ export default function SlidesGenerator() {
|
|||||||
onChange={(e) => setSlideCount(parseInt(e.target.value))}
|
onChange={(e) => setSlideCount(parseInt(e.target.value))}
|
||||||
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="w-full rounded-md border border-input bg-background px-2.5 py-1.5 text-xs ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
>
|
>
|
||||||
{[5, 8, 10, 12, 15, 20].map((n) => (
|
{[5, 8, 10, 12, 15, 20, 25, 30].map((n) => (
|
||||||
<option key={n} value={n}>{n} slides</option>
|
<option key={n} value={n}>{n} slides</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -565,6 +1108,19 @@ export default function SlidesGenerator() {
|
|||||||
className="w-full rounded-md border border-input bg-background px-3 py-1.5 text-xs ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
className="w-full rounded-md border border-input bg-background px-3 py-1.5 text-xs ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Feature badges */}
|
||||||
|
<div className="flex flex-wrap gap-1.5 pt-2">
|
||||||
|
<span className="text-[10px] px-2 py-0.5 rounded-full bg-blue-500/10 text-blue-600 border border-blue-200/50 flex items-center gap-1">
|
||||||
|
<BarChart3 className="h-2.5 w-2.5" /> SVG Charts
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] px-2 py-0.5 rounded-full bg-purple-500/10 text-purple-600 border border-purple-200/50 flex items-center gap-1">
|
||||||
|
<Sparkles className="h-2.5 w-2.5" /> Animations
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px] px-2 py-0.5 rounded-full bg-green-500/10 text-green-600 border border-green-200/50 flex items-center gap-1">
|
||||||
|
<TrendingUp className="h-2.5 w-2.5" /> Data Viz
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -585,17 +1141,17 @@ export default function SlidesGenerator() {
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleGenerate}
|
onClick={handleGenerate}
|
||||||
disabled={isProcessing || !topic.trim()}
|
disabled={isProcessing || !topic.trim()}
|
||||||
className="w-full h-10 lg:h-11 text-sm lg:text-base font-medium bg-gradient-to-r from-violet-600 to-purple-600 hover:from-violet-700 hover:to-purple-700"
|
className="w-full h-10 lg:h-11 text-sm lg:text-base font-medium bg-gradient-to-r from-violet-600 via-purple-600 to-fuchsia-600 hover:from-violet-700 hover:via-purple-700 hover:to-fuchsia-700 shadow-lg shadow-violet-500/25"
|
||||||
>
|
>
|
||||||
{isProcessing ? (
|
{isProcessing ? (
|
||||||
<>
|
<>
|
||||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
Generating Slides...
|
Creating Animated Slides...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Presentation className="mr-2 h-4 w-4" />
|
<Sparkles className="mr-2 h-4 w-4" />
|
||||||
Generate Presentation
|
Generate Animated Presentation
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -652,20 +1208,28 @@ export default function SlidesGenerator() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => goToSlide(currentSlide - 1)}
|
onClick={() => goToSlide(currentSlide - 1)}
|
||||||
disabled={currentSlide === 0}
|
disabled={currentSlide === 0}
|
||||||
className="absolute left-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/50 text-white hover:bg-black/70 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
|
className="absolute left-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/50 text-white hover:bg-black/70 disabled:opacity-30 disabled:cursor-not-allowed transition-all backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="h-5 w-5" />
|
<ChevronLeft className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => goToSlide(currentSlide + 1)}
|
onClick={() => goToSlide(currentSlide + 1)}
|
||||||
disabled={currentSlide >= slidesPresentation.slides.length - 1}
|
disabled={currentSlide >= slidesPresentation.slides.length - 1}
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/50 text-white hover:bg-black/70 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
|
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 rounded-full bg-black/50 text-white hover:bg-black/70 disabled:opacity-30 disabled:cursor-not-allowed transition-all backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<ChevronRight className="h-5 w-5" />
|
<ChevronRight className="h-5 w-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Progress Bar */}
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-1 bg-black/30">
|
||||||
|
<div
|
||||||
|
className="h-full bg-gradient-to-r from-violet-500 to-fuchsia-500 transition-all duration-300"
|
||||||
|
style={{ width: `${((currentSlide + 1) / slidesPresentation.slides.length) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Slide Counter */}
|
{/* Slide Counter */}
|
||||||
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-black/60 text-white text-xs font-medium">
|
<div className="absolute bottom-3 left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-black/60 text-white text-xs font-medium backdrop-blur-sm">
|
||||||
{currentSlide + 1} / {slidesPresentation.slides.length}
|
{currentSlide + 1} / {slidesPresentation.slides.length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -683,7 +1247,7 @@ export default function SlidesGenerator() {
|
|||||||
: "border-muted hover:border-violet-300"
|
: "border-muted hover:border-violet-300"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-full h-full bg-slate-800 flex items-center justify-center text-[8px] text-white/70 font-medium">
|
<div className="w-full h-full bg-gradient-to-br from-slate-800 to-slate-900 flex items-center justify-center text-[8px] text-white/70 font-medium">
|
||||||
{index + 1}
|
{index + 1}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@@ -708,13 +1272,13 @@ export default function SlidesGenerator() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex h-[300px] lg:h-[400px] items-center justify-center text-center">
|
<div className="flex h-[300px] lg:h-[400px] items-center justify-center text-center">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="mx-auto w-16 h-16 rounded-2xl bg-gradient-to-br from-violet-500/20 to-purple-500/20 flex items-center justify-center">
|
<div className="mx-auto w-16 h-16 rounded-2xl bg-gradient-to-br from-violet-500/20 to-fuchsia-500/20 flex items-center justify-center">
|
||||||
<Presentation className="h-8 w-8 text-violet-500/50" />
|
<Sparkles className="h-8 w-8 text-violet-500/50" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-muted-foreground">No presentation yet</p>
|
<p className="text-sm font-medium text-muted-foreground">No presentation yet</p>
|
||||||
<p className="text-xs text-muted-foreground/70 mt-1">
|
<p className="text-xs text-muted-foreground/70 mt-1">
|
||||||
Enter a topic and generate your slides
|
Enter a topic and generate your animated slides
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -194,6 +194,10 @@ export class ModelAdapter {
|
|||||||
slideCount?: number;
|
slideCount?: number;
|
||||||
audience?: string;
|
audience?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
animationStyle?: string;
|
||||||
|
audienceStyle?: string;
|
||||||
|
themeColors?: string[];
|
||||||
|
brandColors?: string[];
|
||||||
} = {},
|
} = {},
|
||||||
provider?: ModelProvider,
|
provider?: ModelProvider,
|
||||||
model?: string
|
model?: string
|
||||||
|
|||||||
@@ -312,85 +312,124 @@ Make's prompt specific, inspiring, and comprehensive. Use professional UX termin
|
|||||||
slideCount?: number;
|
slideCount?: number;
|
||||||
audience?: string;
|
audience?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
animationStyle?: string;
|
||||||
|
audienceStyle?: string;
|
||||||
|
themeColors?: string[];
|
||||||
|
brandColors?: string[];
|
||||||
} = {},
|
} = {},
|
||||||
model?: string
|
model?: string
|
||||||
): Promise<APIResponse<string>> {
|
): Promise<APIResponse<string>> {
|
||||||
const { language = "English", theme = "modern", slideCount = 10, audience = "general", organization = "" } = options;
|
const {
|
||||||
|
language = "English",
|
||||||
|
theme = "executive-dark",
|
||||||
|
slideCount = 10,
|
||||||
|
audience = "Executives & C-Suite",
|
||||||
|
organization = "",
|
||||||
|
animationStyle = "Professional",
|
||||||
|
audienceStyle = "Sophisticated, data-driven, strategic focus",
|
||||||
|
themeColors = ["#09090b", "#6366f1", "#a855f7", "#fafafa"],
|
||||||
|
brandColors = []
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const [bgColor, primaryColor, secondaryColor, textColor] = themeColors;
|
||||||
|
const brandColorStr = brandColors.length > 0
|
||||||
|
? `\nBRAND COLORS TO USE: ${brandColors.join(", ")}`
|
||||||
|
: "";
|
||||||
|
|
||||||
const systemMessage: ChatMessage = {
|
const systemMessage: ChatMessage = {
|
||||||
role: "system",
|
role: "system",
|
||||||
content: `You are a world-class presentation designer and content strategist specializing in creating stunning, impactful slide decks for organizations and professionals.
|
content: `You are a WORLD-CLASS presentation designer who creates STUNNING, AWARD-WINNING slide decks that rival McKinsey, Apple, and TED presentations.
|
||||||
|
|
||||||
Your task is to generate a complete presentation with HTML5-ready slide content. Each slide should be designed with modern, visually impressive aesthetics.
|
Your slides must be VISUALLY SPECTACULAR with:
|
||||||
|
- Modern CSS3 animations (fade-in, slide-in, scale, parallax effects)
|
||||||
|
- Sophisticated gradient backgrounds with depth
|
||||||
|
- SVG charts and data visualizations inline
|
||||||
|
- Glassmorphism and neumorphism effects
|
||||||
|
- Professional typography with Inter/SF Pro fonts
|
||||||
|
- Strategic use of whitespace
|
||||||
|
- Micro-animations on hover/focus states
|
||||||
|
- Progress indicators and visual hierarchy
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
OUTPUT FORMAT - Return ONLY valid JSON:
|
||||||
Return a JSON object with the following structure:
|
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
{
|
{
|
||||||
"title": "Presentation Title",
|
"title": "Presentation Title",
|
||||||
"subtitle": "Presentation Subtitle",
|
"subtitle": "Compelling Subtitle",
|
||||||
"theme": "${theme}",
|
"theme": "${theme}",
|
||||||
"language": "${language}",
|
"language": "${language}",
|
||||||
"slides": [
|
"slides": [
|
||||||
{
|
{
|
||||||
"id": "slide-1",
|
"id": "slide-1",
|
||||||
"title": "Slide Title",
|
"title": "Slide Title",
|
||||||
"content": "Main content text",
|
"content": "Plain text content summary",
|
||||||
"htmlContent": "<div class='slide'>Complete HTML content for the slide</div>",
|
"htmlContent": "<div>FULL HTML with inline CSS and animations</div>",
|
||||||
"notes": "Speaker notes",
|
"notes": "Speaker notes",
|
||||||
"layout": "title|content|two-column|quote|statistics|timeline|comparison",
|
"layout": "title|content|two-column|chart|statistics|timeline|quote|comparison",
|
||||||
"order": 1
|
"order": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
DESIGN GUIDELINES:
|
DESIGN SYSTEM:
|
||||||
1. Create ${slideCount} slides maximum
|
- Primary: ${brandColors[0] || primaryColor}
|
||||||
2. Use the "${theme}" design theme
|
- Secondary: ${brandColors[1] || secondaryColor}
|
||||||
3. All content MUST be in ${language}
|
- Background: ${bgColor}
|
||||||
4. Target audience: ${audience}
|
- Text: ${textColor}${brandColorStr}
|
||||||
${organization ? `5. Organization: ${organization}` : ""}
|
|
||||||
|
|
||||||
SLIDE TYPES TO INCLUDE:
|
ANIMATION STYLE: ${animationStyle}
|
||||||
- Title slide with compelling headline
|
- Professional: Subtle 0.3-0.5s ease transitions, fade and slide
|
||||||
- Problem/Opportunity statement
|
- Dynamic: 0.5-0.8s spring animations, emphasis effects, stagger delays
|
||||||
- Key insights with data visualizations
|
- Impressive: Bold 0.8-1.2s animations, parallax, morphing, particle effects
|
||||||
- Solution/Strategy overview
|
|
||||||
- Timeline or roadmap if applicable
|
|
||||||
- Key metrics or statistics
|
|
||||||
- Call-to-action or next steps
|
|
||||||
- Summary/Conclusion slide
|
|
||||||
|
|
||||||
HTML CONTENT REQUIREMENTS:
|
CSS ANIMATIONS TO INCLUDE:
|
||||||
- Use semantic HTML5 elements
|
\`\`\`css
|
||||||
- Include inline CSS for styling
|
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
- Use modern gradients, shadows, and animations
|
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
|
||||||
- Incorporate icons using Unicode or SVG
|
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
||||||
- Ensure responsive design considerations
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
|
||||||
- Use professional typography (specify font-family)
|
\`\`\`
|
||||||
- Include color schemes matching the theme
|
|
||||||
|
|
||||||
CONTENT QUALITY:
|
SLIDE TYPES TO CREATE:
|
||||||
- Concise, impactful headlines (max 8 words)
|
1. TITLE SLIDE: Hero-style with animated gradient background, large typography
|
||||||
- Bullet points with 3-5 items maximum
|
2. AGENDA/OVERVIEW: Icon grid with staggered fade-in animations
|
||||||
- Relevant statistics with compelling visuals
|
3. DATA/CHARTS: Inline SVG bar/line/pie charts with animated drawing effects
|
||||||
- Professional, business-appropriate language
|
4. KEY METRICS: Large animated numbers with KPI cards
|
||||||
- Clear narrative flow between slides`,
|
5. TIMELINE: Horizontal/vertical timeline with sequential reveal animations
|
||||||
|
6. COMPARISON: Side-by-side cards with hover lift effects
|
||||||
|
7. QUOTE: Large typography with decorative quote marks
|
||||||
|
8. CALL-TO-ACTION: Bold CTA with pulsing button effect
|
||||||
|
|
||||||
|
TARGET AUDIENCE: ${audience}
|
||||||
|
AUDIENCE STYLE: ${audienceStyle}
|
||||||
|
${organization ? `ORGANIZATION BRANDING: ${organization}` : ""}
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
- Create EXACTLY ${slideCount} slides
|
||||||
|
- ALL content in ${language}
|
||||||
|
- Each slide MUST have complete htmlContent with inline <style> tags
|
||||||
|
- Use animation-delay for staggered reveal effects
|
||||||
|
- Include decorative background elements (gradients, shapes)
|
||||||
|
- Ensure text contrast meets WCAG AA standards
|
||||||
|
- Add subtle shadow/glow effects for depth`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userMessage: ChatMessage = {
|
const userMessage: ChatMessage = {
|
||||||
role: "user",
|
role: "user",
|
||||||
content: `Create a stunning presentation about: ${topic}
|
content: `Create a STUNNING, ANIMATED presentation about:
|
||||||
|
|
||||||
Requirements:
|
${topic}
|
||||||
|
|
||||||
|
SPECIFICATIONS:
|
||||||
- Language: ${language}
|
- Language: ${language}
|
||||||
- Theme: ${theme}
|
- Theme: ${theme}
|
||||||
- Number of slides: ${slideCount}
|
- Slides: ${slideCount}
|
||||||
- Target audience: ${audience}
|
- Audience: ${audience} (${audienceStyle})
|
||||||
|
- Animation Style: ${animationStyle}
|
||||||
${organization ? `- Organization: ${organization}` : ""}
|
${organization ? `- Organization: ${organization}` : ""}
|
||||||
|
${brandColors.length > 0 ? `- Brand Colors: ${brandColors.join(", ")}` : ""}
|
||||||
|
|
||||||
Generate the complete presentation with HTML5 content for each slide. Make it visually impressive and content-rich.`,
|
Generate SPECTACULAR slides with CSS3 animations, SVG charts, modern gradients, and corporate-ready design!`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
|
return this.chatCompletion([systemMessage, userMessage], model || "gpt-oss:120b");
|
||||||
@@ -398,3 +437,4 @@ Generate the complete presentation with HTML5 content for each slide. Make it vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default OllamaCloudService;
|
export default OllamaCloudService;
|
||||||
|
|
||||||
|
|||||||
@@ -639,85 +639,124 @@ Make's prompt specific, inspiring, and comprehensive. Use professional UX termin
|
|||||||
slideCount?: number;
|
slideCount?: number;
|
||||||
audience?: string;
|
audience?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
animationStyle?: string;
|
||||||
|
audienceStyle?: string;
|
||||||
|
themeColors?: string[];
|
||||||
|
brandColors?: string[];
|
||||||
} = {},
|
} = {},
|
||||||
model?: string
|
model?: string
|
||||||
): Promise<APIResponse<string>> {
|
): Promise<APIResponse<string>> {
|
||||||
const { language = "English", theme = "modern", slideCount = 10, audience = "general", organization = "" } = options;
|
const {
|
||||||
|
language = "English",
|
||||||
|
theme = "executive-dark",
|
||||||
|
slideCount = 10,
|
||||||
|
audience = "Executives & C-Suite",
|
||||||
|
organization = "",
|
||||||
|
animationStyle = "Professional",
|
||||||
|
audienceStyle = "Sophisticated, data-driven, strategic focus",
|
||||||
|
themeColors = ["#09090b", "#6366f1", "#a855f7", "#fafafa"],
|
||||||
|
brandColors = []
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const [bgColor, primaryColor, secondaryColor, textColor] = themeColors;
|
||||||
|
const brandColorStr = brandColors.length > 0
|
||||||
|
? `\nBRAND COLORS TO USE: ${brandColors.join(", ")}`
|
||||||
|
: "";
|
||||||
|
|
||||||
const systemMessage: ChatMessage = {
|
const systemMessage: ChatMessage = {
|
||||||
role: "system",
|
role: "system",
|
||||||
content: `You are a world-class presentation designer and content strategist specializing in creating stunning, impactful slide decks for organizations and professionals.
|
content: `You are a WORLD-CLASS presentation designer who creates STUNNING, AWARD-WINNING slide decks that rival McKinsey, Apple, and TED presentations.
|
||||||
|
|
||||||
Your task is to generate a complete presentation with HTML5-ready slide content. Each slide should be designed with modern, visually impressive aesthetics.
|
Your slides must be VISUALLY SPECTACULAR with:
|
||||||
|
- Modern CSS3 animations (fade-in, slide-in, scale, parallax effects)
|
||||||
|
- Sophisticated gradient backgrounds with depth
|
||||||
|
- SVG charts and data visualizations inline
|
||||||
|
- Glassmorphism and neumorphism effects
|
||||||
|
- Professional typography with Inter/SF Pro fonts
|
||||||
|
- Strategic use of whitespace
|
||||||
|
- Micro-animations on hover/focus states
|
||||||
|
- Progress indicators and visual hierarchy
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
OUTPUT FORMAT - Return ONLY valid JSON:
|
||||||
Return a JSON object with the following structure:
|
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
{
|
{
|
||||||
"title": "Presentation Title",
|
"title": "Presentation Title",
|
||||||
"subtitle": "Presentation Subtitle",
|
"subtitle": "Compelling Subtitle",
|
||||||
"theme": "${theme}",
|
"theme": "${theme}",
|
||||||
"language": "${language}",
|
"language": "${language}",
|
||||||
"slides": [
|
"slides": [
|
||||||
{
|
{
|
||||||
"id": "slide-1",
|
"id": "slide-1",
|
||||||
"title": "Slide Title",
|
"title": "Slide Title",
|
||||||
"content": "Main content text",
|
"content": "Plain text content summary",
|
||||||
"htmlContent": "<div class='slide'>Complete HTML content for the slide</div>",
|
"htmlContent": "<div>FULL HTML with inline CSS and animations</div>",
|
||||||
"notes": "Speaker notes",
|
"notes": "Speaker notes",
|
||||||
"layout": "title|content|two-column|quote|statistics|timeline|comparison",
|
"layout": "title|content|two-column|chart|statistics|timeline|quote|comparison",
|
||||||
"order": 1
|
"order": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
DESIGN GUIDELINES:
|
DESIGN SYSTEM:
|
||||||
1. Create ${slideCount} slides maximum
|
- Primary: ${brandColors[0] || primaryColor}
|
||||||
2. Use the "${theme}" design theme
|
- Secondary: ${brandColors[1] || secondaryColor}
|
||||||
3. All content MUST be in ${language}
|
- Background: ${bgColor}
|
||||||
4. Target audience: ${audience}
|
- Text: ${textColor}${brandColorStr}
|
||||||
${organization ? `5. Organization: ${organization}` : ""}
|
|
||||||
|
|
||||||
SLIDE TYPES TO INCLUDE:
|
ANIMATION STYLE: ${animationStyle}
|
||||||
- Title slide with compelling headline
|
- Professional: Subtle 0.3-0.5s ease transitions, fade and slide
|
||||||
- Problem/Opportunity statement
|
- Dynamic: 0.5-0.8s spring animations, emphasis effects, stagger delays
|
||||||
- Key insights with data visualizations
|
- Impressive: Bold 0.8-1.2s animations, parallax, morphing, particle effects
|
||||||
- Solution/Strategy overview
|
|
||||||
- Timeline or roadmap if applicable
|
|
||||||
- Key metrics or statistics
|
|
||||||
- Call-to-action or next steps
|
|
||||||
- Summary/Conclusion slide
|
|
||||||
|
|
||||||
HTML CONTENT REQUIREMENTS:
|
CSS ANIMATIONS TO INCLUDE:
|
||||||
- Use semantic HTML5 elements
|
\`\`\`css
|
||||||
- Include inline CSS for styling
|
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
- Use modern gradients, shadows, and animations
|
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
|
||||||
- Incorporate icons using Unicode or SVG
|
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
||||||
- Ensure responsive design considerations
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
|
||||||
- Use professional typography (specify font-family)
|
\`\`\`
|
||||||
- Include color schemes matching the theme
|
|
||||||
|
|
||||||
CONTENT QUALITY:
|
SLIDE TYPES TO CREATE:
|
||||||
- Concise, impactful headlines (max 8 words)
|
1. TITLE SLIDE: Hero-style with animated gradient background, large typography
|
||||||
- Bullet points with 3-5 items maximum
|
2. AGENDA/OVERVIEW: Icon grid with staggered fade-in animations
|
||||||
- Relevant statistics with compelling visuals
|
3. DATA/CHARTS: Inline SVG bar/line/pie charts with animated drawing effects
|
||||||
- Professional, business-appropriate language
|
4. KEY METRICS: Large animated numbers with KPI cards
|
||||||
- Clear narrative flow between slides`,
|
5. TIMELINE: Horizontal/vertical timeline with sequential reveal animations
|
||||||
|
6. COMPARISON: Side-by-side cards with hover lift effects
|
||||||
|
7. QUOTE: Large typography with decorative quote marks
|
||||||
|
8. CALL-TO-ACTION: Bold CTA with pulsing button effect
|
||||||
|
|
||||||
|
TARGET AUDIENCE: ${audience}
|
||||||
|
AUDIENCE STYLE: ${audienceStyle}
|
||||||
|
${organization ? `ORGANIZATION BRANDING: ${organization}` : ""}
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
- Create EXACTLY ${slideCount} slides
|
||||||
|
- ALL content in ${language}
|
||||||
|
- Each slide MUST have complete htmlContent with inline <style> tags
|
||||||
|
- Use animation-delay for staggered reveal effects
|
||||||
|
- Include decorative background elements (gradients, shapes)
|
||||||
|
- Ensure text contrast meets WCAG AA standards
|
||||||
|
- Add subtle shadow/glow effects for depth`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userMessage: ChatMessage = {
|
const userMessage: ChatMessage = {
|
||||||
role: "user",
|
role: "user",
|
||||||
content: `Create a stunning presentation about: ${topic}
|
content: `Create a STUNNING, ANIMATED presentation about:
|
||||||
|
|
||||||
Requirements:
|
${topic}
|
||||||
|
|
||||||
|
SPECIFICATIONS:
|
||||||
- Language: ${language}
|
- Language: ${language}
|
||||||
- Theme: ${theme}
|
- Theme: ${theme}
|
||||||
- Number of slides: ${slideCount}
|
- Slides: ${slideCount}
|
||||||
- Target audience: ${audience}
|
- Audience: ${audience} (${audienceStyle})
|
||||||
|
- Animation Style: ${animationStyle}
|
||||||
${organization ? `- Organization: ${organization}` : ""}
|
${organization ? `- Organization: ${organization}` : ""}
|
||||||
|
${brandColors.length > 0 ? `- Brand Colors: ${brandColors.join(", ")}` : ""}
|
||||||
|
|
||||||
Generate the complete presentation with HTML5 content for each slide. Make it visually impressive and content-rich.`,
|
Generate SPECTACULAR slides with CSS3 animations, SVG charts, modern gradients, and corporate-ready design!`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
|
return this.chatCompletion([systemMessage, userMessage], model || "coder-model");
|
||||||
@@ -740,3 +779,4 @@ Generate the complete presentation with HTML5 content for each slide. Make it vi
|
|||||||
const qwenOAuthService = new QwenOAuthService();
|
const qwenOAuthService = new QwenOAuthService();
|
||||||
export default qwenOAuthService;
|
export default qwenOAuthService;
|
||||||
export { qwenOAuthService };
|
export { qwenOAuthService };
|
||||||
|
|
||||||
|
|||||||
@@ -260,85 +260,149 @@ Make the prompt specific, inspiring, and comprehensive. Use professional UX term
|
|||||||
slideCount?: number;
|
slideCount?: number;
|
||||||
audience?: string;
|
audience?: string;
|
||||||
organization?: string;
|
organization?: string;
|
||||||
|
animationStyle?: string;
|
||||||
|
audienceStyle?: string;
|
||||||
|
themeColors?: string[];
|
||||||
|
brandColors?: string[];
|
||||||
} = {},
|
} = {},
|
||||||
model?: string
|
model?: string
|
||||||
): Promise<APIResponse<string>> {
|
): Promise<APIResponse<string>> {
|
||||||
const { language = "English", theme = "modern", slideCount = 10, audience = "general", organization = "" } = options;
|
const {
|
||||||
|
language = "English",
|
||||||
|
theme = "executive-dark",
|
||||||
|
slideCount = 10,
|
||||||
|
audience = "Executives & C-Suite",
|
||||||
|
organization = "",
|
||||||
|
animationStyle = "Professional",
|
||||||
|
audienceStyle = "Sophisticated, data-driven, strategic focus",
|
||||||
|
themeColors = ["#09090b", "#6366f1", "#a855f7", "#fafafa"],
|
||||||
|
brandColors = []
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const [bgColor, primaryColor, secondaryColor, textColor] = themeColors;
|
||||||
|
const brandColorStr = brandColors.length > 0
|
||||||
|
? `\nBRAND COLORS TO USE: ${brandColors.join(", ")}`
|
||||||
|
: "";
|
||||||
|
|
||||||
const systemMessage: ChatMessage = {
|
const systemMessage: ChatMessage = {
|
||||||
role: "system",
|
role: "system",
|
||||||
content: `You are a world-class presentation designer and content strategist specializing in creating stunning, impactful slide decks for organizations and professionals.
|
content: `You are a WORLD-CLASS presentation designer who creates STUNNING, AWARD-WINNING slide decks that rival McKinsey, Apple, and TED presentations.
|
||||||
|
|
||||||
Your task is to generate a complete presentation with HTML5-ready slide content. Each slide should be designed with modern, visually impressive aesthetics.
|
Your slides must be VISUALLY SPECTACULAR with:
|
||||||
|
- Modern CSS3 animations (fade-in, slide-in, scale, parallax effects)
|
||||||
|
- Sophisticated gradient backgrounds with depth
|
||||||
|
- SVG charts and data visualizations inline
|
||||||
|
- Glassmorphism and neumorphism effects
|
||||||
|
- Professional typography with Inter/SF Pro fonts
|
||||||
|
- Strategic use of whitespace
|
||||||
|
- Micro-animations on hover/focus states
|
||||||
|
- Progress indicators and visual hierarchy
|
||||||
|
|
||||||
OUTPUT FORMAT:
|
OUTPUT FORMAT - Return ONLY valid JSON:
|
||||||
Return a JSON object with the following structure:
|
|
||||||
\`\`\`json
|
\`\`\`json
|
||||||
{
|
{
|
||||||
"title": "Presentation Title",
|
"title": "Presentation Title",
|
||||||
"subtitle": "Presentation Subtitle",
|
"subtitle": "Compelling Subtitle",
|
||||||
"theme": "${theme}",
|
"theme": "${theme}",
|
||||||
"language": "${language}",
|
"language": "${language}",
|
||||||
"slides": [
|
"slides": [
|
||||||
{
|
{
|
||||||
"id": "slide-1",
|
"id": "slide-1",
|
||||||
"title": "Slide Title",
|
"title": "Slide Title",
|
||||||
"content": "Main content text",
|
"content": "Plain text content summary",
|
||||||
"htmlContent": "<div class='slide'>Complete HTML content for the slide</div>",
|
"htmlContent": "<div>FULL HTML with inline CSS and animations</div>",
|
||||||
"notes": "Speaker notes",
|
"notes": "Speaker notes",
|
||||||
"layout": "title|content|two-column|quote|statistics|timeline|comparison",
|
"layout": "title|content|two-column|chart|statistics|timeline|quote|comparison",
|
||||||
"order": 1
|
"order": 1
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
DESIGN GUIDELINES:
|
DESIGN SYSTEM:
|
||||||
1. Create ${slideCount} slides maximum
|
- Primary: ${brandColors[0] || primaryColor}
|
||||||
2. Use the "${theme}" design theme
|
- Secondary: ${brandColors[1] || secondaryColor}
|
||||||
3. All content MUST be in ${language}
|
- Background: ${bgColor}
|
||||||
4. Target audience: ${audience}
|
- Text: ${textColor}${brandColorStr}
|
||||||
${organization ? `5. Organization: ${organization}` : ""}
|
|
||||||
|
|
||||||
SLIDE TYPES TO INCLUDE:
|
ANIMATION STYLE: ${animationStyle}
|
||||||
- Title slide with compelling headline
|
- Professional: Subtle 0.3-0.5s ease transitions, fade and slide
|
||||||
- Problem/Opportunity statement
|
- Dynamic: 0.5-0.8s spring animations, emphasis effects, stagger delays
|
||||||
- Key insights with data visualizations
|
- Impressive: Bold 0.8-1.2s animations, parallax, morphing, particle effects
|
||||||
- Solution/Strategy overview
|
|
||||||
- Timeline or roadmap if applicable
|
|
||||||
- Key metrics or statistics
|
|
||||||
- Call-to-action or next steps
|
|
||||||
- Summary/Conclusion slide
|
|
||||||
|
|
||||||
HTML CONTENT REQUIREMENTS:
|
CSS ANIMATIONS TO INCLUDE:
|
||||||
- Use semantic HTML5 elements
|
\`\`\`css
|
||||||
- Include inline CSS for styling
|
@keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
|
||||||
- Use modern gradients, shadows, and animations
|
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
|
||||||
- Incorporate icons using Unicode or SVG
|
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
|
||||||
- Ensure responsive design considerations
|
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } }
|
||||||
- Use professional typography (specify font-family)
|
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-10px); } }
|
||||||
- Include color schemes matching the theme
|
@keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
CONTENT QUALITY:
|
SLIDE TYPES TO CREATE:
|
||||||
- Concise, impactful headlines (max 8 words)
|
1. TITLE SLIDE: Hero-style with animated gradient background, large typography, subtle floating elements
|
||||||
- Bullet points with 3-5 items maximum
|
2. AGENDA/OVERVIEW: Icon grid with staggered fade-in animations
|
||||||
- Relevant statistics with compelling visuals
|
3. DATA/CHARTS: Inline SVG bar/line/pie charts with animated drawing effects
|
||||||
- Professional, business-appropriate language
|
4. KEY METRICS: Large animated numbers with counting effect styling, KPI cards with glassmorphism
|
||||||
- Clear narrative flow between slides`,
|
5. TIMELINE: Horizontal/vertical timeline with sequential reveal animations
|
||||||
|
6. COMPARISON: Side-by-side cards with hover lift effects
|
||||||
|
7. QUOTE: Large typography with decorative quote marks, subtle background pattern
|
||||||
|
8. CALL-TO-ACTION: Bold CTA with pulsing button effect, clear next steps
|
||||||
|
|
||||||
|
SVG CHART EXAMPLE:
|
||||||
|
\`\`\`html
|
||||||
|
<svg viewBox="0 0 400 200" style="width:100%;max-width:400px;">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="barGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" style="stop-color:${primaryColor}"/>
|
||||||
|
<stop offset="100%" style="stop-color:${secondaryColor}"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="50" y="50" width="60" height="130" fill="url(#barGrad)" rx="8" style="animation: scaleIn 0.8s ease-out 0.2s both; transform-origin: bottom;"/>
|
||||||
|
<rect x="130" y="80" width="60" height="100" fill="url(#barGrad)" rx="8" style="animation: scaleIn 0.8s ease-out 0.4s both; transform-origin: bottom;"/>
|
||||||
|
<rect x="210" y="30" width="60" height="150" fill="url(#barGrad)" rx="8" style="animation: scaleIn 0.8s ease-out 0.6s both; transform-origin: bottom;"/>
|
||||||
|
</svg>
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
TARGET AUDIENCE: ${audience}
|
||||||
|
AUDIENCE STYLE: ${audienceStyle}
|
||||||
|
${organization ? `ORGANIZATION BRANDING: ${organization}` : ""}
|
||||||
|
|
||||||
|
REQUIREMENTS:
|
||||||
|
- Create EXACTLY ${slideCount} slides
|
||||||
|
- ALL content in ${language}
|
||||||
|
- Each slide MUST have complete htmlContent with inline <style> tags
|
||||||
|
- Use animation-delay for staggered reveal effects
|
||||||
|
- Include decorative background elements (gradients, shapes, patterns)
|
||||||
|
- Ensure text contrast meets WCAG AA standards
|
||||||
|
- Add subtle shadow/glow effects for depth
|
||||||
|
- Include progress/slide number indicator styling`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userMessage: ChatMessage = {
|
const userMessage: ChatMessage = {
|
||||||
role: "user",
|
role: "user",
|
||||||
content: `Create a stunning presentation about: ${topic}
|
content: `Create a STUNNING, ANIMATED presentation about:
|
||||||
|
|
||||||
Requirements:
|
${topic}
|
||||||
|
|
||||||
|
SPECIFICATIONS:
|
||||||
- Language: ${language}
|
- Language: ${language}
|
||||||
- Theme: ${theme}
|
- Theme: ${theme}
|
||||||
- Number of slides: ${slideCount}
|
- Slides: ${slideCount}
|
||||||
- Target audience: ${audience}
|
- Audience: ${audience} (${audienceStyle})
|
||||||
|
- Animation Style: ${animationStyle}
|
||||||
${organization ? `- Organization: ${organization}` : ""}
|
${organization ? `- Organization: ${organization}` : ""}
|
||||||
|
${brandColors.length > 0 ? `- Brand Colors: ${brandColors.join(", ")}` : ""}
|
||||||
|
|
||||||
Generate the complete presentation with HTML5 content for each slide. Make it visually impressive and content-rich.`,
|
Generate SPECTACULAR slides with:
|
||||||
|
✨ Animated CSS3 transitions and keyframes
|
||||||
|
📊 SVG charts and data visualizations where relevant
|
||||||
|
🎨 Modern gradients and glassmorphism effects
|
||||||
|
💫 Staggered reveal animations
|
||||||
|
🏢 Corporate-ready, executive-level design
|
||||||
|
|
||||||
|
Return the complete JSON with full htmlContent for each slide. Make each slide VISUALLY IMPRESSIVE and memorable!`,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.chatCompletion([systemMessage, userMessage], model || "glm-4.7", true);
|
return this.chatCompletion([systemMessage, userMessage], model || "glm-4.7", true);
|
||||||
@@ -346,3 +410,4 @@ Generate the complete presentation with HTML5 content for each slide. Make it vi
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default ZaiPlanService;
|
export default ZaiPlanService;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user