fix: send SSE heartbeat to prevent WebChat disconnect during compression (#7003)

* fix: send SSE heartbeat to prevent WebChat disconnect during compression

When context compression triggers with slow reasoning models (e.g.
deepseek-reasoner), the backend can go 30+ seconds without pushing any
SSE data. The client-side EventSource / browser then assumes the
connection is dead and disconnects, causing the WebUI to hang
indefinitely since it never receives the eventual response.

Fix: yield an SSE comment (`: heartbeat`) on every empty poll cycle.
Comment lines are defined in the SSE spec as keep-alive signals --
the EventSource API ignores them but the HTTP connection stays open.

Fixes #6938

* chore(dashboard): extract SSE heartbeat to constant

---------

Co-authored-by: Yufeng He <40085740+universeplayer@users.noreply.github.com>
Co-authored-by: RC-CHN <1051989940@qq.com>
This commit is contained in:
Yufeng He
2026-03-30 15:52:10 +08:00
committed by GitHub
parent bfd129402d
commit 2ffda752ad

View File

@@ -26,6 +26,9 @@ from astrbot.core.utils.datetime_utils import to_utc_isoformat
from .route import Response, Route, RouteContext
# SSE heartbeat message to keep the connection alive during long-running operations
SSE_HEARTBEAT = ": heartbeat\n\n"
@asynccontextmanager
async def track_conversation(convs: dict, conv_id: str):
@@ -40,6 +43,9 @@ async def _poll_webchat_stream_result(back_queue, username: str):
try:
result = await asyncio.wait_for(back_queue.get(), timeout=1)
except asyncio.TimeoutError:
# Return a sentinel so the caller can send an SSE heartbeat to
# keep the connection alive during long-running operations (e.g.
# context compression with reasoning models). See #6938.
return None, False
except asyncio.CancelledError:
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
@@ -364,6 +370,11 @@ class ChatRoute(Route):
client_disconnected = True
break
if not result:
# Send an SSE comment as keep-alive so the client
# doesn't time out during slow backend ops like
# context compression with reasoning models (#6938).
if not client_disconnected:
yield SSE_HEARTBEAT
continue
if (