Spaces:
Sleeping
Sleeping
| // src/components/AnalysisProgress.jsx | |
| import { useEffect, useState } from 'react'; | |
| const STEPS = [ | |
| { | |
| key: 'frames', | |
| label: 'Extracting Frames', | |
| desc: 'Sampling video at 1 fps for visual analysis', | |
| icon: '🎞️', | |
| weight: 20, | |
| }, | |
| { | |
| key: 'audio', | |
| label: 'Analyzing Audio', | |
| desc: 'Running MFCCs + spectral deepfake detection', | |
| icon: '🎙️', | |
| weight: 25, | |
| }, | |
| { | |
| key: 'lipsync', | |
| label: 'Lip-Sync Check', | |
| desc: 'Aligning phoneme boundaries with lip motion', | |
| icon: '👄', | |
| weight: 25, | |
| }, | |
| { | |
| key: 'fusion', | |
| label: 'Multimodal Fusion', | |
| desc: 'Combining modality scores via learned weights', | |
| icon: '⚡', | |
| weight: 20, | |
| }, | |
| { | |
| key: 'done', | |
| label: 'Generating Report', | |
| desc: 'Producing Grad-CAM heatmaps & sync profile', | |
| icon: '📊', | |
| weight: 10, | |
| }, | |
| ]; | |
| function getStepState(stepKey, currentKey) { | |
| const currentIdx = STEPS.findIndex((s) => s.key === currentKey); | |
| const stepIdx = STEPS.findIndex((s) => s.key === stepKey); | |
| if (stepIdx < currentIdx) return 'completed'; | |
| if (stepIdx === currentIdx) return 'active'; | |
| return 'waiting'; | |
| } | |
| function getProgress(currentKey) { | |
| const idx = STEPS.findIndex((s) => s.key === currentKey); | |
| if (idx < 0) return 0; | |
| const completedWeight = STEPS.slice(0, idx).reduce((sum, s) => sum + s.weight, 0); | |
| const activeWeight = STEPS[idx].weight * 0.5; // half-credit for active step | |
| return Math.round(completedWeight + activeWeight); | |
| } | |
| export default function AnalysisProgress({ currentStep, filename }) { | |
| const [displayProgress, setDisplayProgress] = useState(0); | |
| const targetProgress = currentStep === 'done' ? 100 : getProgress(currentStep); | |
| useEffect(() => { | |
| const timer = setTimeout(() => setDisplayProgress(targetProgress), 50); | |
| return () => clearTimeout(timer); | |
| }, [targetProgress]); | |
| return ( | |
| <div className="progress-wrapper glass-card animate-scale"> | |
| {/* Header */} | |
| <div className="progress-header"> | |
| <span aria-hidden="true" style={{ fontSize: '2rem' }}>🔬</span> | |
| <h3>Analyzing Video</h3> | |
| {filename && ( | |
| <p style={{ fontSize: '0.8rem', color: 'var(--text-muted)', marginTop: 4 }}> | |
| {filename} | |
| </p> | |
| )} | |
| </div> | |
| {/* Steps */} | |
| <ol className="progress-steps" aria-label="Analysis progress steps"> | |
| {STEPS.map((step) => { | |
| const state = getStepState(step.key, currentStep); | |
| return ( | |
| <li | |
| key={step.key} | |
| className={`progress-step ${state}`} | |
| aria-current={state === 'active' ? 'step' : undefined} | |
| > | |
| {/* Step icon */} | |
| <div className={`step-icon ${state}`} aria-hidden="true"> | |
| {state === 'completed' ? '✓' : state === 'active' ? ( | |
| <span className="spinner" style={{ width: 16, height: 16 }} /> | |
| ) : ( | |
| step.icon | |
| )} | |
| </div> | |
| {/* Text */} | |
| <div className="step-text"> | |
| <div className="step-label">{step.label}</div> | |
| <div className="step-desc">{step.desc}</div> | |
| </div> | |
| {/* Status */} | |
| <span className={`step-status ${state}`} aria-label={`Status: ${state}`}> | |
| {state === 'completed' && 'Done'} | |
| {state === 'active' && 'Running'} | |
| {state === 'waiting' && 'Queued'} | |
| </span> | |
| </li> | |
| ); | |
| })} | |
| </ol> | |
| {/* Overall progress bar */} | |
| <div className="overall-progress" aria-label="Overall progress"> | |
| <div className="progress-bar-label"> | |
| <span>Overall Progress</span> | |
| <span aria-live="polite">{displayProgress}%</span> | |
| </div> | |
| <div | |
| className="progress-bar-track" | |
| role="progressbar" | |
| aria-valuenow={displayProgress} | |
| aria-valuemin={0} | |
| aria-valuemax={100} | |
| > | |
| <div | |
| className="progress-bar-fill" | |
| style={{ width: `${displayProgress}%` }} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |