From a5bb4cf4ced08866f7f85a886e3cd1c96d0cda71 Mon Sep 17 00:00:00 2001
From: JianFeeeee <109188060+JianFeeeee@users.noreply.github.com>
Date: Thu, 27 Aug 2026 11:10:53 +0800
Subject: [PATCH] =?UTF-8?q?fix(webui):=20SSE=E6=B6=88=E6=81=AF=E5=90=8C?=
=?UTF-8?q?=E6=AD=A5=E4=B8=8D=E5=8F=8A=E6=97=B6=20+=20=E5=90=8E=E7=AB=AF?=
=?UTF-8?q?=E7=BC=93=E5=86=B2=E5=8A=A0=E5=9B=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
handler.go:
- writeCh 512→2048,新增 sendSSE() 函数(100ms短超时重试替代立即丢弃)
- After(id) 为空时发送 sync_required 事件通知前端补拉历史
- 批量 flush 阈值 64→128
dashboard.html:
- 新增 syncChatFromHistory():增量同步,仅追加新消息DOM节点,不重建已有消息→无闪烁
- 监听 sync_required 事件触发增量补拉
- SSE onerror 立即 close 阻止双连接竞态,2s后手动重连(原5s)
- init 顺序:先 loadChatHistory 再 connectSSE(避免事件与历史加载竞态)
- 30s轮询兜底(补偿SSE断连窗口期丢失的跨渠道消息)
---
internal/plugins/webui/dashboard.html | 103 +++++++++++++++++++++++++-
internal/plugins/webui/handler.go | 52 ++++++++-----
2 files changed, 135 insertions(+), 20 deletions(-)
diff --git a/internal/plugins/webui/dashboard.html b/internal/plugins/webui/dashboard.html
index c2db98d5..a25ec7f1 100644
--- a/internal/plugins/webui/dashboard.html
+++ b/internal/plugins/webui/dashboard.html
@@ -4206,6 +4206,87 @@
} catch (e) {}
}
+ // syncChatFromHistory 增量同步:对比服务端历史,仅追加新消息 DOM 节点,
+ // 不重建已有消息 → 无闪烁。用于 SSE 断连恢复期间的轮询兜底。
+ function syncChatFromHistory() {
+ return api("/chat/history").then(function (data) {
+ if (!data || !data.messages || data.messages.length === 0) return;
+ var serverMsgs = data.messages;
+ var localMsgs = state.messages;
+ // 空历史 → 全量加载(首次同步)
+ if (localMsgs.length === 0) {
+ state.messages = serverMsgs;
+ rerenderChat(true);
+ return;
+ }
+ // 无新增消息 → 检查最后一条是否被改写
+ if (serverMsgs.length <= localMsgs.length) {
+ var lastLocal = localMsgs[localMsgs.length - 1];
+ var lastServer = serverMsgs[serverMsgs.length - 1];
+ var localContent = lastLocal.content || lastLocal.Content || "";
+ var serverContent = lastServer.content || lastServer.Content || "";
+ if (lastServer.role === "assistant" && localContent !== serverContent && serverContent) {
+ lastLocal.content = serverContent;
+ if (lastServer.ReasoningContent) lastLocal.reasoning_content = lastServer.ReasoningContent;
+ // 仅更新最后一条消息 DOM,不全量重建
+ var msgsEl = document.getElementById("chat-msgs");
+ if (msgsEl && msgsEl.lastElementChild) {
+ var el = msgsEl.lastElementChild;
+ var textEl = el.querySelector(".msg-bubble .text");
+ if (textEl) textEl.innerHTML = renderMd(serverContent);
+ }
+ }
+ return;
+ }
+ // 有新增消息:追加到 state.messages + DOM(仅追加节点,不触碰已有)
+ var newMsgs = serverMsgs.slice(localMsgs.length);
+ var msgsEl = document.getElementById("chat-msgs");
+ if (msgsEl) {
+ var aiAvatar = '
';
+ var userAvatar = '';
+ newMsgs.forEach(function (m) {
+ var role = m.role || m.Role || "user";
+ var c = m.content || m.Content || "";
+ if (role === "assistant") c = renderMd(c); else c = escHtml(c);
+ var isChan = !!(m.source && m.source !== "webui");
+ var bubble = c ? '
';
+ var pillHtml = "";
+ (state.pendingTools || []).forEach(function (nm) {
+ pillHtml += '' + escHtml(nm) + '';
+ });
+ msgsEl.insertAdjacentHTML("beforeend", '