agent-Specialization/static/src/app/methods/taskPolling.ts

1263 lines
47 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-nocheck
import { debugLog } from './common';
/**
* 任务轮询事件处理器
* 将从 REST API 轮询获取的事件转换为前端状态更新
*/
export const taskPollingMethods = {
/**
* 处理任务事件(从轮询获取)
*/
handleTaskEvent(event: any) {
if (!event || !event.type) {
return;
}
const eventType = event.type;
const eventData = event.data || {};
const eventIdx = event.idx;
// 事件去重检查
if (typeof eventIdx === 'number') {
if (!this._processedEventIndices) {
this._processedEventIndices = new Set();
}
if (this._processedEventIndices.has(eventIdx)) {
debugLog(`[TaskPolling] 跳过重复事件 #${eventIdx}`);
return;
}
this._processedEventIndices.add(eventIdx);
// 限制 Set 大小(保留最近 1000 个)
if (this._processedEventIndices.size > 1000) {
const firstIdx = Math.min(...this._processedEventIndices);
this._processedEventIndices.delete(firstIdx);
}
}
// 检查事件的 conversation_id 是否匹配当前对话
// 如果不匹配,忽略该事件(避免切换对话后旧任务的事件显示到新对话中)
if (eventData.conversation_id && this.currentConversationId) {
if (eventData.conversation_id !== this.currentConversationId) {
console.log(`[DEBUG_AWAITING] 忽略不匹配的事件`, {
eventType,
eventIdx,
eventConversationId: eventData.conversation_id,
currentConversationId: this.currentConversationId
});
debugLog(`[TaskPolling] 忽略不匹配的事件 #${eventIdx}: ${eventType}, 事件对话=${eventData.conversation_id}, 当前对话=${this.currentConversationId}`);
return;
}
}
debugLog(`[TaskPolling] 处理事件 #${eventIdx}: ${eventType}`, eventData);
console.log(`[DEBUG_AWAITING] 处理事件`, {
eventType,
eventIdx,
eventConversationId: eventData.conversation_id,
currentConversationId: this.currentConversationId
});
// 根据事件类型调用对应的处理方法
switch (eventType) {
case 'ai_message_start':
this.handleAiMessageStart(eventData, eventIdx);
break;
case 'thinking_start':
this.handleThinkingStart(eventData, eventIdx);
break;
case 'thinking_chunk':
this.handleThinkingChunk(eventData, eventIdx);
break;
case 'thinking_end':
this.handleThinkingEnd(eventData, eventIdx);
break;
case 'text_start':
this.handleTextStart(eventData, eventIdx);
break;
case 'text_chunk':
this.handleTextChunk(eventData, eventIdx);
break;
case 'text_end':
this.handleTextEnd(eventData, eventIdx);
break;
case 'tool_preparing':
this.handleToolPreparing(eventData, eventIdx);
break;
case 'tool_start':
this.handleToolStart(eventData, eventIdx);
break;
case 'tool_intent':
this.handleToolIntent(eventData, eventIdx);
break;
case 'tool_update_action':
case 'update_action':
this.handleToolUpdateAction(eventData, eventIdx);
break;
case 'append_payload':
this.handleAppendPayload(eventData, eventIdx);
break;
case 'modify_payload':
this.handleModifyPayload(eventData, eventIdx);
break;
case 'task_complete':
this.handleTaskComplete(eventData, eventIdx);
break;
case 'task_stopped':
this.handleTaskStopped(eventData, eventIdx);
break;
case 'error':
this.handleTaskError(eventData, eventIdx);
break;
case 'token_update':
this.handleTokenUpdate(eventData, eventIdx);
break;
case 'conversation_changed':
this.handleConversationChanged(eventData, eventIdx);
break;
case 'conversation_resolved':
this.handleConversationResolved(eventData, eventIdx);
break;
case 'user_message':
this.handleUserMessage(eventData, eventIdx);
break;
default:
debugLog(`[TaskPolling] 未知事件类型: ${eventType}`);
}
// 注意:不要在这里清除 _rebuildingFromScratch 标记
// 该标记应该在恢复完所有历史事件后才清除
},
handleAiMessageStart(data: any, eventIdx: number) {
const lastMessage = this.messages[this.messages.length - 1];
console.log('[DEBUG_AWAITING] ===== handleAiMessageStart =====', {
eventIdx,
lastMessageRole: lastMessage?.role,
lastMessageAwaitingFirstContent: lastMessage?.awaitingFirstContent,
lastMessageGeneratingLabel: lastMessage?.generatingLabel
});
debugLog('[TaskPolling] AI消息开始, idx:', eventIdx);
console.log('[AiMessageStart] 开始处理', {
eventIdx,
messagesLength: this.messages.length,
lastMessageRole: this.messages[this.messages.length - 1]?.role,
streamingMessage: this.streamingMessage,
taskInProgress: this.taskInProgress,
currentConversationId: this.currentConversationId
});
if (this.waitingForSubAgent) {
this.waitingForSubAgent = false;
}
// 检查是否已经有 assistant 消息
const hasAssistantMessage = lastMessage && lastMessage.role === 'assistant';
console.log('[AiMessageStart] 最后一条消息状态', {
hasAssistantMessage,
lastMessageActionsLength: lastMessage?.actions?.length || 0,
lastMessageAwaitingFirstContent: lastMessage?.awaitingFirstContent,
lastMessageGeneratingLabel: lastMessage?.generatingLabel
});
// 判断是否是刷新恢复的情况:
// 1. 有assistant消息
// 2. 且该消息正在streaming或者没有任何内容说明是刚创建的
// 3. 且不是历史加载完成的消息历史消息的awaitingFirstContent应该是false
const isRefreshRestore = hasAssistantMessage &&
(this.streamingMessage || !lastMessage.actions || lastMessage.actions.length === 0) &&
(lastMessage.awaitingFirstContent === true || this.taskInProgress);
console.log('[AiMessageStart] 场景判断', {
isRefreshRestore,
streamingMessage: this.streamingMessage,
hasActions: !!(lastMessage?.actions && lastMessage.actions.length > 0),
awaitingFirstContent: lastMessage?.awaitingFirstContent,
taskInProgress: this.taskInProgress
});
if (isRefreshRestore) {
console.log('[AiMessageStart] 场景=刷新恢复复用现有assistant消息');
debugLog('[TaskPolling] 刷新恢复场景,复用现有 assistant 消息');
// 只更新状态,不创建新消息
this.taskInProgress = true;
this.stopRequested = false;
this.streamingMessage = true;
// 确保 awaitingFirstContent 被正确设置
const hasContent = lastMessage.actions && lastMessage.actions.length > 0;
if (!hasContent) {
lastMessage.awaitingFirstContent = true;
lastMessage.generatingLabel = lastMessage.generatingLabel || '思考中...';
console.log('[AiMessageStart] 设置等待动画', {
awaitingFirstContent: true,
generatingLabel: lastMessage.generatingLabel
});
} else {
// 如果已有内容,确保等待动画不显示
lastMessage.awaitingFirstContent = false;
lastMessage.generatingLabel = '';
console.log('[AiMessageStart] 关闭等待动画(已有内容)');
}
return;
}
// 其他情况:创建新的 assistant 消息
console.log('[AiMessageStart] 场景=创建新消息');
debugLog('[TaskPolling] 创建新的 assistant 消息');
this.monitorResetSpeech();
this.cleanupStaleToolActions();
this.taskInProgress = true;
this.chatStartAssistantMessage();
this.stopRequested = false;
this.streamingMessage = true;
const newMessage = this.messages[this.messages.length - 1];
console.log('[AiMessageStart] 新消息创建完成', {
role: newMessage?.role,
awaitingFirstContent: newMessage?.awaitingFirstContent,
generatingLabel: newMessage?.generatingLabel,
actionsLength: newMessage?.actions?.length || 0,
messagesLength: this.messages.length
});
// 如果是从头重建,标记消息为静默恢复
if (this._rebuildingFromScratch) {
if (newMessage && newMessage.role === 'assistant') {
debugLog('[TaskPolling] 标记消息为静默恢复(从头重建)');
newMessage.awaitingFirstContent = false;
newMessage.generatingLabel = '';
console.log('[AiMessageStart] 从头重建,关闭等待动画');
}
}
// 强制触发Vue响应式更新
this.$forceUpdate();
console.log('[AiMessageStart] 已调用$forceUpdate');
this.$nextTick(() => {
console.log('[AiMessageStart] nextTick回调执行');
this.scrollToBottom();
});
},
handleThinkingStart(data: any, eventIdx: number) {
console.log('[ThinkingStart] 开始处理', { eventIdx });
debugLog('[TaskPolling] 思考开始, idx:', eventIdx);
const ignoreThinking = this.runMode === 'fast' || this.thinkingMode === false;
if (ignoreThinking) {
this.monitorEndModelOutput();
console.log('[ThinkingStart] 忽略思考fast模式或关闭思考');
return;
}
// 防御性检查如果没有assistant消息先创建一个
const lastMessage = this.messages[this.messages.length - 1];
if (!lastMessage || lastMessage.role !== 'assistant') {
console.log('[DEBUG_AWAITING] ThinkingStart 检测到缺少assistant消息自动创建');
this.chatStartAssistantMessage();
}
this.monitorShowThinking();
const result = this.chatStartThinkingAction();
if (result && result.blockId) {
const blockId = result.blockId;
const lastMessage = this.messages[this.messages.length - 1];
// 有内容了,关闭等待动画
if (lastMessage && lastMessage.role === 'assistant') {
console.log('[ThinkingStart] 关闭等待动画', {
beforeAwaitingFirstContent: lastMessage.awaitingFirstContent,
beforeGeneratingLabel: lastMessage.generatingLabel
});
lastMessage.awaitingFirstContent = false;
lastMessage.generatingLabel = '';
}
// 只在非历史恢复时展开思考块
if (!this._rebuildingFromScratch) {
this.chatExpandBlock(blockId);
}
this.scrollToBottom();
this.chatSetThinkingLock(blockId, true);
// 强制触发 actions 数组的响应式更新
if (lastMessage && lastMessage.actions) {
lastMessage.actions = [...lastMessage.actions];
}
this.$forceUpdate();
}
},
handleThinkingChunk(data: any, eventIdx: number) {
if (this.runMode === 'fast' || this.thinkingMode === false) {
return;
}
const thinkingAction = this.chatAppendThinkingChunk(data.content);
if (thinkingAction) {
this.$forceUpdate();
this.$nextTick(() => {
if (thinkingAction && thinkingAction.blockId) {
this.scrollThinkingToBottom(thinkingAction.blockId);
}
this.conditionalScrollToBottom();
});
}
this.monitorShowThinking();
},
handleThinkingEnd(data: any) {
debugLog('[TaskPolling] 思考结束');
if (this.runMode === 'fast' || this.thinkingMode === false) {
return;
}
const blockId = this.chatCompleteThinkingAction(data.full_content);
if (blockId) {
// 解锁思考块
this.chatSetThinkingLock(blockId, false);
// 只在非历史恢复时延迟折叠思考块
if (!this._rebuildingFromScratch) {
// 延迟折叠思考块(给用户一点时间看到思考完成)
setTimeout(() => {
this.chatCollapseBlock(blockId);
this.$forceUpdate();
}, 1000);
} else {
// 历史恢复时立即折叠,不需要动画
this.chatCollapseBlock(blockId);
}
this.$nextTick(() => this.scrollThinkingToBottom(blockId));
}
this.$forceUpdate();
this.monitorEndModelOutput();
},
handleTextStart(data: any) {
console.log('[TextStart] 开始处理');
debugLog('[TaskPolling] 文本开始');
// 防御性检查如果没有assistant消息先创建一个
const lastMessage = this.messages[this.messages.length - 1];
if (!lastMessage || lastMessage.role !== 'assistant') {
console.log('[DEBUG_AWAITING] TextStart 检测到缺少assistant消息自动创建');
this.chatStartAssistantMessage();
}
this.chatStartTextAction();
// 有内容了,关闭等待动画
const currentMessage = this.messages[this.messages.length - 1];
if (currentMessage && currentMessage.role === 'assistant') {
console.log('[TextStart] 关闭等待动画', {
beforeAwaitingFirstContent: currentMessage.awaitingFirstContent,
beforeGeneratingLabel: currentMessage.generatingLabel
});
currentMessage.awaitingFirstContent = false;
currentMessage.generatingLabel = '';
}
this.$forceUpdate();
},
handleTextChunk(data: any) {
if (data && typeof data.content === 'string' && data.content.length) {
this.chatAppendTextChunk(data.content);
this.$forceUpdate();
this.conditionalScrollToBottom();
const speech = data.content.replace(/\r/g, '');
if (speech) {
this.monitorShowSpeech(speech);
}
}
},
handleTextEnd(data: any) {
debugLog('[TaskPolling] 文本结束');
const full = data?.full_content || '';
this.chatCompleteTextAction(full);
this.$forceUpdate();
this.monitorEndModelOutput();
},
handleToolPreparing(data: any) {
console.log('[ToolPreparing] 开始处理', { name: data.name, id: data.id });
debugLog('[TaskPolling] 工具准备中:', data.name);
if (this.dropToolEvents) {
console.log('[ToolPreparing] dropToolEvents=true跳过');
return;
}
const msg = this.chatEnsureAssistantMessage();
if (!msg) {
console.log('[ToolPreparing] 无法获取assistant消息');
return;
}
if (msg.awaitingFirstContent) {
console.log('[ToolPreparing] 关闭等待动画', {
beforeAwaitingFirstContent: msg.awaitingFirstContent,
beforeGeneratingLabel: msg.generatingLabel
});
msg.awaitingFirstContent = false;
msg.generatingLabel = '';
}
const action = {
id: data.id,
type: 'tool',
tool: {
id: data.id,
name: data.name,
arguments: {},
argumentSnapshot: null,
argumentLabel: '',
status: 'preparing',
result: null,
message: data.message || `准备调用 ${data.name}...`,
intent_full: data.intent || '',
intent_rendered: data.intent || ''
},
timestamp: Date.now()
};
msg.actions.push(action);
this.preparingTools.set(data.id, action);
this.toolRegisterAction(action, data.id);
this.toolTrackAction(data.name, action);
this.$forceUpdate();
this.conditionalScrollToBottom();
if (this.monitorPreviewTool) {
this.monitorPreviewTool(data);
}
},
handleToolStart(data: any) {
debugLog('[TaskPolling] 工具开始:', data.name);
if (this.dropToolEvents) {
return;
}
let action = null;
if (data.preparing_id && this.preparingTools.has(data.preparing_id)) {
action = this.preparingTools.get(data.preparing_id);
this.preparingTools.delete(data.preparing_id);
} else {
action = this.toolFindAction(data.id, data.preparing_id, data.execution_id);
}
if (!action) {
const msg = this.chatEnsureAssistantMessage();
if (!msg) {
return;
}
action = {
id: data.id,
type: 'tool',
tool: {
id: data.id,
name: data.name,
arguments: {},
argumentSnapshot: null,
argumentLabel: '',
status: 'running',
result: null
},
timestamp: Date.now()
};
msg.actions.push(action);
}
action.tool.status = 'running';
action.tool.arguments = data.arguments;
action.tool.argumentSnapshot = this.cloneToolArguments(data.arguments);
action.tool.argumentLabel = this.buildToolLabel(action.tool.argumentSnapshot);
action.tool.message = null;
action.tool.id = data.id;
action.tool.executionId = data.id;
this.toolRegisterAction(action, data.id);
this.toolTrackAction(data.name, action);
this.$forceUpdate();
this.conditionalScrollToBottom();
if (this.monitorQueueTool) {
this.monitorQueueTool(data);
}
},
handleToolIntent(data: any) {
debugLog('[TaskPolling] 工具意图:', data.name, data.intent);
if (this.dropToolEvents) {
return;
}
// 查找对应的工具 action
const action = this.toolFindAction(data.id, data.preparing_id, data.execution_id);
if (action && action.tool) {
const newIntent = data.intent || '';
// 如果 intent 没有变化,跳过
if (action.tool.intent_full === newIntent) {
return;
}
// 停止之前的打字机效果
if (action.tool._intentTyping) {
action.tool._intentTyping = false;
if (action.tool._intentTimer) {
clearTimeout(action.tool._intentTimer);
action.tool._intentTimer = null;
}
}
// 更新完整 intent
action.tool.intent_full = newIntent;
// 判断是否是历史恢复:只有从头重建时才直接显示
const isHistoryRestore = this._rebuildingFromScratch;
if (isHistoryRestore) {
// 历史恢复,直接显示完整 intent
action.tool.intent_rendered = newIntent;
debugLog('[TaskPolling] 历史恢复,直接显示 intent');
} else {
// 新工具块,逐字符显示
debugLog('[TaskPolling] 新工具块,开始打字机效果');
action.tool.intent_rendered = '';
action.tool._intentTyping = true;
// 计算每个字符的间隔时间
// 总时长1秒但最少0.5秒
const totalDuration = Math.max(500, Math.min(1000, newIntent.length * 50));
const charInterval = totalDuration / newIntent.length;
let charIndex = 0;
const typeNextChar = () => {
if (charIndex < newIntent.length && action.tool._intentTyping) {
action.tool.intent_rendered += newIntent[charIndex];
charIndex++;
this.$forceUpdate();
action.tool._intentTimer = setTimeout(typeNextChar, charInterval);
} else {
action.tool._intentTyping = false;
action.tool.intent_rendered = newIntent; // 确保完整
action.tool._intentTimer = null;
this.$forceUpdate();
}
};
action.tool._intentTimer = setTimeout(typeNextChar, 50); // 延迟50ms开始
}
// 更新 arguments 和 label
if (action.tool.arguments) {
action.tool.arguments.intent = newIntent;
action.tool.argumentSnapshot = this.cloneToolArguments(action.tool.arguments);
action.tool.argumentLabel = this.buildToolLabel(action.tool.argumentSnapshot);
}
this.$forceUpdate();
debugLog('[TaskPolling] 已更新工具意图:', data.name);
} else {
debugLog('[TaskPolling] 未找到对应的工具 action:', data.id);
}
},
handleToolUpdateAction(data: any) {
if (this.dropToolEvents) {
return;
}
debugLog('[TaskPolling] 更新action:', data.id, 'status:', data.status);
let targetAction = this.toolFindAction(data.id, data.preparing_id, data.execution_id);
if (!targetAction && data.preparing_id && this.preparingTools.has(data.preparing_id)) {
targetAction = this.preparingTools.get(data.preparing_id);
}
if (!targetAction) {
return;
}
if (data.status) {
targetAction.tool.status = data.status;
}
if (data.result !== undefined) {
targetAction.tool.result = data.result;
}
if (data.message !== undefined) {
targetAction.tool.message = data.message;
}
if (data.content !== undefined) {
targetAction.tool.content = data.content;
}
this.$forceUpdate();
this.conditionalScrollToBottom();
if (this.monitorResolveTool && data.status === 'completed') {
this.monitorResolveTool(data);
}
},
handleAppendPayload(data: any) {
debugLog('[TaskPolling] 文件追加:', data.path);
this.chatAddAppendPayloadAction(data);
this.$forceUpdate();
this.conditionalScrollToBottom();
},
handleModifyPayload(data: any) {
debugLog('[TaskPolling] 文件修改:', data.path);
this.chatAddModifyPayloadAction(data);
this.$forceUpdate();
this.conditionalScrollToBottom();
},
markLatestUserWorkCompleted() {
if (!Array.isArray(this.messages) || this.messages.length === 0) {
return;
}
for (let i = this.messages.length - 1; i >= 0; i -= 1) {
const msg = this.messages[i];
if (!msg || msg.role !== 'user') {
continue;
}
msg.metadata = msg.metadata || {};
msg.metadata.work_timer = msg.metadata.work_timer || {};
const timer = msg.metadata.work_timer;
if (timer.status === 'completed' && typeof timer.duration_ms === 'number') {
return;
}
const nowIso = new Date().toISOString();
const startedAt = timer.started_at || msg.timestamp || nowIso;
const startMs = Date.parse(startedAt);
const endMs = Date.now();
const durationMs = Number.isFinite(startMs) ? Math.max(0, endMs - startMs) : 0;
timer.status = 'completed';
timer.started_at = startedAt;
timer.finished_at = nowIso;
timer.duration_ms = durationMs;
return;
}
},
handleTaskComplete(data: any) {
const hasRunningSubAgents = !!data?.has_running_sub_agents;
if (hasRunningSubAgents) {
debugLog('[TaskPolling] 任务完成,但仍有后台子智能体运行');
} else {
debugLog('[TaskPolling] 任务完成');
}
// 同步处理状态更新
this.streamingMessage = false;
this.stopRequested = false;
if (!hasRunningSubAgents) {
this.markLatestUserWorkCompleted();
}
if (hasRunningSubAgents) {
this.taskInProgress = true;
this.waitingForSubAgent = true;
} else {
this.taskInProgress = false;
this.waitingForSubAgent = false;
this.clearTaskState(); // 清理任务状态
}
this.$forceUpdate();
// 只更新统计,不重新加载历史
if (this.currentConversationId) {
setTimeout(() => {
this.fetchConversationTokenStatistics();
this.updateCurrentContextTokens();
}, 500);
}
},
handleTaskStopped(data: any, eventIdx: number) {
debugLog('[TaskPolling] 任务已停止, idx:', eventIdx, data);
this.markLatestUserWorkCompleted();
this.streamingMessage = false;
this.taskInProgress = false;
this.stopRequested = false;
this.waitingForSubAgent = false;
if (typeof this.clearPendingTools === 'function') {
this.clearPendingTools('task_stopped');
}
this.$forceUpdate();
this.clearTaskState();
},
// 新增统一清理方法
clearTaskState() {
(async () => {
const { useTaskStore } = await import('../../stores/task');
const taskStore = useTaskStore();
taskStore.clearTask(); // 使用 clearTask 而不是 stopPolling
})();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
},
handleUserMessage(data: any) {
const message = (data?.message || data?.content || '').trim();
if (!message) {
return;
}
debugLog('[TaskPolling] 收到用户消息事件');
this.chatAddUserMessage(message, data?.images || [], data?.videos || []);
this.taskInProgress = true;
this.streamingMessage = false;
this.stopRequested = false;
if (data?.sub_agent_notice) {
if (typeof data?.has_running_sub_agents === 'boolean') {
this.waitingForSubAgent = data.has_running_sub_agents;
} else if (typeof data?.remaining_count === 'number') {
this.waitingForSubAgent = data.remaining_count > 0;
}
}
this.$forceUpdate();
this.conditionalScrollToBottom();
},
handleTaskError(data: any) {
const shouldRetry = Boolean(data?.retry);
if (shouldRetry) {
const retryIn = Number(data?.retry_in) || 5;
const attempt = Number(data?.attempt) || 1;
const maxAttempts = Number(data?.max_attempts) || attempt;
debugLog('[TaskPolling] API错误等待自动重试', {
retryIn,
attempt,
maxAttempts,
message: data?.message
});
this.stopRequested = false;
this.taskInProgress = true;
this.streamingMessage = true;
this.$forceUpdate();
this.uiPushToast({
title: '即将重试',
message: `将在 ${retryIn} 秒后重试(第 ${attempt}/${maxAttempts} 次)`,
type: 'info',
duration: Math.max(retryIn, 1) * 1000
});
return;
}
const errorMessage = data.message || '未知错误';
const errorType = data.error_type || 'unknown';
let title = '任务执行失败';
let message = errorMessage;
// 根据错误类型提供友好提示
if (errorType === 'api_error') {
title = 'API 调用失败';
message = `模型服务异常:${errorMessage}`;
} else if (errorType === 'timeout') {
title = '任务超时';
message = '任务执行时间过长,已自动停止';
} else if (errorType === 'quota_exceeded') {
title = '配额不足';
message = '您的使用配额已用尽';
}
console.error('[TaskPolling] 任务错误:', data.message);
this.uiPushToast({
title,
message,
type: 'error',
duration: 8000
});
// 清理状态
this.markLatestUserWorkCompleted();
this.streamingMessage = false;
this.taskInProgress = false;
this.stopRequested = false;
this.$forceUpdate();
// 停止轮询
(async () => {
const { useTaskStore } = await import('../../stores/task');
const taskStore = useTaskStore();
taskStore.stopPolling();
})();
},
handleTokenUpdate(data: any) {
if (data.conversation_id === this.currentConversationId) {
this.currentConversationTokens.cumulative_input_tokens = data.cumulative_input_tokens || 0;
this.currentConversationTokens.cumulative_output_tokens = data.cumulative_output_tokens || 0;
this.currentConversationTokens.cumulative_total_tokens = data.cumulative_total_tokens || 0;
if (typeof data.current_context_tokens === 'number') {
this.resourceSetCurrentContextTokens(data.current_context_tokens);
} else {
this.updateCurrentContextTokens();
}
this.$forceUpdate();
}
},
handleConversationChanged(data: any, eventIdx: number) {
debugLog('[TaskPolling] 对话标题已更新, idx:', eventIdx, data);
if (data && data.conversation_id === this.currentConversationId) {
// 更新当前对话标题
if (data.title) {
this.currentConversationTitle = data.title;
debugLog('[TaskPolling] 更新当前对话标题:', data.title);
}
// 更新对话列表中的标题
if (data.title && Array.isArray(this.conversations)) {
const conv = this.conversations.find(c => c && c.id === data.conversation_id);
if (conv) {
conv.title = data.title;
debugLog('[TaskPolling] 更新对话列表中的标题:', data.title);
}
}
this.$forceUpdate();
}
},
handleConversationResolved(data: any) {
if (data && data.conversation_id) {
this.currentConversationId = data.conversation_id;
if (data.title) {
this.currentConversationTitle = data.title;
}
this.promoteConversationToTop(data.conversation_id);
const pathFragment = this.stripConversationPrefix(data.conversation_id);
const currentPath = window.location.pathname.replace(/^\/+/, '');
if (data.created) {
history.pushState({ conversationId: data.conversation_id }, '', `/${pathFragment}`);
} else if (currentPath !== pathFragment) {
history.replaceState({ conversationId: data.conversation_id }, '', `/${pathFragment}`);
}
}
},
/**
* 清理已处理的事件索引
*/
clearProcessedEvents() {
if (this._processedEventIndices) {
this._processedEventIndices.clear();
}
},
/**
* 恢复任务状态(页面刷新后调用)
*/
async restoreTaskState() {
// 清理已处理的事件索引
this.clearProcessedEvents();
try {
const { useTaskStore } = await import('../../stores/task');
const taskStore = useTaskStore();
// 如果已经在流式输出中,不重复恢复
if (this.streamingMessage || this.taskInProgress) {
debugLog('[TaskPolling] 任务已在进行中,跳过恢复', {
streamingMessage: this.streamingMessage,
taskInProgress: this.taskInProgress,
currentConversationId: this.currentConversationId
});
return;
}
// 查找运行中的任务
const runningTask = await taskStore.loadRunningTask(this.currentConversationId);
if (!runningTask) {
debugLog('[TaskPolling] 没有运行中的任务', {
currentConversationId: this.currentConversationId
});
await this.restoreSubAgentWaitingState();
return;
}
debugLog('[TaskPolling] 发现运行中的任务,开始恢复状态', {
taskId: runningTask?.task_id,
status: runningTask?.status,
conversationId: runningTask?.conversation_id
});
// 检查历史是否已加载
const hasMessages = Array.isArray(this.messages) && this.messages.length > 0;
if (!hasMessages) {
debugLog('[TaskPolling] 历史未加载,等待历史加载完成');
setTimeout(() => {
this.restoreTaskState();
}, 500);
return;
}
debugLog('[TaskPolling] 历史已加载,开始精细恢复');
// 获取任务的所有事件
const detailResponse = await fetch(`/api/tasks/${taskStore.currentTaskId}`);
if (!detailResponse.ok) {
debugLog('[TaskPolling] 获取任务详情失败');
return;
}
const detailResult = await detailResponse.json();
if (!detailResult.success || !detailResult.data.events) {
debugLog('[TaskPolling] 任务详情无效');
return;
}
const allEvents = detailResult.data.events;
debugLog(`[TaskPolling] 获取到 ${allEvents.length} 个事件`);
// 找到最后一条消息
const lastMessage = this.messages[this.messages.length - 1];
const isAssistantMessage = lastMessage && lastMessage.role === 'assistant';
// console.log('[TaskPolling] 最后一条消息:', {
// exists: !!lastMessage,
// role: lastMessage?.role,
// actionsCount: lastMessage?.actions?.length || 0,
// isAssistant: isAssistantMessage
// });
// 检查是否需要从头重建
// 1. 最后一条不是 assistant 消息
// 2. 最后一条是空的 assistant 消息
// 3. 事件数量远大于历史中的 actions 数量(说明历史不完整)
const historyActionsCount = lastMessage?.actions?.length || 0;
const eventCount = allEvents.length;
const historyIncomplete = eventCount > historyActionsCount + 5; // 允许5个事件的误差
const needsRebuild = !isAssistantMessage ||
(isAssistantMessage && (!lastMessage.actions || lastMessage.actions.length === 0)) ||
historyIncomplete;
if (needsRebuild) {
// if (historyIncomplete) {
// console.log('[TaskPolling] 历史不完整,从头重建:', {
// historyActionsCount,
// eventCount,
// diff: eventCount - historyActionsCount
// });
// }
debugLog('[TaskPolling] 需要从头重建 assistant 响应');
// 清空所有消息,准备从头重建
// 保留用户消息,只删除最后的 assistant 消息
if (isAssistantMessage) {
debugLog('[TaskPolling] 删除不完整的 assistant 消息');
this.messages.pop();
}
this.streamingMessage = true;
this.taskInProgress = true;
this.$forceUpdate();
// 重置偏移量为 0从头获取所有事件来重建 assistant 消息
taskStore.lastEventIndex = 0;
debugLog('[TaskPolling] 重置偏移量为 0从头开始轮询');
// 标记正在从头重建,用于后续处理
this._rebuildingFromScratch = true;
this._rebuildingEventCount = allEvents.length; // 记录当前事件总数
(window as any).__taskEventHandler = (event: any) => {
this.handleTaskEvent(event);
};
taskStore.startPolling((event: any) => {
this.handleTaskEvent(event);
});
// 延迟清除重建标记,确保所有历史事件都处理完毕
setTimeout(() => {
debugLog('[TaskPolling] 历史事件处理完毕,清除重建标记');
this._rebuildingFromScratch = false;
this._rebuildingEventCount = 0;
}, 2000);
return;
}
// 分析事件,找到当前正在进行的操作
let inThinking = false;
let inText = false;
for (let i = 0; i < allEvents.length; i++) {
const event = allEvents[i];
if (event.type === 'thinking_start') {
inThinking = true;
} else if (event.type === 'thinking_end') {
inThinking = false;
}
if (event.type === 'text_start') {
inText = true;
} else if (event.type === 'text_end') {
inText = false;
}
}
// console.log('[TaskPolling] 分析结果:', {
// inThinking,
// inText,
// totalEvents: allEvents.length,
// lastEventType: allEvents[allEvents.length - 1]?.type,
// lastEventIdx: allEvents[allEvents.length - 1]?.idx
// });
// 恢复思考块状态
if (lastMessage.actions) {
// console.log('[TaskPolling] 历史中的 actions 详情:', lastMessage.actions.map((a, idx) => ({
// index: idx,
// type: a.type,
// id: a.id,
// hasContent: !!a.content,
// contentLength: a.content?.length || 0,
// toolName: a.tool?.name,
// hasBlockId: !!a.blockId,
// blockId: a.blockId,
// collapsed: a.collapsed,
// streaming: a.streaming
// })));
const thinkingActions = lastMessage.actions.filter(a => a.type === 'thinking');
// console.log('[TaskPolling] 思考块数量:', thinkingActions.length);
if (inThinking && thinkingActions.length > 0) {
// 正在思考中,检查最后一个思考块是否正在流式输出
const lastThinking = thinkingActions[thinkingActions.length - 1];
// 只有当思考块正在流式输出时才设置锁定状态(但不展开)
if (lastThinking.streaming && lastThinking.blockId) {
// console.log('[TaskPolling] 找到正在流式输出的思考块,设置锁定状态:', lastThinking.blockId);
this.$nextTick(() => {
this.chatSetThinkingLock(lastThinking.blockId, true);
});
}
}
// 确保所有思考块都是折叠状态
for (const thinking of thinkingActions) {
if (thinking.blockId) {
thinking.collapsed = true;
}
}
// 检查思考块状态(在设置之后)(已禁用)
// thinkingActions.forEach((thinking, idx) => {
// console.log(`[TaskPolling] 思考块 ${idx} (设置后):`, {
// hasBlockId: !!thinking.blockId,
// blockId: thinking.blockId,
// collapsed: thinking.collapsed,
// contentLength: thinking.content?.length || 0
// });
// });
// 恢复文本块状态
const textActions = lastMessage.actions.filter(a => a.type === 'text');
// console.log('[TaskPolling] 文本块数量:', textActions.length);
if (inText && textActions.length > 0) {
const lastText = textActions[textActions.length - 1];
// console.log('[TaskPolling] 标记文本块为流式状态');
lastText.streaming = true;
}
// 注册历史中的工具块到 toolActionIndex
// 这样后续的 update_action 事件可以找到对应的块进行状态更新
const toolActions = lastMessage.actions.filter(a => a.type === 'tool');
// console.log('[TaskPolling] 工具块数量:', toolActions.length);
for (const toolAction of toolActions) {
if (toolAction.tool && toolAction.tool.id) {
// console.log('[TaskPolling] 注册工具块:', {
// id: toolAction.tool.id,
// name: toolAction.tool.name,
// status: toolAction.tool.status
// });
// 注册到 toolActionIndex
this.toolRegisterAction(toolAction, toolAction.tool.id);
// 如果有 executionId也注册
if (toolAction.tool.executionId) {
this.toolRegisterAction(toolAction, toolAction.tool.executionId);
}
// 追踪工具调用
if (toolAction.tool.name) {
this.toolTrackAction(toolAction.tool.name, toolAction);
}
}
}
}
// 标记状态为进行中
this.streamingMessage = true;
this.taskInProgress = true;
// 设置 currentMessageIndex 指向最后一条 assistant 消息
// 这样后续添加的 action 会添加到正确的消息中
const lastMessageIndex = this.messages.length - 1;
if (lastMessage && lastMessage.role === 'assistant') {
this.currentMessageIndex = lastMessageIndex;
// console.log('[TaskPolling] 设置 currentMessageIndex 为:', lastMessageIndex);
}
// 强制更新界面
this.$forceUpdate();
// 滚动到底部
this.$nextTick(() => {
this.conditionalScrollToBottom();
});
// 注册事件处理器到全局
(window as any).__taskEventHandler = (event: any) => {
this.handleTaskEvent(event);
};
// 启动轮询(从当前偏移量开始,只处理新事件)
debugLog('[TaskPolling] 启动轮询,起始偏移量:', taskStore.lastEventIndex);
taskStore.startPolling((event: any) => {
this.handleTaskEvent(event);
});
this.uiPushToast({
title: '任务恢复',
message: '检测到进行中的任务,已恢复连接',
type: 'info',
duration: 3000
});
} catch (error) {
console.error('[TaskPolling] 恢复任务状态失败:', error);
}
},
/**
* 恢复子智能体等待状态(页面刷新后调用)
*/
async restoreSubAgentWaitingState(retry = 0) {
try {
if (!this.currentConversationId) {
if (retry < 5) {
setTimeout(() => {
this.restoreSubAgentWaitingState(retry + 1);
}, 300);
}
return;
}
const response = await fetch('/api/sub_agents');
if (!response.ok) {
debugLog('[TaskPolling] 获取子智能体状态失败');
return;
}
const result = await response.json();
if (!result.success) {
debugLog('[TaskPolling] 子智能体状态响应无效');
return;
}
const tasks = Array.isArray(result.data) ? result.data : [];
debugLog('[TaskPolling] 子智能体状态响应', {
total: tasks.length,
currentConversationId: this.currentConversationId,
tasks: tasks.map((task: any) => ({
task_id: task?.task_id,
status: task?.status,
run_in_background: task?.run_in_background,
notice_pending: task?.notice_pending,
conversation_id: task?.conversation_id
}))
});
const terminalStatuses = new Set(['completed', 'failed', 'timeout', 'terminated']);
const relevant = tasks.filter((task: any) => {
if (task && task.conversation_id && task.conversation_id !== this.currentConversationId) {
return false;
}
return true;
});
const running = relevant.filter((task: any) => task?.run_in_background && !terminalStatuses.has(task?.status));
const pendingNotice = relevant.filter((task: any) => task?.notice_pending);
if (running.length || pendingNotice.length) {
debugLog('[TaskPolling] 恢复子智能体等待状态', {
running: running.length,
pendingNotice: pendingNotice.length,
runningTasks: running.map((task: any) => ({ task_id: task?.task_id, status: task?.status })),
pendingTasks: pendingNotice.map((task: any) => ({ task_id: task?.task_id, status: task?.status }))
});
this.waitingForSubAgent = true;
this.taskInProgress = true;
this.streamingMessage = false;
this.stopRequested = false;
this.$forceUpdate();
}
} catch (error) {
console.error('[TaskPolling] 恢复子智能体等待状态失败:', error);
}
},
};