mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-21 17:38:10 +00:00
fix(webui): 聊天渲染优化 + 触发式 POST
- 删除死的 tool_result SSE 监听器(后端不发布此事件类型) - 流式渲染防抖 90ms:SSE 高频 chunk 合并为一次重建,流式期间跳过 星图/终端/命令历史等无关渲染(renderChat 签名短路 + 防抖双重优化) - sendChat 改触发式 POST:15s 短超时仅确认受理,超时后不报错, 回复靠 SSE 流式渲染(对齐 GUI 行为,解决长 LLM 工具链 60s 超时报错) - sendChat 用户操作走 rerenderChat(true) 全量重渲;SSE 流式走 rerenderChat() 防抖仅聊天
This commit is contained in:
@ -3164,11 +3164,21 @@ background:
|
||||
badge.style.display = "none";
|
||||
}
|
||||
|
||||
function rerenderChat() {
|
||||
renderChat();
|
||||
renderChatStarmap();
|
||||
renderTerminals();
|
||||
renderCmdHistory();
|
||||
// 流式渲染防抖:SSE 高频推送时合并为一次重建,避免每个 chunk 全量 innerHTML。
|
||||
// 聊天内容变化 → 短超时后只重渲聊天(跳过星图/终端/命令历史等无关重建)。
|
||||
var _rerenderTimer = null;
|
||||
function rerenderChat(full) {
|
||||
if (_rerenderTimer) clearTimeout(_rerenderTimer);
|
||||
// 流式增量(非全量)走短防抖,仅重渲聊天;工具调用/历史变化等需全量重渲
|
||||
_rerenderTimer = setTimeout(function () {
|
||||
_rerenderTimer = null;
|
||||
renderChat();
|
||||
if (full) {
|
||||
renderChatStarmap();
|
||||
renderTerminals();
|
||||
renderCmdHistory();
|
||||
}
|
||||
}, 90);
|
||||
}
|
||||
function toggleToolCall(el) {
|
||||
var d = el.querySelector(".tc-detail");
|
||||
@ -3576,36 +3586,40 @@ background:
|
||||
state.chatFinalIdx = -1;
|
||||
state.messages.push({ role: "user", content: text });
|
||||
inp.value = "";
|
||||
rerenderChat();
|
||||
rerenderChat(true);
|
||||
state.chatLoading = true;
|
||||
state.chatStage = __("等待AI回复...", "Waiting for AI...");
|
||||
btn.disabled = true;
|
||||
btn.textContent = "";
|
||||
rerenderChat();
|
||||
rerenderChat(true);
|
||||
// 触发式 POST:短超时仅确认受理;回复靠 SSE 流式渲染(对齐 GUI 行为)。
|
||||
// 长 LLM 工具链不再因同步等全量响应而 60s 超时报错。
|
||||
try {
|
||||
var r = await api("/chat", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message: text }),
|
||||
});
|
||||
var ctrl = new AbortController();
|
||||
var acked = false; // 受理确认是否在超时内返回
|
||||
var ackTimer = setTimeout(function () {
|
||||
if (!acked) ctrl.abort();
|
||||
}, 15000);
|
||||
var r = null;
|
||||
try {
|
||||
r = await api("/chat", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ message: text }),
|
||||
signal: ctrl.signal,
|
||||
});
|
||||
acked = true;
|
||||
} catch (ackErr) {
|
||||
if (acked) throw ackErr;
|
||||
// 受理确认超时:agent 已在后台处理,保持 loading 等待 SSE 流式回包
|
||||
console.log("[sendChat] ack timeout (15s), waiting for SSE stream");
|
||||
state.chatStage = __("AI 处理中...(长任务,请稍候)", "AI processing... (long task)");
|
||||
return; // 保持 chatLoading=true;SSE 完成事件负责收尾
|
||||
} finally {
|
||||
clearTimeout(ackTimer);
|
||||
}
|
||||
state.chatStage = "";
|
||||
var last = state.messages[state.messages.length - 1];
|
||||
console.log(
|
||||
"[sendChat] POST returned, last msg:",
|
||||
last
|
||||
? {
|
||||
role: last.role,
|
||||
_streaming: last._streaming,
|
||||
_final: last._final,
|
||||
tool_calls: last.tool_calls?.length,
|
||||
content_len: last.content?.length,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
if (last && last.role === "assistant" && last._streaming) {
|
||||
console.log(
|
||||
"[sendChat] updating existing streaming msg, tool_calls before:",
|
||||
last.tool_calls?.length,
|
||||
);
|
||||
last.content = r.response || __("(无响应)", "(no response)");
|
||||
last._grow = true;
|
||||
if (!last.reasoning_content) {
|
||||
@ -3627,21 +3641,24 @@ background:
|
||||
});
|
||||
}
|
||||
state.chatFinalIdx = state.messages.length - 1;
|
||||
rerenderChat();
|
||||
rerenderChat(true);
|
||||
} catch (e) {
|
||||
state.messages.push({
|
||||
role: "assistant",
|
||||
content: __("错误: ", "Error: ") + e.message,
|
||||
_final: true,
|
||||
});
|
||||
rerenderChat();
|
||||
toast(__("请求失败: ", "Request failed: ") + e.message, true);
|
||||
// 真实错误(非受理超时):展示错误信息
|
||||
if (!String(e.message || "").includes("aborted")) {
|
||||
state.messages.push({
|
||||
role: "assistant",
|
||||
content: __("错误: ", "Error: ") + e.message,
|
||||
_final: true,
|
||||
});
|
||||
rerenderChat(true);
|
||||
toast(__("请求失败: ", "Request failed: ") + e.message, true);
|
||||
}
|
||||
} finally {
|
||||
state.chatLoading = false;
|
||||
state.chatStage = "";
|
||||
btn.disabled = false;
|
||||
btn.textContent = __("发送", "Send");
|
||||
rerenderChat();
|
||||
rerenderChat(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4154,15 +4171,7 @@ background:
|
||||
console.error("[SSE] tool_call error", ex);
|
||||
}
|
||||
});
|
||||
es.addEventListener("tool_result", function (e) {
|
||||
try {
|
||||
var ev = JSON.parse(e.data);
|
||||
var p = ev.payload || {};
|
||||
state.chatStage = __("工具结果已返回", "Tool result received");
|
||||
var badge = document.getElementById("chat-stage");
|
||||
if (badge) badge.textContent = state.chatStage;
|
||||
} catch (ex) {}
|
||||
});
|
||||
// 注:后端不发布 tool_result 类型事件(工具结果随 EventToolCall 一次发出),无此监听器。
|
||||
es.addEventListener("stage", function (e) {
|
||||
try {
|
||||
var ev = JSON.parse(e.data);
|
||||
|
||||
Reference in New Issue
Block a user