"use client"; import React, { useState, useEffect } from "react"; import { useSearchParams } from "next/navigation"; import { motion, AnimatePresence } from "framer-motion"; import { Loader2, Upload, Link as LinkIcon, FileText, CheckCircle, AlertCircle, Download, Play, Layout, Clock, Settings, Menu, X, Video, ChevronRight, Sparkles } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input, Textarea } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { api, type InputType, type Category, type JobStatus } from "@/lib/api"; import { cn } from "@/lib/utils"; import Link from "next/link"; import { supabaseClient } from "@/lib/supabase-client"; export function VideoGenerator() { const searchParams = useSearchParams(); const [inputType, setInputType] = useState("text"); const [category, setCategory] = useState("tech_system"); const [content, setContent] = useState(""); const [isGenerating, setIsGenerating] = useState(false); const [currentJob, setCurrentJob] = useState(null); const [error, setError] = useState(null); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [credits, setCredits] = useState(0); const [demoVideoUrl, setDemoVideoUrl] = useState(null); const supabase = supabaseClient; // Fetch credits useEffect(() => { const fetchCredits = async () => { const { data: { user } } = await supabase.auth.getUser(); if (user) { const { data } = await supabase .from("users") .select("credits") .eq("id", user.id) .single(); setCredits(data?.credits ?? 0); } }; fetchCredits(); }, [supabase]); // Initialize from URL params useEffect(() => { const promptParam = searchParams.get("prompt"); const categoryParam = searchParams.get("category"); const demoUrlParam = searchParams.get("demoVideoUrl"); if (promptParam) { setContent(promptParam); setInputType("text"); } if (categoryParam && ["tech_system", "product_startup", "mathematical"].includes(categoryParam)) { setCategory(categoryParam as Category); } if (demoUrlParam) { setDemoVideoUrl(demoUrlParam); // Removed auto-start: simulateGeneration(demoUrlParam); } else { setDemoVideoUrl(null); } }, [searchParams]); const simulateGeneration = (videoUrl: string) => { setIsGenerating(true); const jobId = "demo-" + Date.now(); const steps = [ { percentage: 10, message: "Analyzing Input..." }, { percentage: 30, message: "Generating Script..." }, { percentage: 60, message: "Validating Code..." }, { percentage: 85, message: "Rendering Frames..." }, { percentage: 100, message: "Finalizing..." } ]; let step = 0; // Clear any existing job setCurrentJob({ job_id: jobId, status: "pending", progress: { percentage: 0, message: "Initializing..." }, created_at: new Date().toISOString() }); const interval = setInterval(() => { if (step >= steps.length) { clearInterval(interval); setCurrentJob({ job_id: jobId, status: "completed", progress: { percentage: 100, message: "Completed" }, created_at: new Date().toISOString() }); setIsGenerating(false); return; } setCurrentJob({ job_id: jobId, status: "rendering", progress: steps[step], created_at: new Date().toISOString() }); step++; }, 1000); // 1 second per step for a nice flow }; // Poll for job status useEffect(() => { let interval: NodeJS.Timeout; // Only poll if it's NOT a demo job (demo jobs start with "demo-") if (currentJob && ["pending", "generating_code", "rendering"].includes(currentJob.status) && !currentJob.job_id.startsWith("demo-")) { interval = setInterval(async () => { try { const status = await api.getJobStatus(currentJob.job_id); setCurrentJob(status); if (status.status === "failed") { setError(status.error || "Job failed"); setIsGenerating(false); } else if (status.status === "completed") { setIsGenerating(false); } } catch (e) { console.error("Polling error", e); } }, 2000); } return () => clearInterval(interval); }, [currentJob]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!content) return; // If we have a demo video URL, simulate the generation instead of calling the API if (demoVideoUrl) { simulateGeneration(demoVideoUrl); return; } setIsGenerating(true); setError(null); setCurrentJob(null); // setDemoVideoUrl(null); // Don't clear it here, we might want to keep it if user retries? // Actually, if it's a real generation, we should probably clear it. // But wait, if we are here, demoVideoUrl is NULL (because of the check above). // So we don't need to clear it. try { const job = await api.createVideo(content, inputType, category); setCurrentJob({ job_id: job.job_id, status: "pending", progress: { percentage: 0, message: "Initializing system..." }, created_at: new Date().toISOString() }); // Optimistically update credits setCredits(prev => Math.max(0, prev - 1)); } catch (e: any) { setError(e.message); setIsGenerating(false); } }; const categories: { value: Category; label: string; description: string }[] = [ { value: "tech_system", label: "Tech & Systems", description: "Architecture, Data Flow, APIs" }, { value: "product_startup", label: "Product Demo", description: "Features, Value Prop, UI/UX" }, { value: "mathematical", label: "Math & Research", description: "Equations, Graphs, Concepts" }, ]; const SHOWCASE_KEYWORDS = [ "database sharding", "kafka", "transformers", "quantum entanglement", "netflix", "black hole", "sorting algorithms", "uber", "sorting", "bubble sort", "url shortener" ]; const isShowcasePrompt = SHOWCASE_KEYWORDS.some(keyword => (content || "").toLowerCase().includes(keyword)); const canEdit = credits > 0; const canGenerate = canEdit || isShowcasePrompt; return (
{/* Mobile Sidebar Overlay */} {isSidebarOpen && ( setIsSidebarOpen(false)} className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 md:hidden" /> )} {/* Sidebar */}
VidSimplify
Credits
{credits} / 5
{/* Main Content */}
{/* App Header */}

Project / Untitled Animation

System Operational
{/* Left Column: Input Configuration */}

Configure Generation

Define the parameters for your AI-generated animation.

{/* Input Source */}
{[ { id: "text", icon: FileText, label: "Text Prompt" }, { id: "url", icon: LinkIcon, label: "URL / Blog" }, { id: "pdf", icon: Upload, label: "PDF Document" } ].map((type) => ( ))}
{/* Animation Style */}
{categories.map((cat) => ( ))}
{/* Content Input */}
{inputType === "text" ? (