mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-03-27 01:16:46 +00:00
* fix: live flow real-time updates during processing Three gaps caused transcript pages to require manual refresh after live recording/processing: 1. UserEventsProvider only invalidated list queries on TRANSCRIPT_STATUS, not individual transcript queries. Now parses data.id from the event and calls invalidateTranscript for the specific transcript. 2. useWebSockets had no reconnection logic — a dropped WS silently killed all real-time updates. Added exponential backoff reconnection (1s-30s, max 10 retries) with intentional close detection. 3. No polling fallback — WS was single point of failure. Added conditional refetchInterval to useTranscriptGet that polls every 5s when transcript status is processing/uploaded/recording. * feat: type-safe WebSocket events via OpenAPI stub Define Pydantic models with Literal discriminators for all WS events (9 transcript-level, 5 user-level). Expose via stub GET endpoints so pnpm openapi generates TS discriminated unions with exhaustive switch narrowing on the frontend. - New server/reflector/ws_events.py with TranscriptWsEvent and UserWsEvent - Tighten backend emit signatures with TranscriptEventName literal - Frontend uses generated types, removes Zod schema and manual casts - Fix pre-existing bugs: waveform mapping, FINAL_LONG_SUMMARY field name - STATUS value now typed as TranscriptStatus literal end-to-end - TOPIC handler simplified to query invalidation only (avoids shape mismatch) * fix: restore TOPIC WS handler with immediate state update The setTopics call provides instant topic rendering during live transcription. Query invalidation still follows for full data sync. * fix: align TOPIC WS event data with GetTranscriptTopic shape Convert TranscriptTopic → GetTranscriptTopic in pipeline before emitting, so WS sends segments instead of words. Removes the `as unknown as Topic` cast on the frontend. * fix: use NonEmptyString and TranscriptStatus in user WS event models --------- Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
68 lines
2.3 KiB
Python
68 lines
2.3 KiB
Python
"""
|
|
Transcripts websocket API
|
|
=========================
|
|
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect
|
|
|
|
import reflector.auth as auth
|
|
from reflector.db.transcripts import transcripts_controller
|
|
from reflector.ws_events import TranscriptWsEvent
|
|
from reflector.ws_manager import get_ws_manager
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get(
|
|
"/transcripts/{transcript_id}/events",
|
|
response_model=TranscriptWsEvent,
|
|
summary="Transcript WebSocket event schema",
|
|
description="Stub exposing the discriminated union of all transcript-level WS events for OpenAPI type generation. Real events are delivered over the WebSocket at the same path.",
|
|
)
|
|
async def transcript_get_websocket_events(transcript_id: str):
|
|
pass
|
|
|
|
|
|
@router.websocket("/transcripts/{transcript_id}/events")
|
|
async def transcript_events_websocket(
|
|
transcript_id: str,
|
|
websocket: WebSocket,
|
|
):
|
|
_, negotiated_subprotocol = auth.parse_ws_bearer_token(websocket)
|
|
user = await auth.current_user_ws_optional(websocket)
|
|
user_id = user["sub"] if user else None
|
|
transcript = await transcripts_controller.get_by_id_for_http(
|
|
transcript_id, user_id=user_id
|
|
)
|
|
if not transcript:
|
|
raise HTTPException(status_code=404, detail="Transcript not found")
|
|
|
|
# connect to websocket manager
|
|
# use ts:transcript_id as room id
|
|
room_id = f"ts:{transcript_id}"
|
|
ws_manager = get_ws_manager()
|
|
await ws_manager.add_user_to_room(
|
|
room_id, websocket, subprotocol=negotiated_subprotocol
|
|
)
|
|
|
|
try:
|
|
# on first connection, send all events only to the current user
|
|
for event in transcript.events:
|
|
# for now, do not send TRANSCRIPT or STATUS options - theses are live event
|
|
# not necessary to be sent to the client; but keep the rest
|
|
name = event.event
|
|
if name in ("TRANSCRIPT", "STATUS"):
|
|
continue
|
|
await websocket.send_json(event.model_dump(mode="json"))
|
|
|
|
# XXX if transcript is final (locked=True and status=ended)
|
|
# XXX send a final event to the client and close the connection
|
|
|
|
# endless loop to wait for new events
|
|
# we do not have command system now,
|
|
while True:
|
|
await websocket.receive()
|
|
except (RuntimeError, WebSocketDisconnect):
|
|
await ws_manager.remove_user_from_room(room_id, websocket)
|