Spaces:
Sleeping
Sleeping
| import { useEffect, useState } from 'react'; | |
| interface Props { | |
| segmentsCount: number; | |
| currentSegmentIndex?: number; // Optional for streaming mode | |
| realProgress?: number; // Optional for streaming mode | |
| generatedSegments?: any[]; // Optional for streaming mode | |
| onCancel?: () => void; | |
| isStreaming?: boolean; // If true, use real progress; if false, simulate | |
| } | |
| const GENERATION_TIPS = [ | |
| "π‘ AI is analyzing your reference image for character details...", | |
| "π¬ Creating detailed camera movements for cinematic quality...", | |
| "π€ Generating character descriptions with exact physical features...", | |
| "π Crafting micro-expressions and natural gestures...", | |
| "π¨ Designing scene continuity for seamless video flow...", | |
| "π― Ensuring dialogue syncs perfectly with actions...", | |
| "β¨ Adding production-quality details to each segment...", | |
| "π Cross-checking consistency across all segments...", | |
| "πΎ Auto-saving your prompts for recovery...", | |
| "πͺ Almost done! Finalizing segment specifications..." | |
| ]; | |
| export function SegmentGenerationProgress({ segmentsCount, onCancel }: Props) { | |
| const [progress, setProgress] = useState(0); | |
| const [currentTip, setCurrentTip] = useState(0); | |
| const [elapsedTime, setElapsedTime] = useState(0); | |
| const [estimatedTime] = useState( | |
| segmentsCount <= 2 ? 20 : segmentsCount <= 5 ? 50 : 90 | |
| ); | |
| // Progress simulation (realistic timing based on actual API performance) | |
| useEffect(() => { | |
| const interval = setInterval(() => { | |
| setProgress(prev => { | |
| // Slow start (first 30%), medium middle (30-80%), slow end (80-100%) | |
| if (prev < 30) return prev + 0.5; // Slow start | |
| if (prev < 80) return prev + 0.8; // Medium speed | |
| if (prev < 95) return prev + 0.2; // Slow down near end | |
| return prev; // Stop at 95% until actual completion | |
| }); | |
| }, 1000); | |
| return () => clearInterval(interval); | |
| }, []); | |
| // Tip rotation | |
| useEffect(() => { | |
| const interval = setInterval(() => { | |
| setCurrentTip(prev => (prev + 1) % GENERATION_TIPS.length); | |
| }, 4000); // Change tip every 4 seconds | |
| return () => clearInterval(interval); | |
| }, []); | |
| // Elapsed time counter | |
| useEffect(() => { | |
| const interval = setInterval(() => { | |
| setElapsedTime(prev => prev + 1); | |
| }, 1000); | |
| return () => clearInterval(interval); | |
| }, []); | |
| const formatTime = (seconds: number) => { | |
| const mins = Math.floor(seconds / 60); | |
| const secs = seconds % 60; | |
| return mins > 0 ? `${mins}m ${secs}s` : `${secs}s`; | |
| }; | |
| return ( | |
| <div className="max-w-2xl mx-auto p-8 glass-dark rounded-2xl shadow-2xl border-2 border-void-700/50"> | |
| {/* Header */} | |
| <div className="flex justify-between items-center mb-6"> | |
| <div className="flex items-center gap-2 bg-coral-500/20 px-4 py-2 rounded-full border border-coral-500/30"> | |
| <div className="w-2 h-2 bg-coral-400 rounded-full animate-pulse" /> | |
| <span className="text-sm font-medium text-void-100">Generating {segmentsCount} segments</span> | |
| </div> | |
| <div className="font-mono text-lg font-bold text-void-100"> | |
| <span className="text-coral-400">{formatTime(elapsedTime)}</span> | |
| <span className="text-void-600 mx-1">/</span> | |
| <span className="text-void-400">~{formatTime(estimatedTime)}</span> | |
| </div> | |
| </div> | |
| {/* Progress Bar */} | |
| <div className="relative h-10 bg-void-900/80 border border-void-700 rounded-full overflow-hidden mb-8"> | |
| <div | |
| className="h-full bg-gradient-to-r from-coral-500 to-electric-500 rounded-full transition-all duration-500 ease-out relative" | |
| style={{ width: `${progress}%` }} | |
| > | |
| <div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent animate-shimmer" /> | |
| </div> | |
| <span className="absolute inset-0 flex items-center justify-center font-bold text-void-100 drop-shadow-lg"> | |
| {Math.round(progress)}% | |
| </span> | |
| </div> | |
| {/* Generation Steps */} | |
| <div className="grid grid-cols-4 gap-4 mb-8"> | |
| <GenerationStep | |
| icon="π" | |
| label="Analyzing" | |
| status={progress < 20 ? 'active' : 'complete'} | |
| /> | |
| <GenerationStep | |
| icon="π¬" | |
| label="Creating" | |
| status={progress < 20 ? 'pending' : progress < 80 ? 'active' : 'complete'} | |
| /> | |
| <GenerationStep | |
| icon="β " | |
| label="Validating" | |
| status={progress < 80 ? 'pending' : progress < 95 ? 'active' : 'complete'} | |
| /> | |
| <GenerationStep | |
| icon="πΎ" | |
| label="Saving" | |
| status={progress < 95 ? 'pending' : 'active'} | |
| /> | |
| </div> | |
| {/* Rotating Tips */} | |
| <div | |
| key={currentTip} | |
| className="bg-void-900/60 backdrop-blur-sm p-4 rounded-xl mb-8 flex items-center gap-3 min-h-[60px] animate-fade-in border border-void-700/50" | |
| > | |
| <span className="text-2xl">π‘</span> | |
| <p className="text-sm leading-relaxed text-void-200">{GENERATION_TIPS[currentTip]}</p> | |
| </div> | |
| {/* Segments Preview */} | |
| <div className="mb-6"> | |
| <h4 className="text-sm mb-3 text-void-300 font-semibold">Segments Being Generated:</h4> | |
| <div className="grid grid-cols-4 sm:grid-cols-8 gap-2"> | |
| {Array.from({ length: segmentsCount }).map((_, i) => ( | |
| <SegmentCard | |
| key={i} | |
| number={i + 1} | |
| status={ | |
| progress > (i / segmentsCount) * 100 ? 'complete' : | |
| progress > ((i - 0.5) / segmentsCount) * 100 ? 'generating' : | |
| 'pending' | |
| } | |
| /> | |
| ))} | |
| </div> | |
| </div> | |
| {/* Auto-Save Indicator */} | |
| <div className="flex items-center justify-center gap-2 mb-6 text-sm text-void-300"> | |
| <span className={progress < 95 ? '' : 'animate-spin'}>πΎ</span> | |
| <span>Auto-saving prompts for recovery...</span> | |
| </div> | |
| {/* Cancel Button */} | |
| {onCancel && ( | |
| <button | |
| onClick={onCancel} | |
| className="w-full py-3 bg-red-500/20 border border-red-500/50 rounded-lg hover:bg-red-500/30 transition-colors text-void-100 font-medium" | |
| > | |
| Cancel Generation | |
| </button> | |
| )} | |
| </div> | |
| ); | |
| } | |
| function GenerationStep({ icon, label, status }: { | |
| icon: string; | |
| label: string; | |
| status: 'pending' | 'active' | 'complete'; | |
| }) { | |
| return ( | |
| <div className="flex flex-col items-center gap-2"> | |
| <div className={` | |
| w-12 h-12 rounded-full flex items-center justify-center text-xl transition-all border-2 | |
| ${status === 'complete' ? 'bg-electric-500/30 border-electric-400 shadow-lg shadow-electric-500/50' : ''} | |
| ${status === 'active' ? 'bg-coral-500/30 border-coral-400 shadow-lg shadow-coral-500/50 animate-pulse' : ''} | |
| ${status === 'pending' ? 'bg-void-900/50 border-void-700' : ''} | |
| `}> | |
| <span className={status === 'complete' ? 'text-electric-300' : status === 'active' ? 'text-coral-300' : 'text-void-500'}> | |
| {status === 'complete' ? 'β' : icon} | |
| </span> | |
| </div> | |
| <span className={`text-xs ${status === 'active' ? 'font-semibold text-coral-300' : status === 'complete' ? 'text-electric-300' : 'text-void-400'}`}> | |
| {label} | |
| </span> | |
| </div> | |
| ); | |
| } | |
| function SegmentCard({ number, status }: { | |
| number: number; | |
| status: 'pending' | 'generating' | 'complete'; | |
| }) { | |
| return ( | |
| <div className={` | |
| p-2 rounded-lg text-center transition-all border-2 | |
| ${status === 'complete' ? 'bg-electric-500/20 border-electric-500/50 shadow-lg shadow-electric-500/20' : ''} | |
| ${status === 'generating' ? 'bg-coral-500/20 border-coral-500/50 shadow-lg shadow-coral-500/30 animate-pulse' : ''} | |
| ${status === 'pending' ? 'bg-void-900/50 border-void-700' : ''} | |
| `}> | |
| <div className={`font-bold text-xs ${status === 'complete' ? 'text-electric-300' : status === 'generating' ? 'text-coral-300' : 'text-void-400'}`}> | |
| #{number} | |
| </div> | |
| <div className="text-lg"> | |
| {status === 'complete' && <span className="text-electric-400">β</span>} | |
| {status === 'generating' && <span className="animate-spin inline-block">βοΈ</span>} | |
| {status === 'pending' && <span className="text-void-500">β³</span>} | |
| </div> | |
| <div className={`text-[10px] font-medium ${status === 'complete' ? 'text-electric-400' : status === 'generating' ? 'text-coral-400' : 'text-void-500'}`}> | |
| {status === 'complete' && 'Ready'} | |
| {status === 'generating' && 'Queue'} | |
| {status === 'pending' && 'Queue'} | |
| </div> | |
| </div> | |
| ); | |
| } | |