diff --git a/dashboard/frontend/src/lib/voice.js b/dashboard/frontend/src/lib/voice.js index 6ba3836..1493c03 100644 --- a/dashboard/frontend/src/lib/voice.js +++ b/dashboard/frontend/src/lib/voice.js @@ -19,6 +19,8 @@ export class VoiceSession { this.voiceMode = false; this.transcript = ""; this.talkMode = "auto"; + this.voices = []; + this.voiceIdx = -1; this.term = null; this.ws = null; this._lastLine = 0; @@ -32,6 +34,22 @@ export class VoiceSession { get lang() { return LANGS[this.langIdx]; } get langLabel() { return LANG_LABELS[this.langIdx]; } get modeLabel() { return MODE_LABELS[this.talkMode]; } + get voiceName() { return this.voices[this.voiceIdx]?.name || "Default"; } + + loadVoices() { + const all = speechSynthesis.getVoices(); + const prefix = this.lang.split("-")[0]; + this.voices = all.filter(v => v.lang.startsWith(prefix)); + // Try to keep current selection + if (this.voiceIdx >= this.voices.length) this.voiceIdx = 0; + if (!this.voices.length) this.voiceIdx = -1; + this._notify(); + } + + setVoice(idx) { + this.voiceIdx = idx; + this._notify(); + } attach(term, ws) { this.term = term; @@ -50,6 +68,8 @@ export class VoiceSession { speechSynthesis.speak(new SpeechSynthesisUtterance("")); this._ttsUnlocked = true; } + this.loadVoices(); + speechSynthesis.onvoiceschanged = () => this.loadVoices(); if (this.term) { this._lastLine = this.term.buffer.active.baseY + this.term.buffer.active.cursorY; } @@ -193,9 +213,7 @@ export class VoiceSession { speechSynthesis.cancel(); const u = new SpeechSynthesisUtterance(text); u.lang = this.lang; - const voices = speechSynthesis.getVoices(); - const v = voices.find(v => v.lang.startsWith(this.lang.split("-")[0])); - if (v) u.voice = v; + if (this.voices[this.voiceIdx]) u.voice = this.voices[this.voiceIdx]; u.onstart = () => { this.speaking = true; this._notify(); }; u.onend = () => { this.speaking = false; this._notify(); }; u.onerror = () => { this.speaking = false; this._notify(); }; @@ -215,6 +233,7 @@ export class VoiceSession { cycleLang() { this.langIdx = (this.langIdx + 1) % LANGS.length; + this.loadVoices(); if (this.listening) { const wasContinuous = this._recog?.continuous; this._stopSTT(); diff --git a/dashboard/frontend/src/routes/Terminal.svelte b/dashboard/frontend/src/routes/Terminal.svelte index df66374..d441b26 100644 --- a/dashboard/frontend/src/routes/Terminal.svelte +++ b/dashboard/frontend/src/routes/Terminal.svelte @@ -180,6 +180,14 @@
Voice Mode
+