import { useEffect, useState } from "react"; import { Bot, Clock, Film, RefreshCw, Sparkles, UserCircle } from "lucide-react"; interface Project { id: string; title: string; content?: string | null; synopsis?: string | null; description?: string | null; aspect_ratio: string; duration_seconds: number | null; status: string; characters: string[]; metadata: Record; created_at?: string; updated_at?: string; } interface ProjectsResponse { projects?: Project[]; } interface ProjectsViewProps { compact?: boolean; onOpenProject?: (id: string) => void; } const STATUS_STYLES: Record = { draft: "border-gray-700/60 bg-gray-800/40 text-gray-400", planning: "border-violet-500/30 bg-violet-500/10 text-violet-300", ready: "border-emerald-500/30 bg-emerald-500/10 text-emerald-300", rendering: "border-amber-500/30 bg-amber-500/10 text-amber-300", completed: "border-blue-500/30 bg-blue-500/10 text-blue-300", failed: "border-red-500/30 bg-red-500/10 text-red-300", }; function formatRelative(iso?: string): string { if (!iso) return "—"; const then = new Date(iso).getTime(); if (Number.isNaN(then)) return "—"; const diff = Date.now() - then; const mins = Math.round(diff / 60000); if (mins < 1) return "just now"; if (mins < 60) return `${mins}m ago`; const hours = Math.round(mins / 60); if (hours < 24) return `${hours}h ago`; const days = Math.round(hours / 24); return `${days}d ago`; } function projectText(project: Project): string | null { return project.synopsis || project.content || project.description || null; } export function ProjectsView({ compact = false, onOpenProject }: ProjectsViewProps) { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); async function load() { setError(null); try { const response = await fetch("/api/projects"); if (!response.ok) throw new Error(`/api/projects returned ${response.status}`); const data = await response.json() as ProjectsResponse; setProjects(data.projects || []); } catch (e) { setError(e instanceof Error ? e.message : "Failed to load projects"); } finally { setLoading(false); } } useEffect(() => { load(); }, []); return (

Projects

Multi-shot stories your agent is directing.

What this tab is for

This is not raw generation. This is where a rough idea becomes a structured short: synopsis, scenes, shots, and image prompts.

Tell your agent

“Put me inside an Iron Man-style short. Workshop, helmet reveal, first flight. Around 30 seconds.”

“Make a dark cyberpunk teaser with my character walking through neon rain.”

Agent workflow

  1. Agent writes the project outline in conversation.
  2. Agent creates project, scenes, and shots through the API.
  3. User reviews the outline here before rendering.
  4. Agent generates storyboard images shot by shot.
  5. Approved images are animated into video clips.

Existing projects

{projects.length}
{error && (
{error}
)} {!loading && !error && projects.length === 0 && (

No projects yet.

Ask your agent to draft one, then it will appear here.

)} {projects.map((project) => { const statusStyle = STATUS_STYLES[project.status] || STATUS_STYLES.draft; const text = projectText(project); return (
onOpenProject?.(project.id)} className={`rounded-xl border border-gray-800/60 bg-gray-900/30 p-3 space-y-2 ${onOpenProject ? "cursor-pointer hover:bg-gray-900/50 hover:border-gray-700 transition" : ""}`} >

{project.title}

{text &&

{text}

}
{project.status}
{project.aspect_ratio} {project.duration_seconds !== null && ( {project.duration_seconds}s )} {formatRelative(project.updated_at)}
); })} {loading && projects.length === 0 && !error && (

Loading projects…

)}
); }