fakeshield-api / fakeshield /src /pages /VideoLab /VideoLabPage.tsx
Akash4911's picture
Production Deploy: Improved robustness and logging
66b6851
import { useState, useRef, useCallback, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import {
Activity,
Video,
Cpu,
Info,
CheckCircle2,
XCircle,
AlertTriangle,
RotateCcw,
ScanSearch,
Eye,
BarChart2,
Mic2,
Layers,
Thermometer
} from "lucide-react";
import TimelineChart from "./TimelineChart";
import AudioVisualSync from "./AudioVisualSync";
import VLMReasoningPanel from "./VLMReasoningPanel";
import { useAuth } from '../../hooks/useAuth.tsx';
import { API_BASE_URL } from '../../config';
const API = `${API_BASE_URL}`;
const SIGNAL_META: Record<string, { label: string; icon: any; desc: string; color: string }> = {
spatial: { label: 'Spatial Neural', icon: Cpu, desc: 'Detects facial textures & diffusion artifacts', color: '#a78bfa' },
temporal: { label: 'Temporal Flow', icon: Layers, desc: 'RAFT-based pixel-motion logic & morphing', color: '#60a5fa' },
audio: { label: 'Audio-Lip Sync', icon: Mic2, desc: 'Phoneme-to-Viseme alignment audit', color: '#34d399' },
forensic: { label: 'Forensic Noise', icon: Thermometer, desc: 'PRNU sensor noise & spectral FFT signatures', color: '#f97316' },
reasoning: { label: 'VLM Reasoning', icon: ScanSearch, desc: 'Autonomous physics & geometry consistency', color: '#ef4444' },
};
const VERDICT_CONFIG = {
'DEEPFAKE': { color: '#ef4444', bg: 'rgba(239,68,68,0.1)', border: 'rgba(239,68,68,0.3)', icon: XCircle, label: 'AI DEEPFAKE', badge: 'CRITICAL' },
'LIKELY FAKE': { color: '#f97316', bg: 'rgba(249,115,22,0.1)', border: 'rgba(249,115,22,0.3)', icon: AlertTriangle, label: 'LIKELY AI', badge: 'HIGH' },
'UNCERTAIN': { color: '#eab308', bg: 'rgba(234,179,8,0.1)', border: 'rgba(234,179,8,0.3)', icon: Info, label: 'UNCERTAIN', badge: 'MEDIUM' },
'LIKELY REAL': { color: '#22c55e', bg: 'rgba(34,197,94,0.1)', border: 'rgba(34,197,94,0.3)', icon: CheckCircle2, label: 'AUTHENTIC', badge: 'LOW' },
};
import DashboardLayout from '../../components/layout/DashboardLayout';
const VideoLabPage = () => {
const { token } = useAuth();
const [phase, setPhase] = useState<"idle" | "uploading" | "polling" | "done" | "error">("idle");
const [result, setResult] = useState<any | null>(null);
const [error, setError] = useState("");
const [progress, setProgress] = useState("");
const fileRef = useRef<HTMLInputElement>(null);
const pollRef = useRef<ReturnType<typeof setInterval> | null>(null);
const fileNameRef = useRef<string>('');
const pollJob = useCallback(async (jobId: string) => {
try {
const res = await fetch(`${API}/video/status/${jobId}`, {
headers: {
...(token ? { "Authorization": `Bearer ${token}` } : {})
}
});
if (!res.ok) throw new Error("Poll failed");
const data = await res.json();
if (data.status === "complete" || data.status === "success") {
if (pollRef.current) clearInterval(pollRef.current);
setResult(data);
setPhase("done");
} else if (data.status === "error") {
if (pollRef.current) clearInterval(pollRef.current);
setError(data.detail || "Analysis failed");
setPhase("error");
}
} catch (e) {
console.warn("Polling hiccup:", e);
}
}, []);
const handleFile = useCallback(async (file: File) => {
setPhase("uploading");
setProgress("Injecting payload...");
fileNameRef.current = file.name;
const form = new FormData();
form.append("file", file);
try {
const res = await fetch(`${API}/video/analyze/async`, {
method: "POST",
headers: {
...(token ? { "Authorization": `Bearer ${token}` } : {})
},
body: form,
});
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
const { job_id } = await res.json();
setPhase("polling");
setProgress("Analyzing temporal flux...");
pollRef.current = setInterval(() => pollJob(job_id), 2500);
} catch (e: any) {
setError(e.message || "Upload failed");
setPhase("error");
}
}, [pollJob]);
const reset = () => {
if (pollRef.current) clearInterval(pollRef.current);
setPhase("idle");
setResult(null);
setError("");
setProgress("");
if (fileRef.current) fileRef.current.value = "";
};
useEffect(() => {
return () => { if (pollRef.current) clearInterval(pollRef.current); };
}, []);
const vCfg = result?.data ? (VERDICT_CONFIG[result.data.verdict as keyof typeof VERDICT_CONFIG] || VERDICT_CONFIG['UNCERTAIN']) : null;
return (
<DashboardLayout activeTab="Video lab">
<div className="flex-1 flex flex-col min-w-0" style={{ scrollBehavior: 'smooth' }}>
<header className="flex flex-col sm:flex-row justify-between items-start sm:items-end gap-4 px-6 md:px-8 mt-12 mb-8">
<div className="space-y-1">
<h1 className="text-4xl md:text-5xl font-display font-black text-[#00E5CC] tracking-tighter uppercase">Video lab</h1>
<div className="flex items-center gap-2">
<span className="w-8 h-[1px] bg-[var(--panel-border)]"></span>
<p className="text-[10px] font-bold text-[var(--text-muted)] tracking-[0.4em] uppercase">Deepfake motion temporal audit</p>
</div>
</div>
</header>
<main className="flex-1 max-w-6xl w-full mx-auto p-4 sm:p-6 md:p-8 pt-8 md:pt-12">
<AnimatePresence mode="wait">
{phase === "idle" && (
<motion.div key="idle" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -20 }} className="space-y-8 md:space-y-12">
<div className="text-center space-y-4">
<h1 className="text-3xl sm:text-4xl md:text-5xl font-black tracking-tighter bg-gradient-to-r from-[#00E5CC] to-[#2DD4BF] bg-clip-text text-transparent">
Video analysis lab
</h1>
<p className="text-sm max-w-lg mx-auto opacity-60 leading-relaxed">
Slight pixel-motion variations and frequency anomalies distinguish natural photon noise from diffusion dreaming.
</p>
</div>
<div
onClick={() => fileRef.current?.click()}
onDragOver={(e) => { e.preventDefault(); }}
className="relative group cursor-pointer max-w-2xl mx-auto"
>
<div className="absolute -inset-1 bg-gradient-to-r from-[#00E5CC] to-[#2DD4BF] rounded-2xl blur opacity-20 group-hover:opacity-40 transition duration-500"></div>
<div className="relative p-6 sm:p-10 md:p-12 py-14 md:py-20 rounded-2xl border-2 border-dashed border-[var(--panel-border)] bg-[var(--bg-secondary)] hover:border-[#00E5CC]/50 transition-all text-center space-y-6">
<div className="w-20 h-20 bg-[#00E5CC]/10 rounded-3xl flex items-center justify-center mx-auto ring-1 ring-[#00E5CC]/20 group-hover:scale-110 transition-transform duration-500">
<Video className="w-10 h-10 text-[#00E5CC]" />
</div>
<div>
<h3 className="text-xl font-bold tracking-tight mb-2">Initialize Forensic Audit</h3>
<p className="text-xs opacity-50 font-medium tracking-wide uppercase">MP4, MOV, WEBM · Sora, Gemini & Runway v10.0 Consistency Engine</p>
</div>
</div>
<input ref={fileRef} type="file" accept="video/*" className="hidden" onChange={e => { const f = e.target.files?.[0]; if (f) handleFile(f); }} />
</div>
</motion.div>
)}
{(phase === "uploading" || phase === "polling") && (
<motion.div key="loading" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} className="max-w-md mx-auto py-20 text-center space-y-8">
<div className="relative w-24 h-24 mx-auto">
<div className="absolute inset-0 rounded-full border-4 border-[#00E5CC]/10"></div>
<motion.div
animate={{ rotate: 360 }} transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className="absolute inset-0 rounded-full border-t-4 border-[#00E5CC] shadow-[0_0_15px_rgba(0,229,204,0.5)]"
/>
<div className="absolute inset-0 flex items-center justify-center">
<Cpu className="w-8 h-8 text-[#00E5CC]" />
</div>
</div>
<div className="space-y-3">
<h3 className="text-lg font-bold tracking-tight uppercase animate-pulse text-[#00E5CC]">{progress}</h3>
<p className="text-xs opacity-40 leading-relaxed font-mono">
EXTRACTING OPTICAL FLOW VECTORS...<br/>
CALCULATING SENSOR NOISE PRINT...
</p>
</div>
</motion.div>
)}
{phase === "done" && result?.data && (
<motion.div key="results" initial={{ opacity: 0, scale: 0.98 }} animate={{ opacity: 1, scale: 1 }} className="space-y-6">
{/* Result Hero */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Verdict Card */}
<div className="lg:col-span-2 p-5 md:p-8 rounded-2xl border border-[var(--panel-border)] bg-[var(--panel-bg)] relative overflow-hidden flex flex-col md:flex-row items-center gap-6 md:gap-10 shadow-sm">
<div className="relative shrink-0">
<svg width="140" height="140" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="44" fill="none" stroke="rgba(255,255,255,0.05)" strokeWidth="8" />
<motion.circle
cx="50" cy="50" r="44" fill="none" strokeWidth="8" strokeLinecap="round" stroke={vCfg?.color}
strokeDasharray={`${(result.data.ai_probability * 100 / 100) * 276.46} 276.46`}
animate={{ strokeDasharray: `${(result.data.ai_probability * 100 / 100) * 276.46} 276.46` }}
transition={{ duration: 1.5, ease: "easeOut" }}
transform="rotate(-90 50 50)"
/>
<text x="50" y="48" textAnchor="middle" dominantBaseline="middle" className="text-3xl font-bold" style={{ fill: vCfg?.color }}>
{Math.round(result.data.ai_probability * 100)}%
</text>
<text x="50" y="65" textAnchor="middle" dominantBaseline="middle" className="text-[7px] font-bold fill-[var(--text-muted)] uppercase tracking-widest">
AI PROBABILITY
</text>
</svg>
</div>
<div className="flex-1 space-y-5 text-center md:text-left">
<div className="flex flex-col md:flex-row md:items-center gap-4">
<h2 className="text-2xl sm:text-3xl md:text-4xl font-bold tracking-tight text-[var(--text-primary)]">
{vCfg?.label}
</h2>
<span className="px-3 py-1 rounded-md text-[10px] font-bold border uppercase tracking-wider self-center md:self-start" style={{ background: vCfg?.bg, borderColor: vCfg?.border, color: vCfg?.color }}>
{vCfg?.badge} THREAT
</span>
</div>
<p className="text-sm text-[var(--text-secondary)] leading-relaxed max-w-lg">
Our forensic ensemble has detected consistent patterns of synthetic generation.
The analysis of {result.data.metadata?.total_frames || '8'} frames confirms a
<span className="font-bold mx-1" style={{ color: vCfg?.color }}>{result.data.verdict.toLowerCase()}</span>
status with {result.data.agreement_count} module agreement.
</p>
<div className="flex flex-wrap gap-4 justify-center md:justify-start items-center pt-2">
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] text-[11px] font-medium text-[var(--text-secondary)]">
<Activity className="w-4 h-4 text-[var(--text-muted)]" />
<span>Process: <span className="text-[var(--text-primary)] font-bold">{result.data.processing_time}</span></span>
</div>
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] text-[11px] font-medium text-[var(--text-secondary)]">
<BarChart2 className="w-4 h-4 text-[var(--text-muted)]" />
<span>Resolution: <span className="text-[var(--text-primary)] font-bold">{result.data.metadata?.dimensions}</span></span>
</div>
<button
onClick={reset}
className="flex items-center gap-2 px-4 py-2 rounded-lg font-bold uppercase tracking-wider transition-all shadow-sm hover:opacity-90 active:scale-95"
style={{ background: '#00E5CC', color: '#000', fontSize: '11px' }}
>
<RotateCcw className="w-4 h-4" />
New Audit
</button>
</div>
</div>
</div>
{/* Signals Panel */}
<div className="p-5 md:p-8 rounded-2xl border border-[var(--panel-border)] bg-[var(--panel-bg)] space-y-8 flex flex-col justify-center shadow-sm">
<h3 className="text-[10px] font-bold uppercase tracking-[0.2em] text-[var(--text-muted)] px-1">Forensic Signals</h3>
<div className="space-y-6">
{Object.entries(result.data.signals).map(([key, val]: any) => {
const meta = SIGNAL_META[key];
if (!meta) return null;
const pct = Math.round(val * 100);
const Icon = meta.icon;
return (
<div key={key} className="space-y-3">
<div className="flex justify-between items-center text-[11px] font-bold">
<div className="flex items-center gap-2.5 text-[var(--text-secondary)]">
<Icon className="w-4 h-4" style={{ color: meta.color }} />
<span className="uppercase tracking-tight text-[var(--text-primary)]">{meta.label}</span>
</div>
<span className="font-bold text-sm" style={{ color: meta.color }}>{pct}%</span>
</div>
<div className="h-2 w-full bg-[var(--btn-secondary-bg)] rounded-full overflow-hidden border border-[var(--panel-border)]">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${pct}%` }}
className="h-full rounded-full"
style={{ background: meta.color }}
/>
</div>
</div>
);
})}
</div>
</div>
</div>
{/* Evidence Spotlight & RAFT Heatmap */}
{result.data.evidence_heatmap && (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div className="lg:col-span-2 p-5 md:p-8 rounded-2xl border border-[var(--panel-border)] bg-[var(--panel-bg)] relative overflow-hidden flex flex-col md:flex-row items-center gap-6 md:gap-10 shadow-sm">
<div className="shrink-0 w-full md:w-1/2 relative">
<img src={result.data.evidence_heatmap} alt="Optical Flow Heatmap" className="relative rounded-xl border border-[var(--panel-border)] shadow-sm w-full aspect-video object-cover" />
<div className="absolute bottom-2 right-2 px-2 py-1 bg-[var(--bg-primary)]/80 backdrop-blur-sm rounded text-[8px] font-bold text-[var(--text-primary)] uppercase tracking-widest border border-[var(--panel-border)]">
RAFT FLOW ANALYSIS
</div>
</div>
<div className="space-y-4">
<div className="flex items-center gap-2">
<Eye className="w-4 h-4 text-[var(--text-muted)]" />
<h3 className="text-xs font-bold uppercase tracking-widest text-[var(--text-secondary)]">Evidence Spotlight</h3>
</div>
<p className="text-sm text-[var(--text-secondary)] leading-relaxed">
The RAFT-Optical Flow engine has isolated a <span className="text-[var(--text-primary)] font-bold">mass-discontinuity</span> in the temporal flux.
Natural motion is rigid, while AI-generated pixels exhibit "morphing" residuals captured in this forensic heatmap.
</p>
<div className="p-4 rounded-xl bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-[var(--bg-secondary)] border border-[var(--panel-border)] flex items-center justify-center shrink-0 shadow-sm">
<Layers className="w-5 h-5 text-[var(--text-muted)]" />
</div>
<div className="text-[11px] font-medium text-[var(--text-secondary)] italic leading-snug">
"Physical reality breaks detected near <span className="text-[var(--text-primary)] font-bold not-italic">frame {Math.round(result.data.metadata?.total_frames / 2)}</span>"
</div>
</div>
</div>
</div>
<div className="p-5 md:p-8 rounded-2xl border border-[var(--panel-border)] bg-[var(--panel-bg)] flex flex-col justify-center space-y-6 shadow-sm">
<h3 className="text-[10px] font-bold uppercase tracking-[0.2em] text-[var(--text-muted)] px-1 flex items-center gap-2">
<Thermometer className="w-4 h-4" /> CONSISTENCY SCORE
</h3>
<div className="space-y-6">
<div className="flex justify-between items-end">
<span className="text-[10px] font-bold text-[var(--text-secondary)] uppercase">Physics Validation</span>
<span className="text-3xl font-bold" style={{ color: result.data.signals?.reasoning > 0.6 ? '#ef4444' : '#22c55e' }}>
{result.data.signals?.reasoning > 0.6 ? 'POOR' : 'EXCELLENT'}
</span>
</div>
<div className="h-2 w-full bg-[var(--btn-secondary-bg)] rounded-full overflow-hidden">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${(1 - (result.data.signals?.reasoning || 0)) * 100}%` }}
className="h-full"
style={{ background: result.data.signals?.reasoning > 0.6 ? '#ef4444' : '#22c55e' }}
/>
</div>
<p className="text-[10px] text-[var(--text-secondary)] leading-relaxed font-medium">
Measures adherence to mass-conservation and lighting-physics constraints. High scores indicate physical reality.
</p>
</div>
</div>
</div>
)}
{/* New Analysis Visualizations */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Timeline Chart */}
<div className="p-5 md:p-8 rounded-3xl border border-[var(--panel-border)] bg-[var(--bg-secondary)] space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<BarChart2 className="w-4 h-4 text-[#00E5CC]" />
<h3 className="text-xs font-black uppercase tracking-widest pb-1 border-b-2 border-[#00E5CC]/20">Audit Timeline</h3>
</div>
<div className="px-2 py-1 bg-white/5 rounded-md text-[9px] font-mono opacity-40 uppercase tracking-tighter">
ENGINE_CONSISTENCY_v10.0
</div>
</div>
<TimelineChart
data={result.data.timelines?.spatial || []}
label="Spatial Signal (CLIP)"
color="#a78bfa"
/>
<TimelineChart
data={result.data.timelines?.temporal || []}
label="Temporal Flow (RAFT)"
color="#60a5fa"
/>
<AudioVisualSync
lipTimeline={result.data.timelines?.audio_lip || []}
audioSpeaking={result.data.timelines?.audio_speaking || []}
/>
</div>
{/* VLM Reasoning */}
<VLMReasoningPanel report={result.data.reasoning_report || "Consistency check skipped."} />
</div>
{/* Sub Panels */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Evidence List */}
<div className="p-5 md:p-8 rounded-2xl border border-[var(--panel-border)] bg-[var(--panel-bg)] space-y-6 shadow-sm">
<div className="flex items-center gap-2">
<Eye className="w-4 h-4 text-[var(--text-muted)]" />
<h3 className="text-xs font-bold uppercase tracking-widest text-[var(--text-secondary)]">Forensic Brief</h3>
</div>
<div className="space-y-4">
{result.data.reasons.map((reason: string, i: number) => (
<motion.div
key={i} initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: i * 0.1 }}
className="flex items-start gap-4 p-4 rounded-xl bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] group hover:border-[var(--border-hover)] transition-all"
>
<div className="w-6 h-6 rounded-lg bg-[var(--bg-secondary)] border border-[var(--panel-border)] flex items-center justify-center shrink-0 mt-1 shadow-sm">
<CheckCircle2 className="w-3.5 h-3.5 text-[var(--text-muted)]" />
</div>
<p className="text-[13px] font-medium leading-relaxed text-[var(--text-secondary)]">{reason}</p>
</motion.div>
))}
</div>
</div>
{/* Physics Info */}
<div className="p-5 md:p-8 rounded-2xl border border-[var(--panel-border)] bg-[var(--panel-bg)] space-y-6 shadow-sm">
<div className="flex items-center gap-2">
<BarChart2 className="w-4 h-4 text-[var(--text-muted)]" />
<h3 className="text-xs font-bold uppercase tracking-widest text-[var(--text-secondary)]">Temporal Evidence</h3>
</div>
<div className="space-y-6">
<div className="p-6 rounded-2xl bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] space-y-4">
<div className="flex justify-between items-end">
<div>
<div className="text-[10px] font-bold text-[var(--text-muted)] uppercase mb-1">Motion Stability</div>
<div className="text-2xl font-bold" style={{ color: result.data.signals.temporal_flow > 0.7 ? '#ef4444' : '#22c55e' }}>
{result.data.signals.temporal_flow > 0.7 ? 'UNSTABLE' : 'STABLE'}
</div>
</div>
<div className="text-right">
<div className="text-[10px] font-bold text-[var(--text-muted)] uppercase mb-1">PAVR Ratio</div>
<div className="text-sm font-mono font-bold text-[var(--text-primary)]">{(result.data.signals.temporal_flow * 15).toFixed(2)}x</div>
</div>
</div>
<div className="h-2 w-full bg-[var(--bg-secondary)] border border-[var(--panel-border)] rounded-full overflow-hidden">
<div className="h-full" style={{ width: `${result.data.signals.temporal_flow * 100}%`, background: result.data.signals.temporal_flow > 0.7 ? '#ef4444' : '#22c55e' }} />
</div>
<p className="text-[10px] text-[var(--text-secondary)] leading-relaxed font-medium italic">
Video sequences generated by diffusion models (Sora/Gen-3) exhibit significant peaks in pixel-mass variance where objects undergo non-physical morphing.
</p>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="p-4 rounded-xl bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] text-center">
<div className="text-[10px] font-bold text-[var(--text-muted)] uppercase mb-1">Total Frames</div>
<div className="text-xl font-bold text-[var(--text-primary)]">{result.data.metadata?.total_frames}</div>
</div>
<div className="p-4 rounded-xl bg-[var(--btn-secondary-bg)] border border-[var(--panel-border)] text-center">
<div className="text-[10px] font-bold text-[var(--text-muted)] uppercase mb-1">Frame Rate</div>
<div className="text-xl font-bold text-[var(--text-primary)]">{result.data.metadata?.fps} <span className="text-[10px] text-[var(--text-secondary)] font-medium">fps</span></div>
</div>
</div>
</div>
</div>
</div>
</motion.div>
)}
{phase === "error" && (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="max-w-md mx-auto py-20 text-center space-y-6">
<div className="w-20 h-20 bg-red-500/10 rounded-full flex items-center justify-center mx-auto ring-1 ring-red-500/20">
<XCircle className="w-10 h-10 text-red-500" />
</div>
<div className="space-y-2">
<h3 className="text-xl font-bold">Analysis Failed</h3>
<p className="text-sm opacity-50">{error}</p>
</div>
<button onClick={reset} className="px-6 py-2 bg-white/5 border border-white/10 rounded-xl font-bold hover:bg-white/10 transition-all">
Try Again
</button>
</motion.div>
)}
</AnimatePresence>
</main>
</div>
</DashboardLayout>
);
};
export default VideoLabPage;