mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-06 22:06:47 +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>
189 lines
4.8 KiB
Python
189 lines
4.8 KiB
Python
"""Typed WebSocket event models.
|
|
|
|
Defines Pydantic models with Literal discriminators for all WS events.
|
|
Exposed via stub GET endpoints so ``pnpm openapi`` generates TS discriminated unions.
|
|
"""
|
|
|
|
from typing import Annotated, Literal, Union
|
|
|
|
from pydantic import BaseModel, Discriminator
|
|
|
|
from reflector.db.transcripts import (
|
|
TranscriptActionItems,
|
|
TranscriptDuration,
|
|
TranscriptFinalLongSummary,
|
|
TranscriptFinalShortSummary,
|
|
TranscriptFinalTitle,
|
|
TranscriptStatus,
|
|
TranscriptText,
|
|
TranscriptWaveform,
|
|
)
|
|
from reflector.utils.string import NonEmptyString
|
|
from reflector.views.transcripts import GetTranscriptTopic
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Transcript-level event name literal
|
|
# ---------------------------------------------------------------------------
|
|
|
|
TranscriptEventName = Literal[
|
|
"TRANSCRIPT",
|
|
"TOPIC",
|
|
"STATUS",
|
|
"FINAL_TITLE",
|
|
"FINAL_LONG_SUMMARY",
|
|
"FINAL_SHORT_SUMMARY",
|
|
"ACTION_ITEMS",
|
|
"DURATION",
|
|
"WAVEFORM",
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Transcript-level WS event wrappers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TranscriptWsTranscript(BaseModel):
|
|
event: Literal["TRANSCRIPT"] = "TRANSCRIPT"
|
|
data: TranscriptText
|
|
|
|
|
|
class TranscriptWsTopic(BaseModel):
|
|
event: Literal["TOPIC"] = "TOPIC"
|
|
data: GetTranscriptTopic
|
|
|
|
|
|
class TranscriptWsStatusData(BaseModel):
|
|
value: TranscriptStatus
|
|
|
|
|
|
class TranscriptWsStatus(BaseModel):
|
|
event: Literal["STATUS"] = "STATUS"
|
|
data: TranscriptWsStatusData
|
|
|
|
|
|
class TranscriptWsFinalTitle(BaseModel):
|
|
event: Literal["FINAL_TITLE"] = "FINAL_TITLE"
|
|
data: TranscriptFinalTitle
|
|
|
|
|
|
class TranscriptWsFinalLongSummary(BaseModel):
|
|
event: Literal["FINAL_LONG_SUMMARY"] = "FINAL_LONG_SUMMARY"
|
|
data: TranscriptFinalLongSummary
|
|
|
|
|
|
class TranscriptWsFinalShortSummary(BaseModel):
|
|
event: Literal["FINAL_SHORT_SUMMARY"] = "FINAL_SHORT_SUMMARY"
|
|
data: TranscriptFinalShortSummary
|
|
|
|
|
|
class TranscriptWsActionItems(BaseModel):
|
|
event: Literal["ACTION_ITEMS"] = "ACTION_ITEMS"
|
|
data: TranscriptActionItems
|
|
|
|
|
|
class TranscriptWsDuration(BaseModel):
|
|
event: Literal["DURATION"] = "DURATION"
|
|
data: TranscriptDuration
|
|
|
|
|
|
class TranscriptWsWaveform(BaseModel):
|
|
event: Literal["WAVEFORM"] = "WAVEFORM"
|
|
data: TranscriptWaveform
|
|
|
|
|
|
TranscriptWsEvent = Annotated[
|
|
Union[
|
|
TranscriptWsTranscript,
|
|
TranscriptWsTopic,
|
|
TranscriptWsStatus,
|
|
TranscriptWsFinalTitle,
|
|
TranscriptWsFinalLongSummary,
|
|
TranscriptWsFinalShortSummary,
|
|
TranscriptWsActionItems,
|
|
TranscriptWsDuration,
|
|
TranscriptWsWaveform,
|
|
],
|
|
Discriminator("event"),
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User-level event name literal
|
|
# ---------------------------------------------------------------------------
|
|
|
|
UserEventName = Literal[
|
|
"TRANSCRIPT_CREATED",
|
|
"TRANSCRIPT_DELETED",
|
|
"TRANSCRIPT_STATUS",
|
|
"TRANSCRIPT_FINAL_TITLE",
|
|
"TRANSCRIPT_DURATION",
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User-level WS event data models
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class UserTranscriptCreatedData(BaseModel):
|
|
id: NonEmptyString
|
|
|
|
|
|
class UserTranscriptDeletedData(BaseModel):
|
|
id: NonEmptyString
|
|
|
|
|
|
class UserTranscriptStatusData(BaseModel):
|
|
id: NonEmptyString
|
|
value: TranscriptStatus
|
|
|
|
|
|
class UserTranscriptFinalTitleData(BaseModel):
|
|
id: NonEmptyString
|
|
title: NonEmptyString
|
|
|
|
|
|
class UserTranscriptDurationData(BaseModel):
|
|
id: NonEmptyString
|
|
duration: float
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# User-level WS event wrappers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class UserWsTranscriptCreated(BaseModel):
|
|
event: Literal["TRANSCRIPT_CREATED"] = "TRANSCRIPT_CREATED"
|
|
data: UserTranscriptCreatedData
|
|
|
|
|
|
class UserWsTranscriptDeleted(BaseModel):
|
|
event: Literal["TRANSCRIPT_DELETED"] = "TRANSCRIPT_DELETED"
|
|
data: UserTranscriptDeletedData
|
|
|
|
|
|
class UserWsTranscriptStatus(BaseModel):
|
|
event: Literal["TRANSCRIPT_STATUS"] = "TRANSCRIPT_STATUS"
|
|
data: UserTranscriptStatusData
|
|
|
|
|
|
class UserWsTranscriptFinalTitle(BaseModel):
|
|
event: Literal["TRANSCRIPT_FINAL_TITLE"] = "TRANSCRIPT_FINAL_TITLE"
|
|
data: UserTranscriptFinalTitleData
|
|
|
|
|
|
class UserWsTranscriptDuration(BaseModel):
|
|
event: Literal["TRANSCRIPT_DURATION"] = "TRANSCRIPT_DURATION"
|
|
data: UserTranscriptDurationData
|
|
|
|
|
|
UserWsEvent = Annotated[
|
|
Union[
|
|
UserWsTranscriptCreated,
|
|
UserWsTranscriptDeleted,
|
|
UserWsTranscriptStatus,
|
|
UserWsTranscriptFinalTitle,
|
|
UserWsTranscriptDuration,
|
|
],
|
|
Discriminator("event"),
|
|
]
|