初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
"""全局一炁事件 SSE 端点(非 run-scoped)。
供前端 useGlobalEventStream hook 订阅全局广播事件:
- ``proactive.session`` —— 叩心枢新建的主动会话
- ``proactive.message`` —— 叩心枢追加到已有会话的消息
"""
from __future__ import annotations
import asyncio
from collections.abc import AsyncGenerator
from typing import Any
from fastapi import APIRouter, Request
from sse_starlette.sse import EventSourceResponse
from app.core.config import settings
from app.services.event_store import DbEventStore
router = APIRouter(prefix="/events", tags=["events"])
def _get_event_store(request: Request) -> DbEventStore:
return request.app.state.event_store
@router.get("")
async def global_event_stream(
request: Request,
) -> EventSourceResponse:
"""全局事件 SSE 流:通过 EventStore.subscribe_by_pattern 监听所有
proactive.* 事件,推送给前端。"""
if not settings.enable_sse:
from app.core.errors import AppError
raise AppError("invalid_request", "SSE 当前未启用。")
event_store = _get_event_store(request)
return EventSourceResponse(_event_generator(event_store))
async def _event_generator(
event_store: DbEventStore,
) -> AsyncGenerator[dict[str, Any], None]:
queue = event_store.subscribe_by_pattern("proactive.*")
try:
while True:
try:
event = await asyncio.wait_for(queue.get(), timeout=settings.sse_heartbeat_seconds)
yield {
"id": event.id,
"event": event.type,
"data": event.model_dump_json(),
}
except TimeoutError:
yield {
"event": "heartbeat",
"data": '{"type":"heartbeat"}',
}
except asyncio.CancelledError:
pass
finally:
event_store.unsubscribe_by_pattern("proactive.*", queue)