mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-10-06 07:28:11 +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";
|
badge.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
function rerenderChat() {
|
// 流式渲染防抖:SSE 高频推送时合并为一次重建,避免每个 chunk 全量 innerHTML。
|
||||||
renderChat();
|
// 聊天内容变化 → 短超时后只重渲聊天(跳过星图/终端/命令历史等无关重建)。
|
||||||
renderChatStarmap();
|
var _rerenderTimer = null;
|
||||||
renderTerminals();
|
function rerenderChat(full) {
|
||||||
renderCmdHistory();
|
if (_rerenderTimer) clearTimeout(_rerenderTimer);
|
||||||
|
// 流式增量(非全量)走短防抖,仅重渲聊天;工具调用/历史变化等需全量重渲
|
||||||
|
_rerenderTimer = setTimeout(function () {
|
||||||
|
_rerenderTimer = null;
|
||||||
|
renderChat();
|
||||||
|
if (full) {
|
||||||
|
renderChatStarmap();
|
||||||
|
renderTerminals();
|
||||||
|
renderCmdHistory();
|
||||||
|
}
|
||||||
|
}, 90);
|
||||||
}
|
}
|
||||||
function toggleToolCall(el) {
|
function toggleToolCall(el) {
|
||||||
var d = el.querySelector(".tc-detail");
|
var d = el.querySelector(".tc-detail");
|
||||||
@ -3576,36 +3586,40 @@ background:
|
|||||||
state.chatFinalIdx = -1;
|
state.chatFinalIdx = -1;
|
||||||
state.messages.push({ role: "user", content: text });
|
state.messages.push({ role: "user", content: text });
|
||||||
inp.value = "";
|
inp.value = "";
|
||||||
rerenderChat();
|
rerenderChat(true);
|
||||||
state.chatLoading = true;
|
state.chatLoading = true;
|
||||||
state.chatStage = __("等待AI回复...", "Waiting for AI...");
|
state.chatStage = __("等待AI回复...", "Waiting for AI...");
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
btn.textContent = "";
|
btn.textContent = "";
|
||||||
rerenderChat();
|
rerenderChat(true);
|
||||||
|
// 触发式 POST:短超时仅确认受理;回复靠 SSE 流式渲染(对齐 GUI 行为)。
|
||||||
|
// 长 LLM 工具链不再因同步等全量响应而 60s 超时报错。
|
||||||
try {
|
try {
|
||||||
var r = await api("/chat", {
|
var ctrl = new AbortController();
|
||||||
method: "POST",
|
var acked = false; // 受理确认是否在超时内返回
|
||||||
body: JSON.stringify({ message: text }),
|
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 = "";
|
state.chatStage = "";
|
||||||
var last = state.messages[state.messages.length - 1];
|
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) {
|
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.content = r.response || __("(无响应)", "(no response)");
|
||||||
last._grow = true;
|
last._grow = true;
|
||||||
if (!last.reasoning_content) {
|
if (!last.reasoning_content) {
|
||||||
@ -3627,21 +3641,24 @@ background:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
state.chatFinalIdx = state.messages.length - 1;
|
state.chatFinalIdx = state.messages.length - 1;
|
||||||
rerenderChat();
|
rerenderChat(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.messages.push({
|
// 真实错误(非受理超时):展示错误信息
|
||||||
role: "assistant",
|
if (!String(e.message || "").includes("aborted")) {
|
||||||
content: __("错误: ", "Error: ") + e.message,
|
state.messages.push({
|
||||||
_final: true,
|
role: "assistant",
|
||||||
});
|
content: __("错误: ", "Error: ") + e.message,
|
||||||
rerenderChat();
|
_final: true,
|
||||||
toast(__("请求失败: ", "Request failed: ") + e.message, true);
|
});
|
||||||
|
rerenderChat(true);
|
||||||
|
toast(__("请求失败: ", "Request failed: ") + e.message, true);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
state.chatLoading = false;
|
state.chatLoading = false;
|
||||||
state.chatStage = "";
|
state.chatStage = "";
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = __("发送", "Send");
|
btn.textContent = __("发送", "Send");
|
||||||
rerenderChat();
|
rerenderChat(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4154,15 +4171,7 @@ background:
|
|||||||
console.error("[SSE] tool_call error", ex);
|
console.error("[SSE] tool_call error", ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
es.addEventListener("tool_result", function (e) {
|
// 注:后端不发布 tool_result 类型事件(工具结果随 EventToolCall 一次发出),无此监听器。
|
||||||
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) {}
|
|
||||||
});
|
|
||||||
es.addEventListener("stage", function (e) {
|
es.addEventListener("stage", function (e) {
|
||||||
try {
|
try {
|
||||||
var ev = JSON.parse(e.data);
|
var ev = JSON.parse(e.data);
|
||||||
|
|||||||
Reference in New Issue
Block a user