Native: download whisper model on demand + surface real errors
Two native-only fixes behind the "transcription failed [object Object]" report on the APK: 1. Model download. engineImpl.native.loadModel assumed the ggml .bin was already on disk (the downloader was a TODO), so on a real device it rejected — and since in-app recording is web-only, no model was ever present. Now loadModel fetches the model from Hugging Face (ggerganov/whisper.cpp) into <documentDirectory>/models on first use, reporting 0..1 progress through the existing "Loading model… X%" UI. Downloads to a .part file and renames on success so an interrupted download can't leave a truncated model; if initWhisper still rejects, the file is deleted so the next try re-downloads cleanly. Model URLs verified (tiny.en 77.7MB / base.en 148MB / small.en 488MB). 2. Error surfacing. transcribeStore did `String(err)`, which renders a non-Error native rejection (whisper.rn / file system throw plain objects) as "[object Object]". New shared errorMessage() pulls message/reason/code (or JSON) out of whatever was thrown, so failures are actionable instead of opaque. Progress downloads aren't in the new expo-file-system OO API yet, so the fetch uses the still-supported expo-file-system/legacy resumable downloader; native-only, not bundled on web. Validated: tsc clean, 279 tests pass, web export unaffected (legacy not in the web bundle), arm64 APK builds and the native JS bundle resolves the legacy import. On-device download + transcribe still needs a real phone to confirm end to end. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
// Extract a human-readable message from an unknown thrown value.
|
||||
//
|
||||
// `String(err)` on a non-Error object yields the useless "[object Object]",
|
||||
// which is exactly what surfaced when native modules (whisper.rn, the audio
|
||||
// decoder, file system) reject with a plain `{ message, code }`-shaped object
|
||||
// instead of an `Error`. This pulls the most useful text out of whatever was
|
||||
// thrown so the UI shows something actionable.
|
||||
export function errorMessage(err: unknown): string {
|
||||
if (err instanceof Error) return err.message;
|
||||
if (typeof err === 'string') return err;
|
||||
if (err && typeof err === 'object') {
|
||||
const o = err as { message?: unknown; reason?: unknown; code?: unknown };
|
||||
const parts = [o.message, o.reason, o.code]
|
||||
.filter((x) => x != null && x !== '')
|
||||
.map(String);
|
||||
if (parts.length > 0) return parts.join(' — ');
|
||||
try {
|
||||
return JSON.stringify(err);
|
||||
} catch {
|
||||
/* fall through to String() for circular/unserializable objects */
|
||||
}
|
||||
}
|
||||
return String(err);
|
||||
}
|
||||
Reference in New Issue
Block a user