fix: 修复页面加载时出现两次动画的问题

问题原因:
- Socket 事件(conversation_changed, status_update 等)在初始化期间与 bootstrapRoute 并发执行
- loadConversationsList 在初始化期间自动加载第一个对话
- 导致 currentConversationId 被多次设置,触发多次历史加载和动画

解决方案:
- 在 Socket 事件处理中添加 initialRouteResolved 检查
- 初始化完成前,Socket 事件不修改 currentConversationId
- loadConversationsList 在初始化完成前不自动加载对话
- 确保所有数据(历史、文件树、思考模式等)在同一时间加载完成

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
JOJO 2026-03-09 16:59:56 +08:00
parent cd68812743
commit a4d3160b0a
3 changed files with 53 additions and 35 deletions

View File

@ -125,9 +125,13 @@ export const conversationMethods = {
debugLog(`已加载 ${this.conversations.length} 个对话`); debugLog(`已加载 ${this.conversations.length} 个对话`);
if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) { if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) {
const latestConversation = this.conversations[0]; // 只有在初始化完成后,才自动加载第一个对话
if (latestConversation && latestConversation.id) { // 避免与 bootstrapRoute 冲突
await this.loadConversation(latestConversation.id); if (this.initialRouteResolved) {
const latestConversation = this.conversations[0];
if (latestConversation && latestConversation.id) {
await this.loadConversation(latestConversation.id);
}
} }
} }
} else { } else {

View File

@ -953,7 +953,7 @@ export const uiMethods = {
}, },
async bootstrapRoute() { async bootstrapRoute() {
// 在路由解析期间抑制标题动画,避免预置“新对话”闪烁 // 在路由解析期间抑制标题动画,避免预置"新对话"闪烁
this.suppressTitleTyping = true; this.suppressTitleTyping = true;
this.titleReady = false; this.titleReady = false;
this.currentConversationTitle = ''; this.currentConversationTitle = '';

View File

@ -703,7 +703,8 @@ export async function initializeLegacySocket(ctx: any) {
ctx.socket.on('todo_updated', (data) => { ctx.socket.on('todo_updated', (data) => {
socketLog('收到todo更新事件:', data); socketLog('收到todo更新事件:', data);
if (data && data.conversation_id) { // 初始化期间不修改 currentConversationId避免与 bootstrapRoute 冲突
if (ctx.initialRouteResolved && data && data.conversation_id) {
ctx.currentConversationId = data.conversation_id; ctx.currentConversationId = data.conversation_id;
} }
ctx.fileSetTodoList((data && data.todo_list) || null); ctx.fileSetTodoList((data && data.todo_list) || null);
@ -740,26 +741,30 @@ export async function initializeLegacySocket(ctx: any) {
ctx.socket.on('conversation_changed', (data) => { ctx.socket.on('conversation_changed', (data) => {
socketLog('对话已切换:', data); socketLog('对话已切换:', data);
console.log('[conv-trace] socket:conversation_changed', data); console.log('[conv-trace] socket:conversation_changed', data);
ctx.currentConversationId = data.conversation_id;
ctx.currentConversationTitle = data.title || '';
ctx.promoteConversationToTop(data.conversation_id);
if (data.cleared) { // 初始化期间不修改 currentConversationId避免与 bootstrapRoute 冲突
// 对话被清空 if (ctx.initialRouteResolved) {
ctx.logMessageState?.('socket:conversation_changed-clearing', { event: data }); ctx.currentConversationId = data.conversation_id;
ctx.messages = []; ctx.currentConversationTitle = data.title || '';
ctx.logMessageState?.('socket:conversation_changed-cleared', { event: data }); ctx.promoteConversationToTop(data.conversation_id);
ctx.currentConversationId = null;
ctx.currentConversationTitle = ''; if (data.cleared) {
// 重置Token统计 // 对话被清空
ctx.resetTokenStatistics(); ctx.logMessageState?.('socket:conversation_changed-clearing', { event: data });
history.replaceState({}, '', '/new'); ctx.messages = [];
ctx.logMessageState?.('socket:conversation_changed-cleared', { event: data });
ctx.currentConversationId = null;
ctx.currentConversationTitle = '';
// 重置Token统计
ctx.resetTokenStatistics();
history.replaceState({}, '', '/new');
}
// 刷新对话列表
ctx.loadConversationsList();
ctx.fetchTodoList();
ctx.fetchSubAgents();
} }
// 刷新对话列表
ctx.loadConversationsList();
ctx.fetchTodoList();
ctx.fetchSubAgents();
}); });
ctx.socket.on('conversation_resolved', (data) => { ctx.socket.on('conversation_resolved', (data) => {
@ -768,17 +773,21 @@ export async function initializeLegacySocket(ctx: any) {
} }
const convId = data.conversation_id; const convId = data.conversation_id;
console.log('[conv-trace] socket:conversation_resolved', data); console.log('[conv-trace] socket:conversation_resolved', data);
ctx.currentConversationId = convId;
if (data.title) { // 初始化期间不修改 currentConversationId避免与 bootstrapRoute 冲突
ctx.currentConversationTitle = data.title; if (ctx.initialRouteResolved) {
} ctx.currentConversationId = convId;
ctx.promoteConversationToTop(convId); if (data.title) {
const pathFragment = ctx.stripConversationPrefix(convId); ctx.currentConversationTitle = data.title;
const currentPath = window.location.pathname.replace(/^\/+/, ''); }
if (data.created) { ctx.promoteConversationToTop(convId);
history.pushState({ conversationId: convId }, '', `/${pathFragment}`); const pathFragment = ctx.stripConversationPrefix(convId);
} else if (currentPath !== pathFragment) { const currentPath = window.location.pathname.replace(/^\/+/, '');
history.replaceState({ conversationId: convId }, '', `/${pathFragment}`); if (data.created) {
history.pushState({ conversationId: convId }, '', `/${pathFragment}`);
} else if (currentPath !== pathFragment) {
history.replaceState({ conversationId: convId }, '', `/${pathFragment}`);
}
} }
}); });
@ -836,8 +845,13 @@ export async function initializeLegacySocket(ctx: any) {
// 监听状态更新事件 // 监听状态更新事件
ctx.socket.on('status_update', (status) => { ctx.socket.on('status_update', (status) => {
ctx.applyStatusSnapshot(status); ctx.applyStatusSnapshot(status);
// 只有在初始化完成后,才允许 status_update 修改 currentConversationId
// 避免与 bootstrapRoute 冲突
if (status.conversation && status.conversation.current_id) { if (status.conversation && status.conversation.current_id) {
ctx.currentConversationId = status.conversation.current_id; if (ctx.initialRouteResolved && !ctx.currentConversationId) {
// 初始化完成且当前没有对话ID才设置
ctx.currentConversationId = status.conversation.current_id;
}
} }
if (typeof status.run_mode === 'string') { if (typeof status.run_mode === 'string') {
ctx.runMode = status.run_mode; ctx.runMode = status.run_mode;