feat(call): denoise asset smoke check at ML-tier call start
HEAD-checks the copied denoise worklet/wasm/model assets for the selected model and console.warns a single line listing anything missing — a silent asset skew between the EC fork's expectations and vite's copied files would otherwise disable noise suppression with no signal. Fire-and-forget; never blocks call setup. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { CallControl } from './CallControl';
|
import { CallControl } from './CallControl';
|
||||||
import { CallControlState } from './CallControlState';
|
import { CallControlState } from './CallControlState';
|
||||||
|
import { verifyDenoiseAssets } from './denoiseSmokeCheck';
|
||||||
|
|
||||||
// Maximum time to wait for the embedded Element Call iframe to progress from
|
// Maximum time to wait for the embedded Element Call iframe to progress from
|
||||||
// initial load to a ready/joined state. If it hasn't by then, we assume the
|
// initial load to a ready/joined state. If it hasn't by then, we assume the
|
||||||
@@ -205,6 +206,12 @@ export class CallEmbed {
|
|||||||
params.append('lotusModel', denoiseModel);
|
params.append('lotusModel', denoiseModel);
|
||||||
params.append('lotusGate', denoiseGate.toString());
|
params.append('lotusGate', denoiseGate.toString());
|
||||||
params.append('lotusGateThreshold', denoiseGateThreshold.toString());
|
params.append('lotusGateThreshold', denoiseGateThreshold.toString());
|
||||||
|
|
||||||
|
// [lotus] Fire-and-forget: confirm the fork's ML-denoise assets are
|
||||||
|
// actually served under public/element-call/denoise/ (they're copied by
|
||||||
|
// vite.config.js at build time). Warns once if the copy step regressed;
|
||||||
|
// never blocks call start.
|
||||||
|
verifyDenoiseAssets(denoiseModel).catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CallEmbed.startingCall(intent)) {
|
if (CallEmbed.startingCall(intent)) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { trimTrailingSlash } from '../../utils/common';
|
||||||
|
|
||||||
|
// Denoise assets copied into public/element-call/denoise/ by vite.config.js's
|
||||||
|
// lotusDenoise() plugin. The filenames here MUST match what that plugin writes
|
||||||
|
// (and what the fork's TrackProcessor fetches at runtime). Grouped per model so
|
||||||
|
// the smoke-check only probes what the active call will actually load.
|
||||||
|
const DENOISE_ASSETS: Record<string, readonly string[]> = {
|
||||||
|
rnnoise: ['rnnoiseWorklet.js', 'rnnoise.wasm', 'rnnoise_simd.wasm'],
|
||||||
|
speex: ['speexWorklet.js', 'speex.wasm'],
|
||||||
|
dtln: ['workadventure/audio-worklet.js'],
|
||||||
|
deepfilternet: [
|
||||||
|
'deepfilternet/index.esm.js',
|
||||||
|
'deepfilternet/v2/pkg/df_bg.wasm',
|
||||||
|
'deepfilternet/v2/models/DeepFilterNet3_onnx.tar.gz',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// The noise-gate worklet is a shared asset the build ships for every model
|
||||||
|
// (loaded when the gate is enabled), so probe it regardless of the model.
|
||||||
|
const SHARED_ASSETS: readonly string[] = ['noiseGateWorklet.js'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fire-and-forget smoke-check for the ML-denoise asset contract.
|
||||||
|
*
|
||||||
|
* The fork's in-source denoiser (lotusDenoiseSource) loads its worklet/wasm/ESM
|
||||||
|
* from `public/element-call/denoise/` at runtime; if the build's asset copy
|
||||||
|
* step regressed, those fetches 404 and denoise silently degrades to a raw mic.
|
||||||
|
* This HEAD-fetches the critical assets for the selected model and emits a
|
||||||
|
* single console.warn listing any that are missing. No UI, no throw — purely a
|
||||||
|
* developer/operator breadcrumb.
|
||||||
|
*
|
||||||
|
* @param model the selected denoise model (defaults to rnnoise)
|
||||||
|
* @returns true if every probed asset responded OK, false otherwise
|
||||||
|
*/
|
||||||
|
export async function verifyDenoiseAssets(model = 'rnnoise'): Promise<boolean> {
|
||||||
|
const base = new URL(
|
||||||
|
`${trimTrailingSlash(import.meta.env.BASE_URL)}/public/element-call/denoise/`,
|
||||||
|
window.location.origin,
|
||||||
|
);
|
||||||
|
const names = [...(DENOISE_ASSETS[model] ?? DENOISE_ASSETS.rnnoise), ...SHARED_ASSETS];
|
||||||
|
|
||||||
|
const results = await Promise.all(
|
||||||
|
names.map(async (name): Promise<string | null> => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(new URL(name, base).href, { method: 'HEAD' });
|
||||||
|
return res.ok ? null : name;
|
||||||
|
} catch {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const missing = results.filter((n): n is string => n !== null);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
console.warn(
|
||||||
|
`[lotus-denoise] ML denoise assets missing under ${base.href} (model="${model}"): ${missing.join(
|
||||||
|
', ',
|
||||||
|
)} — the in-source denoiser will fall back to a raw mic. Check vite.config.js lotusDenoise().`,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user