mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-23 21:55:19 +00:00
fix: live flow real-time updates during processing (#861)
* 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>
This commit is contained in:
@@ -4,18 +4,22 @@ Transcripts websocket API
|
||||
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect
|
||||
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")
|
||||
@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
|
||||
|
||||
@@ -24,8 +28,9 @@ async def transcript_get_websocket_events(transcript_id: str):
|
||||
async def transcript_events_websocket(
|
||||
transcript_id: str,
|
||||
websocket: WebSocket,
|
||||
user: Optional[auth.UserInfo] = Depends(auth.current_user_optional),
|
||||
):
|
||||
_, 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
|
||||
@@ -37,7 +42,9 @@ async def transcript_events_websocket(
|
||||
# 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)
|
||||
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
|
||||
|
||||
@@ -4,10 +4,22 @@ from fastapi import APIRouter, WebSocket, WebSocketDisconnect
|
||||
|
||||
from reflector.auth.auth_jwt import JWTAuth # type: ignore
|
||||
from reflector.db.users import user_controller
|
||||
from reflector.ws_events import UserWsEvent
|
||||
from reflector.ws_manager import get_ws_manager
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/events",
|
||||
response_model=UserWsEvent,
|
||||
summary="User WebSocket event schema",
|
||||
description="Stub exposing the discriminated union of all user-level WS events for OpenAPI type generation. Real events are delivered over the WebSocket at the same path.",
|
||||
)
|
||||
async def user_get_websocket_events():
|
||||
pass
|
||||
|
||||
|
||||
# Close code for unauthorized WebSocket connections
|
||||
UNAUTHORISED = 4401
|
||||
|
||||
|
||||
Reference in New Issue
Block a user