diff --git a/static/boot.js b/static/boot.js
index 9cf1d71a..5ef00292 100644
--- a/static/boot.js
+++ b/static/boot.js
@@ -851,6 +851,8 @@ function applyBotName(){
if(typeof applyLocaleToDOM==='function')applyLocaleToDOM();
}
applyBotName();
+ // TTS: apply enabled state on boot so buttons show/hide correctly (#499)
+ if(typeof _applyTtsEnabled==='function') _applyTtsEnabled(localStorage.getItem('hermes-tts-enabled')==='true');
}catch(e){
window._sendKey='enter';
window._showTokenUsage=false;
@@ -871,6 +873,7 @@ function applyBotName(){
if(typeof applyLocaleToDOM==='function')applyLocaleToDOM();
}
applyBotName();
+ if(typeof _applyTtsEnabled==='function') _applyTtsEnabled(localStorage.getItem('hermes-tts-enabled')==='true');
}
// Non-blocking update check (fire-and-forget, once per tab session)
// ?test_updates=1 in URL forces banner display for testing (bypasses sessionStorage guards)
diff --git a/static/i18n.js b/static/i18n.js
index 628ee285..9ad3f2ed 100644
--- a/static/i18n.js
+++ b/static/i18n.js
@@ -441,6 +441,17 @@ const LOCALES = {
// Settings detail
settings_label_sound: 'Notification sound',
settings_desc_sound: 'Play a sound when the assistant finishes a response.',
+ // TTS (#499)
+ tts_listen: 'Listen',
+ tts_not_supported: 'Speech synthesis not supported in this browser.',
+ settings_label_tts: 'Text-to-Speech for responses',
+ settings_desc_tts: "Show a speaker button on each assistant message to read it aloud using your browser's speech synthesis.",
+ settings_label_tts_auto_read: 'Auto-read responses aloud',
+ settings_desc_tts_auto_read: 'Automatically speak each new assistant response when it finishes. Pauses when you start typing.',
+ settings_label_tts_voice: 'Voice',
+ settings_desc_tts_voice: "Preferred voice. Populated from your browser's available voices.",
+ settings_label_tts_rate: 'Speech rate',
+ settings_label_tts_pitch: 'Speech pitch',
settings_label_notifications: 'Browser notifications',
settings_desc_notifications: 'Show a system notification when a response completes while the app is in the background.',
settings_desc_token_usage: 'Displays input/output token count below each assistant reply. Also toggled with /usage.',
@@ -1511,6 +1522,17 @@ const LOCALES = {
html_error: 'Не удалось загрузить предпросмотр HTML',
html_open_full: 'Открыть на всю страницу',
html_sandbox_label: 'Предпросмотр HTML',
+ // TTS (#499)
+ tts_listen: 'Прослушать',
+ tts_not_supported: 'Синтез речи не поддерживается',
+ settings_label_tts: 'Синтез речи для ответов',
+ settings_desc_tts: 'Показать кнопку динамика на сообщениях ассистента',
+ settings_label_tts_auto_read: 'Авто-чтение ответов',
+ settings_desc_tts_auto_read: 'Автоматически озвучивать ответы ассистента',
+ settings_label_tts_voice: 'Голос',
+ settings_desc_tts_voice: 'Выберите голос для синтеза речи',
+ settings_label_tts_rate: 'Скорость речи',
+ settings_label_tts_pitch: 'Тон речи',
},
es: {
@@ -2231,6 +2253,17 @@ const LOCALES = {
html_error: 'Error al cargar la vista previa de HTML',
html_open_full: 'Abrir página completa',
html_sandbox_label: 'Vista previa de HTML',
+ // TTS (#499)
+ tts_listen: 'Escuchar',
+ tts_not_supported: 'Síntesis de voz no disponible',
+ settings_label_tts: 'Texto a voz para respuestas',
+ settings_desc_tts: 'Mostrar botón de altavoz en mensajes del asistente',
+ settings_label_tts_auto_read: 'Leer respuestas automáticamente',
+ settings_desc_tts_auto_read: 'Leer en voz alta las respuestas del asistente automáticamente',
+ settings_label_tts_voice: 'Voz',
+ settings_desc_tts_voice: 'Seleccionar voz para síntesis de voz',
+ settings_label_tts_rate: 'Velocidad de voz',
+ settings_label_tts_pitch: 'Tono de voz',
},
de: {
@@ -2955,7 +2988,18 @@ const LOCALES = {
html_error: 'HTML-Vorschau konnte nicht geladen werden',
html_open_full: 'Vollständige Seite öffnen',
html_sandbox_label: 'HTML-Vorschau',
-},
+ // TTS (#499)
+ tts_listen: 'Anhören',
+ tts_not_supported: 'Sprachsynthese nicht verfügbar',
+ settings_label_tts: 'Text-zu-Sprache für Antworten',
+ settings_desc_tts: 'Lautsprecher-Symbol auf Assistenten-Nachrichten anzeigen',
+ settings_label_tts_auto_read: 'Antworten automatisch vorlesen',
+ settings_desc_tts_auto_read: 'Assistenten-Antworten automatisch vorlesen',
+ settings_label_tts_voice: 'Stimme',
+ settings_desc_tts_voice: 'Stimme für Sprachsynthese auswählen',
+ settings_label_tts_rate: 'Sprechgeschwindigkeit',
+ settings_label_tts_pitch: 'Tonhöhe',
+ },
zh: {
_lang: 'zh',
@@ -3673,6 +3717,17 @@ const LOCALES = {
excalidraw_empty: '空图表',
excalidraw_render_error: '渲染图表失败',
excalidraw_simplified: '简化 SVG 预览 — 与 Excalidraw 画布不完全相同',
+ // TTS (#499)
+ tts_listen: '收听',
+ tts_not_supported: '语音合成不可用',
+ settings_label_tts: '回复语音合成',
+ settings_desc_tts: '在助手消息上显示扬声器按钮',
+ settings_label_tts_auto_read: '自动朗读回复',
+ settings_desc_tts_auto_read: '自动朗读助手回复',
+ settings_label_tts_voice: '语音',
+ settings_desc_tts_voice: '选择语音合成声音',
+ settings_label_tts_rate: '语速',
+ settings_label_tts_pitch: '音调',
},
// Traditional Chinese (zh-Hant)
@@ -4499,6 +4554,17 @@ const LOCALES = {
html_error: 'HTML 預覽載入失敗',
html_open_full: '開啟完整頁面',
html_sandbox_label: 'HTML 預覽',
+ // TTS (#499)
+ tts_listen: '收聽',
+ tts_not_supported: '語音合成無法使用',
+ settings_label_tts: '回覆語音合成',
+ settings_desc_tts: '在助手訊息上顯示喇叭按鈕',
+ settings_label_tts_auto_read: '自動朗讀回覆',
+ settings_desc_tts_auto_read: '自動朗讀助手回覆',
+ settings_label_tts_voice: '語音',
+ settings_desc_tts_voice: '選擇語音合成聲音',
+ settings_label_tts_rate: '語速',
+ settings_label_tts_pitch: '音調',
},
@@ -5138,7 +5204,18 @@ const LOCALES = {
approval_skip: 'Pular',
approval_skip_title: 'Pular este prompt de aprovação',
approval_skip_all: 'Pular todos',
- approval_skip_all_title: 'Pular todos prompts de aprovação nesta sessão'
+ approval_skip_all_title: 'Pular todos prompts de aprovação nesta sessão',
+ // TTS (#499)
+ tts_listen: 'Ouvir',
+ tts_not_supported: 'Síntese de voz não disponível',
+ settings_label_tts: 'Texto para voz nas respostas',
+ settings_desc_tts: 'Mostrar botão de alto-falante nas mensagens do assistente',
+ settings_label_tts_auto_read: 'Ler respostas automaticamente',
+ settings_desc_tts_auto_read: 'Ler automaticamente as respostas do assistente',
+ settings_label_tts_voice: 'Voz',
+ settings_desc_tts_voice: 'Selecionar voz para síntese de voz',
+ settings_label_tts_rate: 'Velocidade da fala',
+ settings_label_tts_pitch: 'Tom da fala',
},
ko: {
_lang: 'ko',
@@ -5911,6 +5988,17 @@ const LOCALES = {
excalidraw_empty: '빈 다이어그램',
excalidraw_render_error: '다이어그램 렌더링 실패',
excalidraw_simplified: '단순화된 SVG 미리보기 — Excalidraw 캔버스와 픽셀 동일하지 않음',
+ // TTS (#499)
+ tts_listen: '듣기',
+ tts_not_supported: '음성 합성을 사용할 수 없습니다',
+ settings_label_tts: '답변 음성 합성',
+ settings_desc_tts: '도움말 메시지에 스피커 버튼 표시',
+ settings_label_tts_auto_read: '답변 자동 읽기',
+ settings_desc_tts_auto_read: '도움말 답변을 자동으로 읽어줌',
+ settings_label_tts_voice: '음성',
+ settings_desc_tts_voice: '음성 합성 음성 선택',
+ settings_label_tts_rate: '말 속도',
+ settings_label_tts_pitch: '말 톤',
},
};
diff --git a/static/index.html b/static/index.html
index 6e0d9369..b4441ceb 100644
--- a/static/index.html
+++ b/static/index.html
@@ -683,6 +683,41 @@
Play a sound when the assistant finishes a response.
+
+
+
Show a speaker button on each assistant message to read it aloud using your browser's speech synthesis.
+
+
+
+
Automatically speak each new assistant response when it finishes. Pauses when you start typing.
+
+
+
+
+
Preferred voice. Populated from your browser's available voices.
+
+
+
+
+
+ 1.0x
+
+
+
+
+
+
+ 1.0
+
+