fix: AI 控制台切页/刷新不丢——①EventSource onerror 不再误 endRun(断网/后台节流/瞬时抖动都会触发 onerror 而后端任务仍在跑;EventSource 自动重连 + 服务端队列保留积压事件,重连后补发,连接彻底关闭才提示可刷新恢复);②页面刷新恢复:GET /api/agent/run 增加 run_id/answer/error/history,前端 restoreAgentView 渲染历史轮次、running 时自动重订阅事件流、done/error 展示结果——切走再回来任务与对话都在

This commit is contained in:
2026-09-06 09:41:02 +08:00
parent 0f75db289d
commit 9b8ecd63f7
2 changed files with 65 additions and 5 deletions
+54 -2
View File
@@ -19,6 +19,7 @@ function initAgentChat(){
loadLiveDevices();
loadAgentTargetDevices();
startRunPoll();
restoreAgentView();
// 输入框快捷键
const inp = document.getElementById('agent-input');
inp.addEventListener('keydown', ev=>{
@@ -29,6 +30,43 @@ function initAgentChat(){
});
}
// ================== 页面刷新/重进恢复 ==================
// 刷新后聊天区是空的,但后端任务可能还在跑、历史还在——从这里恢复:
// 1) 渲染会话历史(user/assistant 文本轮)
// 2) 任务运行中 → 重新订阅事件流(服务端队列保留积压,重连后补发)
function restoreAgentView(){
const chat = document.getElementById('agent-chat');
if(!chat) return;
const hasContent = chat.children.length > 0 && !chat.querySelector('.agent-empty');
if(hasContent) return; // 页面未刷新(切 Tab 回来 DOM 还在),无需恢复
apiGet('/api/agent/run').then(r=>{
if(!r||!r.ok) return;
const hist = r.history || [];
hist.forEach(h=>{
const txt = (h.content || '').trim();
if(!txt) return;
if(h.role === 'user'){ addUserMsg(txt); }
else{
const div = newAssistantMsg();
div.querySelector('.agent-text').textContent = txt;
}
});
if(r.state === 'running' && r.run_id){
const div = newAssistantMsg();
div.querySelector('.agent-text').textContent = '↻ 已恢复连接,任务仍在后台运行(此前的内容可在上方查看)…';
setRunning(true);
listenStream(r.run_id);
}else if(r.state === 'error' && r.error){
const div = newAssistantMsg();
div.querySelector('.agent-text').textContent = '⚠ ' + r.error;
}else if(!hist.length && r.state === 'idle'){
clearEmpty();
document.getElementById('agent-chat').innerHTML =
'<div class="agent-empty">👋 给 AI 下达指令…(先选 🎯 目标设备)</div>';
}
});
}
// ================== 配置 ==================
function loadAgentConfig(){
apiGet('/api/agent/config').then(r=>{
@@ -284,8 +322,22 @@ function listenStream(runId){
});
es.onerror = ()=>{
// 事件流正常结束(done 后服务器关流)会触发一次 error——done 已处理则忽略
if(_agentBusy) endRun();
// 重要:不能在这里 endRun()——断网/后台标签页节流/服务端瞬时抖动都会触发
// onerror,但后端任务仍在跑。EventSource 会自动重连,服务端事件队列保留
// 积压(delta/step/done),重连成功后全部补发,界面无缝续上。
// 只有连接彻底关闭(非自动重连态)且任务还在跑时才提示。
if(_agentBusy && es.readyState === EventSource.CLOSED){
const hint = document.getElementById('agent-target-hint');
if(hint) hint.textContent = '⚠ 实时连接已中断,任务仍在后台运行——可刷新页面恢复查看';
// 连接彻底失败时周期探测,恢复后重订阅
setTimeout(()=>{
if(!_agentStream && _agentBusy){
apiGet('/api/agent/run').then(r=>{
if(r&&r.ok&&r.state==='running'&&r.run_id) listenStream(r.run_id);
});
}
}, 5000);
}
};
}