Spaces:
Running
Running
| /** | |
| * Performance Metrics Component | |
| * | |
| * Developer-focused dashboard showing: | |
| * - Real-time inference speed (tokens/sec, RTF) | |
| * - Progressive update latency | |
| * - Window state (growing vs sliding) | |
| * - Memory usage | |
| */ | |
| import { useState, useEffect } from 'react'; | |
| export default function PerformanceMetrics({ | |
| latency, | |
| rtf, | |
| audioDuration, | |
| windowState, | |
| device, | |
| updateInterval, | |
| isProcessingFile, | |
| fileDuration, | |
| transcribedDuration, | |
| }) { | |
| const [memoryUsage, setMemoryUsage] = useState(null); | |
| const [wasProcessingFile, setWasProcessingFile] = useState(false); | |
| // Track if we were processing a file (to keep labels after completion) | |
| useEffect(() => { | |
| if (isProcessingFile) { | |
| setWasProcessingFile(true); | |
| } | |
| }, [isProcessingFile]); | |
| useEffect(() => { | |
| // Monitor memory usage if available | |
| if (performance.memory) { | |
| const interval = setInterval(() => { | |
| const memory = performance.memory; | |
| setMemoryUsage({ | |
| used: (memory.usedJSHeapSize / 1024 / 1024).toFixed(1), | |
| total: (memory.totalJSHeapSize / 1024 / 1024).toFixed(1), | |
| limit: (memory.jsHeapSizeLimit / 1024 / 1024).toFixed(1), | |
| }); | |
| }, 1000); | |
| return () => clearInterval(interval); | |
| } | |
| }, []); | |
| const formatDuration = (seconds) => { | |
| if (seconds === null || seconds === undefined) return null; | |
| if (seconds < 60) { | |
| return seconds.toFixed(1); | |
| } | |
| const minutes = Math.floor(seconds / 60); | |
| const secs = Math.floor(seconds % 60); | |
| return `${minutes}m ${secs}s`; | |
| }; | |
| const MetricCard = ({ label, value, unit, color = 'gray', formatTime = false }) => { | |
| const displayValue = formatTime ? formatDuration(value) : (value !== null && value !== undefined ? value.toFixed(2) : null); | |
| const displayUnit = formatTime ? '' : unit; | |
| return ( | |
| <div className="bg-gray-800 rounded-lg p-4 border border-gray-700"> | |
| <div className="text-xs text-gray-400 uppercase tracking-wider mb-1"> | |
| {label} | |
| </div> | |
| <div className={`text-2xl font-bold text-${color}-400 font-mono`}> | |
| {displayValue !== null ? displayValue : 'β'} | |
| {displayUnit && <span className="text-sm ml-1 text-gray-500">{displayUnit}</span>} | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const getRTFColor = (rtf) => { | |
| if (rtf === null || rtf === undefined) return 'gray'; | |
| // Higher RTF is better (means faster than real-time) | |
| if (rtf > 5) return 'green'; // Excellent (5x+ real-time) | |
| if (rtf > 1) return 'yellow'; // Good (faster than real-time) | |
| return 'red'; // Slow (slower than real-time) | |
| }; | |
| const getLatencyColor = (latency) => { | |
| if (latency === null || latency === undefined) return 'cyan'; | |
| // Lower latency is better | |
| if (latency < 0.5) return 'green'; // Excellent (<500ms) | |
| if (latency < 1.0) return 'yellow'; // Good (<1s) | |
| return 'red'; // Slow (>1s) | |
| }; | |
| const getWindowStateIcon = (state) => { | |
| if (state === 'growing') return 'π'; | |
| if (state === 'sliding') return 'βοΈ'; | |
| return 'βΈοΈ'; | |
| }; | |
| return ( | |
| <div className="w-full max-w-4xl mx-auto mt-6"> | |
| <div className="bg-gray-900 rounded-lg border border-gray-700 p-6 shadow-xl"> | |
| <h2 className="text-xl font-semibold text-gray-100 mb-4 flex items-center gap-2"> | |
| <span>π</span> Performance Metrics | |
| </h2> | |
| {/* Metrics Grid */} | |
| <div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-4"> | |
| <MetricCard | |
| label={(isProcessingFile || wasProcessingFile) ? "Processing Time" : "Latency"} | |
| value={latency} | |
| unit="s" | |
| color={(isProcessingFile || wasProcessingFile) ? "blue" : getLatencyColor(latency)} | |
| formatTime={true} | |
| /> | |
| <MetricCard | |
| label="Real-time Factor" | |
| value={rtf} | |
| unit="x" | |
| color={getRTFColor(rtf)} | |
| /> | |
| <MetricCard | |
| label={(isProcessingFile || wasProcessingFile) ? "Transcribed" : "Window Size"} | |
| value={(isProcessingFile || wasProcessingFile) ? transcribedDuration : audioDuration} | |
| unit="s" | |
| color="blue" | |
| formatTime={true} | |
| /> | |
| </div> | |
| {/* Additional Info */} | |
| <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| {/* Window State */} | |
| <div className="bg-gray-800 rounded-lg p-4 border border-gray-700"> | |
| <div className="text-xs text-gray-400 uppercase tracking-wider mb-1"> | |
| Window State | |
| </div> | |
| <div className="text-lg font-semibold text-gray-200"> | |
| {getWindowStateIcon(windowState)} {windowState || 'idle'} | |
| </div> | |
| <div className="text-xs text-gray-500 mt-1"> | |
| {windowState === 'growing' && 'Building context (0-15s)'} | |
| {windowState === 'sliding' && 'Sliding window (>15s)'} | |
| {!windowState && 'Not recording'} | |
| </div> | |
| </div> | |
| {/* Device */} | |
| <div className="bg-gray-800 rounded-lg p-4 border border-gray-700"> | |
| <div className="text-xs text-gray-400 uppercase tracking-wider mb-1"> | |
| Acceleration | |
| </div> | |
| <div className="text-lg font-semibold text-gray-200"> | |
| {device === 'webgpu-hybrid' && 'π WebGPU Hybrid'} | |
| {device === 'webgpu' && 'π WebGPU'} | |
| {device === 'wasm' && 'βοΈ WebAssembly'} | |
| {device === 'cpu' && 'π₯οΈ CPU'} | |
| {!device && 'β'} | |
| </div> | |
| <div className="text-xs text-gray-500 mt-1"> | |
| {device === 'webgpu-hybrid' && 'GPU encoder + WASM decoder'} | |
| {device === 'webgpu' && 'Hardware accelerated'} | |
| {device === 'wasm' && 'Software optimized'} | |
| {device === 'cpu' && 'Fallback mode'} | |
| </div> | |
| </div> | |
| {/* Memory (if available) */} | |
| {memoryUsage && ( | |
| <div className="bg-gray-800 rounded-lg p-4 border border-gray-700"> | |
| <div className="text-xs text-gray-400 uppercase tracking-wider mb-1"> | |
| Memory Usage | |
| </div> | |
| <div className="text-lg font-semibold text-gray-200"> | |
| {memoryUsage.used} MB | |
| </div> | |
| <div className="text-xs text-gray-500 mt-1"> | |
| of {memoryUsage.total} MB allocated | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* RTF Explanation */} | |
| {rtf !== null && rtf !== undefined && ( | |
| <div className="mt-4 p-3 bg-gray-800 border border-gray-700 rounded text-xs text-gray-400"> | |
| <strong>Real-time Factor (RTF):</strong> How many times faster than real-time. | |
| {rtf > 1 && ` β ${rtf.toFixed(1)}x faster than real-time`} | |
| {rtf <= 1 && ' β οΈ Slower than real-time'} | |
| {' (Higher is better)'} | |
| </div> | |
| )} | |
| </div> | |
| {/* Technical Info */} | |
| <div className="mt-4 text-xs text-gray-500 text-center space-y-1"> | |
| <p>Model: Parakeet TDT 0.6B v3 (ONNX) | Sample Rate: 16kHz</p> | |
| <p>Progressive updates every 500ms | Smart window management (15s max)</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |