mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-24 19:08:10 +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:
@ -1002,6 +1002,12 @@ func TestSettingsNoCrossPluginLeak(t *testing.T) {
|
||||
|
||||
webuiCfg := cfgReg.PluginConfig("webui")
|
||||
webuiCfg.RegisterDef(internalConfig.ConfigDef{Key: "addr", Default: ":8080"})
|
||||
// 隔离聊天记录文件:不设的话会落到 os.TempDir()/webui_chat_history.json,
|
||||
// 与其它用例串味(迁移用例会把自己的数据写进去)。
|
||||
webuiCfg.RegisterDef(internalConfig.ConfigDef{Key: "history_file", Default: ""})
|
||||
if err := webuiCfg.Set("history_file", filepath.Join(t.TempDir(), "chat.json")); err != nil {
|
||||
t.Fatalf("set history_file: %v", err)
|
||||
}
|
||||
webuiCfg.Set("addr", ":8080")
|
||||
webuiCfg.Set("chathistory", `[{"role":"assistant","content":"secret blob"}]`)
|
||||
|
||||
@ -1332,3 +1338,52 @@ func TestDashboardAssetsSplit(t *testing.T) {
|
||||
t.Fatal("组装后的页面缺脚本")
|
||||
}
|
||||
}
|
||||
|
||||
// TestChatHistoryDefaultIsPaged 钉住 /chat/history 的默认页大小。
|
||||
//
|
||||
// 原先缺省 limit=0 表示"不限制",于是任何不带 limit 的调用每次都拿到完整聊天记录
|
||||
// (生产实测 ~5MB;本地 126 条 635KB)——这正是"每次都在发完整聊天记录"的来源。
|
||||
// 现在默认只回一页;想整取必须显式 limit=0。
|
||||
func TestChatHistoryDefaultIsPaged(t *testing.T) {
|
||||
h, _ := newTestHandler(t)
|
||||
// 清空可能从临时目录共享文件加载进来的历史,让用例只依赖自己播的数据
|
||||
h.chatMu.Lock()
|
||||
h.chatHistory = nil
|
||||
for i := 0; i < maxChatHistory; i++ {
|
||||
h.chatHistory = append(h.chatHistory, ChatMsg{Role: "user", Content: "消息", Time: "t"})
|
||||
}
|
||||
h.chatMu.Unlock()
|
||||
|
||||
get := func(q string) map[string]interface{} {
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/chat/history"+q, nil)
|
||||
w := httptest.NewRecorder()
|
||||
h.handleChatHistory(w, req)
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("GET %q: %d", q, w.Code)
|
||||
}
|
||||
var out map[string]interface{}
|
||||
if err := json.Unmarshal(w.Body.Bytes(), &out); err != nil {
|
||||
t.Fatalf("unmarshal: %v", err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
// 不带 limit → 只回一页,且明确告知还有更早的
|
||||
def := get("")
|
||||
if n := len(def["messages"].([]interface{})); n != defaultChatHistoryLimit {
|
||||
t.Fatalf("默认应回 %d 条,实际 %d", defaultChatHistoryLimit, n)
|
||||
}
|
||||
if def["has_more"] != true {
|
||||
t.Fatal("还有更早内容时 has_more 应为 true")
|
||||
}
|
||||
// 显式 limit=0 → 整取(逃生口):返回条数应等于 total
|
||||
all := get("?limit=0")
|
||||
total := int(all["total"].(float64))
|
||||
if n := len(all["messages"].([]interface{})); n != total {
|
||||
t.Fatalf("limit=0 应整取 total=%d 条,实际 %d", total, n)
|
||||
}
|
||||
// 显式分页仍然照旧
|
||||
page := get("?limit=5")
|
||||
if n := len(page["messages"].([]interface{})); n != 5 {
|
||||
t.Fatalf("limit=5 应回 5 条,实际 %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user