fix: voice status global store, per-view mic reactivity, single-voice preview

- Move voice status into settings store (voiceSttReady, voiceTtsReady),
  checked once at login and refreshed after admin model reload
- ChatView, BriefingView, DashboardChatInput now use computed refs from
  the store — mic buttons appear reactively without needing a page reload
- BriefingView: separate STT-only guard for mic PTT vs TTS-only guard for
  listen mode / speak buttons
- Add ▶ Preview button to Voice & Speed section in Settings for single-
  voice testing without enabling blend mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-30 20:53:06 -04:00
parent b4b4b0d9d6
commit 71ca0ecb5c
7 changed files with 70 additions and 37 deletions
+27
View File
@@ -484,6 +484,8 @@ async function reloadVoiceModels() {
if (status.stt && status.tts) {
voiceStatus.value = status;
voiceTabLoaded.value = true;
// Propagate to global store so mic buttons appear everywhere
store.checkVoiceStatus();
break;
}
} catch { /* keep polling */ }
@@ -569,6 +571,28 @@ async function loadVoiceTab() {
}
}
const voicePreviewing = ref(false);
let _voicePreviewUrl = "";
async function previewVoice() {
if (voicePreviewing.value) return;
voicePreviewing.value = true;
try {
const blob = await synthesiseSpeech(
"Hello! I'm your assistant. How can I help you today?",
voiceTtsVoice.value,
voiceTtsSpeed.value,
);
if (_voicePreviewUrl) URL.revokeObjectURL(_voicePreviewUrl);
_voicePreviewUrl = URL.createObjectURL(blob);
new Audio(_voicePreviewUrl).play();
} catch {
toastStore.show("Preview failed — TTS may not be ready", "error");
} finally {
voicePreviewing.value = false;
}
}
async function saveVoiceSettings() {
savingVoice.value = true;
voiceSaved.value = false;
@@ -2250,6 +2274,9 @@ function formatUserDate(iso: string): string {
</div>
<div class="form-actions">
<button class="btn-secondary" @click="previewVoice" :disabled="voicePreviewing || !voiceStatus?.tts">
{{ voicePreviewing ? 'Generating' : ' Preview' }}
</button>
<button class="btn-save" @click="saveVoiceSettings" :disabled="savingVoice">
{{ voiceSaved ? 'Saved ' : savingVoice ? 'Saving' : 'Save Voice Settings' }}
</button>