From 6d887a63d94875b3a281e3d86ac1042441cef439 Mon Sep 17 00:00:00 2001 From: Google Gemini Date: Sun, 22 Mar 2026 23:21:21 +0000 Subject: [PATCH 1/2] Add voice settings route and pass voices to template --- src/dashboard/routes/voice.py | 43 ++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/src/dashboard/routes/voice.py b/src/dashboard/routes/voice.py index 10ea95ad..746e743c 100644 --- a/src/dashboard/routes/voice.py +++ b/src/dashboard/routes/voice.py @@ -66,10 +66,51 @@ async def tts_speak(text: str = Form(...)): # ── Voice button page ──────────────────────────────────────────────────── +@router.post("/settings") +async def update_voice_settings( + voice_id: str = Form(None), + rate: int = Form(None), + volume: float = Form(None), +): + """Update TTS settings.""" + try: + from timmy_serve.voice_tts import voice_tts + if voice_id: + voice_tts.set_voice(voice_id) + if rate: + voice_tts.set_rate(rate) + if volume is not None: + voice_tts.set_volume(volume) + return {"status": "ok", "message": "Voice settings updated"} + except Exception as exc: + return {"status": "error", "message": str(exc)} + + @router.get("/button", response_class=HTMLResponse) async def voice_button_page(request: Request): """Render the voice button UI.""" - return templates.TemplateResponse(request, "voice_button.html") + try: + from timmy_serve.voice_tts import voice_tts + voices = voice_tts.get_voices() if voice_tts.available else [] + current_voice = voice_tts._engine.getProperty("voice") if voice_tts._engine else None + current_rate = voice_tts._rate + current_volume = voice_tts._volume + except Exception: + voices = [] + current_voice = None + current_rate = 175 + current_volume = 0.9 + + return templates.TemplateResponse( + request, + "voice_button.html", + { + "voices": voices, + "current_voice": current_voice, + "current_rate": current_rate, + "current_volume": current_volume + } + ) @router.post("/command") -- 2.43.0 From 656fb0ad34ad004630c05c4fc1d49bcc19a4c5fb Mon Sep 17 00:00:00 2001 From: Google Gemini Date: Sun, 22 Mar 2026 23:21:31 +0000 Subject: [PATCH 2/2] Add voice customization UI to voice button page --- src/dashboard/templates/voice_button.html | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/dashboard/templates/voice_button.html b/src/dashboard/templates/voice_button.html index 5f79247b..ac088f33 100644 --- a/src/dashboard/templates/voice_button.html +++ b/src/dashboard/templates/voice_button.html @@ -30,6 +30,27 @@ +
+

// VOICE CUSTOMIZATION

+
+ + +
+
+ + +
+
+ + +
+ +
+

Try saying:

    @@ -87,6 +108,38 @@ function startListening() { function stopListening() { if (recognition && isListening) { recognition.stop(); } } +function updateRateLabel(val) { document.getElementById('rate-val').textContent = val; } +function updateVolumeLabel(val) { document.getElementById('volume-val').textContent = val; } + +async function updateVoiceSettings() { + const voice_id = document.getElementById('voice-select').value; + const rate = document.getElementById('rate-slider').value; + const volume = document.getElementById('volume-slider').value; + + try { + await fetch('/voice/settings', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'voice_id=' + encodeURIComponent(voice_id) + '&rate=' + rate + '&volume=' + volume + }); + } catch (e) { + console.error('Failed to update voice settings:', e); + } +} + +async function testVoice() { + const text = "Hello, I am Timmy. How can I help you today?"; + try { + await fetch('/voice/tts/speak', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'text=' + encodeURIComponent(text) + }); + } catch (e) { + console.error('Failed to test voice:', e); + } +} + function resetButton() { document.getElementById('voice-status').textContent = 'Tap and hold to speak'; document.getElementById('voice-btn').classList.remove('listening'); -- 2.43.0