fix(dictation): self-host Silero VAD / onnxruntime-web assets
Streaming dictation failed at runtime with "no available backend found /
'text/html' is not a valid JavaScript MIME type": @ricky0123/vad-web 0.0.30
defaults baseAssetPath/onnxWASMBasePath to "./" (relative to the page URL),
so the worklet, Silero model and ORT wasm/mjs were requested against the SPA
catch-all and came back as index.html.
Serve them from a fixed /vad/ instead:
- scripts/copy-vad-assets.mjs copies the 4 runtime assets (vad worklet,
silero_vad_v5.onnx, ort-wasm-simd-threaded.jsep.{mjs,wasm}) from node_modules
into apps/client/public/vad/ (gitignored — the ORT wasm is ~26 MB)
- client dev/build scripts run the copy first so the assets are always present
- useStreamingDictation points both path constants at "/vad/"
Verified: dev server serves all four under /vad/ with HTTP 200 and correct
Content-Type (js/wasm, never text/html); tsc clean. Prod (Docker) build runs
the copy step, so dist/vad/* ships in the image.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
59
apps/client/scripts/copy-vad-assets.mjs
Normal file
59
apps/client/scripts/copy-vad-assets.mjs
Normal file
@@ -0,0 +1,59 @@
|
||||
// Self-host the @ricky0123/vad-web + onnxruntime-web runtime assets under
|
||||
// apps/client/public/vad/.
|
||||
//
|
||||
// WHY THIS EXISTS:
|
||||
// Both vad-web and onnxruntime-web resolve their assets by URL *at runtime* (the
|
||||
// VAD audio worklet + Silero model, and ORT's wasm/mjs backend). In vad-web
|
||||
// 0.0.30 the default baseAssetPath / onnxWASMBasePath is "./" — i.e. relative to
|
||||
// the current page URL — NOT a CDN. In this SPA that "./" request hits the
|
||||
// client-side catch-all route and gets served index.html (text/html), so the
|
||||
// onnxruntime ESM/wasm backend fails to initialize ("'text/html' is not a valid
|
||||
// JavaScript MIME type"). We fix that by copying the four needed files into
|
||||
// public/vad/ and pointing both path constants at the fixed absolute "/vad/".
|
||||
//
|
||||
// These copies are NOT committed (the ORT wasm is ~26 MB); this script runs
|
||||
// before `dev` and `build` (see package.json) to repopulate them from
|
||||
// node_modules. It is idempotent: it (re)creates the dir and overwrites.
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||
const outDir = path.join(here, "..", "public", "vad");
|
||||
|
||||
// vad-web exposes ./package.json, so derive its dist dir from there.
|
||||
const vadDist = path.join(
|
||||
path.dirname(require.resolve("@ricky0123/vad-web/package.json")),
|
||||
"dist",
|
||||
);
|
||||
|
||||
// onnxruntime-web's "exports" map does NOT expose ./package.json, so resolving
|
||||
// it would throw ERR_PACKAGE_PATH_NOT_EXPORTED. It DOES export the exact asset
|
||||
// subpaths we need, so resolve those files directly.
|
||||
const ortMjs = require.resolve(
|
||||
"onnxruntime-web/ort-wasm-simd-threaded.jsep.mjs",
|
||||
);
|
||||
const ortWasm = require.resolve(
|
||||
"onnxruntime-web/ort-wasm-simd-threaded.jsep.wasm",
|
||||
);
|
||||
|
||||
// [absolute source path, output filename]
|
||||
const files = [
|
||||
[path.join(vadDist, "vad.worklet.bundle.min.js"), "vad.worklet.bundle.min.js"],
|
||||
[path.join(vadDist, "silero_vad_v5.onnx"), "silero_vad_v5.onnx"],
|
||||
[ortMjs, "ort-wasm-simd-threaded.jsep.mjs"],
|
||||
[ortWasm, "ort-wasm-simd-threaded.jsep.wasm"],
|
||||
];
|
||||
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
for (const [src, name] of files) {
|
||||
if (!fs.existsSync(src)) {
|
||||
console.error(`[copy-vad-assets] missing source: ${src}`);
|
||||
process.exit(1);
|
||||
}
|
||||
fs.copyFileSync(src, path.join(outDir, name));
|
||||
console.log(`[copy-vad-assets] ${name}`);
|
||||
}
|
||||
Reference in New Issue
Block a user