The websocket handler now accumulates the full session's PCM audio
(not just the per-chunk buffer that gets discarded after each
worker call) and, on stop, writes it as a wav to UPLOAD_DIR plus a
result JSON to RESULT_DIR via the new _save_realtime_history()
helper — same shape /transcribe already writes, tagged
gateway_backend="realtime" so the history table visually
distinguishes it from file uploads. This reuses /asr/history and
/asr/uploads/{filename} as-is; no new endpoints needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Same Authlib-based pattern as v1: SessionMiddleware + core/auth.py
(login/callback/logout + require_login dependency), gating "/",
/asr/* and /tts/* behind Authentik (application slug "asr-v2",
provider pk 14). The websocket route can't use a FastAPI dependency
(no Request in its scope) so it's split into its own router and
checks websocket.session manually before accept().
v2 has no public domain yet, so the redirect_uri points at the
internal 172.30.1.41:18101 address over plain http — hence
https_only=False on the session cookie. Client id/secret and the
session secret live in /srv/asr/env/asr.env under ASR_V2_-prefixed
names (that env file is shared with v1, which already owns the
unprefixed OIDC_* names).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split the single ASR gateway into app/main.py (entrypoint) + app/core
(shared env helpers) + app/asr (all existing ASR logic, unchanged
behavior) + app/tts (placeholder router/config, no engine yet), so a
real TTS backend can be dropped in later without reshuffling ASR code.
API moved under /asr/* and /tts/* prefixes; UI gained a top-level
ASR/TTS tab switcher built on a reusable nested .tab-group mechanism,
with JS split into common.js/asr.js/tts.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>