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:
Mohamed Boudra
2025-10-20 12:47:09 +02:00
parent 14230c681f
commit 13b5fab456
6 changed files with 306 additions and 20 deletions
+2 -1
View File
@@ -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;
}
+36 -1
View File
@@ -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"),