diff --git a/internal/handler/agent.go b/internal/handler/agent.go
index 065fa9d2..ddb843f7 100644
--- a/internal/handler/agent.go
+++ b/internal/handler/agent.go
@@ -290,15 +290,43 @@ func (h *AgentHandler) AgentLoopStream(c *gin.Context) {
defer cancelWithCause(nil)
if _, err := h.tasks.StartTask(conversationID, req.Message, cancelWithCause); err != nil {
+ var errorMsg string
if errors.Is(err, ErrTaskAlreadyRunning) {
- sendEvent("error", "当前会话已有任务正在执行,请先停止后再尝试。", map[string]interface{}{
+ errorMsg = "⚠️ 当前会话已有任务正在执行中,请等待当前任务完成或点击「停止任务」按钮后再尝试。"
+ sendEvent("error", errorMsg, map[string]interface{}{
"conversationId": conversationID,
+ "errorType": "task_already_running",
})
} else {
- sendEvent("error", "无法启动任务: "+err.Error(), map[string]interface{}{
+ errorMsg = "❌ 无法启动任务: " + err.Error()
+ sendEvent("error", errorMsg, map[string]interface{}{
"conversationId": conversationID,
+ "errorType": "task_start_failed",
})
}
+
+ // 更新助手消息内容并保存错误详情到数据库
+ if assistantMessageID != "" {
+ if _, updateErr := h.db.Exec(
+ "UPDATE messages SET content = ? WHERE id = ?",
+ errorMsg,
+ assistantMessageID,
+ ); updateErr != nil {
+ h.logger.Warn("更新错误后的助手消息失败", zap.Error(updateErr))
+ }
+ // 保存错误详情到数据库
+ if err := h.db.AddProcessDetail(assistantMessageID, conversationID, "error", errorMsg, map[string]interface{}{
+ "errorType": func() string {
+ if errors.Is(err, ErrTaskAlreadyRunning) {
+ return "task_already_running"
+ }
+ return "task_start_failed"
+ }(),
+ }); err != nil {
+ h.logger.Warn("保存错误详情失败", zap.Error(err))
+ }
+ }
+
sendEvent("done", "", map[string]interface{}{
"conversationId": conversationID,
})
diff --git a/web/static/js/app.js b/web/static/js/app.js
index 6054c26d..8b9b94e3 100644
--- a/web/static/js/app.js
+++ b/web/static/js/app.js
@@ -478,15 +478,18 @@ function toggleProgressDetails(progressId) {
// 折叠所有进度详情
function collapseAllProgressDetails(assistantMessageId, progressId) {
// 折叠集成到MCP区域的详情
- const detailsId = 'process-details-' + assistantMessageId;
- const detailsContainer = document.getElementById(detailsId);
- if (detailsContainer) {
- const timeline = detailsContainer.querySelector('.progress-timeline');
- if (timeline && timeline.classList.contains('expanded')) {
- timeline.classList.remove('expanded');
- const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
- if (btn) {
- btn.innerHTML = '展开详情';
+ if (assistantMessageId) {
+ const detailsId = 'process-details-' + assistantMessageId;
+ const detailsContainer = document.getElementById(detailsId);
+ if (detailsContainer) {
+ const timeline = detailsContainer.querySelector('.progress-timeline');
+ if (timeline) {
+ // 确保移除expanded类(无论是否包含)
+ timeline.classList.remove('expanded');
+ const btn = document.querySelector(`#${assistantMessageId} .process-detail-btn`);
+ if (btn) {
+ btn.innerHTML = '展开详情';
+ }
}
}
}
@@ -497,7 +500,7 @@ function collapseAllProgressDetails(assistantMessageId, progressId) {
allDetails.forEach(detail => {
const timeline = detail.querySelector('.progress-timeline');
const toggleBtn = detail.querySelector('.progress-toggle');
- if (timeline && timeline.classList.contains('expanded')) {
+ if (timeline) {
timeline.classList.remove('expanded');
if (toggleBtn) {
toggleBtn.textContent = '展开详情';
@@ -509,7 +512,7 @@ function collapseAllProgressDetails(assistantMessageId, progressId) {
if (progressId) {
const progressTimeline = document.getElementById(progressId + '-timeline');
const progressToggleBtn = document.querySelector(`#${progressId} .progress-toggle`);
- if (progressTimeline && progressTimeline.classList.contains('expanded')) {
+ if (progressTimeline) {
progressTimeline.classList.remove('expanded');
if (progressToggleBtn) {
progressToggleBtn.textContent = '展开详情';
@@ -592,10 +595,11 @@ function integrateProgressToMCPSection(progressId, assistantMessageId) {
`;
- // 确保初始状态是折叠的(默认折叠)
+ // 确保初始状态是折叠的(默认折叠,特别是错误时)
if (hasContent) {
const timeline = document.getElementById(detailsId + '-timeline');
if (timeline) {
+ // 如果有错误,确保折叠;否则也默认折叠
timeline.classList.remove('expanded');
}
@@ -844,16 +848,74 @@ function handleStreamEvent(event, progressElement, progressId,
break;
case 'cancelled':
+ // 显示错误
addTimelineItem(timeline, 'cancelled', {
title: '⛔ 任务已取消',
message: event.message,
data: event.data
});
+
+ // 更新进度标题为取消状态
const cancelTitle = document.querySelector(`#${progressId} .progress-title`);
if (cancelTitle) {
cancelTitle.textContent = '⛔ 任务已取消';
}
- finalizeProgressTask(progressId, '已取消');
+
+ // 更新进度容器为已完成状态(添加completed类)
+ const cancelProgressContainer = document.querySelector(`#${progressId} .progress-container`);
+ if (cancelProgressContainer) {
+ cancelProgressContainer.classList.add('completed');
+ }
+
+ // 完成进度任务(标记为已取消)
+ if (progressTaskState.has(progressId)) {
+ finalizeProgressTask(progressId, '已取消');
+ }
+
+ // 如果取消事件包含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);
+ }
+ } else {
+ // 如果没有messageId,创建助手消息并集成详情
+ const assistantId = addMessage('assistant', event.message, null, progressId);
+ setAssistantId(assistantId);
+
+ // 将进度详情集成到工具调用区域
+ setTimeout(() => {
+ integrateProgressToMCPSection(progressId, assistantId);
+ // 确保详情默认折叠
+ collapseAllProgressDetails(assistantId, progressId);
+ }, 100);
+ }
+
+ // 立即刷新任务状态
loadActiveTasks();
break;
@@ -895,6 +957,23 @@ function handleStreamEvent(event, progressElement, progressId,
data: event.data
});
+ // 更新进度标题为错误状态
+ const errorTitle = document.querySelector(`#${progressId} .progress-title`);
+ if (errorTitle) {
+ errorTitle.textContent = '❌ 执行失败';
+ }
+
+ // 更新进度容器为已完成状态(添加completed类)
+ const progressContainer = document.querySelector(`#${progressId} .progress-container`);
+ if (progressContainer) {
+ progressContainer.classList.add('completed');
+ }
+
+ // 完成进度任务(标记为失败)
+ if (progressTaskState.has(progressId)) {
+ finalizeProgressTask(progressId, '已失败');
+ }
+
// 如果错误事件包含messageId,说明有助手消息,需要显示错误内容
if (event.data && event.data.messageId) {
// 检查助手消息是否已存在
@@ -925,6 +1004,17 @@ function handleStreamEvent(event, progressElement, progressId,
collapseAllProgressDetails(assistantId, progressId);
}, 100);
}
+ } else {
+ // 如果没有messageId(比如任务已运行时的错误),创建助手消息并集成详情
+ const assistantId = addMessage('assistant', event.message, null, progressId);
+ setAssistantId(assistantId);
+
+ // 将进度详情集成到工具调用区域
+ setTimeout(() => {
+ integrateProgressToMCPSection(progressId, assistantId);
+ // 确保详情默认折叠
+ collapseAllProgressDetails(assistantId, progressId);
+ }, 100);
}
// 立即刷新任务状态(执行失败时任务状态会更新)
@@ -1301,6 +1391,8 @@ function renderProcessDetails(messageId, processDetails) {
itemTitle = `${statusIcon} 工具 ${escapeHtml(toolName)} 执行${success ? '完成' : '失败'}`;
} else if (eventType === 'error') {
itemTitle = '❌ 错误';
+ } else if (eventType === 'cancelled') {
+ itemTitle = '⛔ 任务已取消';
}
addTimelineItem(timeline, eventType, {
@@ -1309,6 +1401,20 @@ function renderProcessDetails(messageId, processDetails) {
data: data
});
});
+
+ // 检查是否有错误或取消事件,如果有,确保详情默认折叠
+ const hasErrorOrCancelled = processDetails.some(d =>
+ d.eventType === 'error' || d.eventType === 'cancelled'
+ );
+ if (hasErrorOrCancelled) {
+ // 确保时间线是折叠的
+ timeline.classList.remove('expanded');
+ // 更新按钮文本为"展开详情"
+ const processDetailBtn = messageElement.querySelector('.process-detail-btn');
+ if (processDetailBtn) {
+ processDetailBtn.innerHTML = '展开详情';
+ }
+ }
}
// 移除消息
@@ -1608,12 +1714,32 @@ async function loadConversation(conversationId) {
// 加载消息
if (conversation.messages && conversation.messages.length > 0) {
conversation.messages.forEach(msg => {
- const messageId = addMessage(msg.role, msg.content, msg.mcpExecutionIds || []);
+ // 检查消息内容是否为"处理中...",如果是,检查processDetails中是否有错误或取消事件
+ let displayContent = msg.content;
+ if (msg.role === 'assistant' && msg.content === '处理中...' && msg.processDetails && msg.processDetails.length > 0) {
+ // 查找最后一个error或cancelled事件
+ for (let i = msg.processDetails.length - 1; i >= 0; i--) {
+ const detail = msg.processDetails[i];
+ if (detail.eventType === 'error' || detail.eventType === 'cancelled') {
+ displayContent = detail.message || msg.content;
+ break;
+ }
+ }
+ }
+
+ const messageId = addMessage(msg.role, displayContent, msg.mcpExecutionIds || []);
// 如果有过程详情,显示它们
if (msg.processDetails && msg.processDetails.length > 0 && msg.role === 'assistant') {
// 延迟一下,确保消息已经渲染
setTimeout(() => {
renderProcessDetails(messageId, msg.processDetails);
+ // 检查是否有错误或取消事件,如果有,确保详情默认折叠
+ const hasErrorOrCancelled = msg.processDetails.some(d =>
+ d.eventType === 'error' || d.eventType === 'cancelled'
+ );
+ if (hasErrorOrCancelled) {
+ collapseAllProgressDetails(messageId, null);
+ }
}, 100);
}
});