feat: add MaiSaka real-time chat flow monitoring component and WebSocket event handling
- Implemented the MaiSakaMonitor component for real-time monitoring of chat flow using WebSocket. - Created a custom hook `useMaisakaMonitor` to manage WebSocket subscriptions and event states. - Developed a backend module for broadcasting various monitoring events through WebSocket. - Added serialization functions for messages and tool calls to optimize data transmission. - Included event emission functions for session start, message ingestion, cycle start, timing gate results, planner requests/responses, tool executions, and replier requests/responses.
This commit is contained in:
@@ -14,11 +14,9 @@ def get_api_router() -> APIRouter:
|
||||
|
||||
def get_all_routers() -> List[APIRouter]:
|
||||
"""获取所有需要独立注册的路由器列表"""
|
||||
from src.webui.api.planner import router as planner_router
|
||||
from src.webui.api.replier import router as replier_router
|
||||
from src.webui.routers.chat import router as chat_router
|
||||
from src.webui.routers.memory import compat_router as memory_compat_router
|
||||
from src.webui.routers.knowledge import router as knowledge_router
|
||||
from src.webui.routers.memory import compat_router as memory_compat_router
|
||||
from src.webui.routes import router as main_router
|
||||
|
||||
return [
|
||||
@@ -26,8 +24,6 @@ def get_all_routers() -> List[APIRouter]:
|
||||
memory_compat_router,
|
||||
knowledge_router,
|
||||
chat_router,
|
||||
planner_router,
|
||||
replier_router,
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"""统一 WebSocket 连接管理器。"""
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, Optional, Set
|
||||
|
||||
import asyncio
|
||||
|
||||
from fastapi import WebSocket
|
||||
from starlette.websockets import WebSocketState
|
||||
|
||||
from src.common.logger import get_logger
|
||||
|
||||
@@ -42,6 +44,24 @@ class UnifiedWebSocketManager:
|
||||
"""
|
||||
return f"{domain}:{topic}"
|
||||
|
||||
async def _close_websocket(self, connection: WebSocketConnection) -> None:
|
||||
"""显式关闭底层 WebSocket 连接。
|
||||
|
||||
某些异常退出路径只会执行清理逻辑,但不会自动向客户端发送关闭帧。
|
||||
这里主动关闭底层连接,确保浏览器能够及时感知断线并触发重连。
|
||||
|
||||
Args:
|
||||
connection: 目标连接上下文。
|
||||
"""
|
||||
websocket = connection.websocket
|
||||
if (
|
||||
websocket.client_state == WebSocketState.DISCONNECTED
|
||||
or websocket.application_state == WebSocketState.DISCONNECTED
|
||||
):
|
||||
return
|
||||
|
||||
await websocket.close()
|
||||
|
||||
async def _sender_loop(self, connection: WebSocketConnection) -> None:
|
||||
"""串行发送指定连接的出站消息。
|
||||
|
||||
@@ -85,6 +105,11 @@ class UnifiedWebSocketManager:
|
||||
if connection is None:
|
||||
return
|
||||
|
||||
try:
|
||||
await self._close_websocket(connection)
|
||||
except Exception as exc:
|
||||
logger.debug("关闭统一 WebSocket 底层连接时出现异常: connection=%s, error=%s", connection_id, exc)
|
||||
|
||||
await connection.send_queue.put(None)
|
||||
if connection.sender_task is not None:
|
||||
try:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""统一 WebSocket 路由。"""
|
||||
|
||||
from typing import Any, Dict, Optional, Set, cast
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
import uuid
|
||||
@@ -140,6 +141,26 @@ async def _handle_plugin_progress_subscribe(connection_id: str, request_id: Opti
|
||||
)
|
||||
|
||||
|
||||
async def _handle_maisaka_monitor_subscribe(connection_id: str, request_id: Optional[str]) -> None:
|
||||
"""处理 MaiSaka 监控域订阅请求。
|
||||
|
||||
Args:
|
||||
connection_id: 连接 ID。
|
||||
request_id: 请求 ID。
|
||||
"""
|
||||
logger.info(
|
||||
f"MaiSaka 监控订阅请求: connection_id={connection_id} "
|
||||
f"manager_id={id(websocket_manager)}"
|
||||
)
|
||||
websocket_manager.subscribe(connection_id, domain="maisaka_monitor", topic="main")
|
||||
await websocket_manager.send_response(
|
||||
connection_id,
|
||||
request_id=request_id,
|
||||
ok=True,
|
||||
data={"domain": "maisaka_monitor", "topic": "main"},
|
||||
)
|
||||
|
||||
|
||||
async def _handle_subscribe(connection_id: str, message: Dict[str, Any]) -> None:
|
||||
"""处理主题订阅请求。
|
||||
|
||||
@@ -160,6 +181,10 @@ async def _handle_subscribe(connection_id: str, message: Dict[str, Any]) -> None
|
||||
await _handle_plugin_progress_subscribe(connection_id, request_id)
|
||||
return
|
||||
|
||||
if domain == "maisaka_monitor" and topic == "main":
|
||||
await _handle_maisaka_monitor_subscribe(connection_id, request_id)
|
||||
return
|
||||
|
||||
await websocket_manager.send_response(
|
||||
connection_id,
|
||||
request_id=request_id,
|
||||
@@ -541,8 +566,16 @@ async def websocket_endpoint(websocket: WebSocket, token: Optional[str] = Query(
|
||||
await handle_client_message(connection_id, cast(Dict[str, Any], raw_message))
|
||||
except WebSocketDisconnect:
|
||||
logger.info("统一 WebSocket 客户端已断开: connection=%s", connection_id)
|
||||
except asyncio.CancelledError:
|
||||
logger.warning("统一 WebSocket 连接处理被取消: connection=%s", connection_id)
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.error(f"统一 WebSocket 处理失败: {exc}")
|
||||
logger.error("统一 WebSocket 处理失败: connection=%s, error=%s", connection_id, exc, exc_info=True)
|
||||
finally:
|
||||
chat_manager.disconnect_connection(connection_id)
|
||||
await websocket_manager.disconnect(connection_id)
|
||||
logger.info(
|
||||
"统一 WebSocket 连接清理完成: connection=%s, 剩余连接=%s",
|
||||
connection_id,
|
||||
len(websocket_manager.connections),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user