feat(tts): new default tone (1.35/-0.07/-0.30) + collapsible dashboard panel

- Change default voice controls to the user's tuned values: glyph speed 1.35,
  word_gap -0.07s (-70ms), sentence_gap -0.30s, pitch 0 (env defaults, slider
  initial values, and the 기본값 reset button all updated).
- Make the "봇 목소리(TTS) 조절" dashboard panel collapsible via its header;
  starts collapsed (▸), expands on click (▾).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-08-26 22:57:52 +09:00
parent 2ce2806102
commit 39b743d976
2 changed files with 37 additions and 28 deletions

View File

@@ -463,9 +463,9 @@ class Dashboard:
def _tts_values(self) -> dict:
t = self.tts
return {
"speed": float(getattr(t, "speed", 1.25)),
"word_gap": float(getattr(t, "word_gap", 0.25)),
"sentence_gap": float(getattr(t, "sentence_gap", 0.75)),
"speed": float(getattr(t, "speed", 1.35)),
"word_gap": float(getattr(t, "word_gap", -0.07)),
"sentence_gap": float(getattr(t, "sentence_gap", -0.30)),
"pitch": float(getattr(t, "pitch", 0.0)),
}
@@ -723,6 +723,8 @@ PAGE = r"""<!DOCTYPE html>
.demobar b{color:#ffe08a}
.sttbox{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px;margin:0 0 16px}
.sttbox h2{font-size:13px;margin:0 0 10px;font-weight:600;color:var(--fg)}
.sttbox h2.collap{cursor:pointer;user-select:none;margin:0}
.sttbox h2.collap #ttsCaret{display:inline-block;width:12px;color:var(--muted)}
.sttrow{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.btn{background:#173042;border:1px solid #234a63;color:var(--fg);border-radius:10px;padding:8px 14px;font-size:13px;cursor:pointer}
.btn:hover{background:#1d3d54}
@@ -865,25 +867,27 @@ PAGE = r"""<!DOCTYPE html>
<div id="sttres" class="sttres"></div>
</section>
<section class="sttbox" id="ttsbox" style="display:none">
<h2>🔊 봇 목소리(TTS) 조절 · MeloTTS</h2>
<div class="ttsgrid">
<label>글자 속도 <b id="tvSpeed">1.25x</b>
<input type="range" id="tSpeed" min="0.5" max="2.0" step="0.05" value="1.25"></label>
<label>단어 간격 <b id="tvWord">250 ms</b>
<input type="range" id="tWord" min="-0.2" max="0.5" step="0.01" value="0.25"></label>
<label>문장 간격 <b id="tvSent">0.75 s</b>
<input type="range" id="tSent" min="-0.5" max="1.5" step="0.05" value="0.75"></label>
<label>피치 <b id="tvPitch">0 반음</b>
<input type="range" id="tPitch" min="-12" max="12" step="1" value="0"></label>
<h2 id="ttsToggle" class="collap"><span id="ttsCaret">▸</span> 🔊 봇 목소리(TTS) 조절 · MeloTTS</h2>
<div id="ttsBody" style="display:none">
<div class="ttsgrid">
<label>글자 속도 <b id="tvSpeed">1.35x</b>
<input type="range" id="tSpeed" min="0.5" max="2.0" step="0.05" value="1.35"></label>
<label>단어 간격 <b id="tvWord">-70 ms</b>
<input type="range" id="tWord" min="-0.2" max="0.5" step="0.01" value="-0.07"></label>
<label>문장 간격 <b id="tvSent">-0.30 s</b>
<input type="range" id="tSent" min="-0.5" max="1.5" step="0.05" value="-0.3"></label>
<label>피치 <b id="tvPitch">0 반음</b>
<input type="range" id="tPitch" min="-12" max="12" step="1" value="0"></label>
</div>
<div class="sttrow">
<input id="tText" class="ttstext" placeholder="미리들을 문장" value="안녕하세요, 목소리 미리듣기입니다.">
<button id="tPreview" class="btn">▶ 미리듣기</button>
<button id="tApply" class="btn">💾 봇에 적용</button>
<button id="tReset" class="btn hbtn">기본값</button>
<span id="tStat" class="sttstat">슬라이더로 조절 → 미리듣기로 확인 → 봇에 적용하면 다음 답변부터 반영됩니다.</span>
</div>
<audio id="tAudio" controls style="display:none;margin-top:10px;width:100%"></audio>
</div>
<div class="sttrow">
<input id="tText" class="ttstext" placeholder="미리들을 문장" value="안녕하세요, 목소리 미리듣기입니다.">
<button id="tPreview" class="btn">▶ 미리듣기</button>
<button id="tApply" class="btn">💾 봇에 적용</button>
<button id="tReset" class="btn hbtn">기본값</button>
<span id="tStat" class="sttstat">슬라이더로 조절 → 미리듣기로 확인 → 봇에 적용하면 다음 답변부터 반영됩니다.</span>
</div>
<audio id="tAudio" controls style="display:none;margin-top:10px;width:100%"></audio>
</section>
<div class="demobar" id="demobar" style="display:none"></div>
<div class="comp" id="comp"></div>
@@ -1188,6 +1192,11 @@ function ttsSet(s){
}
async function initTts(){
if(ttsInited) return; ttsInited = true;
$('ttsToggle').onclick = ()=>{
const b=$('ttsBody'), open=b.style.display==='none';
b.style.display = open ? 'block' : 'none';
$('ttsCaret').textContent = open ? '▾' : '▸';
};
['tSpeed','tWord','tSent','tPitch'].forEach(id => $(id).addEventListener('input', ttsLabels));
$('tPreview').onclick = async ()=>{
const text=($('tText').value||'').trim();
@@ -1211,7 +1220,7 @@ async function initTts(){
else { $('tStat').textContent='적용 실패: '+(j.error||''); }
}catch(e){ $('tStat').textContent='적용 오류: '+e; }
};
$('tReset').onclick = ()=>{ ttsSet({speed:1.25,word_gap:0.25,sentence_gap:0.75,pitch:0});
$('tReset').onclick = ()=>{ ttsSet({speed:1.35,word_gap:-0.07,sentence_gap:-0.3,pitch:0});
$('tStat').textContent='기본값으로 되돌림 (미리듣기/적용을 눌러 반영).'; };
try{
const j=await (await fetch('/api/tts/settings')).json();