voice-cloning-backend / frontend /src /components /audio /RealTimeStatsDashboard.tsx
AJ50's picture
Initial voice cloning backend with all dependencies
5008b66
import { useEffect, useRef, useState } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
interface RealTimeStatsDashboardProps {
isOpen: boolean;
onOpenChange: (open: boolean) => void;
synthesizerStartTime: number | null;
isSynthesizing: boolean;
currentVoiceName?: string;
enrolledVoiceCount?: number;
}
export default function RealTimeStatsDashboard({
isOpen,
onOpenChange,
synthesizerStartTime,
isSynthesizing,
currentVoiceName = 'Current Voice',
enrolledVoiceCount = 5
}: RealTimeStatsDashboardProps) {
const [elapsedSeconds, setElapsedSeconds] = useState(0);
const animationFrameRef = useRef<number>();
// Update elapsed time in real-time
useEffect(() => {
const updateElapsed = () => {
if (synthesizerStartTime && isSynthesizing) {
const elapsed = (Date.now() - synthesizerStartTime) / 1000;
setElapsedSeconds(elapsed);
animationFrameRef.current = requestAnimationFrame(updateElapsed);
} else {
setElapsedSeconds(0);
}
};
if (isOpen && isSynthesizing) {
animationFrameRef.current = requestAnimationFrame(updateElapsed);
}
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}
};
}, [isOpen, isSynthesizing, synthesizerStartTime]);
// Stage timing configuration
const stages = [
{ name: 'Speaker Encoder', duration: 3, startTime: 0, color: '#ff6b6b' },
{ name: 'Tacotron2 Synthesizer', duration: 45, startTime: 3, color: '#4ecdc4' },
{ name: 'WaveRNN Vocoder', duration: 12, startTime: 48, color: '#45b7d1' }
];
// Calculate metrics based on actual elapsed time
const getStageProgress = (stage: typeof stages[0]) => {
if (elapsedSeconds < stage.startTime) {
return 0;
} else if (elapsedSeconds >= stage.startTime && elapsedSeconds < stage.startTime + stage.duration) {
const stageElapsed = elapsedSeconds - stage.startTime;
return Math.min(99, (stageElapsed / stage.duration) * 100);
} else {
return 100;
}
};
// Simulated CPU and memory (would come from backend in production)
const cpuUsage = Math.min(95, Math.max(10, 50 + Math.sin(elapsedSeconds) * 30));
const memoryUsage = Math.min(90, Math.max(20, 60 + Math.cos(elapsedSeconds * 0.5) * 20));
return (
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Real-Time Synthesis Dashboard</DialogTitle>
<DialogDescription>
Synthesis progress and system metrics
</DialogDescription>
</DialogHeader>
<div className="space-y-6">
{/* Metrics Grid */}
<div className="grid grid-cols-3 gap-3">
{stages.map((stage) => {
const progress = getStageProgress(stage);
return (
<Card key={stage.name} className="bg-surface border-border">
<CardHeader className="pb-3">
<CardTitle className="text-sm">{stage.name}</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between items-center text-xs">
<span className="text-muted-foreground">Progress</span>
<span className="font-mono text-primary">{Math.round(progress)}%</span>
</div>
<div className="w-full h-2 bg-slate-700 rounded-full overflow-hidden">
<div
className="h-full transition-all duration-300"
style={{
width: `${progress}%`,
backgroundColor: stage.color
}}
/>
</div>
<div className="text-xs text-muted-foreground">
{progress < 100
? `${stage.startTime}s - ${stage.startTime + stage.duration}s`
: 'Completed'}
</div>
</CardContent>
</Card>
);
})}
</div>
{/* System Metrics */}
<div className="grid grid-cols-2 gap-3">
<Card className="bg-surface border-border">
<CardHeader className="pb-3">
<CardTitle className="text-sm">CPU Usage</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between items-center text-xs">
<span className="text-muted-foreground">Current</span>
<span className="font-mono text-accent">{Math.round(cpuUsage)}%</span>
</div>
<div className="w-full h-2 bg-slate-700 rounded-full overflow-hidden">
<div
className="h-full bg-accent transition-all duration-300"
style={{ width: `${cpuUsage}%` }}
/>
</div>
</CardContent>
</Card>
<Card className="bg-surface border-border">
<CardHeader className="pb-3">
<CardTitle className="text-sm">Memory Usage</CardTitle>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex justify-between items-center text-xs">
<span className="text-muted-foreground">Current</span>
<span className="font-mono text-primary-glow">{Math.round(memoryUsage)}%</span>
</div>
<div className="w-full h-2 bg-slate-700 rounded-full overflow-hidden">
<div
className="h-full bg-primary-glow transition-all duration-300"
style={{ width: `${memoryUsage}%` }}
/>
</div>
</CardContent>
</Card>
</div>
{/* Elapsed Time Display */}
<Card className="bg-surface border-border">
<CardContent className="pt-6">
<div className="flex justify-between items-center">
<span className="text-sm text-muted-foreground">Total Elapsed Time</span>
<span className="text-2xl font-mono font-bold text-primary">
{elapsedSeconds.toFixed(1)}s
</span>
</div>
</CardContent>
</Card>
{isSynthesizing && (
<div className="text-center text-xs text-green-400 animate-pulse">
● Synthesis in progress...
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}