import { Component, createMemo, For, Show, createSignal, onCleanup, createEffect } from 'solid-js'; import { appStore, type TranscriptionMode } from '../stores/appStore'; import type { AudioEngine } from '../lib/audio/types'; import type { MelWorkerClient } from '../lib/audio/MelWorkerClient'; import { LayeredBufferVisualizer } from './LayeredBufferVisualizer'; interface DebugPanelProps { audioEngine?: AudioEngine; melClient?: MelWorkerClient; } const MODES: { id: TranscriptionMode; label: string; short: string }[] = [ { id: 'v4-utterance', label: 'Utterance (v4)', short: 'v4' }, { id: 'v3-streaming', label: 'Streaming (v3)', short: 'v3' }, { id: 'v2-utterance', label: 'Legacy (v2)', short: 'v2' }, ]; export const DebugPanel: Component = (props) => { const isRecording = () => appStore.recordingState() === 'recording'; const isV4 = () => appStore.transcriptionMode() === 'v4-utterance'; const isV3 = () => appStore.transcriptionMode() === 'v3-streaming'; const [height, setHeight] = createSignal(260); const [isResizing, setIsResizing] = createSignal(false); let startY = 0; let startHeight = 0; let scrollContainer: HTMLDivElement | undefined; // Auto-scroll to bottom of finalized sentences createEffect(() => { appStore.matureText(); // Track dependency if (scrollContainer) { scrollContainer.scrollTop = scrollContainer.scrollHeight; } }); const handleMouseDown = (e: MouseEvent) => { setIsResizing(true); startY = e.clientY; startHeight = height(); window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); }; const handleMouseMove = (e: MouseEvent) => { if (!isResizing()) return; const delta = startY - e.clientY; const newHeight = Math.min(Math.max(startHeight + delta, 150), 600); setHeight(newHeight); }; const handleMouseUp = () => { setIsResizing(false); window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; onCleanup(() => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }); const rtfColor = createMemo(() => { const rtfx = appStore.rtfxAverage(); if (rtfx === 0) return 'text-[var(--color-earthy-soft-brown)]'; if (rtfx >= 2) return 'text-[var(--color-earthy-muted-green)] font-bold'; if (rtfx >= 1) return 'text-[var(--color-earthy-coral)] font-bold'; return 'text-[var(--color-earthy-coral)] font-bold'; }); return (
{/* Resize Handle */}
{/* ---- Column 1: System & Signal (merged indicators) ---- */}
System & Signal
{appStore.backend()}
VAD
Mode
{(mode) => ( )}
RTFx {appStore.rtfxAverage() > 0 ? Math.round(appStore.rtfxAverage()) : '–'}
Latency {Math.round(appStore.inferenceLatencyAverage())}ms
Buffer {(appStore.bufferMetrics().fillRatio * 100).toFixed(0)}%
Merger
Sent
{appStore.v4MergerStats().sentencesFinalized}
Cursor
{appStore.matureCursorTime().toFixed(1)}s
Uttr
{appStore.v4MergerStats().utterancesProcessed}
RMS Energy appStore.energyThreshold() ? 'text-[var(--color-earthy-muted-green)]' : 'text-[var(--color-earthy-soft-brown)]'}> {(appStore.audioLevel() * 100).toFixed(1)}%
0 ? 'opacity-100' : 'opacity-40'}`}>
VAD Prob appStore.sileroThreshold() ? 'text-[var(--color-earthy-coral)] font-bold' : 'text-[var(--color-earthy-soft-brown)]'}> {(appStore.vadState().sileroProbability * 100).toFixed(0)}%
appStore.sileroThreshold() ? 'bg-[var(--color-earthy-coral)]' : 'bg-[var(--color-earthy-soft-brown)]'}`} style={{ width: `${Math.min(100, appStore.vadState().sileroProbability * 100)}%` }} />
SNR 3 ? 'text-[var(--color-earthy-muted-green)]' : 'text-[var(--color-earthy-soft-brown)]'}`}> {appStore.vadState().snr.toFixed(1)} dB
Overlap
{appStore.streamingOverlap().toFixed(1)}s
Chunks
{appStore.mergeInfo().chunkCount}
State
{appStore.vadState().hybridState}
Windows
{appStore.v4MergerStats().utterancesProcessed}
{/* ---- Column 2: Live Context (mode-dependent) ---- */}
{isV4() ? 'Transcript State' : isV3() ? 'Stream Sync' : 'Segments'} {/* v3: LCS indicators */}
Lock
join_inner Match: {appStore.mergeInfo().lcsLength}
{/* v4: VAD state indicator */}
{appStore.vadState().hybridState}
0 ? 'opacity-100' : 'opacity-0'}`}> VAD 0.5 ? 'text-[var(--color-earthy-coral)]' : 'text-[var(--color-earthy-soft-brown)]'}`}> {(appStore.vadState().sileroProbability * 100).toFixed(0)}%
{/* v4: Mature + Immature text display */}
{/* Mature (finalized) sentences */}

Finalized Sentences

No finalized sentences yet... }> {appStore.matureText()}
{/* Immature (active) sentence */}

Active Sentence

Waiting for speech... }> {appStore.immatureText()}
{/* Pending sentence info */} 0}>
{appStore.v4MergerStats().sentencesFinalized} sentences finalized | Cursor at {appStore.matureCursorTime().toFixed(2)}s | {appStore.v4MergerStats().utterancesProcessed} windows processed
{/* v3: Transition cache + anchors */}

Transition Cache

{(token) => (
0.8 ? '#F9F7F2' : 'rgba(249,247,242,0.6)', "border-color": `rgba(107, 112, 92, ${Math.max(0.2, token.confidence * 0.4)})`, "color": token.confidence > 0.8 ? '#3D405B' : '#A5A58D', "opacity": Math.max(0.5, token.confidence) }} title={`Confidence: ${(token.confidence * 100).toFixed(0)}%`} > {token.text}
)}
{appStore.pendingText()}... Waiting for speech input...

Stable Anchors

{(token) => ( {token} )} No stable anchors locked yet.
{/* v2: basic info */}
Legacy per-utterance mode. Segments are transcribed individually.
{/* New Layered Buffer Visualizer */}
); };