Spaces:
Sleeping
Sleeping
| import { useState } from 'react'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| import { Toaster } from 'react-hot-toast'; | |
| import VideoInput from './components/VideoInput'; | |
| import AnalysisDashboard from './components/AnalysisDashboard'; | |
| import ClipCustomizer from './components/ClipCustomizer'; | |
| import GenerationTracker from './components/GenerationTracker'; | |
| import DownloadGrid from './components/DownloadGrid'; | |
| const STEPS = ['input', 'analysis', 'customize', 'generate', 'download']; | |
| function App() { | |
| const [currentStep, setCurrentStep] = useState('input'); | |
| const [sessionId, setSessionId] = useState(null); | |
| const [metadata, setMetadata] = useState(null); | |
| const [analysis, setAnalysis] = useState(null); | |
| const [clipConfigs, setClipConfigs] = useState([]); | |
| const [generationStatus, setGenerationStatus] = useState(null); | |
| const goToStep = (step) => setCurrentStep(step); | |
| const handleVideoLoaded = (sessionData) => { | |
| setSessionId(sessionData.session_id); | |
| setMetadata(sessionData.metadata); | |
| goToStep('analysis'); | |
| }; | |
| const handleAnalysisComplete = (analysisData) => { | |
| setAnalysis(analysisData); | |
| // Initialize clip configs from candidates | |
| const configs = analysisData.clip_candidates.map((candidate) => ({ | |
| clip_id: candidate.id, | |
| title: candidate.title || '', | |
| caption_style: 'heatwave', | |
| voice_preset: 'energetic_male', | |
| music_genre: 'energetic_electronic', | |
| color_grade: 'teal_orange', | |
| include_original_audio: false, | |
| include_stock_images: true, | |
| })); | |
| setClipConfigs(configs); | |
| goToStep('customize'); | |
| }; | |
| const handleCustomizeDone = (configs) => { | |
| setClipConfigs(configs); | |
| goToStep('generate'); | |
| }; | |
| const handleGenerationComplete = (status) => { | |
| setGenerationStatus(status); | |
| goToStep('download'); | |
| }; | |
| return ( | |
| <div className="min-h-screen bg-dark-950"> | |
| <Toaster position="top-right" toastOptions={{ | |
| style: { background: '#1a1b2e', color: '#fff', border: '1px solid rgba(255,255,255,0.1)' } | |
| }} /> | |
| {/* Header */} | |
| <header className="border-b border-white/5 bg-dark-900/50 backdrop-blur-xl sticky top-0 z-50"> | |
| <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between"> | |
| <div className="flex items-center gap-3"> | |
| <span className="text-2xl">🎬</span> | |
| <h1 className="text-xl font-bold gradient-text">ViralClipFactory</h1> | |
| </div> | |
| {/* Progress Steps */} | |
| <div className="hidden md:flex items-center gap-2"> | |
| {STEPS.map((step, i) => ( | |
| <div key={step} className="flex items-center"> | |
| <div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold transition-all | |
| ${currentStep === step ? 'bg-primary-500 text-white glow' : | |
| STEPS.indexOf(currentStep) > i ? 'bg-green-500/20 text-green-400 border border-green-500/30' : | |
| 'bg-dark-800 text-white/30 border border-white/10'}`}> | |
| {STEPS.indexOf(currentStep) > i ? '✓' : i + 1} | |
| </div> | |
| {i < STEPS.length - 1 && ( | |
| <div className={`w-8 h-0.5 mx-1 ${STEPS.indexOf(currentStep) > i ? 'bg-green-500/50' : 'bg-white/10'}`} /> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| {sessionId && ( | |
| <div className="text-xs text-white/40 font-mono"> | |
| Session: {sessionId} | |
| </div> | |
| )} | |
| </div> | |
| </header> | |
| {/* Main Content */} | |
| <main className="max-w-7xl mx-auto px-6 py-8"> | |
| <AnimatePresence mode="wait"> | |
| {currentStep === 'input' && ( | |
| <motion.div key="input" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}> | |
| <VideoInput onVideoLoaded={handleVideoLoaded} /> | |
| </motion.div> | |
| )} | |
| {currentStep === 'analysis' && ( | |
| <motion.div key="analysis" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}> | |
| <AnalysisDashboard | |
| sessionId={sessionId} | |
| metadata={metadata} | |
| onAnalysisComplete={handleAnalysisComplete} | |
| /> | |
| </motion.div> | |
| )} | |
| {currentStep === 'customize' && ( | |
| <motion.div key="customize" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}> | |
| <ClipCustomizer | |
| candidates={analysis?.clip_candidates || []} | |
| configs={clipConfigs} | |
| onDone={handleCustomizeDone} | |
| /> | |
| </motion.div> | |
| )} | |
| {currentStep === 'generate' && ( | |
| <motion.div key="generate" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}> | |
| <GenerationTracker | |
| sessionId={sessionId} | |
| clipConfigs={clipConfigs} | |
| onComplete={handleGenerationComplete} | |
| /> | |
| </motion.div> | |
| )} | |
| {currentStep === 'download' && ( | |
| <motion.div key="download" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }}> | |
| <DownloadGrid | |
| sessionId={sessionId} | |
| clips={generationStatus?.clips || []} | |
| /> | |
| </motion.div> | |
| )} | |
| </AnimatePresence> | |
| </main> | |
| {/* Footer */} | |
| <footer className="border-t border-white/5 py-6 mt-20"> | |
| <div className="max-w-7xl mx-auto px-6 text-center text-white/30 text-sm"> | |
| Built with 🔥 by ViralClipFactory • AI-Powered Viral Content Generator | |
| </div> | |
| </footer> | |
| </div> | |
| ); | |
| } | |
| export default App; | |