Better tests and more user feedback while doing transcription; WIP.

This commit is contained in:
Lucas Winther committed 2026-07-14 14:44:01 +02:00
1 parent 18acead43c
commit be82ecfe57
19 files changed
+447 -138

No files matched your search

+39
View File
@@ -59,6 +59,11 @@ export function App() {
);
const nextLiveRequest = useRef(0);
// File-flow run counter (D-018). Cancel bumps it, so a decode that was in
// flight when the user cancelled finds itself stale and never posts its
// audio to the (fresh) worker. Decoding itself cannot be aborted.
const fileRun = useRef(0);
const [busy, setBusy] = useState(false);
const [decoding, setDecoding] = useState(false);
const [loadStarted, setLoadStarted] = useState(false);
@@ -213,8 +218,40 @@ export function App() {
if (loadStarted) startLoad(modelId, preference);
}
/**
* Cancel the in-flight file transcription (D-018). Transformers.js has no
* abort API for a running `asr()` call — and on the WASM backend the pass
* blocks the worker's event loop, so even a "cancel" message would sit
* unread until the pass finished. Terminating the worker is the only
* reliable preemption; it also aborts an in-flight model download. The next
* run spawns a fresh worker and reloads the pipeline — cheap, because the
* weights stay in the browser Cache API (D-005).
*/
function cancelTranscription() {
if (!busy) return;
fileRun.current++; // any decode still running is now stale
workerRef.current?.terminate();
workerRef.current = null;
// The file flow never has live passes pending, but if anything is in the
// map (defensive), unblock it so nothing awaits a dead worker forever.
const cancelled = new Error("Transcription cancelled.");
for (const pending of pendingLive.current.values()) pending.reject(cancelled);
pendingLive.current.clear();
setBusy(false);
setPartialText("");
setResult(null);
setFileName(null);
setError(null);
// The worker (and any pipeline / partial download) is gone — reset the
// load state so the next transcription reloads from a clean slate.
setLoadStarted(false);
setLoadProgress(initialLoadProgress);
setActiveDevice(null);
}
async function handleFile(file: File) {
if (busy || liveStatus !== "idle") return;
const run = ++fileRun.current;
setBusy(true);
setError(null);
setPartialText("");
@@ -237,6 +274,7 @@ export function App() {
// Decoded entirely on-device via Web Audio (D-012); the audio never
// touches the network (Architecture.md §5).
const audio = await decodeAudioFile(file);
if (fileRun.current !== run) return; // cancelled while decoding (D-018)
// Transfer the buffer to the worker — zero copy. Language/task only
// apply to multilingual models; `.en` variants take neither.
postToWorker(
@@ -247,6 +285,7 @@ export function App() {
[audio.buffer as ArrayBuffer],
);
} catch (decodeError) {
if (fileRun.current !== run) return; // this run was already cancelled
setError(decodeError instanceof Error ? decodeError.message : String(decodeError));
setBusy(false);
} finally {