From 5c8455930fb2f920b5b546be26a83c07330735d1 Mon Sep 17 00:00:00 2001 From: Ben de Roo Date: Thu, 14 May 2026 13:47:01 +0200 Subject: [PATCH] Update gpt/index.html --- gpt/index.html | 135 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/gpt/index.html b/gpt/index.html index e2ead0f..3f3734f 100644 --- a/gpt/index.html +++ b/gpt/index.html @@ -589,6 +589,7 @@ + @@ -744,6 +745,50 @@ Date|What is today's date?"> + + +
@@ -780,6 +825,12 @@ const DEFAULT_CONFIG = { macroBar: true, macros: 'Hello|Hello! Who are you?\nHelp|What can you help me with?\nDate|What is today\'s date?', enabled: false, + tts: false, + ttsUser: false, + ttsVoice: '', + ttsRate: 1.0, + ttsPitch: 1.0, + ttsVol: 1.0, }; let cfg = { ...DEFAULT_CONFIG }; @@ -817,8 +868,90 @@ const sidebar = document.getElementById('sidebar'); const debugPre = document.getElementById('debugPre'); /* ══════════════════════════════════════════ - INIT APPLY + TTS ENGINE ══════════════════════════════════════════ */ +let ttsVoices = []; +let ttsSpeaking = false; + +function ttsLoadVoices() { + ttsVoices = speechSynthesis.getVoices(); + const sel = document.getElementById('ttsVoiceSelect'); + if (!sel) return; + sel.innerHTML = ''; + ttsVoices.forEach((v, i) => { + const opt = document.createElement('option'); + opt.value = v.name; + opt.textContent = `${v.name} (${v.lang})`; + if (cfg.ttsVoice && v.name === cfg.ttsVoice) opt.selected = true; + sel.appendChild(opt); + }); +} + +if ('speechSynthesis' in window) { + speechSynthesis.onvoiceschanged = ttsLoadVoices; + ttsLoadVoices(); +} else { + console.warn('Web Speech API not supported.'); +} + +function ttsSpeak(text, isUser = false) { + if (!cfg.tts) return; + if (isUser && !cfg.ttsUser) return; + if (!('speechSynthesis' in window)) return; + + // Strip markdown-ish symbols so they don't get read aloud + const clean = text + .replace(/```[\s\S]*?```/g, 'code block') + .replace(/`[^`]+`/g, 'code') + .replace(/[*_#>\[\]]/g, '') + .replace(/https?:\/\/\S+/g, 'link') + .trim(); + if (!clean) return; + + const utt = new SpeechSynthesisUtterance(clean); + const voice = ttsVoices.find(v => v.name === cfg.ttsVoice); + if (voice) utt.voice = voice; + utt.rate = cfg.ttsRate; + utt.pitch = cfg.ttsPitch; + utt.volume = cfg.ttsVol; + + utt.onstart = () => { ttsSpeaking = true; updateTtsIndicator(true); }; + utt.onend = () => { ttsSpeaking = false; updateTtsIndicator(false); }; + utt.onerror = () => { ttsSpeaking = false; updateTtsIndicator(false); }; + + speechSynthesis.cancel(); // stop any ongoing + speechSynthesis.speak(utt); +} + +function ttsStop() { + if ('speechSynthesis' in window) speechSynthesis.cancel(); + ttsSpeaking = false; + updateTtsIndicator(false); +} + +function ttsTest() { + // Re-read config values from sliders directly so test is live + const rate = parseFloat(document.getElementById('ttsRateRange')?.value ?? cfg.ttsRate); + const pitch = parseFloat(document.getElementById('ttsPitchRange')?.value ?? cfg.ttsPitch); + const vol = parseFloat(document.getElementById('ttsVolRange')?.value ?? cfg.ttsVol); + const voice = document.getElementById('ttsVoiceSelect')?.value ?? cfg.ttsVoice; + + const utt = new SpeechSynthesisUtterance('Neural Net Terminal online. Voice readout active. All systems nominal.'); + const v = ttsVoices.find(v => v.name === voice); + if (v) utt.voice = v; + utt.rate = rate; utt.pitch = pitch; utt.volume = vol; + speechSynthesis.cancel(); + speechSynthesis.speak(utt); +} + +function updateTtsIndicator(active) { + const el = document.getElementById('ttsIndicator'); + if (!el) return; + el.style.opacity = active ? '1' : '0.3'; + el.style.animation = active ? 'pulse 1s ease-in-out infinite' : 'none'; +} + + applyTheme(); applyInterface(); updateStatus();