import { useEffect, useState, useRef } from 'react' import { CheckCircle2, Circle, Zap, Film, FileText } from 'lucide-react' import { useWebSocket } from '../hooks/useWebSocket' import { getJob } from '../utils/api' /** * Processing view with progress indicators * BUG FIX: data.result doesn't exist — backend returns full JobResult. * BUG FIX: Added polling fallback (every 2.5s) when WebSocket drops. * Props: { jobId, jobType, onComplete } */ function ProcessingView({ jobId, jobType = 'clip', onComplete }) { const { jobData } = useWebSocket(jobId) const [jobInfo, setJobInfo] = useState(null) const [error, setError] = useState(null) const pollingRef = useRef(null) const completedRef = useRef(false) const clipSteps = [ { id: 'downloading', label: 'Downloading' }, { id: 'transcribing', label: 'Transcribing Audio' }, { id: 'analyzing', label: 'Analyzing Clips' }, { id: 'editing', label: 'Editing Video' }, { id: 'captioning', label: 'Adding Captions' }, { id: 'completed', label: 'Done' }, ] const articleSteps = [ { id: 'processing', label: 'Scraping Article' }, { id: 'processing', label: 'Generating Script' }, { id: 'processing', label: 'Creating Voiceover' }, { id: 'processing', label: 'Assembling Reel' }, { id: 'completed', label: 'Done' }, ] const steps = jobType === 'article' ? articleSteps : clipSteps const handleJobData = (data) => { if (!data) return setJobInfo(data) if (data.status === 'completed' && !completedRef.current) { completedRef.current = true clearInterval(pollingRef.current) // BUG FIX: pass `data` (the full JobResult), NOT `data.result` onComplete(data) } if (data.status === 'failed') { clearInterval(pollingRef.current) setError(data.error || 'Processing failed. Please try again.') } } // WebSocket updates useEffect(() => { if (jobData) handleJobData(jobData) }, [jobData]) // eslint-disable-line // Polling fallback — fires every 2.5s regardless of WebSocket useEffect(() => { if (!jobId) return pollingRef.current = setInterval(async () => { try { const data = await getJob(jobId) handleJobData(data) } catch (err) { console.warn('[poll]', err.message) } }, 2500) return () => clearInterval(pollingRef.current) }, [jobId]) // eslint-disable-line // Map backend status string → step index const statusToStep = { queued: 0, downloading: 0, transcribing: 1, analyzing: 2, editing: 3, captioning: 4, completed: 5, failed: 0, } const currentStepIndex = statusToStep[jobInfo?.status] ?? 0 const progressPct = jobInfo?.progress ?? Math.round(((currentStepIndex + 1) / steps.length) * 100) return (
{jobType === 'article' ? 'Turning the article into viral social content…' : 'Hold tight, AI magic in progress…'}
Processing
{jobInfo.source_url}
{jobInfo.message}
)}{jobType === 'article' ? 'Article → Reel' : 'Pro Tip'}
{jobType === 'article' ? 'Your article is being condensed into a punchy ~45-second reel with AI voiceover and a chainstreet.io call-to-action.' : 'ClipCraft AI analyzes emotional beats, engagement patterns, and viewer retention to find the perfect viral moments.'}