/* ── Upload Area ── */
function UploadArea({ file, onFileChange }) {
const inputRef = React.useRef();
return (
inputRef.current.click()}
>
Drop a video here or click to upload — MP4, AVI, MOV, max 200 MB
{file &&
{file.name}
}
{ onFileChange(e.target.files[0] || null); e.target.value = ''; }}
/>
);
}
/* ── Status Spinner ── */
function StatusIndicator({ status }) {
if (!status || status === 'done') return null;
return (
<>
{STATUS_MESSAGES[status] || 'Processing\u2026'}
>
);
}
/* ── Video Comparison ── */
const VideoComparison = React.memo(function VideoComparison({ file, processedUrl, isProcessing }) {
const videoRef = React.useRef(null);
const [localUrl, setLocalUrl] = React.useState(null);
React.useEffect(() => {
if (file) {
const url = URL.createObjectURL(file);
setLocalUrl(url);
return () => URL.revokeObjectURL(url);
}
setLocalUrl(null);
}, [file]);
if (!localUrl) return null;
const showProcessed = processedUrl || isProcessing;
return (
{showProcessed ? 'Face Detection' : 'Uploaded Video'}
{showProcessed && (
{processedUrl
?
:
}
{processedUrl ? 'Detected Faces' : 'Generating\u2026'}
)}
);
});
/* ── Face Row ── */
function FaceRow({ face, index }) {
const pct = (face.score * 100).toFixed(2);
const w = (face.score * 100).toFixed(1);
return (
);
}
/* ── Results Panel ── */
function ResultsPanel({ data }) {
if (!data || !data.result) return null;
const cls = data.result.toLowerCase();
return (
Results
Authenticity score: likelihood the face is real.{' '}
Red <20%,{' '}
Orange 20-80%,{' '}
Green >80%.
{data.result}
Confidence: {data.confidence}%
Model Score: {data.score}
Faces Analyzed: {data.num_faces}
{data.faces_detail && data.faces_detail.map((face, i) => (
))}
);
}
/* ── Product Page ── */
function ProductPage({ file, setFile, status, setStatus, error, setError, result, setResult, submitting, setSubmitting }) {
const timerRef = React.useRef(null);
const reset = () => { setResult(null); setError(null); setStatus(null); };
const handleFileChange = (f) => { setFile(f); reset(); };
const pollJob = React.useCallback((jobId) => {
timerRef.current = setTimeout(async () => {
try {
const res = await fetch(`/status/${jobId}`);
const data = await res.json();
setStatus(data.status);
if (data.result) {
setResult(prev => ({ ...prev, ...data }));
}
if (data.status === 'done') {
setSubmitting(false);
if (data.error && !data.result) setError(data.error);
} else {
pollJob(jobId);
}
} catch {
setSubmitting(false);
setStatus(null);
setError('Connection lost. Please try again.');
}
}, 1000);
}, []);
React.useEffect(() => () => { if (timerRef.current) clearTimeout(timerRef.current); }, []);
const handleSubmit = async (e) => {
e.preventDefault();
if (!file) return;
reset();
setSubmitting(true);
setStatus('uploading');
const fd = new FormData();
fd.append('video', file);
try {
const res = await fetch('/predict', { method: 'POST', body: fd });
const data = await res.json();
if (data.error) {
setError(data.error);
setSubmitting(false);
setStatus(null);
} else {
pollJob(data.job_id);
}
} catch {
setError('Upload failed. Please try again.');
setSubmitting(false);
setStatus(null);
}
};
return (
<>
AI-Deepfake Video Detection
Free deepfake detection tool for videos. Upload a video and get
per-face authenticity scores in seconds. AI-powered synthetic face detection.
{error &&
{error}
}
>
);
}