import React, { useState, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { Flame, Activity, Search, Frame, Camera, Palette, BarChart3, Volume2, FileText, Download, RotateCcw, AlertTriangle, CheckCircle2, ShieldAlert, Info, Lightbulb, Star, ChevronUp, ChevronDown, ZoomIn, X, Focus, ScanSearch, BookOpen, FileVideo, Film, Cpu, Maximize2, Minimize2 } from 'lucide-react'; import { Radar, RadarChart, PolarGrid, PolarAngleAxis, PolarRadiusAxis, ResponsiveContainer, Tooltip as RechartsTooltip, BarChart, Bar, XAxis, YAxis, CartesianGrid, AreaChart, Area } from 'recharts'; import SimpleSparkline from './ui/SimpleSparkline'; import ScoreRing from './ui/ScoreRing'; import MetricCard from './ui/MetricCard'; import VerdictBadge from './ui/VerdictBadge'; import TestDefinition from './ui/TestDefinition'; import TestExplanation from './ui/TestExplanation'; const FeaturesTab = React.lazy(() => import('./tabs/FeaturesTab')); const VisualTab = React.lazy(() => import('./tabs/VisualTab')); const FrequencyTab = React.lazy(() => import('./tabs/FrequencyTab')); const CfaTab = React.lazy(() => import('./tabs/CfaTab')); const CornealTab = React.lazy(() => import('./tabs/CornealTab')); const ElaTab = React.lazy(() => import('./tabs/ElaTab')); const GeometryTab = React.lazy(() => import('./tabs/GeometryTab')); const NoiseTab = React.lazy(() => import('./tabs/NoiseTab')); const ColorTab = React.lazy(() => import('./tabs/ColorTab')); const AudioTab = React.lazy(() => import('./tabs/AudioTab')); const RppgTab = React.lazy(() => import('./tabs/RppgTab')); const LightingTab = React.lazy(() => import('./tabs/LightingTab')); const MetaTab = React.lazy(() => import('./tabs/MetaTab')); const EyeTab = React.lazy(() => import('./tabs/EyeTab')); const VoiceTab = React.lazy(() => import('./tabs/VoiceTab')); const FlowTab = React.lazy(() => import('./tabs/FlowTab')); const API_BASE = import.meta.env.VITE_API_URL || 'http://127.0.0.1:8000'; const containerVariants = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1 } } }; const itemVariants = { hidden: { opacity: 0, y: 20 }, show: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 24 } } }; const ReportDashboard = ({ result, resetApp, jobId, fileName }) => { const isFake = result.overall_score > 0.55; const [activeTab, setActiveTab] = useState('features'); const [zoomedImage, setZoomedImage] = useState(null); const [showFullGradcamInfo, setShowFullGradcamInfo] = useState(false); const [showFullSpectralInfo, setShowFullSpectralInfo] = useState(false); const [showFullElaInfo, setShowFullElaInfo] = useState(false); const [showFullGeometryInfo, setShowFullGeometryInfo] = useState(false); const isVideo = useMemo(() => fileName && fileName.toLowerCase().match(/\.(mp4|avi|mov|mkv|webm)$/), [fileName]); const [hiddenCards, setHiddenCards] = useState({}); const [expandedCards, setExpandedCards] = useState({}); const toggleExpand = useCallback((id) => setExpandedCards(prev => ({ ...prev, [id]: !prev[id] })), []); const hideCard = useCallback((id) => setHiddenCards(prev => ({ ...prev, [id]: true })), []); const restoreCards = useCallback(() => { setHiddenCards({}); setExpandedCards({}); }, []); const downloadReport = useCallback(() => { // Navigate directly to the download endpoint. // This allows IDM or the browser to natively handle the file download without throwing JavaScript fetch errors. window.location.href = `${API_BASE}/api/reports/${jobId}/pdf`; }, [jobId]); const getSyncColor = useCallback((score) => { if (score > 0.6) return 'danger'; if (score > 0.3) return 'warning'; return 'success'; }, []); const getScoreColor = useCallback((score, invert = false) => { const s = invert ? 1 - score : score; if (s > 0.6) return 'var(--danger)'; if (s > 0.35) return 'var(--warning)'; return 'var(--success)'; }, []); const getVerdictDetails = useCallback(() => { if (result.overall_score > 0.70) return { icon: , color: 'var(--danger)', bg: 'rgba(251,113,133,0.06)' }; if (result.overall_score > 0.55) return { icon: , color: 'var(--warning)', bg: 'rgba(251,191,36,0.06)' }; if (result.overall_score > 0.40) return { icon: , color: 'var(--text-muted)', bg: 'rgba(100,116,139,0.06)' }; return { icon: , color: 'var(--success)', bg: 'rgba(52,211,153,0.06)' }; }, [result.overall_score]); const verdictStyle = useMemo(() => getVerdictDetails(), [getVerdictDetails]); const beginnerTabs = useMemo(() => [ { id: 'features', icon: , label: 'Ensemble' }, { id: 'visual', icon: , label: 'Neural Net' }, ...(isVideo && result.file_metadata?.has_audio ? [{ id: 'audio', icon: , label: 'Audio Sync' }] : []), ...(isVideo && result.file_metadata?.has_audio ? [{ id: 'voice', icon: , label: 'Voice Spoofing' }] : []), { id: 'meta', icon: , label: 'Metadata' }, ], [isVideo, result.file_metadata?.has_audio]); const advancedTabs = useMemo(() => [ { id: 'geometry', icon: , label: 'Face Geometry' }, { id: 'corneal', icon: , label: 'Corneal Optics' }, ...(isVideo ? [{ id: 'eye', icon: , label: 'Eye & Gaze' }] : []), { id: 'color', icon: , label: 'Color Space' }, { id: 'ela', icon: , label: 'ELA' }, { id: 'noise', icon: , label: 'Sensor Noise' }, { id: 'cfa', icon: , label: 'CFA Artifacts' }, { id: 'frequency', icon: , label: 'Frequency' }, ...(isVideo ? [{ id: 'rppg', icon: , label: 'Pulse (rPPG)' }] : []), { id: 'lighting', icon: , label: 'Lighting' }, ...(isVideo ? [{ id: 'flow', icon: , label: 'Optical Flow' }] : []), ], [isVideo]); return ( { if (e.target.tagName === 'IMG' && (e.target.classList.contains('result-img') || e.target.classList.contains('heatmap-image'))) { setZoomedImage(e.target.src); } }} > {/* Left Sidebar */}
{/* Action Buttons */}
{/* Main Verdict Card */} {/* Top Banner / Glow effect based on verdict */}
{React.cloneElement(verdictStyle.icon, { size: 40 })}
Final Meta-Verdict
{result.verdict}
Ensemble Confidence
{(result.overall_score * 100).toFixed(1)}%
Forensic Metadata
{fileName || 'Analyzed_Media'}
{result.frames_analyzed} frames analyzed
ResNet Meta-Classifier
{isVideo ? (result.file_metadata?.has_audio ? 14 : 12) : 10} sensory inputs
{/* Mini Score Grid */}
Sub-Model Signals
{[ { label: 'Neural Net', score: result.nn_score, key: 'nn' }, { label: 'Spectral', score: result.spectral_anomaly_score, key: 'sp' }, { label: 'ELA', score: result.ela_score, key: 'el' }, { label: 'Geometry', score: result.geometry_anomaly_score, key: 'geo' }, { label: 'Noise', score: result.noise_score, key: 'ns' }, { label: 'Color', score: result.color_score, key: 'cl' }, { label: 'Lighting', score: result.lighting_score || 0, key: 'li' }, { label: 'CFA', score: result.cfa_score || 0, key: 'cfa' }, { label: 'Corneal', score: result.corneal_score || 0, key: 'corn' }, ...(isVideo ? [{ label: 'rPPG', score: result.rppg_score || 0, key: 'rppg' }] : []), ...(isVideo ? [{ label: 'Eye/Gaze', score: result.eye_score || 0, key: 'eye' }] : []), ...(isVideo ? [{ label: 'Opt Flow', score: result.flow_score || 0, key: 'flow' }] : []), ...(isVideo && result.file_metadata?.has_audio ? [{ label: 'Desync', score: result.sync_score, key: 'syn' }] : []), ...(isVideo && result.file_metadata?.has_audio ? [{ label: 'Voice', score: result.voice_score || 0, key: 'voice' }] : []), ].map(item => (
{item.label} {(item.score * 100).toFixed(0)}%
))}
{/* Right Main Content */}
{/* Tab Bar */}
Core Analysis
{beginnerTabs.map(tab => ( ))}
Advanced Forensics
{advancedTabs.map(tab => ( ))}
Loading analysis module...
}> {/* ========== ENSEMBLE TAB ========== */} {activeTab === 'features' && } {/* ========== NEURAL NET TAB ========== */} {activeTab === 'visual' && } {/* ========== FREQUENCY TAB ========== */} {activeTab === 'frequency' && } {/* ========== CFA TAB ========== */} {activeTab === 'cfa' && } {/* ========== CORNEAL OPTICS TAB ========== */} {activeTab === 'corneal' && } {/* ========== ELA TAB ========== */} {activeTab === 'ela' && } {/* ========== FACE GEOMETRY TAB ========== */} {activeTab === 'geometry' && } {/* ========== NOISE TAB ========== */} {activeTab === 'noise' && } {/* ========== COLOR TAB ========== */} {activeTab === 'color' && } {/* ========== AUDIO SYNC TAB ========== */} {activeTab === 'audio' && } {/* ========== RPPG TAB ========== */} {activeTab === 'rppg' && } {/* ========== LIGHTING TAB ========== */} {activeTab === 'lighting' && } {/* ========== METADATA TAB ========== */} {activeTab === 'meta' && } {/* ========== EYE GAZE & BLINK TAB ========== */} {activeTab === 'eye' && } {/* ========== VOICE SPOOFING TAB ========== */} {activeTab === 'voice' && } {/* ========== OPTICAL FLOW TAB ========== */} {activeTab === 'flow' && } {/* End Main Content */} {/* Fullscreen Image Modal using React Portal to escape CSS transforms */} {zoomedImage && createPortal(
setZoomedImage(null)}> Fullscreen View { e.stopPropagation(); setZoomedImage(null); }} />
, document.body )}
); }; export default React.memo(ReportDashboard);