Run browser file transcription through the long-form pipeline when asked and the VAD model is available

Adds an Advanced "Skip silence in uploaded files" option (off by default
pending measurement), the vad/longForm protocol fields, the old-path
fallback when the VAD model is missing, and a plain message for files
with no speech.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-29 22:02:47 +01:00
1 parent 0c3a83c23c
commit f55ea6ecf8
14 files changed
+363 -8

No files matched your search

+9
View File
@@ -77,6 +77,15 @@ describe("BrowserEngine", () => {
expect("modelId" in message).toBe(false);
});
test("forwards the long-form request only when set (D-WS3a)", () => {
const worker = new FakeWorker();
const engine = new BrowserEngine(worker);
void engine.transcribe(new Float32Array(4), { longForm: true });
void engine.transcribe(new Float32Array(4));
expect(worker.posted[0]!.message.longForm).toBe(true);
expect("longForm" in worker.posted[1]!.message).toBe(false);
});
test("asks the worker to stream only when a consumer listens", () => {
const worker = new FakeWorker();
const engine = new BrowserEngine(worker);
+55
View File
@@ -0,0 +1,55 @@
import { describe, expect, test } from "bun:test";
import { emptyTranscriptNotice } from "../src/lib/export";
import {
LONG_FORM_DEFAULT,
LONG_FORM_KEY,
loadLongFormChoice,
saveLongFormChoice,
} from "../src/lib/longForm";
function memoryStorage(initial: Record<string, string> = {}) {
const data = { ...initial };
return {
data,
getItem: (key: string) => data[key] ?? null,
setItem: (key: string, value: string) => {
data[key] = value;
},
};
}
describe("long-form setting persistence (D-WS3a)", () => {
test("defaults when nothing is stored or storage is unavailable", () => {
expect(loadLongFormChoice(memoryStorage())).toBe(LONG_FORM_DEFAULT);
expect(loadLongFormChoice(null)).toBe(LONG_FORM_DEFAULT);
const throwing = {
getItem: () => {
throw new Error("denied");
},
setItem: () => {
throw new Error("denied");
},
};
expect(loadLongFormChoice(throwing)).toBe(LONG_FORM_DEFAULT);
expect(() => saveLongFormChoice(true, throwing)).not.toThrow();
});
test("round-trips an explicit choice", () => {
const storage = memoryStorage();
saveLongFormChoice(!LONG_FORM_DEFAULT, storage);
expect(storage.data[LONG_FORM_KEY]).toBeDefined();
expect(loadLongFormChoice(storage)).toBe(!LONG_FORM_DEFAULT);
});
});
describe("emptyTranscriptNotice (plan 001-0 Review Focus 4)", () => {
test("a finished transcript with no words says so plainly", () => {
expect(emptyTranscriptNotice({ text: "" })).toBe("No speech was found in this recording.");
expect(emptyTranscriptNotice({ text: " \n" })).toBe("No speech was found in this recording.");
});
test("no notice while running or when there is text", () => {
expect(emptyTranscriptNotice(null)).toBeNull();
expect(emptyTranscriptNotice({ text: " Hello." })).toBeNull();
});
});
+19
View File
@@ -4,6 +4,7 @@ import {
deviceAttempts,
dtypeRequiresF16,
initialLoadProgress,
choosePipeline,
isUiToWorkerMessage,
isWorkerToUiMessage,
TranscribeProgressTracker,
@@ -35,7 +36,9 @@ describe("isUiToWorkerMessage", () => {
{ type: "transcribe", audio: new Float32Array(8), requestId: "live-3" },
{ type: "transcribe", audio: new Float32Array(8), requestId: "engine-1-1", stream: true },
{ type: "transcribe", audio: new Float32Array(8), stream: false },
{ type: "transcribe", audio: new Float32Array(8), stream: true, longForm: true },
];
loads.push({ type: "load", modelId: "onnx-community/whisper-base", device: "auto", vad: true });
for (const message of [...loads, ...transcribes]) {
expect(isUiToWorkerMessage(message)).toBe(true);
}
@@ -65,10 +68,26 @@ describe("isUiToWorkerMessage", () => {
expect(
isUiToWorkerMessage({ type: "transcribe", audio: new Float32Array(4), stream: "yes" }),
).toBe(false);
expect(
isUiToWorkerMessage({ type: "transcribe", audio: new Float32Array(4), longForm: 1 }),
).toBe(false);
expect(isUiToWorkerMessage({ type: "load", modelId: "x", device: "auto", vad: "yes" })).toBe(
false,
);
expect(isUiToWorkerMessage({ type: "result", text: "", chunks: [] })).toBe(false);
});
});
describe("choosePipeline (D-WS3a/D-WS3b)", () => {
test("long-form only when asked for and the VAD model loaded", () => {
expect(choosePipeline({ longForm: true, vadReady: true })).toBe("longform");
// Offline / never downloaded: the old path, never an error (Review Focus 3).
expect(choosePipeline({ longForm: true, vadReady: false })).toBe("chunked");
expect(choosePipeline({ longForm: false, vadReady: true })).toBe("chunked");
expect(choosePipeline({ longForm: undefined, vadReady: true })).toBe("chunked");
});
});
describe("isWorkerToUiMessage", () => {
test("accepts every worker → UI message shape", () => {
const messages: WorkerToUiMessage[] = [