mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 18:38:11 +00:00
fix(webui): 聊天记录不再"每次都发完整记录",并修掉视口跳顶
两个都是你指出的症状,都定位到了具体代码路径。
① 「每次都发完整聊天记录」
a) API 缺省值错了:/api/v1/chat/history 的 limit 缺省是 0 = **不限制**,
于是任何不带 limit 的调用每次都拿到整段记录。实测(126 条):
不带 limit 635,297 字节;现在默认只回一页 180,668 字节。
显式 limit=0 仍可整取(逃生口)。WebUI/GUI 本来都带 limit,不受影响。
b) 前端 30s 轮询(以及每次 SSE 报错)都直接拉一页 40 条:
浏览器实测单次 180,813 字节。现在先做"尾巴探测"(limit=1,362 字节),
尾巴一致就直接跳过;不一致才拉整页。
② 「聊天记录会跳到顶部」——两条会导致视口丢失的路径都堵上
a) syncChatFromHistory 在"找不到重合点"时直接 `state.messages = serverMsgs`:
服务端只回一页,而本地可能已经向上翻了好几页;一覆盖,容器立刻变矮,
视口被夹回顶部,用户翻过的旧消息也凭空消失。现在只在服务端页**不短于**本地时
才整体替换。
b) renderChat 全量重建 innerHTML 后,仅在粘底时滚到底;非粘底(用户正在向上读)
时位置没人管。改为重建前记住 scrollTop、非粘底时原样还回去。
浏览器实测(CDP 驱动真实页面,126 条历史):
* 15s/30s 定时器跑满 40s:聊天区滚动位置 **0 px 变化**,未跳顶;
* 期间 chat/history 请求:limit=1 × 2(各 362 字节)+ 首屏 limit=40 一次;
* 控制台无报错;新增消息后轮询仍能正确并进来(尾巴探测→拉整页→合并)。
测试:新增 TestChatHistoryDefaultIsPaged(默认一页 / has_more / limit=0 整取 / 显式分页)。
顺带修测试串味:迁移用例往 os.TempDir() 写共享历史文件,会让其它用例的
NewHandler 加载到脏历史(表现为条数多 1);现在各用例用自己的临时文件。
This commit is contained in:
@ -1209,9 +1209,14 @@
|
||||
: "") +
|
||||
"</div></div></div>";
|
||||
}
|
||||
// 重建前记住阅读位置:非粘底(用户正向上翻)时,innerHTML 重建后必须把位置还回去,
|
||||
// 否则视口会被重置——这就是"聊天记录跳到顶部"的直接来源。
|
||||
var prevTop = msgsEl.scrollTop;
|
||||
msgsEl.innerHTML = html;
|
||||
if (state.chatStick !== false) {
|
||||
msgsEl.scrollTop = msgsEl.scrollHeight;
|
||||
} else {
|
||||
msgsEl.scrollTop = prevTop;
|
||||
}
|
||||
updateChatBadge();
|
||||
}
|
||||
@ -2161,10 +2166,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
// syncChatFromHistory 增量同步:对比服务端历史,仅追加新消息 DOM 节点,
|
||||
// 不重建已有消息 → 无闪烁。用于 SSE 断连恢复期间的轮询兜底。
|
||||
// syncChatFromHistory 增量同步:先做一次极轻的"尾巴探测",只有尾巴变了才拉整页。
|
||||
//
|
||||
// 原先每 30s(以及每次 SSE 报错)都直接拉一页 40 条:本地实测 180KB、
|
||||
// 生产消息更大时可达 ~1MB —— 这是"每次都在发完整聊天记录"的观感来源。
|
||||
// 探测只需 1 条(约几 KB),尾巴一致就直接跳过。
|
||||
var _syncingChat = false;
|
||||
function syncChatFromHistory() {
|
||||
return api("/chat/history?limit=" + CHAT_PAGE_SIZE).then(function (data) {
|
||||
if (_syncingChat) return Promise.resolve();
|
||||
_syncingChat = true;
|
||||
return api("/chat/history?limit=1")
|
||||
.then(function (tail) {
|
||||
var t = tail && tail.messages && tail.messages[0];
|
||||
var local = state.messages.length
|
||||
? state.messages[state.messages.length - 1]
|
||||
: null;
|
||||
var same =
|
||||
t &&
|
||||
local &&
|
||||
t.role === (local.role || local.Role) &&
|
||||
(t.content || "") === (local.content || local.Content || "");
|
||||
if (same) return null; // 尾巴一致:无需拉整页
|
||||
return api("/chat/history?limit=" + CHAT_PAGE_SIZE);
|
||||
})
|
||||
.then(function (data) {
|
||||
if (!data) return;
|
||||
return mergeChatFromHistory(data);
|
||||
})
|
||||
.catch(function () {})
|
||||
.then(function () {
|
||||
_syncingChat = false;
|
||||
});
|
||||
}
|
||||
|
||||
// mergeChatFromHistory 把服务端的一页历史并进本地:只追加新消息,不重建已有节点。
|
||||
// 关键约束:**绝不**用更短的服务端页替换更长的本地列表(那会让用户翻上来的旧页
|
||||
// 凭空消失、视口跳回顶部)。
|
||||
function mergeChatFromHistory(data) {
|
||||
{
|
||||
if (!data || !data.messages || data.messages.length === 0) return;
|
||||
var serverMsgs = data.messages;
|
||||
var localMsgs = state.messages;
|
||||
@ -2215,9 +2254,14 @@
|
||||
newMsgs = serverMsgs.slice(serverMsgs.length - overlap);
|
||||
if (newMsgs.length === 0) return; // 无新增(内容改写走上面的分支)
|
||||
} else {
|
||||
// 找不到重含点(本地领先太多,超出服务端页)→ 无法精确差异,安全退化为全量刷新
|
||||
state.messages = serverMsgs;
|
||||
rerenderChat(true);
|
||||
// 找不到重合点:**不能**直接拿服务端页覆盖本地。
|
||||
// 服务端只回一页,本地翻上来的旧页更长;覆盖会同时造成两个后果:
|
||||
// 用户翻过的旧消息凭空消失、容器变矮后视口被夹回顶部。
|
||||
// 只有在服务端页不短于本地时才整体替换(那种情况下不丢内容)。
|
||||
if (serverMsgs.length >= localMsgs.length) {
|
||||
state.messages = serverMsgs;
|
||||
rerenderChat(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var msgsEl = document.getElementById("chat-msgs");
|
||||
@ -2246,7 +2290,7 @@
|
||||
Array.prototype.push.apply(state.messages, newMsgs);
|
||||
// 同步聊天占位符(如果有新消息但最后一条非 assistant → 显示流式占位)
|
||||
syncStreamingPlaceholder();
|
||||
}).catch(function () {});
|
||||
}
|
||||
}
|
||||
// syncStreamingPlaceholder:同步聊天占位符的可见性
|
||||
function syncStreamingPlaceholder() {
|
||||
|
||||
Reference in New Issue
Block a user