From a4df77fc137d90a852f9299a48860a09ee45aa86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Sun, 16 Nov 2025 22:35:51 +0800 Subject: [PATCH] Add files via upload --- internal/handler/agent.go | 13 ++++ internal/handler/task_manager.go | 15 +++++ web/static/js/app.js | 110 +++++++++++++++++++++++++++---- 3 files changed, 127 insertions(+), 11 deletions(-) diff --git a/internal/handler/agent.go b/internal/handler/agent.go index 372108ec..065fa9d2 100644 --- a/internal/handler/agent.go +++ b/internal/handler/agent.go @@ -10,6 +10,7 @@ import ( "cyberstrike-ai/internal/agent" "cyberstrike-ai/internal/database" + "github.com/gin-gonic/gin" "go.uber.org/zap" ) @@ -318,6 +319,10 @@ func (h *AgentHandler) AgentLoopStream(c *gin.Context) { case errors.Is(cause, ErrTaskCancelled): taskStatus = "cancelled" cancelMsg := "任务已被用户取消,后续操作已停止。" + + // 在发送事件前更新任务状态,确保前端能及时看到状态变化 + h.tasks.UpdateTaskStatus(conversationID, taskStatus) + if assistantMessageID != "" { if _, updateErr := h.db.Exec( "UPDATE messages SET content = ? WHERE id = ?", @@ -339,6 +344,10 @@ func (h *AgentHandler) AgentLoopStream(c *gin.Context) { case errors.Is(err, context.DeadlineExceeded) || errors.Is(cause, context.DeadlineExceeded): taskStatus = "timeout" timeoutMsg := "任务执行超时,已自动终止。" + + // 在发送事件前更新任务状态,确保前端能及时看到状态变化 + h.tasks.UpdateTaskStatus(conversationID, taskStatus) + if assistantMessageID != "" { if _, updateErr := h.db.Exec( "UPDATE messages SET content = ? WHERE id = ?", @@ -360,6 +369,10 @@ func (h *AgentHandler) AgentLoopStream(c *gin.Context) { default: taskStatus = "failed" errorMsg := "执行失败: " + err.Error() + + // 在发送事件前更新任务状态,确保前端能及时看到状态变化 + h.tasks.UpdateTaskStatus(conversationID, taskStatus) + if assistantMessageID != "" { if _, updateErr := h.db.Exec( "UPDATE messages SET content = ? WHERE id = ?", diff --git a/internal/handler/task_manager.go b/internal/handler/task_manager.go index 02a549ad..7c44a338 100644 --- a/internal/handler/task_manager.go +++ b/internal/handler/task_manager.go @@ -89,6 +89,21 @@ func (m *AgentTaskManager) CancelTask(conversationID string, cause error) (bool, return true, nil } +// UpdateTaskStatus 更新任务状态但不删除任务(用于在发送事件前更新状态) +func (m *AgentTaskManager) UpdateTaskStatus(conversationID string, status string) { + m.mu.Lock() + defer m.mu.Unlock() + + task, exists := m.tasks[conversationID] + if !exists { + return + } + + if status != "" { + task.Status = status + } +} + // FinishTask 完成任务并从管理器中移除 func (m *AgentTaskManager) FinishTask(conversationID string, finalStatus string) { m.mu.Lock() diff --git a/web/static/js/app.js b/web/static/js/app.js index e0c14ece..6054c26d 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -558,6 +558,9 @@ function integrateProgressToMCPSection(progressId, assistantMessageId) { // 获取时间线内容 const hasContent = timelineHTML.trim().length > 0; + // 检查时间线中是否有错误项 + const hasError = timeline && timeline.querySelector('.timeline-item-error'); + // 确保按钮容器存在 let buttonsContainer = mcpSection.querySelector('.mcp-call-buttons'); if (!buttonsContainer) { @@ -582,19 +585,25 @@ function integrateProgressToMCPSection(progressId, assistantMessageId) { } } - // 设置详情内容(默认折叠状态) + // 设置详情内容(如果有错误,默认折叠;否则默认折叠) detailsContainer.innerHTML = `
${hasContent ? `
${timelineHTML}
` : '
暂无过程详情
'}
`; - // 确保初始状态是折叠的 + // 确保初始状态是折叠的(默认折叠) if (hasContent) { const timeline = document.getElementById(detailsId + '-timeline'); if (timeline) { timeline.classList.remove('expanded'); } + + // 更新按钮文本为"展开详情"(因为默认折叠) + const processDetailBtn = buttonsContainer.querySelector('.process-detail-btn'); + if (processDetailBtn) { + processDetailBtn.innerHTML = '展开详情'; + } } // 移除原来的进度消息 @@ -717,13 +726,21 @@ function convertProgressToDetails(progressId, assistantMessageId) { // 获取时间线HTML内容 const hasContent = timelineHTML.trim().length > 0; + // 检查时间线中是否有错误项 + const hasError = timeline && timeline.querySelector('.timeline-item-error'); + + // 如果有错误,默认折叠;否则默认展开 + const shouldExpand = !hasError; + const expandedClass = shouldExpand ? 'expanded' : ''; + const toggleText = shouldExpand ? '收起详情' : '展开详情'; + // 总是显示详情组件,即使没有内容也显示 bubble.innerHTML = `
📋 渗透测试详情 - ${hasContent ? `` : ''} + ${hasContent ? `` : ''}
- ${hasContent ? `
${timelineHTML}
` : '
暂无过程详情(可能执行过快或未触发详细事件)
'} + ${hasContent ? `
${timelineHTML}
` : '
暂无过程详情(可能执行过快或未触发详细事件)
'} `; contentWrapper.appendChild(bubble); @@ -877,6 +894,41 @@ function handleStreamEvent(event, progressElement, progressId, message: event.message, data: event.data }); + + // 如果错误事件包含messageId,说明有助手消息,需要显示错误内容 + if (event.data && event.data.messageId) { + // 检查助手消息是否已存在 + let assistantId = event.data.messageId; + let assistantElement = document.getElementById(assistantId); + + // 如果助手消息不存在,创建它 + if (!assistantElement) { + assistantId = addMessage('assistant', event.message, null, progressId); + setAssistantId(assistantId); + assistantElement = document.getElementById(assistantId); + } else { + // 如果已存在,更新内容 + const bubble = assistantElement.querySelector('.message-bubble'); + if (bubble) { + bubble.innerHTML = escapeHtml(event.message).replace(/\n/g, '
'); + } + } + + // 将进度详情集成到工具调用区域(如果还没有) + if (assistantElement) { + const detailsId = 'process-details-' + assistantId; + if (!document.getElementById(detailsId)) { + integrateProgressToMCPSection(progressId, assistantId); + } + // 立即折叠详情(错误时应该默认折叠) + setTimeout(() => { + collapseAllProgressDetails(assistantId, progressId); + }, 100); + } + } + + // 立即刷新任务状态(执行失败时任务状态会更新) + loadActiveTasks(); break; case 'done': @@ -894,7 +946,18 @@ function handleStreamEvent(event, progressElement, progressId, if (progressTaskState.has(progressId)) { finalizeProgressTask(progressId, '已完成'); } + + // 检查时间线中是否有错误项 + const hasError = timeline && timeline.querySelector('.timeline-item-error'); + + // 立即刷新任务状态(确保任务状态同步) loadActiveTasks(); + + // 延迟再次刷新任务状态(确保后端已完成状态更新) + setTimeout(() => { + loadActiveTasks(); + }, 200); + // 完成时自动折叠所有详情(延迟一下确保response事件已处理) setTimeout(() => { const assistantIdFromDone = getAssistantId(); @@ -904,6 +967,14 @@ function handleStreamEvent(event, progressElement, progressId, // 如果无法获取助手ID,尝试折叠所有详情 collapseAllProgressDetails(null, progressId); } + + // 如果有错误,确保详情是折叠的(错误时应该默认折叠) + if (hasError) { + // 再次确保折叠(延迟一点确保DOM已更新) + setTimeout(() => { + collapseAllProgressDetails(assistantIdFromDone || null, progressId); + }, 200); + } }, 500); break; } @@ -1646,22 +1717,39 @@ function renderActiveTasks(tasks) { ? startedTime.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : ''; + // 根据任务状态显示不同的文本 + const statusMap = { + 'running': '执行中', + 'cancelling': '取消中', + 'failed': '执行失败', + 'timeout': '执行超时', + 'cancelled': '已取消', + 'completed': '已完成' + }; + const statusText = statusMap[task.status] || '执行中'; + const isFinalStatus = ['failed', 'timeout', 'cancelled', 'completed'].includes(task.status); + item.innerHTML = `
- ${task.status === 'cancelling' ? '取消中' : '执行中'} + ${statusText} ${escapeHtml(task.message || '未命名任务')}
${timeText ? `${timeText}` : ''} - + ${!isFinalStatus ? '' : ''}
`; - const cancelBtn = item.querySelector('.active-task-cancel'); - cancelBtn.onclick = () => cancelActiveTask(task.conversationId, cancelBtn); - if (task.status === 'cancelling') { - cancelBtn.disabled = true; - cancelBtn.textContent = '取消中...'; + // 只有非最终状态的任务才显示停止按钮 + if (!isFinalStatus) { + const cancelBtn = item.querySelector('.active-task-cancel'); + if (cancelBtn) { + cancelBtn.onclick = () => cancelActiveTask(task.conversationId, cancelBtn); + if (task.status === 'cancelling') { + cancelBtn.disabled = true; + cancelBtn.textContent = '取消中...'; + } + } } bar.appendChild(item);