fix: copy Silero VAD model out of asar so native code can read it
The bundled silero_vad.onnx lives inside Electron's app.asar which native C++ (sherpa-onnx) cannot open via OS file I/O. On first voice activation, copy the model to ~/.paseo/models/local-speech/silero-vad/ using Node.js fs (asar-aware) and point the native code there.
This commit is contained in:
@@ -22,7 +22,7 @@ import { SherpaParakeetRealtimeTranscriptionSession } from "./sherpa/sherpa-para
|
||||
import { SherpaRealtimeTranscriptionSession } from "./sherpa/sherpa-realtime-session.js";
|
||||
import { SherpaOnnxSTT } from "./sherpa/sherpa-stt.js";
|
||||
import { SherpaOnnxTTS } from "./sherpa/sherpa-tts.js";
|
||||
import { SherpaSileroTurnDetectionProvider } from "./sherpa/silero-vad-provider.js";
|
||||
import { ensureSileroVadModel, SherpaSileroTurnDetectionProvider } from "./sherpa/silero-vad-provider.js";
|
||||
|
||||
type LocalSttEngine =
|
||||
| { kind: "offline"; engine: SherpaOfflineRecognizerEngine }
|
||||
@@ -236,7 +236,15 @@ export async function initializeLocalSpeechServices(params: {
|
||||
providers.voiceTurnDetection.enabled !== false &&
|
||||
providers.voiceTurnDetection.provider === "local"
|
||||
) {
|
||||
turnDetectionService = new SherpaSileroTurnDetectionProvider({}, logger);
|
||||
let vadModelPath: string | undefined;
|
||||
if (localConfig) {
|
||||
try {
|
||||
vadModelPath = await ensureSileroVadModel(localConfig.modelsDir, logger);
|
||||
} catch (err) {
|
||||
logger.warn({ err }, "Failed to provision Silero VAD model, falling back to bundled");
|
||||
}
|
||||
}
|
||||
turnDetectionService = new SherpaSileroTurnDetectionProvider({ modelPath: vadModelPath }, logger);
|
||||
}
|
||||
|
||||
if (providers.voiceStt.enabled !== false && providers.voiceStt.provider === "local") {
|
||||
|
||||
@@ -1,10 +1,43 @@
|
||||
import { copyFile, mkdir, stat } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import type { Logger } from "pino";
|
||||
|
||||
import type {
|
||||
TurnDetectionProvider,
|
||||
TurnDetectionSession,
|
||||
} from "../../../turn-detection-provider.js";
|
||||
import { SherpaSileroVadSession, type SherpaSileroVadSessionConfig } from "./silero-vad-session.js";
|
||||
import {
|
||||
resolveBundledSileroVadModelPath,
|
||||
SherpaSileroVadSession,
|
||||
type SherpaSileroVadSessionConfig,
|
||||
} from "./silero-vad-session.js";
|
||||
|
||||
const SILERO_VAD_DIR = "silero-vad";
|
||||
const SILERO_VAD_FILE = "silero_vad.onnx";
|
||||
|
||||
/**
|
||||
* Ensure the Silero VAD ONNX model exists in modelsDir where native code can
|
||||
* read it. The bundled asset lives inside Electron's app.asar which native C++
|
||||
* cannot open, so we copy it out on first run using Node.js fs (asar-aware).
|
||||
*/
|
||||
export async function ensureSileroVadModel(modelsDir: string, logger: Logger): Promise<string> {
|
||||
const destDir = path.join(modelsDir, SILERO_VAD_DIR);
|
||||
const destPath = path.join(destDir, SILERO_VAD_FILE);
|
||||
|
||||
try {
|
||||
const s = await stat(destPath);
|
||||
if (s.isFile() && s.size > 0) return destPath;
|
||||
} catch {
|
||||
// not present yet
|
||||
}
|
||||
|
||||
const bundledPath = resolveBundledSileroVadModelPath();
|
||||
await mkdir(destDir, { recursive: true });
|
||||
await copyFile(bundledPath, destPath);
|
||||
logger.info({ destPath }, "Copied Silero VAD model to models directory");
|
||||
return destPath;
|
||||
}
|
||||
|
||||
export class SherpaSileroTurnDetectionProvider implements TurnDetectionProvider {
|
||||
public readonly id = "local" as const;
|
||||
|
||||
@@ -34,7 +34,7 @@ type SherpaVadModule = {
|
||||
CircularBuffer: new (capacity: number) => SherpaCircularBufferHandle;
|
||||
};
|
||||
|
||||
function resolveBundledSileroVadModelPath(): string {
|
||||
export function resolveBundledSileroVadModelPath(): string {
|
||||
return fileURLToPath(new URL("./assets/silero_vad.onnx", import.meta.url));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user