import React, { useState, useEffect, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useGeneration } from '@/context/GenerationContext';
import { SegmentGenerationProgress } from './SegmentGenerationProgress';
import { SegmentPromptsViewer } from './SegmentPromptsViewer';
// Icons
const CheckIcon = () => (
);
const SpinnerIcon = () => (
);
const ClockIcon = () => (
);
const VideoIcon = () => (
);
const WaveformIcon = () => (
);
const BrainIcon = () => (
);
const DownloadIcon = () => (
);
const ImageIcon = () => (
);
interface ActivityLog {
id: string;
message: string;
timestamp: Date;
type: 'info' | 'success' | 'warning' | 'processing';
icon?: 'video' | 'audio' | 'brain' | 'download' | 'image';
}
const XIcon = () => (
);
export const GenerationProgress: React.FC = () => {
const { state, cancelGeneration } = useGeneration();
const { progress, provider, generatedVideos, segments, isCancelling, step } = state;
// Show enhanced UX during prompt generation (streaming)
// Use estimated count if segments not yet loaded, or actual count if available
if (step === 'generating_prompts') {
const estimatedCount = segments.length > 0 ? segments.length : (progress.total > 0 ? progress.total : 5);
const accentColor = provider === 'kling' ? 'coral' : 'electric';
return (
{segments.length > 0 && (
)}
);
}
const [elapsedTime, setElapsedTime] = useState(0);
const [activityLog, setActivityLog] = useState([]);
const [startTime] = useState(() => Date.now());
const lastMessageRef = useRef('');
const logContainerRef = useRef(null);
const percentage = progress.total > 0
? Math.round((progress.current / progress.total) * 100)
: 0;
const accentColor = provider === 'kling' ? 'coral' : 'electric';
const accentClass = accentColor === 'coral' ? 'text-coral-400' : 'text-electric-400';
const accentBg = accentColor === 'coral' ? 'bg-coral-500' : 'bg-electric-500';
const accentBorder = accentColor === 'coral' ? 'border-coral-500' : 'border-electric-500';
// Update elapsed time
useEffect(() => {
const interval = setInterval(() => {
setElapsedTime(Math.floor((Date.now() - startTime) / 1000));
}, 1000);
return () => clearInterval(interval);
}, [startTime]);
// Track progress messages and add to activity log
useEffect(() => {
if (progress.message && progress.message !== lastMessageRef.current) {
lastMessageRef.current = progress.message;
// Determine log type and icon based on message content
let type: ActivityLog['type'] = 'info';
let icon: ActivityLog['icon'] = undefined;
const msg = progress.message.toLowerCase();
if (msg.includes('complete') || msg.includes('success') || msg.includes('✅')) {
type = 'success';
} else if (msg.includes('warning') || msg.includes('⚠️') || msg.includes('fallback')) {
type = 'warning';
} else if (msg.includes('generating') || msg.includes('processing') || msg.includes('submitting')) {
type = 'processing';
icon = 'video';
}
if (msg.includes('whisper') || msg.includes('audio') || msg.includes('transcri')) {
icon = 'audio';
} else if (msg.includes('prompt') || msg.includes('refin') || msg.includes('gpt')) {
icon = 'brain';
} else if (msg.includes('download')) {
icon = 'download';
} else if (msg.includes('image') || msg.includes('frame') || msg.includes('upload')) {
icon = 'image';
} else if (msg.includes('video') || msg.includes('segment')) {
icon = 'video';
}
const newLog: ActivityLog = {
id: `${Date.now()}-${Math.random()}`,
message: progress.message,
timestamp: new Date(),
type,
icon,
};
setActivityLog(prev => [...prev.slice(-20), newLog]); // Keep last 20 entries
}
}, [progress.message]);
// Auto-scroll activity log
useEffect(() => {
if (logContainerRef.current) {
logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
}
}, [activityLog]);
// Format time
const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
// Estimate remaining time
const estimatedTotal = progress.current > 0
? Math.round((elapsedTime / progress.current) * progress.total)
: 0;
const remainingTime = Math.max(0, estimatedTotal - elapsedTime);
// Get icon component
const getIcon = (iconType?: ActivityLog['icon']) => {
switch (iconType) {
case 'video': return ;
case 'audio': return ;
case 'brain': return ;
case 'download': return ;
case 'image': return ;
default: return ;
}
};
return (
{/* Header with Time */}
Generating Videos
{provider === 'kling' ? 'KIE API' : 'Replicate'} • {segments.length} segments
Elapsed: {formatTime(elapsedTime)}
{progress.current > 0 && remainingTime > 0 && (
Est. remaining: {formatTime(remainingTime)}
)}
{/* Main Progress Area */}
{/* Left: Circular Progress */}
{/* Background circle */}
{/* Center content */}
{percentage}%
{generatedVideos.length} / {progress.total} videos
{/* Current Status */}
{progress.message}
{/* Right: Segment Progress & Activity Log */}
{/* Segment Cards */}
Segment Progress
{segments.map((segment, index) => {
const isCompleted = index < generatedVideos.length;
const isCurrent = index === generatedVideos.length && index < progress.total;
return (
{/* Status indicator */}
{isCompleted ? : isCurrent ? : {index + 1}}
Segment {index + 1}
{segment.action_timeline?.dialogue?.substring(0, 40) || 'Processing...'}...
{/* Thumbnail preview when completed */}
{isCompleted && generatedVideos[index]?.thumbnails?.[0] && (
)}
);
})}
{/* Activity Log */}
Live Activity
{activityLog.map((log) => (
{log.type === 'success' ? : getIcon(log.icon)}
{log.message}
{log.timestamp.toLocaleTimeString()}
))}
{activityLog.length === 0 && (
Waiting for activity...
)}
{/* Segment Prompts Viewer - Show during video generation */}
{segments.length > 0 && (
)}
{/* Bottom Progress Bar */}
Overall Progress
{percentage}%
{/* Step indicators */}
0 ? accentClass : ''}>
{generatedVideos.length > 0 ? '✓' : '○'} Generating
= segments.length / 2 ? accentClass : ''}>
{generatedVideos.length >= segments.length / 2 ? '✓' : '○'} Halfway
{generatedVideos.length === segments.length ? '✓' : '○'} Complete
{/* Tips */}
💡 Each video typically takes 1-2 minutes. Stay on this page to track progress.
);
};