From 8de3b5d24a4905204e25fe96d46b6068dd4f5d34 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Thu, 1 Jan 2026 06:15:01 +0800 Subject: [PATCH] fix: prevent duplicate history animation --- static/src/app.ts | 33 +++++++++- static/src/composables/useLegacySocket.ts | 79 ++++++++++++++++++----- 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/static/src/app.ts b/static/src/app.ts index 0cddee6..8a934c3 100644 --- a/static/src/app.ts +++ b/static/src/app.ts @@ -130,6 +130,8 @@ const appOptions = { toolStacks: new Map(), // 当前任务是否仍在进行中(用于保持输入区的“停止”状态) taskInProgress: false, + // 记录上一次成功加载历史的对话ID,防止初始化阶段重复加载导致动画播放两次 + lastHistoryLoadedConversationId: null, // ========================================== // 对话管理相关状态 @@ -1132,6 +1134,9 @@ const appOptions = { this.ensureScrollListener(); }); + // 重置已加载对话标记,便于后续重新加载新对话历史 + this.lastHistoryLoadedConversationId = null; + this.logMessageState('resetAllStates:after-cleanup', { reason }); }, @@ -1182,6 +1187,8 @@ const appOptions = { if (statusConversationId) { if (!this.currentConversationId) { this.skipConversationHistoryReload = true; + // 首次从状态恢复对话时,避免 socket 的 conversation_loaded 再次触发历史加载 + this.skipConversationLoadedEvent = true; this.currentConversationId = statusConversationId; // 如果有当前对话,尝试获取标题和历史 @@ -1192,7 +1199,13 @@ const appOptions = { this.currentConversationTitle = convData.data.title; } // 初始化时调用一次,因为 skipConversationHistoryReload 会阻止 watch 触发 - await this.fetchAndDisplayHistory(); + if ( + this.lastHistoryLoadedConversationId !== this.currentConversationId || + !Array.isArray(this.messages) || + this.messages.length === 0 + ) { + await this.fetchAndDisplayHistory(); + } // 获取当前对话的Token统计 this.fetchConversationTokenStatistics(); this.updateCurrentContextTokens(); @@ -1417,7 +1430,8 @@ const appOptions = { // ========================================== // 关键功能:获取并显示历史对话内容 // ========================================== - async fetchAndDisplayHistory() { + async fetchAndDisplayHistory(options = {}) { + const { force = false } = options as { force?: boolean }; const targetConversationId = this.currentConversationId; if (!targetConversationId || targetConversationId.startsWith('temp_')) { debugLog('没有当前对话ID,跳过历史加载'); @@ -1431,6 +1445,20 @@ const appOptions = { 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; @@ -1743,6 +1771,7 @@ const appOptions = { debugLog(`历史消息渲染完成,共 ${this.messages.length} 条消息`); this.logMessageState('renderHistoryMessages:after-render'); + this.lastHistoryLoadedConversationId = this.currentConversationId || null; // 强制更新视图 this.$forceUpdate(); diff --git a/static/src/composables/useLegacySocket.ts b/static/src/composables/useLegacySocket.ts index 24a4047..4839cb0 100644 --- a/static/src/composables/useLegacySocket.ts +++ b/static/src/composables/useLegacySocket.ts @@ -522,16 +522,30 @@ export async function initializeLegacySocket(ctx: any) { }; - const scheduleHistoryReload = (delay = 0) => { + const scheduleHistoryReload = (delay = 0, options: { force?: boolean } = {}) => { + const { force = false } = options; if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') { return; } - if (!ctx.currentConversationId || ctx.currentConversationId.startsWith('temp_')) { - return; - } setTimeout(() => { + const targetConversationId = ctx.currentConversationId; + if (!targetConversationId || targetConversationId.startsWith('temp_')) { + return; + } try { - ctx.fetchAndDisplayHistory(); + if (!force) { + const loadingSame = + !!ctx.historyLoading && ctx.historyLoadingFor === targetConversationId; + const historyReady = + ctx.lastHistoryLoadedConversationId === targetConversationId && + Array.isArray(ctx.messages) && + ctx.messages.length > 0; + if (loadingSame || historyReady) { + socketLog('跳过重复历史加载(scheduleHistoryReload)'); + return; + } + } + ctx.fetchAndDisplayHistory({ force }); if (typeof ctx.fetchConversationTokenStatistics === 'function') { ctx.fetchConversationTokenStatistics(); } @@ -565,13 +579,31 @@ export async function initializeLegacySocket(ctx: any) { }); } + let hasConnectedOnce = false; + // 连接事件 ctx.socket.on('connect', () => { + const isReconnect = hasConnectedOnce; + const historyLoadingSame = + !!ctx.historyLoading && ctx.historyLoadingFor === ctx.currentConversationId; + const historyReady = + ctx.lastHistoryLoadedConversationId === ctx.currentConversationId && + Array.isArray(ctx.messages) && + ctx.messages.length > 0; + ctx.isConnected = true; socketLog('WebSocket已连接'); - // 连接时重置所有状态并刷新当前对话 - ctx.resetAllStates('socket:connect'); - scheduleHistoryReload(200); + + // 首次连接且历史已在加载/已就绪时,避免重复清空与重复动画 + if (!isReconnect && (historyLoadingSame || historyReady)) { + socketLog('初次连接已存在历史,跳过重复重置与加载'); + hasConnectedOnce = true; + return; + } + + hasConnectedOnce = true; + ctx.resetAllStates(isReconnect ? 'socket:reconnect' : 'socket:connect'); + scheduleHistoryReload(200, { force: isReconnect }); }); ctx.socket.on('disconnect', () => { @@ -731,15 +763,30 @@ export async function initializeLegacySocket(ctx: any) { socketLog('跳过重复的 conversation_loaded 处理'); return; } - if (data.clear_ui) { - // 清理当前UI状态,准备显示历史内容 - ctx.resetAllStates('socket:conversation_loaded'); - } + const targetConversationId = (data && data.conversation_id) || ctx.currentConversationId; + const historyLoadingSame = + !!targetConversationId && + !!ctx.historyLoading && + ctx.historyLoadingFor === targetConversationId; + const historyReady = + !!targetConversationId && + ctx.lastHistoryLoadedConversationId === targetConversationId && + Array.isArray(ctx.messages) && + ctx.messages.length > 0; + const forceReload = !!(data && data.force_reload); - // 延迟获取并显示历史对话内容 - setTimeout(() => { - ctx.fetchAndDisplayHistory(); - }, 300); + if (!forceReload && (historyLoadingSame || historyReady)) { + socketLog('conversation_loaded: 历史已加载/加载中,跳过重复刷新'); + } else { + if (data.clear_ui) { + // 清理当前UI状态,准备显示历史内容 + ctx.resetAllStates('socket:conversation_loaded'); + } + // 延迟获取并显示历史对话内容 + setTimeout(() => { + ctx.fetchAndDisplayHistory({ force: forceReload }); + }, 300); + } // 延迟获取Token统计(累计+当前上下文) setTimeout(() => {