feat(dashboard): browser STT recognition test on the GPU
The status page was view-only, so there was no way to actually verify Korean recognition end-to-end. Add a live test: record from the mic (localhost/https) or upload an audio file (works over LAN http, where browsers block getUserMedia), POST it to a new /api/stt endpoint that ffmpeg-normalises the blob to 16 kHz mono and runs the real GPU faster-whisper, then shows the recognised text + latency + device. Results also land in the live turn feed. The dashboard now optionally holds a WhisperSTT and drives it from a private asyncio loop thread. New `python -m wsai --stt-test` serves the page with STT enabled and pre-warms the GPU worker so the first recognition is instant. WhisperSTT.resolved_device is exposed for the UI. Verified: wav and browser-style webm/opus uploads both return the correct Korean text on device=cuda in ~240-280ms. 12 tests pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -65,12 +65,49 @@ async def _run(
|
||||
await build(settings, monitor=monitor).run()
|
||||
|
||||
|
||||
def _run_stt_test(host: str, port: int) -> None:
|
||||
"""Serve the dashboard with a real GPU STT backend so a human can test
|
||||
recognition from the browser (record mic or upload an audio file). No mock
|
||||
conversation loop — the page just hosts the recognition test."""
|
||||
import time
|
||||
|
||||
from .backends.whisper import WhisperSTT
|
||||
from .dashboard import Dashboard
|
||||
from .monitor import Monitor
|
||||
|
||||
monitor = Monitor()
|
||||
stt = WhisperSTT()
|
||||
dash = Dashboard(monitor, host=host, port=port, stt=stt)
|
||||
dash.start()
|
||||
monitor.set_components({"source": "none", "vision": "none", "stt": "whisper",
|
||||
"brain": "none", "tts": "none"})
|
||||
monitor.set_status(running=True, listening=False)
|
||||
monitor.log("info", "STT 인식 테스트 서버 시작 — GPU 워밍업 중…")
|
||||
print("\n STT 워밍업 중… (모델 로드 + CUDA 예열)")
|
||||
dash.warm() # load + warm the GPU worker so the first recognition is instant
|
||||
dev = getattr(stt, "resolved_device", None) or "?"
|
||||
monitor.log("info", f"STT 준비 완료 (device={dev}). 녹음/파일 업로드로 인식하세요.")
|
||||
|
||||
shown = host if host not in ("0.0.0.0", "") else _lan_ip()
|
||||
print(f"\n 음성 인식 테스트 사이트: http://{shown}:{port} (STT device: {dev})")
|
||||
print(f" (로컬: http://127.0.0.1:{port} )\n")
|
||||
try:
|
||||
while True:
|
||||
time.sleep(3600)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
dash.stop()
|
||||
|
||||
|
||||
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")
|
||||
ap.add_argument("--dashboard-loop-demo", action="store_true",
|
||||
help="keep generating mock demo utterances forever (off by default)")
|
||||
ap.add_argument("--stt-test", action="store_true",
|
||||
help="serve the dashboard with a live GPU STT recognition test")
|
||||
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")),
|
||||
@@ -85,6 +122,10 @@ def main() -> None:
|
||||
format="%(levelname)s %(name)s: %(message)s",
|
||||
)
|
||||
|
||||
if args.stt_test:
|
||||
_run_stt_test(args.host, args.port)
|
||||
return
|
||||
|
||||
if args.dashboard:
|
||||
# Default to the eyes-free voice preset for the demo; env can override.
|
||||
settings = Settings.from_env() if args.env else Settings.voice()
|
||||
|
||||
Reference in New Issue
Block a user