DeepEx-AI / src /components /VideoPolling.jsx
yogesh rana
update
1601d0a
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 (
<div className="video-processing-box">
<div className="processing-spinner"></div>
<div className="processing-label">Analysing your video…</div>
<div className="processing-sub">
Sampling frames &amp; running inference β€” this usually takes under a minute.
</div>
<div className="pulse-bar"><div className="pulse-fill"></div></div>
<div className="poll-status-text">{statusText}</div>
</div>
);
}
if (status === 'error') {
return (
<div className="poll-error-box">
❌ Processing failed: {error}
</div>
);
}
// 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 (
<div style={{ marginTop: '35px', borderTop: '1px solid var(--glass-border)', paddingTop: '20px' }}>
<div style={{
...badgeStyle,
padding: '8px 16px',
borderRadius: '99px',
display: 'inline-block',
marginBottom: '15px',
}}>
{label}
</div>
<p style={{ fontSize: '14px', color: 'var(--text-muted)' }}>
Confidence Score: {result.confidence}%
</p>
<ConfidenceBar confidence={result.confidence} color={barColor} />
<FrameTable frameResults={result.frame_results} />
{isFake && <DMCASection mediaType="video" />}
</div>
);
}