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; } const DEVICE_OPTIONS: readonly { value: DevicePreference; label: string }[] = [ { value: "auto", label: "Auto (WebGPU if available)" }, { value: "webgpu", label: "WebGPU" }, { value: "wasm", label: "WASM (CPU)" }, ]; /** * 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, }: 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" : "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

{disabled && (

Model switching is disabled while a transcription is running.

)}

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. Load a model once 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 (
  • ); })}
))}
); }