mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
171 lines
5.8 KiB
JavaScript
171 lines
5.8 KiB
JavaScript
import { createRequire } from 'node:module';
|
|
import path from 'node:path';
|
|
|
|
// Two of the LiteRT-LM package's four builds: relaxed SIMD with JavaScript
|
|
// promise integration (current Chrome and Edge) and the most compatible one.
|
|
const LITERT_VARIANTS = {
|
|
internal: 'litertlm_wasm_internal',
|
|
compatAsyncify: 'litertlm_wasm_compat_asyncify_internal',
|
|
};
|
|
const ASSETS_MODULE = path.join(
|
|
'src',
|
|
'voice',
|
|
'local',
|
|
'workers',
|
|
'runtimeAssets.js',
|
|
);
|
|
// kokoro-js imports `phonemizer`, which embeds eSpeak NG. The build never
|
|
// includes it: the import resolves to this loader, which downloads the pinned
|
|
// file from the public npm CDN at runtime and checks its SHA-256.
|
|
const PHONEMIZER_MODULE = path.join(
|
|
'src',
|
|
'voice',
|
|
'local',
|
|
'workers',
|
|
'phonemizerRuntime.js',
|
|
);
|
|
|
|
/** Strings that only appear in the eSpeak NG phonemizer build. */
|
|
export const ESPEAK_MARKERS = Object.freeze([
|
|
'eSpeakNGWorker',
|
|
'espeak-ng-data',
|
|
'espeakEVENT',
|
|
]);
|
|
|
|
/** Absolute path of the runtime phonemizer loader for a checkout. */
|
|
export function phonemizerLoaderPath(root = process.cwd()) {
|
|
return path.join(root, PHONEMIZER_MODULE);
|
|
}
|
|
|
|
/** Names of emitted chunks or assets that contain eSpeak NG code. */
|
|
export function findEspeakChunks(bundle) {
|
|
const found = [];
|
|
for (const [name, output] of Object.entries(bundle || {})) {
|
|
const text =
|
|
typeof output.code === 'string'
|
|
? output.code
|
|
: typeof output.source === 'string'
|
|
? output.source
|
|
: output.source instanceof Uint8Array
|
|
? Buffer.from(output.source).toString('latin1')
|
|
: '';
|
|
if (ESPEAK_MARKERS.some((marker) => text.includes(marker)))
|
|
found.push(name);
|
|
}
|
|
return found;
|
|
}
|
|
|
|
const posix = (file) => file.split(path.sep).join('/');
|
|
|
|
/** Directory of the installed package that `from` resolves `name` to. */
|
|
function packageDir(from, name) {
|
|
const entry = createRequire(from).resolve(name);
|
|
const marker = `${path.sep}node_modules${path.sep}${name.split('/').join(path.sep)}${path.sep}`;
|
|
const at = entry.lastIndexOf(marker);
|
|
if (at < 0) throw new Error(`Cannot locate ${name} from ${from}`);
|
|
return entry.slice(0, at + marker.length - 1);
|
|
}
|
|
|
|
/**
|
|
* Serves the on-device voice runtimes from the application build instead of
|
|
* a CDN. It replaces src/voice/local/workers/runtimeAssets.js with imports
|
|
* of the installed LiteRT-LM Emscripten loaders (classic scripts, exported
|
|
* here as modules) and wasm files, and the ONNX Runtime Web files that the
|
|
* two transformers.js copies (speech recognition, Kokoro) use.
|
|
*/
|
|
export function onDeviceRuntimePlugin({ root = process.cwd() } = {}) {
|
|
let paths = null;
|
|
const resolvePaths = () => {
|
|
if (paths) return paths;
|
|
const rootFile = path.join(root, 'package.json');
|
|
const require = createRequire(rootFile);
|
|
const litert = path.join(packageDir(rootFile, '@litert-lm/core'), 'wasm');
|
|
const ort = path.join(
|
|
packageDir(
|
|
require.resolve('@huggingface/transformers'),
|
|
'onnxruntime-web',
|
|
),
|
|
'dist',
|
|
);
|
|
const kokoroTransformers = path.join(
|
|
packageDir(require.resolve('kokoro-js'), '@huggingface/transformers'),
|
|
'dist',
|
|
);
|
|
paths = {
|
|
assets: path.join(root, ASSETS_MODULE),
|
|
loaders: new Set(
|
|
Object.values(LITERT_VARIANTS).map((name) =>
|
|
path.join(litert, `${name}.js`),
|
|
),
|
|
),
|
|
source() {
|
|
const lines = [];
|
|
const runtimes = [];
|
|
for (const [key, name] of Object.entries(LITERT_VARIANTS)) {
|
|
const file = posix(path.join(litert, name));
|
|
lines.push(
|
|
`import ${key}Factory from ${JSON.stringify(`${file}.js`)};`,
|
|
);
|
|
lines.push(
|
|
`import ${key}Wasm from ${JSON.stringify(`${file}.wasm?url`)};`,
|
|
);
|
|
runtimes.push(
|
|
`${key}: { ModuleFactory: ${key}Factory, wasmUrl: ${key}Wasm }`,
|
|
);
|
|
}
|
|
const onnx = [];
|
|
for (const [key, dir, name] of [
|
|
['transformers', ort, 'ort-wasm-simd-threaded.asyncify'],
|
|
['kokoro', kokoroTransformers, 'ort-wasm-simd-threaded.jsep'],
|
|
]) {
|
|
const file = posix(path.join(dir, name));
|
|
lines.push(
|
|
`import ${key}Mjs from ${JSON.stringify(`${file}.mjs?url`)};`,
|
|
);
|
|
lines.push(
|
|
`import ${key}OrtWasm from ${JSON.stringify(`${file}.wasm?url`)};`,
|
|
);
|
|
onnx.push(`${key}: { mjs: ${key}Mjs, wasm: ${key}OrtWasm }`);
|
|
}
|
|
lines.push(`export const litertRuntimes = { ${runtimes.join(', ')} };`);
|
|
lines.push(`export const onnxWasmPaths = { ${onnx.join(', ')} };`);
|
|
lines.push('export function requireRuntime(value) { return value; }');
|
|
return lines.join('\n');
|
|
},
|
|
};
|
|
return paths;
|
|
};
|
|
const phonemizer = phonemizerLoaderPath(root);
|
|
return {
|
|
name: 'gev-on-device-runtime',
|
|
enforce: 'pre',
|
|
resolveId(source) {
|
|
return source === 'phonemizer' ? phonemizer : null;
|
|
},
|
|
generateBundle(_options, bundle) {
|
|
const found = findEspeakChunks(bundle);
|
|
if (found.length)
|
|
this.error(
|
|
`eSpeak NG phonemizer code must not be bundled (found in ${found.join(', ')})`,
|
|
);
|
|
},
|
|
load(id) {
|
|
const file = path.normalize(id.split('?')[0]);
|
|
return file === resolvePaths().assets ? resolvePaths().source() : null;
|
|
},
|
|
transform(code, id) {
|
|
const file = path.normalize(id.split('?')[0]);
|
|
if (!file.endsWith('.js') || !resolvePaths().loaders.has(file))
|
|
return null;
|
|
// Modules are strict: the loaders' debug hook declares a function
|
|
// inside a block, which only sloppy scripts hoist.
|
|
const prelude =
|
|
'var custom_dbg = function () { console.warn.apply(console, arguments); };\n';
|
|
return {
|
|
code: `${prelude}${code}\nexport default ModuleFactory;\n`,
|
|
map: null,
|
|
};
|
|
},
|
|
};
|
|
}
|