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*