Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'

import { playSpeechText, stopVoicePlayback } from '@/lib/voice-playback'
import { notify, notifyError } from '@/store/notifications'
import { $autoTts } from '@/store/voice-playback'

import { useMicRecorder } from './use-mic-recorder'

Expand Down Expand Up @@ -47,6 +48,8 @@ export function useVoiceConversation({
const busyRef = useRef(busy)
const statusRef = useRef<ConversationStatus>('idle')
const wasEnabledRef = useRef(enabled)
// auto_tts read from $autoTts store — refreshed on every config refresh


useEffect(() => {
enabledRef.current = enabled
Expand Down Expand Up @@ -218,6 +221,15 @@ export function useVoiceConversation({
}, [handle, handleTurn, onFatalError])

const speak = useCallback(async (text: string) => {
if (!$autoTts.get()) {
// TTS disabled — skip speech, but keep conversation moving
if (enabledRef.current) {
pendingStartRef.current = true
}
setStatus('idle')
return
}

setStatus('speaking')

try {
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/app/session/hooks/use-hermes-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { type MutableRefObject, useCallback, useState } from 'react'

import { getHermesConfig, getHermesConfigDefaults } from '@/hermes'
import { BUILTIN_PERSONALITIES, normalizePersonalityValue, personalityNamesFromConfig } from '@/lib/chat-runtime'
import { setAutoTts } from '@/store/voice-playback'
import {
$currentCwd,
setAvailablePersonalities,
Expand Down Expand Up @@ -65,6 +66,7 @@ export function useHermesConfig({ activeSessionIdRef, refreshProjectBranch }: He

setVoiceMaxRecordingSeconds(recordingLimit(config.voice?.max_recording_seconds))
setSttEnabled(config.stt?.enabled !== false)
setAutoTts(config.voice?.auto_tts === true)
} catch {
// Config is nice-to-have; chat still works without it.
}
Expand Down
8 changes: 8 additions & 0 deletions apps/desktop/src/store/voice-playback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,11 @@ export const $voicePlayback = atom<VoicePlaybackState>({
export function setVoicePlaybackState(next: VoicePlaybackState) {
$voicePlayback.set(next)
}

/** Whether auto-TTS is enabled. Defaults to false (fail-closed). Updated
* when config refreshes so toggling the setting takes effect immediately. */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main already has the fail-closed voice.auto_tts mirror in apps/desktop/src/store/voice-prefs.ts:8, hydrated by useHermesConfig at apps/desktop/src/app/session/hooks/use-hermes-config.ts:90. Please use that existing $autoSpeakReplies store for the voice-conversation gate instead of introducing a second mirror that can diverge.

export const $autoTts = atom(false)

export function setAutoTts(value: boolean) {
$autoTts.set(value)
}
1 change: 1 addition & 0 deletions apps/desktop/src/types/hermes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export interface HermesConfig {
enabled?: boolean
}
voice?: {
auto_tts?: boolean
max_recording_seconds?: number
}
}
Expand Down
Loading