Update gpt/index.html
This commit is contained in:
+134
-1
@@ -589,6 +589,7 @@
|
|||||||
<button class="modal-tab" data-section="system">SYSTEM PROMPT</button>
|
<button class="modal-tab" data-section="system">SYSTEM PROMPT</button>
|
||||||
<button class="modal-tab" data-section="interface">INTERFACE</button>
|
<button class="modal-tab" data-section="interface">INTERFACE</button>
|
||||||
<button class="modal-tab" data-section="macros">MACROS</button>
|
<button class="modal-tab" data-section="macros">MACROS</button>
|
||||||
|
<button class="modal-tab" data-section="voice">VOICE</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- CONNECTION -->
|
<!-- CONNECTION -->
|
||||||
@@ -744,6 +745,50 @@ Date|What is today's date?"></textarea>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- VOICE -->
|
||||||
|
<div class="modal-section" id="section-voice">
|
||||||
|
<div class="toggle-row">
|
||||||
|
<span class="toggle-label">ENABLE VOICE READOUT</span>
|
||||||
|
<label class="toggle"><input type="checkbox" id="ttsToggle"><span class="toggle-slider"></span></label>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-row">
|
||||||
|
<span class="toggle-label">READ USER MESSAGES TOO</span>
|
||||||
|
<label class="toggle"><input type="checkbox" id="ttsUserToggle"><span class="toggle-slider"></span></label>
|
||||||
|
</div>
|
||||||
|
<div class="cfg-group" style="margin-top:8px">
|
||||||
|
<label>VOICE</label>
|
||||||
|
<select id="ttsVoiceSelect"><option value="">-- Loading voices... --</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="cfg-group">
|
||||||
|
<label>RATE (speed)</label>
|
||||||
|
<div class="range-row">
|
||||||
|
<input type="range" id="ttsRateRange" min="0.5" max="2.5" step="0.05" value="1">
|
||||||
|
<span class="range-val" id="ttsRateVal">1.00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cfg-group">
|
||||||
|
<label>PITCH</label>
|
||||||
|
<div class="range-row">
|
||||||
|
<input type="range" id="ttsPitchRange" min="0" max="2" step="0.05" value="1">
|
||||||
|
<span class="range-val" id="ttsPitchVal">1.00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="cfg-group">
|
||||||
|
<label>VOLUME</label>
|
||||||
|
<div class="range-row">
|
||||||
|
<input type="range" id="ttsVolRange" min="0" max="1" step="0.05" value="1">
|
||||||
|
<span class="range-val" id="ttsVolVal">1.00</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:8px;margin-top:10px">
|
||||||
|
<button class="cfg-btn" onclick="ttsTest()">▶ TEST VOICE</button>
|
||||||
|
<button class="cfg-btn" onclick="ttsStop()">■ STOP</button>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:11px;color:var(--t-fg-dim);margin-top:8px" id="ttsStatus">
|
||||||
|
Web Speech API — voices depend on your browser & OS.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="cfg-buttons">
|
<div class="cfg-buttons">
|
||||||
<button class="cfg-btn danger" onclick="resetConfig()">RESET</button>
|
<button class="cfg-btn danger" onclick="resetConfig()">RESET</button>
|
||||||
<button class="cfg-btn" onclick="closeConfig()">CANCEL</button>
|
<button class="cfg-btn" onclick="closeConfig()">CANCEL</button>
|
||||||
@@ -780,6 +825,12 @@ const DEFAULT_CONFIG = {
|
|||||||
macroBar: true,
|
macroBar: true,
|
||||||
macros: 'Hello|Hello! Who are you?\nHelp|What can you help me with?\nDate|What is today\'s date?',
|
macros: 'Hello|Hello! Who are you?\nHelp|What can you help me with?\nDate|What is today\'s date?',
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
tts: false,
|
||||||
|
ttsUser: false,
|
||||||
|
ttsVoice: '',
|
||||||
|
ttsRate: 1.0,
|
||||||
|
ttsPitch: 1.0,
|
||||||
|
ttsVol: 1.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cfg = { ...DEFAULT_CONFIG };
|
let cfg = { ...DEFAULT_CONFIG };
|
||||||
@@ -817,8 +868,90 @@ const sidebar = document.getElementById('sidebar');
|
|||||||
const debugPre = document.getElementById('debugPre');
|
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 = '<option value="">System default</option>';
|
||||||
|
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();
|
applyTheme();
|
||||||
applyInterface();
|
applyInterface();
|
||||||
updateStatus();
|
updateStatus();
|
||||||
|
|||||||
Reference in New Issue
Block a user