Spaces:
Running
Running
File size: 2,575 Bytes
6c3af4e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | /// <reference lib="webworker" />
import { DICTATION_MODEL_VERSION } from "@shared/dictationModel";
import { AssetDownloader, ModelArch, Transcriber } from "./moonshine";
import type {
WorkerRequest,
WorkerResponse,
} from "./speechToTextWorkerProtocol";
function post(message: WorkerResponse, transfer: Transferable[] = []) {
self.postMessage(message, transfer);
}
/**
* Lines keyed by their opaque id, in the order they first appeared, so the
* transcript is the join of the map's values. Completed lines keep their final
* text; only the in-progress line changes.
*/
const lines = new Map<string, string>();
function emitTranscript() {
post({ type: "transcript", text: [...lines.values()].join(" ").trim() });
}
let transcriber: Transcriber | null = null;
async function load(modelFiles: Record<string, string>): Promise<void> {
const downloader = new AssetDownloader({
// Versioned, so a model bump does not leave ~51 MB of unreachable entries
// behind in every user's Cache Storage.
cacheName: `minisearch-dictation-${DICTATION_MODEL_VERSION}`,
onProgress: (loaded, total) => post({ type: "progress", loaded, total }),
});
transcriber = await Transcriber.loadFromUrls(modelFiles, {
modelArch: ModelArch.TinyStreaming,
downloader,
});
transcriber.addListener({
onLineStarted: (event) => {
lines.set(event.line.id, event.line.text);
emitTranscript();
},
onLineTextChanged: (event) => {
lines.set(event.line.id, event.line.text);
emitTranscript();
},
onLineCompleted: (event) => {
lines.set(event.line.id, event.line.text);
emitTranscript();
},
onError: (event) => {
post({
type: "error",
message: event.error?.message ?? "Dictation failed",
});
},
});
transcriber.start();
post({ type: "loaded" });
}
self.onmessage = async ({ data }: MessageEvent<WorkerRequest>) => {
try {
if (data.type === "load") {
await load(data.modelFiles);
return;
}
if (data.type === "audio") {
transcriber?.addAudio(
new Float32Array(data.buffer),
data.sampleRate,
undefined,
);
return;
}
try {
transcriber?.stop();
} finally {
// Always acknowledged: without this the main thread waits out its whole
// timeout before terminating a worker that already gave up.
post({ type: "stopped" });
}
} catch (error) {
post({
type: "error",
message: error instanceof Error ? error.message : String(error),
});
}
};
|