From 6cf880506d623325904c64f5cf04ff5972a1e743 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 16 Apr 2026 20:10:29 -0400 Subject: [PATCH] fix(voice): allow WASM compilation in CSP for Silero VAD Add 'wasm-unsafe-eval' to script-src and blob: to worker-src in Content-Security-Policy header. Required by onnxruntime-web to compile the Silero VAD ONNX model. Also surface VAD init errors as a toast instead of silent console log. Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/ChatInputBar.vue | 5 +++-- frontend/src/composables/useVad.ts | 7 ++----- src/fabledassistant/app.py | 5 +++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/ChatInputBar.vue b/frontend/src/components/ChatInputBar.vue index cd9beb0..22be473 100644 --- a/frontend/src/components/ChatInputBar.vue +++ b/frontend/src/components/ChatInputBar.vue @@ -153,10 +153,11 @@ const vad = useVad({ void stopRecording(false) }, onNoSpeech: () => { - // User manually stopped without VAD detecting speech. Toast it; the - // stopRecording path below will skip Whisper based on vadSawSpeech. useToastStore().show('No speech detected', 'warning') }, + onVadError: (msg) => { + useToastStore().show(`VAD failed: ${msg}`, 'error') + }, }) // Live mic halo. A solid red disc sits behind the mic button and scales diff --git a/frontend/src/composables/useVad.ts b/frontend/src/composables/useVad.ts index ad0718b..09c4e22 100644 --- a/frontend/src/composables/useVad.ts +++ b/frontend/src/composables/useVad.ts @@ -2,13 +2,10 @@ import { ref, readonly } from 'vue' import type { MicVAD } from '@ricky0123/vad-web' export interface UseVadOptions { - /** Called when VAD detects a real speech-end event after the grace period. */ onSpeechEnd: () => void - /** Called when stopAndCheck() is invoked but no speech was ever detected. */ onNoSpeech: () => void - /** Minimum ms between speech-start and speech-end before the end event fires. */ + onVadError?: (message: string) => void graceMs?: number - /** Minimum total recording duration before speech-end can fire. */ minRecordingMs?: number } @@ -108,8 +105,8 @@ export function useVad(options: UseVadOptions) { await micVad.start() loaded.value = true } catch (e) { - // VAD init failed — the user can still manual-stop. Log for debugging. console.error('VAD init failed:', e) + options.onVadError?.(e instanceof Error ? e.message : String(e)) } } diff --git a/src/fabledassistant/app.py b/src/fabledassistant/app.py index d703170..69589ab 100644 --- a/src/fabledassistant/app.py +++ b/src/fabledassistant/app.py @@ -143,10 +143,11 @@ def create_app() -> Quart: response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin") response.headers.setdefault( "Content-Security-Policy", - "default-src 'self'; script-src 'self' 'unsafe-inline'; " + "default-src 'self'; " + "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval'; " "style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; " "connect-src 'self'; font-src 'self' data:; object-src 'none'; " - "base-uri 'self'; worker-src 'self';" + "base-uri 'self'; worker-src 'self' blob:;" ) return response