From 09abbf8a63f8faada154850fd716b23a6dae9ee8 Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 29 Jun 2026 15:22:37 -0500 Subject: [PATCH] feat(desktop): mirror voice.auto_tts into an $autoSpeakReplies store --- .../app/session/hooks/use-hermes-config.ts | 2 + apps/desktop/src/store/voice-prefs.ts | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 apps/desktop/src/store/voice-prefs.ts diff --git a/apps/desktop/src/app/session/hooks/use-hermes-config.ts b/apps/desktop/src/app/session/hooks/use-hermes-config.ts index 59406c8dff2..84bff0e5943 100644 --- a/apps/desktop/src/app/session/hooks/use-hermes-config.ts +++ b/apps/desktop/src/app/session/hooks/use-hermes-config.ts @@ -12,6 +12,7 @@ import { setCurrentServiceTier, setIntroPersonality } from '@/store/session' +import { applyAutoSpeakFromConfig } from '@/store/voice-prefs' const DEFAULT_VOICE_SECONDS = 120 const FAST_TIERS = new Set(['fast', 'priority', 'on']) @@ -65,6 +66,7 @@ export function useHermesConfig({ activeSessionIdRef, refreshProjectBranch }: He setVoiceMaxRecordingSeconds(recordingLimit(config.voice?.max_recording_seconds)) setSttEnabled(config.stt?.enabled !== false) + applyAutoSpeakFromConfig(config) } catch { // Config is nice-to-have; chat still works without it. } diff --git a/apps/desktop/src/store/voice-prefs.ts b/apps/desktop/src/store/voice-prefs.ts new file mode 100644 index 00000000000..f7e414e2556 --- /dev/null +++ b/apps/desktop/src/store/voice-prefs.ts @@ -0,0 +1,38 @@ +import { atom } from 'nanostores' + +import { getHermesConfigRecord, saveHermesConfig } from '@/hermes' + +// "Read replies aloud" — mirrors the canonical `voice.auto_tts` config key (also +// in Settings → Voice, honored by the messaging gateway) so the composer toggle +// and the Settings switch are one source of truth, not two that can disagree. +export const $autoSpeakReplies = atom(false) + +/** Seed the atom from a loaded config payload (mount / refresh). */ +export function applyAutoSpeakFromConfig(config: { voice?: { auto_tts?: unknown } | null } | null | undefined) { + $autoSpeakReplies.set(Boolean(config?.voice?.auto_tts)) +} + +/** + * Flip the preference and persist it. Optimistic — the atom updates instantly and + * reverts if the config write fails. Read-modify-writes the whole record (the + * same path the Settings page uses; there's no partial-update endpoint). + */ +export async function setAutoSpeakReplies(enabled: boolean): Promise { + const previous = $autoSpeakReplies.get() + + if (previous === enabled) { + return + } + + $autoSpeakReplies.set(enabled) + + try { + const record = await getHermesConfigRecord() + const voice = record.voice && typeof record.voice === 'object' ? (record.voice as Record) : {} + + await saveHermesConfig({ ...record, voice: { ...voice, auto_tts: enabled } }) + } catch (error) { + $autoSpeakReplies.set(previous) + throw error + } +}