agent-Specialization/static/src/app/methods/history.ts
JOJO cd68812743 fix: 修复页面刷新时思考块自动展开的问题
- 在历史加载时给思考块设置 collapsed: true 默认折叠
- 在任务恢复时跳过思考块的展开/折叠动画
- 延迟清除 _rebuildingFromScratch 标记,确保所有历史事件处理完毕
- 删除过早清除重建标记的逻辑,避免后续思考块被展开

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-09 16:31:00 +08:00

400 lines
19 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';
export const historyMethods = {
// ==========================================
// 关键功能:获取并显示历史对话内容
// ==========================================
async fetchAndDisplayHistory(options = {}) {
const { force = false } = options as { force?: boolean };
const targetConversationId = this.currentConversationId;
if (!targetConversationId || targetConversationId.startsWith('temp_')) {
debugLog('没有当前对话ID跳过历史加载');
this.refreshBlankHeroState();
return;
}
// 若同一对话正在加载,直接复用;若是切换对话则允许并发但后来的请求会赢
if (this.historyLoading && this.historyLoadingFor === targetConversationId) {
debugLog('同一对话历史正在加载,跳过重复请求');
return;
}
// 已经有完整历史且非强制刷新时,避免重复加载导致动画播放两次
const alreadyHydrated =
!force &&
this.lastHistoryLoadedConversationId === targetConversationId &&
Array.isArray(this.messages) &&
this.messages.length > 0;
if (alreadyHydrated) {
debugLog('历史已加载,跳过重复请求');
this.logMessageState('fetchAndDisplayHistory:skip-duplicate', {
conversationId: targetConversationId
});
return;
}
const loadSeq = ++this.historyLoadSeq;
this.historyLoading = true;
this.historyLoadingFor = targetConversationId;
try {
debugLog('开始获取历史对话内容...');
this.logMessageState('fetchAndDisplayHistory:start', { conversationId: this.currentConversationId });
try {
// 使用专门的API获取对话消息历史
const messagesResponse = await fetch(`/api/conversations/${targetConversationId}/messages`);
if (!messagesResponse.ok) {
console.warn('无法获取消息历史,尝试备用方法');
// 备用方案通过状态API获取
const statusResponse = await fetch('/api/status');
const status = await statusResponse.json();
debugLog('系统状态:', status);
this.applyStatusSnapshot(status);
// 如果状态中有对话历史字段
if (status.conversation_history && Array.isArray(status.conversation_history)) {
this.renderHistoryMessages(status.conversation_history);
return;
}
debugLog('备用方案也无法获取历史消息');
return;
}
// 如果在等待期间用户已切换到其他对话,则丢弃结果
if (loadSeq !== this.historyLoadSeq || this.currentConversationId !== targetConversationId) {
debugLog('检测到对话已切换,丢弃过期的历史加载结果');
return;
}
const messagesData = await messagesResponse.json();
debugLog('获取到消息数据:', messagesData);
if (messagesData.success && messagesData.data && messagesData.data.messages) {
const messages = messagesData.data.messages;
debugLog(`发现 ${messages.length} 条历史消息`);
if (messages.length > 0) {
// 清空当前显示的消息
this.logMessageState('fetchAndDisplayHistory:before-clear-existing');
this.messages = [];
this.logMessageState('fetchAndDisplayHistory:after-clear-existing');
// 渲染历史消息 - 这是关键功能
this.renderHistoryMessages(messages);
// 滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
});
debugLog('历史对话内容显示完成');
} else {
debugLog('对话存在但没有历史消息');
this.logMessageState('fetchAndDisplayHistory:no-history-clear');
this.messages = [];
this.logMessageState('fetchAndDisplayHistory:no-history-cleared');
}
} else {
debugLog('消息数据格式不正确:', messagesData);
this.logMessageState('fetchAndDisplayHistory:invalid-data-clear');
this.messages = [];
this.logMessageState('fetchAndDisplayHistory:invalid-data-cleared');
}
} catch (error) {
console.error('获取历史对话失败:', error);
debugLog('尝试不显示错误弹窗,仅在控制台记录');
// 不显示alert避免打断用户体验
this.logMessageState('fetchAndDisplayHistory:error-clear', { error: error?.message || String(error) });
this.messages = [];
this.logMessageState('fetchAndDisplayHistory:error-cleared');
}
} finally {
// 仅在本次加载仍是最新请求时清除 loading 状态
if (loadSeq === this.historyLoadSeq) {
this.historyLoading = false;
this.historyLoadingFor = null;
}
this.refreshBlankHeroState();
}
},
// ==========================================
// 关键功能:渲染历史消息
// ==========================================
renderHistoryMessages(historyMessages) {
debugLog('开始渲染历史消息...', historyMessages);
debugLog('历史消息数量:', historyMessages.length);
this.logMessageState('renderHistoryMessages:start', { historyCount: historyMessages.length });
if (!Array.isArray(historyMessages)) {
console.error('历史消息不是数组格式');
return;
}
let currentAssistantMessage = null;
let historyHasImages = false;
let historyHasVideos = false;
historyMessages.forEach((message, index) => {
debugLog(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message);
const meta = message.metadata || {};
if (message.role === 'user' && (meta.system_injected_image || meta.system_injected_video)) {
debugLog('跳过系统代发的图片/视频消息(仅用于模型查看,不在前端展示)');
return;
}
if (message.role === 'user') {
// 用户消息 - 先结束之前的assistant消息
if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
this.messages.push(currentAssistantMessage);
currentAssistantMessage = null;
}
const images = message.images || (message.metadata && message.metadata.images) || [];
const videos = message.videos || (message.metadata && message.metadata.videos) || [];
if (Array.isArray(images) && images.length) {
historyHasImages = true;
}
if (Array.isArray(videos) && videos.length) {
historyHasVideos = true;
}
this.messages.push({
role: 'user',
content: message.content || '',
images,
videos
});
debugLog('添加用户消息:', message.content?.substring(0, 50) + '...');
} else if (message.role === 'assistant') {
// AI消息 - 如果没有当前assistant消息创建一个
if (!currentAssistantMessage) {
currentAssistantMessage = {
role: 'assistant',
actions: [],
streamingThinking: '',
streamingText: '',
currentStreamingType: null,
activeThinkingId: null,
awaitingFirstContent: false,
generatingLabel: ''
};
}
const content = message.content || '';
const reasoningText = (message.reasoning_content || '').trim();
if (reasoningText) {
const blockId = `history-thinking-${Date.now()}-${Math.random().toString(36).slice(2)}`;
currentAssistantMessage.actions.push({
id: `history-think-${Date.now()}-${Math.random()}`,
type: 'thinking',
content: reasoningText,
streaming: false,
collapsed: true,
timestamp: Date.now(),
blockId
});
debugLog('添加思考内容:', reasoningText.substring(0, 50) + '...');
}
// 处理普通文本内容(移除思考标签后的内容)
const metadata = message.metadata || {};
const appendPayloadMeta = metadata.append_payload;
const modifyPayloadMeta = metadata.modify_payload;
const isAppendMessage = message.name === 'append_to_file';
const isModifyMessage = message.name === 'modify_file';
const containsAppendMarkers = /<<<\s*(APPEND|MODIFY)/i.test(content || '') || /<<<END_\s*(APPEND|MODIFY)>>>/i.test(content || '');
const textContent = content.trim();
if (appendPayloadMeta) {
currentAssistantMessage.actions.push({
id: `history-append-payload-${Date.now()}-${Math.random()}`,
type: 'append_payload',
append: {
path: appendPayloadMeta.path || '未知文件',
forced: !!appendPayloadMeta.forced,
success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success,
lines: appendPayloadMeta.lines ?? null,
bytes: appendPayloadMeta.bytes ?? null
},
timestamp: Date.now()
});
debugLog('添加append占位信息:', appendPayloadMeta.path);
} else if (modifyPayloadMeta) {
currentAssistantMessage.actions.push({
id: `history-modify-payload-${Date.now()}-${Math.random()}`,
type: 'modify_payload',
modify: {
path: modifyPayloadMeta.path || '未知文件',
total: modifyPayloadMeta.total_blocks ?? null,
completed: modifyPayloadMeta.completed || [],
failed: modifyPayloadMeta.failed || [],
forced: !!modifyPayloadMeta.forced,
details: modifyPayloadMeta.details || []
},
timestamp: Date.now()
});
debugLog('添加modify占位信息:', modifyPayloadMeta.path);
}
if (textContent && !appendPayloadMeta && !modifyPayloadMeta && !isAppendMessage && !isModifyMessage && !containsAppendMarkers) {
currentAssistantMessage.actions.push({
id: `history-text-${Date.now()}-${Math.random()}`,
type: 'text',
content: textContent,
streaming: false,
timestamp: Date.now()
});
debugLog('添加文本内容:', textContent.substring(0, 50) + '...');
}
// 处理工具调用
if (message.tool_calls && Array.isArray(message.tool_calls)) {
message.tool_calls.forEach((toolCall, tcIndex) => {
let arguments_obj = {};
try {
arguments_obj = typeof toolCall.function.arguments === 'string'
? JSON.parse(toolCall.function.arguments || '{}')
: (toolCall.function.arguments || {});
} catch (e) {
console.warn('解析工具参数失败:', e);
arguments_obj = {};
}
const action = {
id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`,
type: 'tool',
tool: {
id: toolCall.id,
name: toolCall.function.name,
arguments: arguments_obj,
argumentSnapshot: this.cloneToolArguments(arguments_obj),
argumentLabel: this.buildToolLabel(arguments_obj),
intent_full: arguments_obj.intent || '',
intent_rendered: arguments_obj.intent || '',
status: 'preparing',
result: null
},
timestamp: Date.now()
};
// 如果是历史加载的动作且状态仍为进行中,标记为 stale避免刷新后按钮卡死
if (['preparing', 'running', 'awaiting_content'].includes(action.tool.status)) {
action.tool.status = 'stale';
action.tool.awaiting_content = false;
action.streaming = false;
}
currentAssistantMessage.actions.push(action);
debugLog('添加工具调用:', toolCall.function.name);
});
}
} else if (message.role === 'tool') {
// 工具结果 - 更新当前assistant消息中对应的工具
if (currentAssistantMessage) {
// 查找对应的工具action - 使用更灵活的匹配
let toolAction = null;
// 优先按tool_call_id匹配
if (message.tool_call_id) {
toolAction = currentAssistantMessage.actions.find(action =>
action.type === 'tool' &&
action.tool.id === message.tool_call_id
);
}
// 如果找不到按name匹配最后一个同名工具
if (!toolAction && message.name) {
const sameNameTools = currentAssistantMessage.actions.filter(action =>
action.type === 'tool' &&
action.tool.name === message.name
);
toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个
}
if (toolAction) {
// 解析工具结果优先使用JSON其次使用元数据的 tool_payload以保证搜索结果在刷新后仍可展示
let result;
try {
result = JSON.parse(message.content);
} catch (e) {
if (message.metadata && message.metadata.tool_payload) {
result = message.metadata.tool_payload;
} else {
result = {
output: message.content,
success: true
};
}
}
toolAction.tool.status = 'completed';
toolAction.tool.result = result;
if (result && typeof result === 'object') {
if (result.error) {
toolAction.tool.message = result.error;
} else if (result.message && !toolAction.tool.message) {
toolAction.tool.message = result.message;
}
}
if (message.name === 'append_to_file' && result && result.message) {
toolAction.tool.message = result.message;
}
debugLog(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`);
// append_to_file 的摘要在 append_payload 占位中呈现,此处无需重复
} else {
console.warn('找不到对应的工具调用:', message.name, message.tool_call_id);
}
}
} else {
// 其他类型消息如system- 先结束当前assistant消息
if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
this.messages.push(currentAssistantMessage);
currentAssistantMessage = null;
}
debugLog('处理其他类型消息:', message.role);
this.messages.push({
role: message.role,
content: message.content || ''
});
}
});
// 处理最后一个assistant消息
if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) {
this.messages.push(currentAssistantMessage);
}
this.conversationHasImages = historyHasImages;
this.conversationHasVideos = historyHasVideos;
debugLog(`历史消息渲染完成,共 ${this.messages.length} 条消息`);
this.logMessageState('renderHistoryMessages:after-render');
this.lastHistoryLoadedConversationId = this.currentConversationId || null;
// 强制更新视图
this.$forceUpdate();
// 确保滚动到底部
this.$nextTick(() => {
this.scrollToBottom();
setTimeout(() => {
const blockCount = this.$el && this.$el.querySelectorAll
? this.$el.querySelectorAll('.message-block').length
: 'N/A';
debugLog('[Messages] DOM 渲染统计', {
blocks: blockCount,
conversationId: this.currentConversationId
});
}, 0);
});
}
};