"use client"; import { useState, useCallback, useRef, useEffect } from "react"; import { createSpecStreamCompiler } from "@json-render/core"; import { Player, PlayerRef } from "@remotion/player"; import { Renderer, type TimelineSpec } from "@json-render/remotion"; import { createHighlighter, type Highlighter } from "shiki"; /** * Check if spec is complete enough to render */ function isSpecComplete(spec: TimelineSpec): spec is Required { return !!( spec.composition && spec.tracks && Array.isArray(spec.clips) && spec.clips.length > 0 ); } /** * Shiki theme (Vercel-inspired dark theme) */ const darkTheme = { name: "custom-dark", type: "dark" as const, colors: { "editor.background": "transparent", "editor.foreground": "#EDEDED", }, settings: [ { scope: ["string", "string.quoted"], settings: { foreground: "#50E3C2" }, }, { scope: [ "constant.numeric", "constant.language.boolean", "constant.language.null", ], settings: { foreground: "#50E3C2" }, }, { scope: ["punctuation", "meta.brace", "meta.bracket"], settings: { foreground: "#888888" }, }, { scope: ["support.type.property-name", "entity.name.tag.json"], settings: { foreground: "#EDEDED" }, }, ], }; // Preload highlighter let highlighterPromise: Promise | null = null; function getHighlighter() { if (!highlighterPromise) { highlighterPromise = createHighlighter({ themes: [darkTheme], langs: ["json"], }); } return highlighterPromise; } // Start loading immediately if (typeof window !== "undefined") { getHighlighter(); } /** * Code block with syntax highlighting */ function CodeBlock({ code }: { code: string }) { const [html, setHtml] = useState(""); useEffect(() => { getHighlighter().then((highlighter) => { setHtml( highlighter.codeToHtml(code, { lang: "json", theme: "custom-dark", }), ); }); }, [code]); if (!html) { return (
        {code}
      
); } return (
); } /** * Copy button component */ function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false); const handleCopy = async () => { await navigator.clipboard.writeText(text); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return ( ); } const EXAMPLE_PROMPTS = [ "Create a 10-second product intro with title cards and images", "Make a photo slideshow with 5 different images", "Build a testimonial video with quotes and background images", "Design a dynamic company intro with animated entrances", ]; export default function Home() { const [prompt, setPrompt] = useState(""); const [isGenerating, setIsGenerating] = useState(false); const [spec, setSpec] = useState(null); const [error, setError] = useState(null); const inputRef = useRef(null); const playerRef = useRef(null); const generate = useCallback(async () => { if (!prompt.trim() || isGenerating) return; setIsGenerating(true); setError(null); setSpec(null); try { const response = await fetch("/api/generate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt }), }); if (!response.ok) { throw new Error("Generation failed"); } const reader = response.body?.getReader(); if (!reader) throw new Error("No response body"); const decoder = new TextDecoder(); const compiler = createSpecStreamCompiler(); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value, { stream: true }); const { result, newPatches } = compiler.push(chunk); if (newPatches.length > 0) { setSpec(result); } } // Get final result (processes any remaining buffer) const finalSpec = compiler.getResult(); setSpec(finalSpec); // Final validation and auto-play if (isSpecComplete(finalSpec)) { // Auto-play after a short delay to ensure Player is mounted setTimeout(() => { playerRef.current?.play(); }, 100); } else { setError("Generated timeline is incomplete"); } } catch (err) { setError(err instanceof Error ? err.message : "Generation failed"); } finally { setIsGenerating(false); } }, [prompt, isGenerating]); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); generate(); }; const handleExampleClick = (examplePrompt: string) => { setPrompt(examplePrompt); setTimeout(() => inputRef.current?.focus(), 0); }; const handleExport = useCallback(() => { if (!spec) return; const blob = new Blob([JSON.stringify(spec, null, 2)], { type: "application/json", }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "timeline.json"; a.click(); URL.revokeObjectURL(url); }, [spec]); return (
{/* Hero */}
@json-render/remotion

AI → json-render → Video

Define a video catalog. Users prompt. AI outputs timeline JSON constrained to your components. Remotion renders it.

{/* Demo */}
{/* Prompt Input */}
> setPrompt(e.target.value)} placeholder="Describe the video you want to create..." className="flex-1 bg-transparent outline-none placeholder:text-muted-foreground/50" disabled={isGenerating} maxLength={500} /> {isGenerating ? ( ) : ( )}
{/* Example prompts */}
{EXAMPLE_PROMPTS.map((examplePrompt) => ( ))}
{error && (
{error}
)}
{/* JSON Panel */}
json {isGenerating && ( generating... )}
{spec && (
)} {spec ? ( ) : isGenerating ? (
Generating timeline...
) : (
Enter a prompt to generate a video timeline
)}
{/* Video Preview Panel */}
preview
{spec && isSpecComplete(spec) && ( )}
{spec && isSpecComplete(spec) ? ( ) : (
{isGenerating ? (
Generating timeline...
) : spec ? (
Building timeline...
) : ( "Enter a prompt to generate a video" )}
)}
); }