import React, { useState, useRef, useCallback, useMemo } from 'react'; import type { ChangeEvent, DragEvent } from 'react'; import Sidebar from '../../components/layout/Sidebar'; import { FileAudio, Search, ChevronRight, Shield, Activity, Mic2, AlertCircle, Clock, HardDrive, Upload, BarChart2, Layers, } from 'lucide-react'; import { analyzeAudioAsync, isValidAudioFile } from '../../services/audioService'; import type { AudioResult } from '../../services/audioService'; import WaveformHeatmap from './WaveformVisualizer'; import AudioReportPanel from './AudioReportPanel'; import PitchStability from './PitchStability'; import SpectrogramView from './SpectrogramView'; import AnomalyMarkers from './AnomalyMarkers'; import { useAuth } from '../../hooks/useAuth.tsx'; /* ─── Inline styles injected once ────────────────────────────────── */ const PAGE_STYLES = ` .audio-glass-panel { background: var(--panel-bg); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid var(--panel-border); border-radius: 14px; } .audio-glow { box-shadow: 0 0 24px rgba(0, 229, 204, 0.25); } .gauge-container { position: relative; width: 180px; height: 180px; } .gauge-svg { transform: rotate(-90deg); } .gauge-bg { fill: none; stroke: var(--panel-border); stroke-width: 10; } .gauge-fill { fill: none; stroke-width: 10; stroke-linecap: round; stroke-dasharray: 440; transition: stroke-dashoffset 1.5s cubic-bezier(0.4, 0, 0.2, 1), stroke 0.5s ease; } .audio-fade-in { animation: audioFadeIn 0.45s ease-out both; } @keyframes audioFadeIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } .drop-zone-active { border-color: var(--accent-blue) !important; box-shadow: 0 0 32px rgba(0, 229, 204, 0.20); background: rgba(0, 229, 204, 0.04) !important; } @keyframes audioLoader { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } } .audio-loader-bar { animation: audioLoader 1.6s ease-in-out infinite; width: 30%; } `; /* ─── Helpers ──────────────────────────────────────────────────────── */ function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(2)} MB`; } function threatColor(prob: number): string { if (prob > 65) return 'var(--accent-red)'; if (prob > 40) return 'var(--accent-orange)'; return '#00E5CC'; } /* ─── Component ────────────────────────────────────────────────────── */ import DashboardLayout from '../../components/layout/DashboardLayout'; const AudioLabPage = () => { const { token } = useAuth(); const [selectedFile, setSelectedFile] = useState(null); const [isDragging, setIsDragging] = useState(false); const [isAnalyzing, setIsAnalyzing] = useState(false); const [progressMsg, setProgressMsg] = useState(''); const [progressStep, setProgressStep] = useState(0); const [result, setResult] = useState(null); const [error, setError] = useState(null); const fileInputRef = useRef(null); /* ─── File selection ─────────────────────────────────────────── */ const acceptFile = useCallback((file: File) => { const { valid, error: err } = isValidAudioFile(file); if (!valid) { setError(err ?? 'Invalid file'); return; } setSelectedFile(file); setResult(null); setError(null); }, []); const handleBrowseClick = () => fileInputRef.current?.click(); const handleFileChange = (e: ChangeEvent) => { const file = e.target.files?.[0]; if (file) acceptFile(file); }; /* ─── Drag-and-drop ──────────────────────────────────────────── */ const handleDragOver = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(true); }; const handleDragLeave = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); }; const handleDrop = (e: DragEvent) => { e.preventDefault(); e.stopPropagation(); setIsDragging(false); const file = e.dataTransfer.files?.[0]; if (file) acceptFile(file); }; /* ─── Analysis ───────────────────────────────────────────────── */ const handleStartAnalysis = async () => { if (!selectedFile) return; setIsAnalyzing(true); setProgressStep(0); setError(null); try { const res = await analyzeAudioAsync(selectedFile, token, (msg) => { setProgressMsg(msg); setProgressStep(prev => prev + 1); }); setResult(res); } catch (err: any) { setError(err.message || 'Analysis failed. Check that the backend is running.'); } finally { setIsAnalyzing(false); } }; const handleReset = () => { setSelectedFile(null); setResult(null); setError(null); setProgressStep(0); if (fileInputRef.current) fileInputRef.current.value = ''; }; /* ─── Derived gauge values ───────────────────────────────────── */ const gaugeOffset = useMemo(() => { if (!result) return 440; return 440 - (440 * result.ai_probability / 100); }, [result]); const gaugeColor = useMemo(() => { if (!result) return 'var(--accent-blue)'; return threatColor(result.ai_probability); }, [result]); /* ─── Render ─────────────────────────────────────────────────── */ return (

Audio lab

Synthetic voice forensic laboratory

{result && ( )}
{/* ═══ Left: Input / Results ═══════════════════════════════════ */}
{/* Page title */}

AI voice analysis

WavLM · Wav2Vec2 · Prosody · Speaker Drift · Codec Analysis

{/* ── Upload Zone ── */} {!result && !isAnalyzing && (
{/* Icon */}
{isDragging ? : }
{isDragging ? (

Drop to analyze

) : (

Drag & drop an audio file

)} {/* Hidden file input */} {/* Upload Audio button */}
{/* Selected file info */} {selectedFile && (

{selectedFile.name}

{formatFileSize(selectedFile.size)} · {selectedFile.type || selectedFile.name.split('.').pop()?.toUpperCase()}

)} {/* Format note */}

Supported: WAV, MP3, FLAC, OGG, M4A · Max 50 MB

{/* Scan button */} {/* Error */} {error && (

{error}

)}
)} {/* ── Loading State ── */} {isAnalyzing && (
{/* Spinner */}

Analyzing Forensic Signals

{progressMsg || 'Initializing...'}

Step {progressStep} of 10

{/* Progress bar */}

{selectedFile?.name}

)} {/* ── Results ── */} {result && !isAnalyzing && (
{/* Temporal Forensic Map */}

Temporal Forensic Map

{result.audio_metadata.num_chunks} segments · {result.audio_metadata.duration_sec.toFixed(1)}s
{result.timeline.length > 0 && (
Summary:

Signal agreement across {result.agreement}. Temporal scan reveals{' '} t.level === 'critical').length > 0 ? 'var(--accent-red)' : 'var(--text-primary)' }}> {result.timeline.filter(t => t.level === 'critical').length} {' '} critical and{' '} t.level === 'high').length > 0 ? 'var(--accent-orange)' : 'var(--text-primary)' }}> {result.timeline.filter(t => t.level === 'high').length} {' '} high-risk synthesized markers.

)}
{/* Signal Integrity + Prosody */}
{/* Signal Integrity bar chart */}
Signal Integrity Engine
{([ { name: 'WavLM ITW', value: result.signal_scores.wavlm }, { name: 'Wav2Vec2 ASVspoof', value: result.signal_scores.wav2vec }, { name: 'Spectral Heuristics', value: result.signal_scores.spectral }, { name: 'Speaker Consistency', value: result.signal_scores.speaker }, { name: 'Prosody Analysis', value: result.signal_scores.prosody }, { name: 'Codec Forensics', value: result.signal_scores.codec }, ] as { name: string; value: number }[]).map((s) => { const color = s.value > 65 ? 'var(--accent-red)' : s.value > 40 ? 'var(--accent-orange)' : '#00E5CC'; return (
{s.name} {s.value.toFixed(1)}%
); })}
{/* Spectrogram + Anomaly Markers */}
{/* Speaker identity detail */} {result.signal_details.speaker && (
Speaker Identity Analysis
{[ { label: 'Similarity', val: result.signal_details.speaker.mean_sim, fmt: (v: number) => v.toFixed(3), warn: (v: number) => v < 0.82, }, { label: 'Std Dev', val: result.signal_details.speaker.std_sim, fmt: (v: number) => v.toFixed(4), warn: (v: number) => v < 0.012, }, { label: 'Jumps', val: result.signal_details.speaker.identity_jumps, fmt: (v: number) => `${v}`, warn: (v: number) => v > 2, }, { label: 'Score', val: result.signal_scores.speaker, fmt: (v: number) => `${v.toFixed(1)}%`, warn: (v: number) => v > 65, }, ].map(({ label, val, fmt, warn }) => { if (val === undefined || val === null) return null; const isWarn = warn(val as number); return (
{fmt(val as number)}
{label}
); })}
)}
)}
{/* ═══ Right: Forensic Dashboard Sidebar ═══════════════════════ */}
{/* ── Footer Telemetry Bar ── */}
); }; export default AudioLabPage;