import { Component, For, Show } from 'solid-js'; import { appStore } from '../stores/appStore'; import { getModelDisplayName, MODELS } from './ModelLoadingOverlay'; import type { AudioEngine } from '../lib/audio/types'; const formatInterval = (ms: number) => { if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`; return `${ms}ms`; }; export type SettingsPanelSection = 'full' | 'audio' | 'model'; export interface SettingsContentProps { /** When 'audio' or 'model', only that section is shown (e.g. hover on mic or load button). */ section?: SettingsPanelSection; onClose: () => void; onLoadModel: () => void; onLocalLoad?: (files: FileList) => void; onOpenDebug: () => void; onDeviceSelect?: (id: string) => void; audioEngine?: AudioEngine | null; /** When true, panel expands upward (bar in lower half); content order is reversed so ASR model stays adjacent to the bar. */ expandUp?: () => boolean; } /** Embeddable settings form (e.g. inside floating bar expansion). */ export const SettingsContent: Component = (props) => { const isV4 = () => appStore.transcriptionMode() === 'v4-utterance'; const isV3 = () => appStore.transcriptionMode() === 'v3-streaming'; const expandUp = () => props.expandUp?.() ?? false; const section = () => props.section ?? 'full'; const showAsr = () => section() === 'full' || section() === 'model'; const showAudio = () => section() === 'full' || section() === 'audio'; const showSliders = () => section() === 'full'; const showDebug = () => section() === 'full'; return (

ASR model

{appStore.modelState() === 'ready' ? getModelDisplayName(appStore.selectedModelId()) : appStore.modelState()}

{appStore.modelMessage()} {Math.round(appStore.modelProgress())}%

Audio input

Energy threshold {(appStore.energyThreshold() * 100).toFixed(1)}%
{ const val = parseFloat(e.currentTarget.value); appStore.setEnergyThreshold(val); props.audioEngine?.updateConfig({ energyThreshold: val }); }} class="debug-slider w-full h-2 rounded-full appearance-none cursor-pointer bg-[var(--color-earthy-sage)]/30" />
VAD threshold {(appStore.sileroThreshold() * 100).toFixed(0)}%
appStore.setSileroThreshold(parseFloat(e.currentTarget.value))} class="debug-slider w-full h-2 rounded-full appearance-none cursor-pointer bg-[var(--color-earthy-sage)]/30" />
Tick interval {formatInterval(appStore.v4InferenceIntervalMs())}
appStore.setV4InferenceIntervalMs(parseInt(e.currentTarget.value))} class="debug-slider w-full h-2 rounded-full appearance-none cursor-pointer bg-[var(--color-earthy-sage)]/30" />
320ms 8.0s
Silence flush {appStore.v4SilenceFlushSec().toFixed(1)}s
appStore.setV4SilenceFlushSec(parseFloat(e.currentTarget.value))} class="debug-slider w-full h-2 rounded-full appearance-none cursor-pointer bg-[var(--color-earthy-sage)]/30" />
Window {appStore.streamingWindow().toFixed(1)}s
appStore.setStreamingWindow(parseFloat(e.currentTarget.value))} class="debug-slider w-full h-2 rounded-full appearance-none cursor-pointer bg-[var(--color-earthy-sage)]/30" />
); };