fix(chat): recover assistant placeholder after stop and switch

This commit is contained in:
JOJO 2026-04-04 19:47:03 +08:00
parent 0296418cff
commit 8d5a034c70
5 changed files with 237 additions and 47 deletions

View File

@ -4,6 +4,8 @@ import { debugLog, traceLog } from './common';
export const conversationMethods = {
// 完整重置所有状态
resetAllStates(reason = 'unspecified', options: { preserveMonitorWindows?: boolean } = {}) {
console.log('[DEBUG_AWAITING] ===== resetAllStates =====', { reason });
// 如果正在等待子智能体完成,不重置任务状态
if (this.waitingForSubAgent) {
debugLog('跳过状态重置:正在等待子智能体完成', { reason });
@ -30,18 +32,44 @@ export const conversationMethods = {
// 清理工具状态
this.toolResetTracking();
// 新增:将所有未完成的工具标记为已完成
this.messages.forEach(msg => {
if (msg.role === 'assistant' && msg.actions) {
msg.actions.forEach(action => {
if (action.type === 'tool' &&
(action.tool.status === 'preparing' || action.tool.status === 'running')) {
action.tool.status = 'completed';
}
});
// 新增将所有未完成的工具标记为已完成并清理awaitingFirstContent状态
const assistantMsgsBefore = this.messages.filter(m => m.role === 'assistant').map(m => ({
awaitingFirstContent: m.awaitingFirstContent,
generatingLabel: m.generatingLabel
}));
this.messages.forEach((msg) => {
if (msg.role === 'assistant') {
// 清理等待动画状态
if (msg.awaitingFirstContent) {
msg.awaitingFirstContent = false;
}
if (msg.generatingLabel) {
msg.generatingLabel = '';
}
// 清理工具状态
if (msg.actions) {
msg.actions.forEach(action => {
if (action.type === 'tool' &&
(action.tool.status === 'preparing' || action.tool.status === 'running')) {
action.tool.status = 'completed';
}
});
}
}
});
const assistantMsgsAfter = this.messages.filter(m => m.role === 'assistant').map(m => ({
awaitingFirstContent: m.awaitingFirstContent,
generatingLabel: m.generatingLabel
}));
console.log('[DEBUG_AWAITING] resetAllStates 清理完成', {
before: assistantMsgsBefore,
after: assistantMsgsAfter
});
// 清理Markdown缓存
if (this.markdownCache) {
this.markdownCache.clear();
@ -232,6 +260,9 @@ export const conversationMethods = {
if (taskStore.hasActiveTask) {
await taskStore.cancelTask();
taskStore.clearTask();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
}
if (this.waitingForSubAgent && !taskStore.hasActiveTask) {
@ -317,6 +348,8 @@ export const conversationMethods = {
},
async createNewConversation() {
console.log('[DEBUG_AWAITING] ===== createNewConversation =====');
debugLog('创建新对话...');
traceLog('createNewConversation:start', {
currentConversationId: this.currentConversationId,
@ -383,6 +416,9 @@ export const conversationMethods = {
if (taskStore.hasActiveTask) {
await taskStore.cancelTask();
taskStore.clearTask();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
}
// 重置任务相关状态

View File

@ -179,7 +179,7 @@ export const historyMethods = {
streamingText: '',
currentStreamingType: null,
activeThinkingId: null,
awaitingFirstContent: false,
awaitingFirstContent: false, // 历史消息不应该显示等待动画
generatingLabel: ''
};
}

View File

@ -110,6 +110,8 @@ export const messageMethods = {
},
async sendMessage() {
console.log('[DEBUG_AWAITING] ===== sendMessage =====');
if (this.streamingUi) {
return;
}
@ -204,6 +206,9 @@ export const messageMethods = {
try {
const { useTaskStore } = await import('../../stores/task');
const taskStore = useTaskStore();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
await taskStore.createTask(message, images, videos, this.currentConversationId);
@ -252,6 +257,8 @@ export const messageMethods = {
// 新增:停止任务方法
async stopTask() {
console.log('[DEBUG_AWAITING] ===== stopTask =====');
const canStop = this.composerBusy && !this.stopRequested;
if (!canStop) {
return;
@ -277,6 +284,31 @@ export const messageMethods = {
this.streamingMessage = false;
this.taskInProgress = false;
this.forceUnlockMonitor('user_stop');
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
// 清理assistant消息的等待动画状态
const lastMessage = this.messages[this.messages.length - 1];
const before = {
hasLastMessage: !!lastMessage,
role: lastMessage?.role,
awaitingFirstContent: lastMessage?.awaitingFirstContent,
generatingLabel: lastMessage?.generatingLabel
};
if (lastMessage && lastMessage.role === 'assistant') {
lastMessage.awaitingFirstContent = false;
lastMessage.generatingLabel = '';
}
console.log('[DEBUG_AWAITING] stopTask 清理完成', {
before,
after: {
awaitingFirstContent: lastMessage?.awaitingFirstContent,
generatingLabel: lastMessage?.generatingLabel
}
});
} catch (error) {
console.error('[Message] 取消任务失败:', error);
@ -285,6 +317,16 @@ export const messageMethods = {
this.streamingMessage = false;
this.taskInProgress = false;
this.forceUnlockMonitor('user_stop');
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
// 清理assistant消息的等待动画状态
const lastMessage = this.messages[this.messages.length - 1];
if (lastMessage && lastMessage.role === 'assistant') {
lastMessage.awaitingFirstContent = false;
lastMessage.generatingLabel = '';
}
this.uiPushToast({
title: '停止失败',
@ -388,6 +430,9 @@ export const messageMethods = {
try {
const { useTaskStore } = await import('../../stores/task');
const taskStore = useTaskStore();
if (typeof this.clearProcessedEvents === 'function') {
this.clearProcessedEvents();
}
await taskStore.createTask(message, [], [], this.currentConversationId);
} catch (error) {
console.error('[Message] 自动消息创建任务失败:', error);

View File

@ -42,6 +42,12 @@ export const taskPollingMethods = {
// 如果不匹配,忽略该事件(避免切换对话后旧任务的事件显示到新对话中)
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;
}
@ -49,6 +55,13 @@ export const taskPollingMethods = {
debugLog(`[TaskPolling] 处理事件 #${eventIdx}: ${eventType}`, eventData);
console.log(`[DEBUG_AWAITING] 处理事件`, {
eventType,
eventIdx,
eventConversationId: eventData.conversation_id,
currentConversationId: this.currentConversationId
});
// 根据事件类型调用对应的处理方法
switch (eventType) {
case 'ai_message_start':
@ -137,25 +150,82 @@ export const taskPollingMethods = {
},
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 lastMessage = this.messages[this.messages.length - 1];
// 检查是否已经有 assistant 消息
const hasAssistantMessage = lastMessage && lastMessage.role === 'assistant';
if (hasAssistantMessage) {
debugLog('[TaskPolling] 已有 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 消息,创建新的
// 其他情况:创建新的 assistant 消息
console.log('[AiMessageStart] 场景=创建新消息');
debugLog('[TaskPolling] 创建新的 assistant 消息');
this.monitorResetSpeech();
this.cleanupStaleToolActions();
@ -164,66 +234,71 @@ export const taskPollingMethods = {
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) {
const newMessage = this.messages[this.messages.length - 1];
if (newMessage && newMessage.role === 'assistant') {
debugLog('[TaskPolling] 标记消息为静默恢复(从头重建)');
newMessage.awaitingFirstContent = false;
newMessage.generatingLabel = '';
console.log('[AiMessageStart] 从头重建,关闭等待动画');
}
}
this.scrollToBottom();
// 强制触发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();
// 获取当前 actions 数量,用于调试(已禁用)
// const lastMessage = this.messages[this.messages.length - 1];
// const beforeCount = lastMessage?.actions?.length || 0;
// console.log('[TaskPolling] 添加思考块前actions 数量:', beforeCount);
// console.log('[TaskPolling] 当前 currentMessageIndex:', this.currentMessageIndex);
// console.log('[TaskPolling] messages 总数:', this.messages.length);
// console.log('[TaskPolling] 最后一条消息 role:', lastMessage?.role);
const result = this.chatStartThinkingAction();
// console.log('[TaskPolling] chatStartThinkingAction 返回:', result);
if (result && result.blockId) {
const blockId = result.blockId;
// 检查新添加的 action已禁用
// const afterCount = lastMessage?.actions?.length || 0;
// console.log('[TaskPolling] 添加思考块后actions 数量:', afterCount);
// 检查 currentMessageIndex 指向的消息(已禁用)
// const currentMsg = this.messages[this.currentMessageIndex];
// if (currentMsg) {
// console.log('[TaskPolling] currentMessageIndex 指向的消息 actions 数量:', currentMsg.actions?.length || 0);
// }
// if (afterCount > beforeCount) {
// const newAction = lastMessage.actions[afterCount - 1];
// console.log('[TaskPolling] 新添加的思考块:', {
// type: newAction.type,
// blockId: newAction.blockId,
// hasId: !!newAction.id,
// timestamp: newAction.timestamp
// });
// }
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);
@ -287,8 +362,29 @@ export const taskPollingMethods = {
},
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();
},
@ -313,18 +409,25 @@ export const taskPollingMethods = {
},
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 = '';
}

View File

@ -179,6 +179,12 @@ export const useChatStore = defineStore('chat', {
this.currentMessageIndex = this.messages.length - 1;
this.streamingMessage = true;
message.awaitingFirstContent = true;
console.log('[DEBUG_AWAITING] ===== startAssistantMessage =====', {
awaitingFirstContent: message.awaitingFirstContent,
generatingLabel: message.generatingLabel
});
return message;
},
startThinkingAction() {