multisense_df / app /frontend /src /components /AnalysisProgress.jsx
vijay-2132's picture
Clean deployment without large checkpoints
a94bd47
Raw
History Blame Contribute Delete
4.21 kB
// 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>
);
}