feat(dashboard): per-stage STT/LLM/TTS timing + rename 두뇌 -> LLM

- voice_turn now records three separate timed steps (STT, LLM, TTS) instead of
  one combined "STT+두뇌+TTS" step, so each stage's latency shows in the turn.
- Rename 두뇌 -> LLM everywhere user-facing: component chip, sub header, demo
  banner, model label, brain-failure log, startup print, pipeline step name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-08-27 00:03:34 +09:00
parent 7c899d3b19
commit 0588ee21ac
3 changed files with 19 additions and 11 deletions

View File

@@ -650,6 +650,13 @@ class Dashboard:
self.monitor.log("info", f"LLM 모델 변경: {model} (다음 답변부터 적용)", cat="MODEL")
return {**self.models_settings()["llm"], "changed": changed}
@staticmethod
def _step(turn, name: str, ms: float) -> None:
"""Record one finished timing step (STT / LLM / TTS) on a turn."""
st = turn.step(name)
st.ok, st.ms = True, float(ms)
turn._steps.append(st)
def voice_turn(self, audio_bytes: bytes, speaker: str = "",
guild: str = "", channel: str = "") -> dict:
"""One Discord voice turn: decode the uploaded utterance, recognise it
@@ -680,6 +687,7 @@ class Dashboard:
check=True, capture_output=True,
)
heard = (self._submit(self.stt.transcribe(wav)) or "").strip()
self._step(turn, "STT", (time.monotonic() - t0) * 1000) # 인식(+디코드)
turn.heard(heard or "(빈 결과)")
if not heard:
# Nothing recognised (silence/noise): mark it as [잡음] and skip
@@ -688,16 +696,16 @@ class Dashboard:
turn.replied("[잡음]")
turn.finish()
return {"heard": heard, "reply": "[잡음]", "wav": b""}
t_llm = time.monotonic()
reply_text = self._think(heard)
self._step(turn, "LLM" if self.brain else "echo", (time.monotonic() - t_llm) * 1000)
turn.thought(_thought_summary(reply_text))
turn.replied(reply_text)
t_tts = time.monotonic()
out_path = self._submit(self.tts.synth(_speech_text(reply_text)))
with open(out_path, "rb") as f:
reply_wav = f.read()
ms = int((time.monotonic() - t0) * 1000)
step = turn.step("STT+두뇌+TTS" if self.brain else "STT+TTS(GPU)")
step.ok, step.ms = True, float(ms)
turn._steps.append(step)
self._step(turn, "TTS", (time.monotonic() - t_tts) * 1000) # 합성
turn.finish()
try:
os.remove(out_path)
@@ -733,7 +741,7 @@ class Dashboard:
self.monitor.add_claude_usage(u.get("input", 0), u.get("output", 0))
except Exception as exc: # noqa: BLE001
log.exception("brain failed")
self.monitor.log("error", f"두뇌 응답 실패: {exc}", cat="BRAIN")
self.monitor.log("error", f"LLM 응답 실패: {exc}", cat="BRAIN")
blob = f"{getattr(exc, 'status_code', '')} {exc}".lower()
if "529" in blob or "overload" in blob:
# Transient server overload survived the SDK retries.
@@ -1008,7 +1016,7 @@ PAGE = r"""<!DOCTYPE html>
<header>
<div>
<h1>watch_sceen_ai · 실시간 상태</h1>
<div class="sub">STT → 두뇌 → TTS 음성 루프를 단계별로 관찰</div>
<div class="sub">STT → LLM → TTS 음성 루프를 단계별로 관찰</div>
</div>
<div class="pill"><span id="dot" class="dot off"></span><span id="listen">연결 대기</span></div>
<button id="promptBtn" class="btn hbtn">📝 프롬프트</button>
@@ -1081,7 +1089,7 @@ PAGE = r"""<!DOCTYPE html>
<span id="mSTTstat" class="sttstat"></span>
</div>
<div class="sttrow" style="margin-top:8px">
<label style="font-size:12.5px;color:var(--muted)">LLM(두뇌)
<label style="font-size:12.5px;color:var(--muted)">LLM
<select id="mLLM" class="ttssel"></select></label>
<button id="mLLMapply" class="btn">적용</button>
<span id="mLLMstat" class="sttstat"></span>
@@ -1194,14 +1202,14 @@ function renderStatus(s){
const bar = $('demobar');
if(mockParts.length){
bar.style.display='block';
bar.innerHTML = '⚠ <b>데모 모드</b> — 실제 음성/STT/두뇌/TTS가 아직 연결되지 않아, 아래 대화는 '
bar.innerHTML = '⚠ <b>데모 모드</b> — 실제 음성/STT/LLM/TTS가 아직 연결되지 않아, 아래 대화는 '
+ '실제로 들은 내용이 아니라 <b>목(mock) 예시 스크립트</b>입니다. '
+ '실제 엔진(faster-whisper·Claude·MeloTTS)을 붙이면 이 자리에 진짜 발화·지연·오류가 표시됩니다.';
} else {
bar.style.display='none';
}
const el = $('comp'); el.innerHTML = '';
const names = {source:'눈(소스)', vision:'시각', stt:'귀(STT)', brain:'두뇌', tts:'입(TTS)', text:'텍스트'};
const names = {source:'눈(소스)', vision:'시각', stt:'귀(STT)', brain:'LLM', tts:'입(TTS)', text:'텍스트'};
for(const k of Object.keys(names)){
if(!(k in comps)) continue;
const v = comps[k];