diff --git a/src/App.tsx b/src/App.tsx index e282021..1f11bda 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,9 +36,15 @@ import { estimatePeakLoadBytes, formatModelSize, getModel, + modelFamily, resolveDefaultDtype, } from "./lib/models"; -import { shouldWarnBeforeLoad, type MobileMemoryHint } from "./lib/mobileMemoryGuard"; +import { + isModelAllowedOnDevice, + modelsForDevice, + shouldWarnBeforeLoad, + type MobileMemoryHint, +} from "./lib/mobileMemoryGuard"; import { applyLoadProgress, initialLoadProgress, @@ -68,6 +74,16 @@ interface AppErrorState { const MODEL_IDS = MODELS.map((model) => model.id); +/** Device signals for the mobile memory guard (D-027) and desktop-only models (D-WS7a). */ +function memoryHint(): MobileMemoryHint { + const nav = typeof navigator !== "undefined" ? navigator : undefined; + return { + userAgent: nav?.userAgent, + maxTouchPoints: nav?.maxTouchPoints, + deviceMemoryGB: (nav as (Navigator & { deviceMemory?: number }) | undefined)?.deviceMemory, + }; +} + export function App() { const workerRef = useRef(null); @@ -260,15 +276,7 @@ export function App() { const targetModel = getModel(forModelId); if (!targetModel) return false; const peakBytes = estimatePeakLoadBytes(targetModel, resolveDefaultDtype(forModelId, device)); - const hint: MobileMemoryHint = { - userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined, - maxTouchPoints: typeof navigator !== "undefined" ? navigator.maxTouchPoints : undefined, - deviceMemoryGB: - typeof navigator !== "undefined" - ? (navigator as Navigator & { deviceMemory?: number }).deviceMemory - : undefined, - }; - return shouldWarnBeforeLoad(hint, peakBytes); + return shouldWarnBeforeLoad(memoryHint(), peakBytes); } /** @@ -281,6 +289,9 @@ export function App() { function confirmRiskyMobileLoad(forModelId: string, preference: DevicePreference): boolean { const targetModel = getModel(forModelId); if (!targetModel) return true; + // Desktop-only models are never offered on phones (D-WS7a); this guards + // any path that could still name one (e.g. a stale selection). + if (!isModelAllowedOnDevice(targetModel, memoryHint())) return false; const device = guessDevice(preference); const key = overrideKey(forModelId, device); if (mobileWarningAcknowledged.current.has(key)) return true; @@ -979,9 +990,15 @@ export function App() { const serverSelected = engineChoice === "server" && auth.user !== null && serverStatus?.available === true; - const availableModels = serverSelected - ? MODELS.filter((m) => m.fasterWhisperModel === "large-v3-turbo" || m.fasterWhisperModel === "medium.en") - : MODELS; + // Phones never see desktop-only models (Parakeet, D-WS7a); the server + // engine only offers what the sidecar runs. + const availableModels = modelsForDevice( + serverSelected + ? MODELS.filter((m) => m.fasterWhisperModel === "large-v3-turbo" || m.fasterWhisperModel === "medium.en") + : MODELS, + memoryHint(), + ); + const parakeetSelected = modelFamily(model) === "parakeet"; useEffect(() => { if (serverSelected) { @@ -1032,6 +1049,15 @@ export function App() { serverEngine={serverSelected} /> + {/* Parakeet has no language or task choice (D-WS7a) — say so where + those controls would be, rather than leaving their absence unexplained. */} + {parakeetSelected && ( +

+ Parakeet understands English only, so there is no language or translation + choice. For other languages, pick a Multilingual Whisper model. +

+ )} + {model?.multilingual && ( = 300 && ( + {liveActive && + activeDevice === "wasm" && + !parakeetSelected && + (model?.encoderParamsM ?? 0) >= 300 && (

{model?.displayName} runs slower than real time on the WASM backend, so live text will lag behind speech. A smaller model (Tiny, Base or Small) diff --git a/src/components/ModelPicker.tsx b/src/components/ModelPicker.tsx index 61834b1..d8221f0 100644 --- a/src/components/ModelPicker.tsx +++ b/src/components/ModelPicker.tsx @@ -158,7 +158,16 @@ export function ModelPicker({ disabled={disabled} onChange={() => onSelect(model.id)} /> - {model.displayName} + + {model.displayName} + {model.family === "parakeet" && ( + // Not Whisper (D-WS7a): say what it is good for and + // what it can't do, in plain words. + + Not Whisper · English only · fast on long recordings + + )} + {isDownloaded && (