| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | 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); |
| |
|
| | |
| | useEffect(() => { |
| | if (isProcessingFile) { |
| | setWasProcessingFile(true); |
| | } |
| | }, [isProcessingFile]); |
| |
|
| | useEffect(() => { |
| | |
| | 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'; |
| | |
| | if (rtf > 5) return 'green'; |
| | if (rtf > 1) return 'yellow'; |
| | return 'red'; |
| | }; |
| |
|
| | const getLatencyColor = (latency) => { |
| | if (latency === null || latency === undefined) return 'cyan'; |
| | |
| | if (latency < 0.5) return 'green'; |
| | if (latency < 1.0) return 'yellow'; |
| | return 'red'; |
| | }; |
| |
|
| | 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> |
| | ); |
| | } |
| |
|