import { estimateDefaultDownloadBytes, formatModelSize, type WhisperModel, } from "../lib/models"; import type { DevicePreference, InferenceDevice } from "../worker/protocol"; interface ModelPickerProps { models: readonly WhisperModel[]; selectedId: string; onSelect: (modelId: string) => void; devicePreference: DevicePreference; onDevicePreferenceChange: (preference: DevicePreference) => void; /** Backend the worker actually selected; null until the first load (D-004). */ activeDevice: InferenceDevice | null; /** Model ids with weights already in the browser cache (D-005). */ downloaded: ReadonlySet; /** True when the service worker controls the page — app shell cached (M3). */ offlineReady: boolean; /** True while a transcription is running — switching is disabled then. */ disabled?: boolean; /** Explicitly download the selected model's weights (D-020). */ onDownloadSelected: () => void; /** True while a model load/download is in flight. */ downloading: boolean; /** True when transcription will run on the server — no download needed (D-020). */ serverEngine: boolean; } const DEVICE_OPTIONS: readonly { value: DevicePreference; label: string }[] = [ { value: "auto", label: "Auto (WebNN → WebGPU → WASM)" }, { value: "webgpu", label: "WebGPU" }, { value: "webnn", label: "WebNN" }, { value: "wasm", label: "WASM (CPU)" }, ]; /** Human-readable backend names, shared with the active-device badge. */ export const DEVICE_LABELS: Record = { webnn: "WebNN", webgpu: "WebGPU", wasm: "WASM", }; /** * Curated model list (D-006): size estimate before download, English-only vs * multilingual grouping, downloaded badge from the Cache API (D-005), and a * device preference select (default "auto", D-004). */ export function ModelPicker({ models, selectedId, onSelect, devicePreference, onDevicePreferenceChange, activeDevice, downloaded, offlineReady, disabled = false, onDownloadSelected, downloading, serverEngine, }: ModelPickerProps) { // Size estimates depend on the dtype the worker will pick, which depends on // the device. Until the worker has reported one, assume WASM unless the // user explicitly asked for WebGPU. const estimateDevice: InferenceDevice = activeDevice ?? (devicePreference === "webgpu" ? "webgpu" : devicePreference === "webnn" ? "webnn" : "wasm"); const groups: { title: string; items: WhisperModel[] }[] = [ { title: "English-only", items: models.filter((model) => !model.multilingual), }, { title: "Multilingual", items: models.filter((model) => model.multilingual), }, ]; return (

Model

{/* Which backend the worker actually selected (D-004/D-017) — the user's preference is only a preference, so show the outcome. */} {activeDevice && ( Running on {DEVICE_LABELS[activeDevice]} )}
{disabled && (

Model switching is disabled while a transcription is running.

)} {serverEngine && (

Transcription is set to run on the server — the selected model runs there, so nothing is downloaded to this device. Downloading is only useful if you plan to transcribe on-device or offline.

)}

0 ? " offline-status-ready" : ""}`} > {offlineReady ? downloaded.size > 0 ? "Offline ready — the app and the models marked “available offline” work without a network connection." : "The app is cached for offline use. Download a model (or run one on-device transcription) to make it available offline too." : "Offline support is not active yet (the service worker is still installing, or this browser does not support it)."}

{groups.map((group) => (
{group.title}
    {group.items.map((model) => { const size = formatModelSize( estimateDefaultDownloadBytes(model, estimateDevice), ); const isDownloaded = downloaded.has(model.id); const isSelected = model.id === selectedId; return (
  • ); })}
))}
); }