Switch TTS to edge-tts (ko-KR-SunHiNeural), add TTS toggle + mic auto-send
- Replace piper TTS with edge-tts (Microsoft neural Korean voice)
- piper ko_KR-kss-medium uses pygoruut phoneme type unsupported by C++ binary,
causing Chinese-sounding output; edge-tts solves this via online neural TTS
- Added src/tools/edge_tts_synth.py Python helper script
- Server TTS endpoint now dispatches by provider (edge_tts vs piper)
- Config updated: voice.tts.provider = "edge_tts", voice = "ko-KR-SunHiNeural"
- Add TTS toggle switch to web UI (chat input bar)
- Toggles visibility of all 🔊 buttons and stops active playback
- State persisted in localStorage (default: on)
- Mic input now auto-submits after speech recognition completes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1025,6 +1025,7 @@
|
||||
@keyframes mic-pulse { 0%,100%{opacity:1} 50%{opacity:0.55} }
|
||||
.tts-btn { background:none;border:none;cursor:pointer;font-size:13px;padding:1px 4px;border-radius:4px;opacity:0.45;vertical-align:middle;line-height:1; }
|
||||
.tts-btn:hover,.tts-btn.playing{opacity:1;background:var(--panel-2);}
|
||||
body.tts-disabled .tts-btn { display:none !important; }
|
||||
.export-dropdown { position:relative; display:inline-block; }
|
||||
.export-menu { display:none; position:absolute; right:0; top:calc(100% + 4px); background:var(--panel-2); border:1px solid var(--border); border-radius:8px; box-shadow:0 4px 16px rgba(0,0,0,0.18); z-index:2000; min-width:160px; overflow:hidden; }
|
||||
.export-menu button { display:block; width:100%; padding:8px 14px; text-align:left; background:none; border:none; cursor:pointer; font-size:12px; color:var(--text); white-space:nowrap; }
|
||||
@@ -2374,6 +2375,12 @@
|
||||
</label>
|
||||
<span class="agent-toggle-label" id="agent-mode-label">채팅</span>
|
||||
<span class="agent-mode-badge" id="agent-mode-badge">AGENT</span>
|
||||
<span style="color:var(--line);margin:0 6px;user-select:none">|</span>
|
||||
<span class="agent-toggle-label">TTS</span>
|
||||
<label class="toggle-switch" title="AI 응답 음성 읽기 켜기/끄기">
|
||||
<input type="checkbox" id="tts-mode-toggle" onchange="updateTTSMode()">
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
<div class="agent-toggle-right" style="display:flex;align-items:center;gap:6px">
|
||||
<button class="quick-mode-btn" id="context-pin-btn" onclick="toggleContextPinMode()" title="메시지를 컨텍스트에 고정">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>
|
||||
@@ -3946,6 +3953,7 @@ let agentSessionId = '';
|
||||
let currentUser = null;
|
||||
setAgentSessionId(generateSessionId()); // always fresh session on page load
|
||||
checkAuth(); // Check authentication on page load
|
||||
initTTSToggle();
|
||||
|
||||
// ── Server log panel ──────────────────────────────────────────────────────────
|
||||
let logScrollLocked = true;
|
||||
@@ -8866,6 +8874,7 @@ function startMicRecording() {
|
||||
input.value = (input.value ? input.value + ' ' : '') + transcript;
|
||||
input.focus();
|
||||
input.dispatchEvent(new Event('input'));
|
||||
setTimeout(function() { handleSendStop(); }, 50);
|
||||
}
|
||||
};
|
||||
_speechRecog.onerror = function(e) {
|
||||
@@ -8884,9 +8893,26 @@ function stopMicRecording() {
|
||||
}
|
||||
|
||||
// ── Voice: TTS (음성 출력) ───────────────────────────────────────────────
|
||||
var _ttsEnabled = localStorage.getItem('ttsEnabled') !== 'false'; // default on
|
||||
var _ttsAudio = null;
|
||||
var _ttsBtnActive = null;
|
||||
|
||||
function updateTTSMode() {
|
||||
_ttsEnabled = document.getElementById('tts-mode-toggle').checked;
|
||||
localStorage.setItem('ttsEnabled', String(_ttsEnabled));
|
||||
document.body.classList.toggle('tts-disabled', !_ttsEnabled);
|
||||
if (!_ttsEnabled && _ttsAudio) {
|
||||
_ttsAudio.pause(); _ttsAudio = null;
|
||||
if (_ttsBtnActive) { _ttsBtnActive.textContent = '🔊'; _ttsBtnActive.classList.remove('playing'); _ttsBtnActive = null; }
|
||||
}
|
||||
}
|
||||
|
||||
function initTTSToggle() {
|
||||
var toggle = document.getElementById('tts-mode-toggle');
|
||||
if (toggle) toggle.checked = _ttsEnabled;
|
||||
document.body.classList.toggle('tts-disabled', !_ttsEnabled);
|
||||
}
|
||||
|
||||
function playTTSMsg(btn, idx) {
|
||||
var msg = chatHistory[idx];
|
||||
if (!msg) return;
|
||||
|
||||
Reference in New Issue
Block a user