From a32ba4035378e8a92a4fed61517f65d6a86a7e6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Tue, 30 Dec 2025 23:21:09 +0800 Subject: [PATCH] Add files via upload --- web/static/css/style.css | 71 +++++++++++++++++++++++++++++++++++++++- web/static/js/monitor.js | 68 +++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 2 deletions(-) diff --git a/web/static/css/style.css b/web/static/css/style.css index ff83bd83..e79d25cc 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -2335,6 +2335,75 @@ header { color: var(--text-secondary); } +/* 工具调用状态徽章 */ +.tool-status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 12px; + font-size: 0.75rem; + font-weight: 600; + margin-left: 8px; + white-space: nowrap; + vertical-align: middle; +} + +.tool-status-badge.tool-status-running { + background: rgba(0, 102, 255, 0.12); + color: var(--accent-color); + border: 1px solid rgba(0, 102, 255, 0.3); +} + +.tool-status-badge.tool-status-running::before { + content: ''; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent-color); + display: inline-block; + animation: tool-running-pulse 1.5s infinite; +} + +@keyframes tool-running-pulse { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.5; + transform: scale(0.8); + } +} + +.tool-status-badge.tool-status-completed { + background: rgba(40, 167, 69, 0.12); + color: var(--success-color); + border: 1px solid rgba(40, 167, 69, 0.3); +} + +.tool-status-badge.tool-status-failed { + background: rgba(220, 53, 69, 0.12); + color: var(--error-color); + border: 1px solid rgba(220, 53, 69, 0.3); +} + +/* 工具调用项状态样式 */ +.timeline-item-tool_call.tool-call-running { + border-left-color: var(--accent-color); + background: rgba(0, 102, 255, 0.08); +} + +.timeline-item-tool_call.tool-call-completed { + border-left-color: var(--success-color); + background: rgba(40, 167, 69, 0.08); +} + +.timeline-item-tool_call.tool-call-failed { + border-left-color: var(--error-color); + background: rgba(220, 53, 69, 0.08); +} + /* 活跃任务栏 */ .active-tasks-bar { display: none; @@ -4060,7 +4129,7 @@ header { .attack-chain-container { flex: 1; min-height: 0; - background: #ffffff; // 使用纯白色背景,提高节点对比度 + background: #ffffff; border: none; position: relative; overflow: hidden; diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 08de8de5..ca161278 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -3,6 +3,9 @@ let activeTaskInterval = null; const ACTIVE_TASK_REFRESH_INTERVAL = 10000; // 10秒检查一次 const TASK_FINAL_STATUSES = new Set(['failed', 'timeout', 'cancelled', 'completed']); +// 存储工具调用ID到DOM元素的映射,用于更新执行状态 +const toolCallStatusMap = new Map(); + const conversationExecutionTracker = { activeConversations: new Set(), update(tasks = []) { @@ -493,12 +496,26 @@ function handleStreamEvent(event, progressElement, progressId, const toolName = toolInfo.toolName || '未知工具'; const index = toolInfo.index || 0; const total = toolInfo.total || 0; - addTimelineItem(timeline, 'tool_call', { + const toolCallId = toolInfo.toolCallId || null; + + // 添加工具调用项,并标记为执行中 + const toolCallItemId = addTimelineItem(timeline, 'tool_call', { title: `🔧 调用工具: ${escapeHtml(toolName)} (${index}/${total})`, message: event.message, data: toolInfo, expanded: false }); + + // 如果有toolCallId,存储映射关系以便后续更新状态 + if (toolCallId && toolCallItemId) { + toolCallStatusMap.set(toolCallId, { + itemId: toolCallItemId, + timeline: timeline + }); + + // 添加执行中状态指示器 + updateToolCallStatus(toolCallId, 'running'); + } break; case 'tool_result': @@ -507,6 +524,15 @@ function handleStreamEvent(event, progressElement, progressId, const resultToolName = resultInfo.toolName || '未知工具'; const success = resultInfo.success !== false; const statusIcon = success ? '✅' : '❌'; + const resultToolCallId = resultInfo.toolCallId || null; + + // 如果有关联的toolCallId,更新工具调用项的状态 + if (resultToolCallId && toolCallStatusMap.has(resultToolCallId)) { + updateToolCallStatus(resultToolCallId, success ? 'completed' : 'failed'); + // 从映射中移除(已完成) + toolCallStatusMap.delete(resultToolCallId); + } + addTimelineItem(timeline, 'tool_result', { title: `${statusIcon} 工具 ${escapeHtml(resultToolName)} 执行${success ? '完成' : '失败'}`, message: event.message, @@ -767,9 +793,46 @@ function handleStreamEvent(event, progressElement, progressId, messagesDiv.scrollTop = messagesDiv.scrollHeight; } +// 更新工具调用状态 +function updateToolCallStatus(toolCallId, status) { + const mapping = toolCallStatusMap.get(toolCallId); + if (!mapping) return; + + const item = document.getElementById(mapping.itemId); + if (!item) return; + + const titleElement = item.querySelector('.timeline-item-title'); + if (!titleElement) return; + + // 移除之前的状态类 + item.classList.remove('tool-call-running', 'tool-call-completed', 'tool-call-failed'); + + // 根据状态更新样式和文本 + let statusText = ''; + if (status === 'running') { + item.classList.add('tool-call-running'); + statusText = ' 执行中...'; + } else if (status === 'completed') { + item.classList.add('tool-call-completed'); + statusText = ' ✅ 已完成'; + } else if (status === 'failed') { + item.classList.add('tool-call-failed'); + statusText = ' ❌ 执行失败'; + } + + // 更新标题(保留原有文本,追加状态) + const originalText = titleElement.innerHTML; + // 移除之前可能存在的状态标记 + const cleanText = originalText.replace(/\s*