From 9bac6d170aa3e446b26a91fa78c821b7c011dfa9 Mon Sep 17 00:00:00 2001 From: EJClaw Date: Sun, 16 Aug 2026 23:06:30 +0900 Subject: [PATCH] fix(dashboard): stop infinite mock loop; add demo-mode warning banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --dashboard defaulted to looping mock STT forever, so the status page piled up thousands of fake "conversations" (all mock, 0ms, same reply) that looked like real traffic. Now it plays 3 sample utterances then idles; a loud 데모 모드 banner states the turns are mock samples, not real STT/Brain/TTS. Continuous demo moved behind --dashboard-loop-demo. Co-Authored-By: Claude Opus 4.7 --- README.md | 3 +++ wsai/__main__.py | 41 ++++++++++++++++++++++++++++++++--------- wsai/dashboard.py | 16 ++++++++++++++++ 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4a2a637..8d2d27e 100644 --- a/README.md +++ b/README.md @@ -130,12 +130,15 @@ ffmpeg 디코드 → 프레임. DAVE는 `@snazzah/davey`(Rust NAPI), 수신은 ` ```bash .venv/bin/python -m wsai --voice # 눈 없는 음성 루프 데모 (STT→두뇌→TTS, 지금 초점) +.venv/bin/python -m wsai --dashboard # 상태 사이트 + 짧은 mock 샘플 후 대기(무한 생성 안 함) .venv/bin/python -m wsai # mock 데모 (눈 포함 전체 흐름, 몇 프레임 돌고 종료) .venv/bin/python -m wsai --env # WSAI_* 환경변수로 백엔드 조립 .venv/bin/python -m pip install pytest && .venv/bin/python -m pytest -q # 스모크 테스트 ``` - `WSAI_SOURCE=none WSAI_VISION=none` 으로도 눈 없이(음성 루프만) 조립할 수 있다. +- `--dashboard`는 기본으로 mock 발화 3개만 만든 뒤 대기한다. UI 시연용으로 계속 만들고 싶을 때만 + `--dashboard-loop-demo`를 추가한다. - 백엔드별 추가 설치는 `requirements.txt` 주석 참고(faster-whisper, mss/pillow, anthropic 등). --- diff --git a/wsai/__main__.py b/wsai/__main__.py index 6b31399..686353d 100644 --- a/wsai/__main__.py +++ b/wsai/__main__.py @@ -2,12 +2,13 @@ python -m wsai # mock pipeline (no deps, no keys) — runs a demo python -m wsai --voice # eyes-free voice loop demo (STT -> Brain -> TTS) - python -m wsai --dashboard # live status website + a continuous voice demo + python -m wsai --dashboard # live status website + a short voice demo, then idle python -m wsai --live # capture this screen + Claude eyes/brain python -m wsai --env # build from WSAI_* environment variables The mock run is bounded (a few frames + a scripted conversation) so it exits on -its own; --dashboard/--live/--env run until Ctrl-C. +its own; --dashboard keeps the site open after a short demo; --live/--env run +until Ctrl-C. """ from __future__ import annotations @@ -34,7 +35,14 @@ def _lan_ip() -> str: return "127.0.0.1" -async def _run(settings: Settings, demo: bool, monitor: Monitor | None) -> None: +async def _run( + settings: Settings, + demo: bool, + monitor: Monitor | None, + *, + demo_loop: bool = False, + keep_dashboard_open: bool = False, +) -> None: if demo: # Bounded demo so CI / a quick check terminates. from .backends.mock import MockFrameSource, MockSTT @@ -43,11 +51,16 @@ async def _run(settings: Settings, demo: bool, monitor: Monitor | None) -> None: if pipe.source is not None: # keep eyes-free configs eyes-free pipe.source = MockFrameSource(interval=0.3, limit=4) if pipe.stt is not None: - # When serving the dashboard, keep talking forever so there's always - # something live to watch; otherwise stay bounded so the demo exits. - loop = monitor is not None - pipe.stt = MockSTT(interval=2.0 if loop else 0.4, loop=loop) + pipe.stt = MockSTT(interval=2.0 if demo_loop else 0.4, loop=demo_loop) await pipe.run() + if keep_dashboard_open: + # Keep the status page alive without generating fake conversations + # forever. The previous default looped mock STT indefinitely, which + # made the dashboard look like it had heard thousands of real users. + if monitor is not None: + monitor.set_status(running=True, listening=False) + monitor.log("info", "mock 데모 완료 — 실제 음성 파이프라인 연결 대기") + await asyncio.Event().wait() return await build(settings, monitor=monitor).run() @@ -55,7 +68,9 @@ async def _run(settings: Settings, demo: bool, monitor: Monitor | None) -> None: def main() -> None: ap = argparse.ArgumentParser(prog="wsai") ap.add_argument("--voice", action="store_true", help="eyes-free voice loop (STT -> Brain -> TTS)") - ap.add_argument("--dashboard", action="store_true", help="serve the live status website (voice demo loops)") + ap.add_argument("--dashboard", action="store_true", help="serve the live status website") + ap.add_argument("--dashboard-loop-demo", action="store_true", + help="keep generating mock demo utterances forever (off by default)") ap.add_argument("--live", action="store_true", help="capture screen + Claude backends") ap.add_argument("--env", action="store_true", help="build from WSAI_* env vars") ap.add_argument("--port", type=int, default=int(os.environ.get("WSAI_DASHBOARD_PORT", "8787")), @@ -96,7 +111,15 @@ def main() -> None: print(f" (로컬: http://127.0.0.1:{args.port} )\n") try: - asyncio.run(_run(settings, demo, monitor)) + asyncio.run( + _run( + settings, + demo, + monitor, + demo_loop=args.dashboard and args.dashboard_loop_demo, + keep_dashboard_open=args.dashboard and demo and not args.dashboard_loop_demo, + ) + ) except KeyboardInterrupt: pass finally: diff --git a/wsai/dashboard.py b/wsai/dashboard.py index 6f09397..1183cc8 100644 --- a/wsai/dashboard.py +++ b/wsai/dashboard.py @@ -179,6 +179,9 @@ PAGE = r""" .ev.error{color:var(--err)} .ev .et{flex:0 0 68px;color:#5f7488} code{background:#0c1219;padding:1px 5px;border-radius:5px;border:1px solid var(--line)} + .demobar{background:#2a2210;border:1px solid #6b5417;color:var(--warn);border-radius:12px; + padding:11px 15px;margin:0 0 16px;font-size:13px;line-height:1.55} + .demobar b{color:#ffe08a} @@ -196,6 +199,7 @@ PAGE = r"""
+
아직 대화가 없습니다. 사용자가 말하면 여기에 단계별로 나타납니다.
@@ -232,6 +236,18 @@ function renderStatus(s){ $('dot').className = 'dot ' + (listening ? 'live' : 'off'); $('listen').textContent = listening ? '듣는 중' : (s.running ? '실행 중 (대기)' : '중지됨'); const comps = s.components || {}; + // Demo banner: if the ears/brain/mouth are still mock, everything below is + // replayed sample data, not a real conversation. Say so loudly. + const mockParts = ['stt','brain','tts'].filter(k => comps[k]==='mock'); + const bar = $('demobar'); + if(mockParts.length){ + bar.style.display='block'; + bar.innerHTML = '⚠ 데모 모드 — 실제 음성/STT/두뇌/TTS가 아직 연결되지 않아, 아래 대화는 ' + + '실제로 들은 내용이 아니라 목(mock) 예시 스크립트입니다. ' + + '실제 엔진(faster-whisper·Claude·MeloTTS)을 붙이면 이 자리에 진짜 발화·지연·오류가 표시됩니다.'; + } else { + bar.style.display='none'; + } const el = $('comp'); el.innerHTML = ''; const names = {source:'눈(소스)', vision:'시각', stt:'귀(STT)', brain:'두뇌', tts:'입(TTS)', text:'텍스트'}; for(const k of Object.keys(names)){