ver0.1
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
"""语音桌面终端:assignment 状态、WebSocket 推送与 HTTP 轮询兜底。"""
|
||||
"""语音桌面终端:assignment 状态、WebSocket 推送与 HTTP 拉取兜底。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from asyncio import Lock
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any
|
||||
|
||||
from fastapi import WebSocket
|
||||
from loguru import logger
|
||||
@@ -14,6 +16,8 @@ from starlette.websockets import WebSocketDisconnect
|
||||
from app.config import Settings
|
||||
from app.services.voice_terminal_binding import VoiceTerminalBindingIndex
|
||||
|
||||
PendingHeadFetcher = Callable[[str], Awaitable[Any]]
|
||||
|
||||
|
||||
async def assign_voice_terminal_after_recording_started(
|
||||
hub: VoiceTerminalHub,
|
||||
@@ -45,12 +49,18 @@ async def assign_voice_terminal_after_recording_started(
|
||||
class VoiceTerminalHub:
|
||||
"""进程内终端连接与当前手术分配(多 worker 需另行同步)。"""
|
||||
|
||||
def __init__(self, settings: Settings) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
settings: Settings,
|
||||
*,
|
||||
pending_head_fetcher: PendingHeadFetcher | None = None,
|
||||
) -> None:
|
||||
cfg = settings.load_or_site_config()
|
||||
self._bindings = cfg.voice_bindings if cfg else None
|
||||
self._assignments: dict[str, str] = {}
|
||||
self._lock = Lock()
|
||||
self._connections: dict[str, set[WebSocket]] = defaultdict(set)
|
||||
self._pending_head_fetcher = pending_head_fetcher
|
||||
|
||||
@property
|
||||
def bindings(self) -> VoiceTerminalBindingIndex | None:
|
||||
@@ -81,6 +91,15 @@ class VoiceTerminalHub:
|
||||
tid,
|
||||
surgery_id,
|
||||
)
|
||||
self.schedule_notify_pending_head(tid, surgery_id)
|
||||
|
||||
def schedule_notify_pending_head(self, terminal_id: str, surgery_id: str) -> None:
|
||||
"""异步推送队首(含 TTS),不阻塞调用方。"""
|
||||
tid = terminal_id.strip()
|
||||
sid = (surgery_id or "").strip()
|
||||
if not tid or not sid:
|
||||
return
|
||||
asyncio.create_task(self._notify_pending_head_safe(tid, sid))
|
||||
|
||||
async def notify_end(self, terminal_id: str | None, surgery_id: str) -> None:
|
||||
if not terminal_id:
|
||||
@@ -103,6 +122,50 @@ class VoiceTerminalHub:
|
||||
surgery_id,
|
||||
)
|
||||
|
||||
async def notify_pending_head(self, terminal_id: str, surgery_id: str) -> None:
|
||||
"""向终端推送当前 FIFO 队首(含 TTS),无队首时推送 voice_pending_empty。"""
|
||||
fetcher = self._pending_head_fetcher
|
||||
tid = terminal_id.strip()
|
||||
sid = (surgery_id or "").strip()
|
||||
if not fetcher or not tid or not sid:
|
||||
return
|
||||
try:
|
||||
payload = await fetcher(sid)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"voice_pending 构建失败 surgery_id={} terminal_id={}: {}",
|
||||
sid,
|
||||
tid,
|
||||
exc,
|
||||
)
|
||||
return
|
||||
if payload is None:
|
||||
await self._broadcast(
|
||||
tid,
|
||||
{"type": "voice_pending_empty", "surgery_id": sid},
|
||||
)
|
||||
return
|
||||
try:
|
||||
data = payload.model_dump(mode="json")
|
||||
except Exception as exc:
|
||||
logger.warning("voice_pending 序列化失败 surgery_id={}: {}", sid, exc)
|
||||
return
|
||||
data["type"] = "voice_pending"
|
||||
await self._broadcast(tid, data)
|
||||
|
||||
async def _notify_pending_head_safe(
|
||||
self, terminal_id: str, surgery_id: str
|
||||
) -> None:
|
||||
try:
|
||||
await self.notify_pending_head(terminal_id, surgery_id)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"后台 voice_pending 推送失败 terminal_id={} surgery_id={}: {}",
|
||||
terminal_id,
|
||||
surgery_id,
|
||||
exc,
|
||||
)
|
||||
|
||||
async def handle_websocket(self, websocket: WebSocket, terminal_id: str) -> None:
|
||||
tid = terminal_id.strip()
|
||||
if not tid:
|
||||
@@ -125,6 +188,7 @@ class VoiceTerminalHub:
|
||||
ensure_ascii=False,
|
||||
)
|
||||
)
|
||||
self.schedule_notify_pending_head(tid, sid)
|
||||
# 不能用 receive_text():桌面端 websocket-client 会发 ping/二进制控制帧,
|
||||
# ASGI 可能呈现为无 "text" 的 websocket.receive,receive_text 会 KeyError 并掐断连接。
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user