```
chore: configure VAD assets and update Playwright MCP settings - Add vite-plugin-static-copy to bundle VAD WASM files - Configure VAD with adjusted speech detection thresholds - Switch Playwright MCP to omit image responses - Fix WebSocket abort controller handling for interruptions - Add WebFetch permission for VAD documentation ```
This commit is contained in:
@@ -46,6 +46,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"tsx": "^4.6.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^7.1.10"
|
||||
"vite": "^7.1.10",
|
||||
"vite-plugin-static-copy": "^3.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function getPlaywrightMCPClient() {
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command: "npx",
|
||||
args: ["@playwright/mcp", "--browser", "firefox"],
|
||||
args: ["@playwright/mcp", "--image-responses", "omit"],
|
||||
});
|
||||
|
||||
playwrightMCPClient = await experimental_createMCPClient({
|
||||
|
||||
@@ -127,17 +127,23 @@ export class VoiceAssistantWebSocketServer {
|
||||
// Handle user message through orchestrator
|
||||
const payload = message.payload as { message: string };
|
||||
const conversationId = this.conversationIds.get(ws);
|
||||
const abortController = this.abortControllers.get(ws);
|
||||
if (!conversationId) {
|
||||
console.error("[WS] No conversation found for client");
|
||||
break;
|
||||
}
|
||||
if (!abortController) {
|
||||
console.error("[WS] No abort controller found for client");
|
||||
break;
|
||||
|
||||
// Abort any ongoing request
|
||||
const oldController = this.abortControllers.get(ws);
|
||||
if (oldController) {
|
||||
oldController.abort();
|
||||
}
|
||||
|
||||
// Create new abort controller for this request
|
||||
const newController = new AbortController();
|
||||
this.abortControllers.set(ws, newController);
|
||||
|
||||
if (this.messageHandler) {
|
||||
await this.messageHandler(conversationId, payload.message, abortController.signal);
|
||||
await this.messageHandler(conversationId, payload.message, newController.signal);
|
||||
} else {
|
||||
console.warn("[WS] No message handler registered");
|
||||
}
|
||||
@@ -258,13 +264,16 @@ export class VoiceAssistantWebSocketServer {
|
||||
// Clear buffer
|
||||
this.audioBuffers.delete(clientId);
|
||||
|
||||
// Process audio through handler (STT)
|
||||
const abortController = this.abortControllers.get(ws);
|
||||
if (!abortController) {
|
||||
console.error("[WS] No abort controller found for client");
|
||||
return;
|
||||
// Abort any ongoing request
|
||||
const oldAbortController = this.abortControllers.get(ws);
|
||||
if (oldAbortController) {
|
||||
oldAbortController.abort();
|
||||
}
|
||||
|
||||
// Create new abort controller for this request
|
||||
const newAbortController = new AbortController();
|
||||
this.abortControllers.set(ws, newAbortController);
|
||||
|
||||
if (this.audioHandler) {
|
||||
this.broadcastActivityLog({
|
||||
id: Date.now().toString(),
|
||||
@@ -273,7 +282,7 @@ export class VoiceAssistantWebSocketServer {
|
||||
content: "Transcribing audio...",
|
||||
});
|
||||
|
||||
const transcript = await this.audioHandler(conversationId, completeAudio, format, abortController.signal);
|
||||
const transcript = await this.audioHandler(conversationId, completeAudio, format, newAbortController.signal);
|
||||
|
||||
// Send transcription result back to client
|
||||
this.sendToClient(ws, {
|
||||
|
||||
@@ -65,9 +65,19 @@ export function createRealtimeVAD(config: RealtimeVADConfig): RealtimeVAD {
|
||||
},
|
||||
positiveSpeechThreshold: config.positiveSpeechThreshold ?? 0.5,
|
||||
negativeSpeechThreshold: config.negativeSpeechThreshold ?? 0.35,
|
||||
redemptionMs: config.redemptionFrames ? config.redemptionFrames * 16 : 128,
|
||||
preSpeechPadMs: config.preSpeechPadFrames ? config.preSpeechPadFrames * 16 : 16,
|
||||
minSpeechMs: config.minSpeechFrames ? config.minSpeechFrames * 16 : 48,
|
||||
// Increased redemptionMs to wait longer before deciding speech has ended (allows for natural pauses)
|
||||
redemptionMs: config.redemptionFrames
|
||||
? config.redemptionFrames * 16
|
||||
: 2000,
|
||||
// Padding before speech starts
|
||||
preSpeechPadMs: config.preSpeechPadFrames
|
||||
? config.preSpeechPadFrames * 16
|
||||
: 300,
|
||||
// Minimum speech duration to avoid capturing very short sounds
|
||||
minSpeechMs: config.minSpeechFrames ? config.minSpeechFrames * 16 : 500,
|
||||
ortConfig: (ort) => {
|
||||
ort.env.wasm.wasmPaths = "/";
|
||||
},
|
||||
});
|
||||
|
||||
isActive = true;
|
||||
@@ -75,8 +85,7 @@ export function createRealtimeVAD(config: RealtimeVADConfig): RealtimeVAD {
|
||||
console.log("[RealtimeVAD] VAD created and started successfully");
|
||||
} catch (error) {
|
||||
console.error("[RealtimeVAD] Failed to start VAD:", error);
|
||||
const err =
|
||||
error instanceof Error ? error : new Error(String(error));
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
config.onError?.(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,45 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import path from "path";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
plugins: [
|
||||
react(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/@ricky0123/vad-web/dist/vad.worklet.bundle.min.js'),
|
||||
dest: './'
|
||||
},
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/@ricky0123/vad-web/dist/silero_vad_v5.onnx'),
|
||||
dest: './'
|
||||
},
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/@ricky0123/vad-web/dist/silero_vad_legacy.onnx'),
|
||||
dest: './'
|
||||
},
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.mjs'),
|
||||
dest: './'
|
||||
},
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.wasm'),
|
||||
dest: './'
|
||||
},
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.jsep.mjs'),
|
||||
dest: './'
|
||||
},
|
||||
{
|
||||
src: path.resolve(__dirname, '../../node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.jsep.wasm'),
|
||||
dest: './'
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
root: "src/ui",
|
||||
build: {
|
||||
outDir: path.resolve(__dirname, "dist/ui"),
|
||||
|
||||
Reference in New Issue
Block a user