import React, { useEffect, useRef, useState } from 'react'; import ConfidenceBar from './ConfidenceBar'; import FrameTable from './FrameTable'; import DMCASection from './DMCASection'; const MAX_ATTEMPTS = 90; // 90 × 2s = 3 minutes max before giving up export default function VideoPolling({ jobId, onComplete, onError }) { const [status, setStatus] = useState('processing'); const [statusText, setStatusText] = useState('Connecting to server…'); const [result, setResult] = useState(null); const [error, setError] = useState(null); const attemptsRef = useRef(0); // FIX: use a ref to track if component is still mounted — prevents // setState calls after unmount which cause React warnings & ghost updates const mountedRef = useRef(true); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); useEffect(() => { if (!jobId) return; // FIX: reset attempts on every new jobId so a re-upload starts fresh attemptsRef.current = 0; function poll() { if (!mountedRef.current) return; // component unmounted — stop silently attemptsRef.current++; // FIX: hard cap — stop polling after MAX_ATTEMPTS instead of running forever if (attemptsRef.current > MAX_ATTEMPTS) { const msg = 'Processing timed out. The video may be too long or the server is busy.'; if (mountedRef.current) { setStatus('error'); setError(msg); } // FIX: notify App.jsx so it can clear jobId and show error in parent if (onError) onError(msg); return; } if (mountedRef.current) { setStatusText(`Checking… (attempt ${attemptsRef.current} / ${MAX_ATTEMPTS})`); } fetch(`/status/${jobId}`) .then(r => { if (!r.ok) throw new Error(`Server returned ${r.status}`); return r.json(); }) .then(data => { if (!mountedRef.current) return; if (data.status === 'processing') { setTimeout(poll, 2000); } else if (data.status === 'done') { setResult(data); setStatus('done'); // FIX: lift result up to App.jsx so ResultSection can render there too if (onComplete) onComplete(data); } else { // status === 'error' or anything unexpected const msg = data.error || 'Unknown server error.'; setStatus('error'); setError(msg); if (onError) onError(msg); } }) .catch(() => { if (!mountedRef.current) return; // Network hiccup — keep retrying until MAX_ATTEMPTS setTimeout(poll, 3000); }); } poll(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [jobId]); // ── Rendering ───────────────────────────────────────────────────────────── if (status === 'processing') { return (
Analysing your video…
Sampling frames & running inference — this usually takes under a minute.
{statusText}
); } if (status === 'error') { return (
❌ Processing failed: {error}
); } // status === 'done' — render result inline inside VideoPolling // (App.jsx also gets the result via onComplete for its own ResultSection) if (!result) return null; const isFake = result.result !== 'REAL' && result.result !== 'REAL IMAGE'; const label = isFake ? '⚠ Deepfake Detected in Video' : '✔ Real Video'; const badgeStyle = isFake ? { background: 'rgba(239,68,68,0.2)', color: '#f87171', border: '1px solid #ef4444' } : { background: 'rgba(34,197,94,0.2)', color: '#4ade80', border: '1px solid #22c55e' }; const barColor = isFake ? '#ef4444' : '#22c55e'; return (
{label}

Confidence Score: {result.confidence}%

{isFake && }
); }