import React, { DragEvent, useEffect, useMemo, useRef, useState } from "react"; import { createRoot } from "react-dom/client"; import { Download, ExternalLink, FileText, Image as ImageIcon, Layers, LayoutTemplate, Loader2, Palette, Paperclip, Pencil, Save, Upload, WandSparkles, X } from "lucide-react"; import "./styles.css"; type Mode = "template-build" | "deck-build" | "template-edit" | "deck-edit"; type DeckResult = { id: string; title: string; slides: number; specUrl: string; pptxUrl: string; source?: { title?: string; type?: string; sourceName?: string; }; }; type DesignTemplate = { id: string; name: string; description: string; sourceName?: string; colors: string[]; updatedAt: string; locked?: boolean; }; const initialContent = `# Customer Discovery Notes Paste notes, transcript excerpts, or brief content here. - Confirm the top decisions. - Build an executive narrative. - Render a PPTX for review.`; const starterTemplate: DesignTemplate = { id: "incorta", name: "Incorta Starter", description: "White executive deck, orange brand accent, restrained blue secondary accents, Aptos typography, compact business density, fixed title/body/two-column/roadmap layouts.", colors: ["#ffffff", "#f05a28", "#1e6bff", "#172033", "#596579"], updatedAt: "2026-06-09T00:00:00.000Z", locked: true }; const modeTabs: Array<{ id: Mode; label: string; icon: React.ElementType }> = [ { id: "template-build", label: "Build Template", icon: LayoutTemplate }, { id: "deck-build", label: "Build Slide / Deck", icon: Layers }, { id: "template-edit", label: "Edit Template", icon: Palette }, { id: "deck-edit", label: "Edit Slide / Deck", icon: Pencil } ]; function App() { const [activeMode, setActiveMode] = useState("template-build"); const [templates, setTemplates] = useState([starterTemplate]); const [selectedTemplateId, setSelectedTemplateId] = useState(starterTemplate.id); const [content, setContent] = useState(initialContent); const [instructions, setInstructions] = useState("Create a concise executive deck."); const [file, setFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [dragging, setDragging] = useState(false); const [templateName, setTemplateName] = useState("New design standard"); const [templateDescription, setTemplateDescription] = useState( "Executive presentation system with clear hierarchy, reusable slide sections, consistent spacing, and restrained visual emphasis." ); const [templateFile, setTemplateFile] = useState(null); const [templatePreviewUrl, setTemplatePreviewUrl] = useState(null); const [templateDragging, setTemplateDragging] = useState(false); const [editTemplateId, setEditTemplateId] = useState(starterTemplate.id); const [editTemplateName, setEditTemplateName] = useState(starterTemplate.name); const [editTemplateDescription, setEditTemplateDescription] = useState(starterTemplate.description); const [result, setResult] = useState(null); const [notice, setNotice] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); const templateInputRef = useRef(null); useEffect(() => { try { const stored = window.localStorage.getItem("slideFactory.designTemplates.v1"); if (!stored) return; const parsed = JSON.parse(stored) as DesignTemplate[]; const customTemplates = parsed.filter((template) => template.id !== starterTemplate.id); setTemplates([starterTemplate, ...customTemplates]); } catch { setTemplates([starterTemplate]); } }, []); useEffect(() => { const customTemplates = templates.filter((template) => !template.locked); window.localStorage.setItem("slideFactory.designTemplates.v1", JSON.stringify(customTemplates)); }, [templates]); const selectedTemplate = useMemo( () => templates.find((template) => template.id === selectedTemplateId) || starterTemplate, [selectedTemplateId, templates] ); const editableTemplate = useMemo( () => templates.find((template) => template.id === editTemplateId) || starterTemplate, [editTemplateId, templates] ); const activeTemplatePreview = useMemo(() => { if (activeMode === "template-build") { return { id: "draft-template", name: templateName.trim() || "Untitled design standard", description: templateDescription.trim(), sourceName: templateFile?.name, colors: starterTemplate.colors, updatedAt: new Date().toISOString() }; } if (activeMode === "template-edit") { return { ...editableTemplate, name: editTemplateName.trim() || editableTemplate.name, description: editTemplateDescription.trim() }; } return selectedTemplate; }, [ activeMode, editableTemplate, editTemplateDescription, editTemplateName, selectedTemplate, templateDescription, templateFile, templateName ]); const sourceLabel = useMemo(() => { if (file) return `${file.name} · ${formatBytes(file.size)}`; const chars = content.trim().length; return chars ? `${chars.toLocaleString()} characters pasted` : "No source selected"; }, [content, file]); const templateSourceLabel = useMemo(() => { if (templateFile) return `${templateFile.name} · ${formatBytes(templateFile.size)}`; return "No design source selected"; }, [templateFile]); function setSourceFile(nextFile: File | null) { if (previewUrl) URL.revokeObjectURL(previewUrl); setFile(nextFile); setResult(null); setError(null); if (nextFile?.type.startsWith("image/")) { setPreviewUrl(URL.createObjectURL(nextFile)); } else { setPreviewUrl(null); } } function setTemplateSourceFile(nextFile: File | null) { if (templatePreviewUrl) URL.revokeObjectURL(templatePreviewUrl); setTemplateFile(nextFile); setNotice(null); setError(null); if (nextFile?.type.startsWith("image/")) { setTemplatePreviewUrl(URL.createObjectURL(nextFile)); } else { setTemplatePreviewUrl(null); } } function onSourceDrop(event: DragEvent) { event.preventDefault(); setDragging(false); const dropped = event.dataTransfer.files?.[0]; if (dropped) setSourceFile(dropped); } function onTemplateDrop(event: DragEvent) { event.preventDefault(); setTemplateDragging(false); const dropped = event.dataTransfer.files?.[0]; if (dropped) setTemplateSourceFile(dropped); } async function onPaste(event: React.ClipboardEvent) { const image = Array.from(event.clipboardData.files).find((item) => item.type.startsWith("image/")); if (image) { event.preventDefault(); setSourceFile(image); } } function saveTemplate() { const name = templateName.trim() || "Untitled design standard"; const template: DesignTemplate = { id: `template-${Date.now()}`, name, description: templateDescription.trim(), sourceName: templateFile?.name, colors: starterTemplate.colors, updatedAt: new Date().toISOString() }; setTemplates((current) => [starterTemplate, template, ...current.filter((item) => item.id !== starterTemplate.id)]); setSelectedTemplateId(template.id); setEditTemplateId(template.id); setEditTemplateName(template.name); setEditTemplateDescription(template.description); setNotice(`${template.name} saved`); } function loadEditableTemplate(templateId: string) { const template = templates.find((item) => item.id === templateId) || starterTemplate; setEditTemplateId(template.id); setEditTemplateName(template.name); setEditTemplateDescription(template.description); setNotice(null); setError(null); } function updateTemplate() { if (editableTemplate.locked) { const copy: DesignTemplate = { ...editableTemplate, id: `template-${Date.now()}`, name: `${editTemplateName.trim() || editableTemplate.name} Copy`, description: editTemplateDescription.trim(), locked: false, updatedAt: new Date().toISOString() }; setTemplates((current) => [starterTemplate, copy, ...current.filter((item) => item.id !== starterTemplate.id)]); setSelectedTemplateId(copy.id); setEditTemplateId(copy.id); setEditTemplateName(copy.name); setNotice(`${copy.name} saved`); return; } setTemplates((current) => current.map((template) => template.id === editTemplateId ? { ...template, name: editTemplateName.trim() || template.name, description: editTemplateDescription.trim(), updatedAt: new Date().toISOString() } : template ) ); setNotice(`${editTemplateName.trim() || editableTemplate.name} updated`); } function removeEditableTemplate() { if (editableTemplate.locked) return; setTemplates((current) => current.filter((template) => template.id !== editableTemplate.id)); setSelectedTemplateId(starterTemplate.id); loadEditableTemplate(starterTemplate.id); setNotice(`${editableTemplate.name} removed`); } async function createDeck() { setBusy(true); setError(null); setNotice(null); setResult(null); try { const response = file ? await createDeckFromFile(file) : await createDeckFromText(); const json = await response.json(); if (!response.ok) throw new Error(json.error || "Deck generation failed"); setResult(json); } catch (err) { setError(err instanceof Error ? err.message : "Unknown error"); } finally { setBusy(false); } } function designAwareInstructions() { return [ instructions, "", `Selected design template: ${selectedTemplate.name}.`, selectedTemplate.description ] .filter(Boolean) .join("\n"); } function createDeckFromFile(sourceFile: File) { const formData = new FormData(); formData.set("source", sourceFile); formData.set("style", "incorta"); formData.set("audience", "executives"); formData.set("instructions", designAwareInstructions()); return fetch("/api/decks/from-source", { method: "POST", body: formData }); } function createDeckFromText() { return fetch("/api/decks", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ style: "incorta", instructions: designAwareInstructions(), input: { type: "markdown", content } }) }); } const title = modeTabs.find((mode) => mode.id === activeMode)?.label || "Slide Factory"; return (

Slide Factory

{title}

Google Slides workflow {activeMode === "template-build" && ( )} {activeMode === "deck-build" && ( )} {activeMode === "template-edit" && ( )} {activeMode === "deck-edit" && ( )}
{activeMode === "template-build" && ( )} {activeMode === "deck-build" && ( )} {activeMode === "template-edit" && ( )} {activeMode === "deck-edit" && ( )}
); } function TemplateBuildPane(props: { dragging: boolean; inputRef: React.RefObject; name: string; previewUrl: string | null; sourceLabel: string; description: string; onDrop: (event: DragEvent) => void; onDragging: (dragging: boolean) => void; onFile: (file: File | null) => void; onName: (value: string) => void; onDescription: (value: string) => void; onSave: () => void; }) { return ( <>
{props.sourceLabel} {props.previewUrl && ( )}
{props.previewUrl && (
)}