import { useState } from "react"; import { X, Sparkles, Wand2, Loader2, Check, ArrowRight } from "lucide-react"; import { useStore, type SceneSpec } from "../store"; import { MG_THEME_LIST, MG_ACCENTS, MG_TEMPLATES, type ThemeKey } from "../remotion/mg/templates"; import { MG_FONTS } from "../remotion/mg/catalog.mjs"; import { aiHeaders } from "../lib/aiKeys"; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); // the cohesive subset of type systems worth surfacing here (caps-display + Arabic presets are omitted — // they only read well with very short copy / RTL). "Auto" lets the AI director pick the best-fitting one. const FONT_PICK_IDS = ["grotesk", "clean", "geometric", "modern", "tech", "editorial", "elegant", "magazine", "mono", "condensed"]; const FONT_PICKS = FONT_PICK_IDS.map((id) => MG_FONTS.find((f: { id: string }) => f.id === id)).filter(Boolean) as { id: string; label: string; blurb: string; preview: string }[]; // Guided, step-by-step generator: pick a look → AI writes on-topic scenes → // they drop onto the timeline one at a time so you can watch the build. export function GenerateScenesModal({ open, onClose }: { open: boolean; onClose: () => void }) { const projectName = useStore((s) => s.projectName); const words = useStore((s) => s.words); const sections = useStore((s) => s.sections); const addGeneratedScene = useStore((s) => s.addGeneratedScene); const selectMotion = useStore((s) => s.selectMotion); const [theme, setTheme] = useState("dark"); const [accent, setAccent] = useState(MG_ACCENTS[0]); const [font, setFont] = useState(""); // "" = Auto (the AI picks one cohesive type system for the whole set) const [count, setCount] = useState(5); const [topic, setTopic] = useState(""); const [phase, setPhase] = useState<"setup" | "running" | "done">("setup"); const [log, setLog] = useState<{ label: string; template: string; state: "pending" | "active" | "done" }[]>([]); const [err, setErr] = useState(""); const [skipped, setSkipped] = useState(0); if (!open) return null; async function run() { setErr(""); setPhase("running"); setLog([]); try { const res = await fetch("/api/generate-scenes", { method: "POST", headers: { "Content-Type": "application/json", ...aiHeaders() }, body: JSON.stringify({ topic: topic.trim() || projectName, count, font: font || undefined, // omit → AI picks one cohesive typeface; set → locks it transcript: words.slice(0, 400).map((w) => w.text).join(" "), sections: sections.map((s) => ({ title: s.title })), }), }).catch(() => { throw new Error("couldn't reach the generator — is the dev server running? try again"); }); const d = await res.json(); if (!res.ok || !Array.isArray(d.scenes) || !d.scenes.length) throw new Error(d.error || "no scenes returned"); const specs: SceneSpec[] = d.scenes.filter((s: SceneSpec) => s.template && MG_TEMPLATES[s.template]); if (!specs.length) throw new Error("the model returned no usable scenes — try again"); // ONE type system for the whole sequence (user pick wins → else the AI's choice → else a cohesive default), // applied to every scene alongside the shared theme+accent so colors AND fonts stay consistent. const seqFont = font || d.font || "clean"; setSkipped((d.dropped || 0) + (d.scenes.length - specs.length)); setLog(specs.map((s) => ({ label: s.label || s.template, template: s.template, state: "pending" }))); // drop them in one at a time → visible step-by-step build let firstId = ""; for (let i = 0; i < specs.length; i++) { setLog((l) => l.map((x, j) => (j === i ? { ...x, state: "active" } : x))); await sleep(420); const id = addGeneratedScene({ ...specs[i], theme, accent, font: seqFont }); if (!firstId) firstId = id; setLog((l) => l.map((x, j) => (j === i ? { ...x, state: "done" } : x))); } if (firstId) selectMotion(firstId); // open the first scene in the inspector for tweaks setPhase("done"); } catch (e) { setErr(e instanceof Error ? e.message : String(e)); setPhase("setup"); } } return (
e.stopPropagation()}>
Generate motion graphics
{phase !== "done" ? (

The AI will design {count} scenes written about {topic.trim() || projectName} — unique to this project, then place them on your timeline. Edit any of them after.

Topic — defaults to the project name
setTopic(e.target.value)} placeholder={projectName} className="w-full rounded-md border border-border bg-surface px-3 py-2 text-[13px] outline-none focus:border-primary" />
Look
{MG_THEME_LIST.map((t) => ( ))}
Accent
{MG_ACCENTS.map((c) => (
Typeface — one type system across every scene
{FONT_PICKS.map((f) => ( ))}
Scenes · {count}
setCount(Number(e.target.value))} className="w-full accent-primary" />
Art-directed by the Eldouma AI director · {Object.keys(MG_TEMPLATES).length} cinematic templates · ~10s
{err &&
⚠️ {err}
} {phase === "running" ? (
{log.map((s, i) => (
{s.state === "done" ? : s.state === "active" ? : } {s.label} {s.template}
))} {!log.length &&
Designing scenes about “{topic.trim() || projectName}”…
}
) : ( )}
) : (
Added {log.length} scenes to your timeline
{skipped > 0 &&

({skipped} scene{skipped === 1 ? "" : "s"} from the model were unusable and skipped.)

}

They're laid out end-to-end. The first one is open in the editor on the left — click any scene (timeline or list) to tweak its text, colors, and timing.

)}
); }