diff --git a/web/static/css/style.css b/web/static/css/style.css index 519563b9..f79dd1d7 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -2871,6 +2871,24 @@ header { background: rgba(0, 102, 255, 0.05); } +/* Eino 多代理:主编排器 vs 子代理时间线区分 */ +.timeline-eino-role-orchestrator { + border-left-color: #5c6bc0 !important; + background: rgba(92, 107, 192, 0.09) !important; +} +.timeline-eino-role-sub { + border-left-color: #00897b !important; + background: rgba(0, 137, 123, 0.08) !important; +} +.timeline-item-iteration.timeline-eino-scope-main { + border-left-color: #3949ab !important; + background: rgba(57, 73, 171, 0.1) !important; +} +.timeline-item-iteration.timeline-eino-scope-sub { + border-left-color: #00695c !important; + background: rgba(0, 105, 92, 0.09) !important; +} + .timeline-item-thinking { border-left-color: #9c27b0; background: rgba(156, 39, 176, 0.05); diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 48f455f0..1d76291c 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -147,6 +147,8 @@ "addNewGroup": "+ New group", "callNumber": "Call #{{n}}", "iterationRound": "Iteration {{n}}", + "einoOrchestratorRound": "Orchestrator · round {{n}}", + "einoSubAgentStep": "Sub-agent {{agent}} · step {{n}}", "aiThinking": "AI thinking", "planning": "Planning", "toolCallsDetected": "Detected {{count}} tool call(s)", @@ -156,6 +158,7 @@ "knowledgeRetrieval": "Knowledge retrieval", "knowledgeRetrievalTag": "Knowledge retrieval", "error": "Error", + "streamNetworkErrorHint": "Connection lost ({{detail}}). A long task may still be running on the server; check running tasks at the top or refresh this conversation later.", "taskCancelled": "Task cancelled", "unknownTool": "Unknown tool", "einoAgentReplyTitle": "Sub-agent reply", diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index 434b1fa0..c1625e5c 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -147,6 +147,8 @@ "addNewGroup": "+ 新增分组", "callNumber": "调用 #{{n}}", "iterationRound": "第 {{n}} 轮迭代", + "einoOrchestratorRound": "主代理 · 第 {{n}} 轮", + "einoSubAgentStep": "子代理 {{agent}} · 第 {{n}} 步", "aiThinking": "AI思考", "planning": "规划中", "toolCallsDetected": "检测到 {{count}} 个工具调用", @@ -156,6 +158,7 @@ "knowledgeRetrieval": "知识检索", "knowledgeRetrievalTag": "知识检索", "error": "错误", + "streamNetworkErrorHint": "连接已中断({{detail}})。长时间任务可能仍在后端执行,请查看顶部「运行中」任务或稍后刷新本对话。", "taskCancelled": "任务已取消", "unknownTool": "未知工具", "einoAgentReplyTitle": "子代理回复", diff --git a/web/static/js/chat.js b/web/static/js/chat.js index 16c1beae..44046180 100644 --- a/web/static/js/chat.js +++ b/web/static/js/chat.js @@ -361,7 +361,18 @@ async function sendMessage() { } catch (error) { removeMessage(progressId); - addMessage('system', '错误: ' + error.message); + const msg = error && error.message != null ? String(error.message) : String(error); + const isNetwork = /network|fetch|Failed to fetch|aborted|AbortError|load failed|NetworkError/i.test(msg); + if (isNetwork && typeof window.t === 'function') { + addMessage('system', window.t('chat.streamNetworkErrorHint', { detail: msg })); + } else if (isNetwork) { + addMessage('system', '连接已中断(' + msg + ')。长时间任务可能仍在后端执行,请查看顶部运行中任务或稍后刷新对话。'); + } else { + addMessage('system', '错误: ' + msg); + } + if (typeof loadActiveTasks === 'function') { + loadActiveTasks(); + } // 发送失败时,不恢复草稿,因为消息已经显示在对话框中了 } } diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 171f41f0..2318f9d9 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -96,6 +96,21 @@ function timelineAgentBracketPrefix(data) { return s ? ('[' + s + '] ') : ''; } +/** 主/子代理视觉区分:左边框与浅底色(与工具黄/绿状态并存时由具体项类型覆盖次要边) */ +function applyEinoTimelineRole(item, data) { + if (!item || !data) return; + const role = data.einoRole; + if (role === 'orchestrator' || role === 'sub') { + item.dataset.einoRole = role; + item.classList.add('timeline-eino-role-' + role); + } + const scope = data.einoScope; + if (scope === 'main' || scope === 'sub') { + item.dataset.einoScope = scope; + item.classList.add('timeline-eino-scope-' + scope); + } +} + // markdown 渲染(用于最终合并渲染;流式增量阶段用纯转义避免部分语法不稳定) const assistantMarkdownSanitizeConfig = { ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'u', 's', 'code', 'pre', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'a', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr'], @@ -710,15 +725,32 @@ function handleStreamEvent(event, progressElement, progressId, }, 200); } break; - case 'iteration': - // 添加迭代标记(data 属性供语言切换时重算标题) + case 'iteration': { + const d = event.data || {}; + const n = d.iteration != null ? d.iteration : 1; + let iterTitle; + if (d.einoScope === 'main') { + iterTitle = typeof window.t === 'function' + ? window.t('chat.einoOrchestratorRound', { n: n }) + : ('主代理 · 第 ' + n + ' 轮'); + } else if (d.einoScope === 'sub') { + const ag = d.einoAgent != null ? String(d.einoAgent).trim() : ''; + iterTitle = typeof window.t === 'function' + ? window.t('chat.einoSubAgentStep', { n: n, agent: ag }) + : ('子代理 · ' + ag + ' · 第 ' + n + ' 步'); + } else { + iterTitle = typeof window.t === 'function' + ? window.t('chat.iterationRound', { n: n }) + : ('第 ' + n + ' 轮迭代'); + } addTimelineItem(timeline, 'iteration', { - title: typeof window.t === 'function' ? window.t('chat.iterationRound', { n: event.data?.iteration || 1 }) : '第 ' + (event.data?.iteration || 1) + ' 轮迭代', + title: iterTitle, message: event.message, data: event.data, - iterationN: event.data?.iteration || 1 + iterationN: n }); break; + } case 'thinking_stream_start': { const d = event.data || {}; @@ -1381,6 +1413,9 @@ function addTimelineItem(timeline, type, options) { if (type === 'iteration') { const n = options.iterationN != null ? options.iterationN : (options.data && options.data.iteration != null ? options.data.iteration : 1); item.dataset.iterationN = String(n); + if (options.data && options.data.einoScope) { + item.dataset.einoScope = String(options.data.einoScope); + } } if (type === 'progress' && options.message) { item.dataset.progressMessage = options.message; @@ -1493,6 +1528,9 @@ function addTimelineItem(timeline, type, options) { } item.innerHTML = content; + if (options.data) { + applyEinoTimelineRole(item, options.data); + } timeline.appendChild(item); // 自动展开详情 @@ -2298,7 +2336,15 @@ function refreshProgressAndTimelineI18n() { const ap = (item.dataset.einoAgent && item.dataset.einoAgent !== '') ? ('[' + item.dataset.einoAgent + '] ') : ''; if (type === 'iteration' && item.dataset.iterationN) { const n = parseInt(item.dataset.iterationN, 10) || 1; - titleSpan.textContent = ap + _t('chat.iterationRound', { n: n }); + const scope = item.dataset.einoScope; + if (scope === 'main') { + titleSpan.textContent = _t('chat.einoOrchestratorRound', { n: n }); + } else if (scope === 'sub') { + const agent = item.dataset.einoAgent || ''; + titleSpan.textContent = _t('chat.einoSubAgentStep', { n: n, agent: agent }); + } else { + titleSpan.textContent = ap + _t('chat.iterationRound', { n: n }); + } } else if (type === 'thinking') { titleSpan.textContent = ap + '\uD83E\uDD14 ' + _t('chat.aiThinking'); } else if (type === 'tool_calls_detected' && item.dataset.toolCallsCount != null) {