fix: prevent duplicate history animation

This commit is contained in:
JOJO 2026-01-01 06:15:01 +08:00
parent 4262922694
commit 8de3b5d24a
2 changed files with 94 additions and 18 deletions

View File

@ -130,6 +130,8 @@ const appOptions = {
toolStacks: new Map(), toolStacks: new Map(),
// 当前任务是否仍在进行中(用于保持输入区的“停止”状态) // 当前任务是否仍在进行中(用于保持输入区的“停止”状态)
taskInProgress: false, taskInProgress: false,
// 记录上一次成功加载历史的对话ID防止初始化阶段重复加载导致动画播放两次
lastHistoryLoadedConversationId: null,
// ========================================== // ==========================================
// 对话管理相关状态 // 对话管理相关状态
@ -1132,6 +1134,9 @@ const appOptions = {
this.ensureScrollListener(); this.ensureScrollListener();
}); });
// 重置已加载对话标记,便于后续重新加载新对话历史
this.lastHistoryLoadedConversationId = null;
this.logMessageState('resetAllStates:after-cleanup', { reason }); this.logMessageState('resetAllStates:after-cleanup', { reason });
}, },
@ -1182,6 +1187,8 @@ const appOptions = {
if (statusConversationId) { if (statusConversationId) {
if (!this.currentConversationId) { if (!this.currentConversationId) {
this.skipConversationHistoryReload = true; this.skipConversationHistoryReload = true;
// 首次从状态恢复对话时,避免 socket 的 conversation_loaded 再次触发历史加载
this.skipConversationLoadedEvent = true;
this.currentConversationId = statusConversationId; this.currentConversationId = statusConversationId;
// 如果有当前对话,尝试获取标题和历史 // 如果有当前对话,尝试获取标题和历史
@ -1192,7 +1199,13 @@ const appOptions = {
this.currentConversationTitle = convData.data.title; this.currentConversationTitle = convData.data.title;
} }
// 初始化时调用一次,因为 skipConversationHistoryReload 会阻止 watch 触发 // 初始化时调用一次,因为 skipConversationHistoryReload 会阻止 watch 触发
await this.fetchAndDisplayHistory(); if (
this.lastHistoryLoadedConversationId !== this.currentConversationId ||
!Array.isArray(this.messages) ||
this.messages.length === 0
) {
await this.fetchAndDisplayHistory();
}
// 获取当前对话的Token统计 // 获取当前对话的Token统计
this.fetchConversationTokenStatistics(); this.fetchConversationTokenStatistics();
this.updateCurrentContextTokens(); this.updateCurrentContextTokens();
@ -1417,7 +1430,8 @@ const appOptions = {
// ========================================== // ==========================================
// 关键功能:获取并显示历史对话内容 // 关键功能:获取并显示历史对话内容
// ========================================== // ==========================================
async fetchAndDisplayHistory() { async fetchAndDisplayHistory(options = {}) {
const { force = false } = options as { force?: boolean };
const targetConversationId = this.currentConversationId; const targetConversationId = this.currentConversationId;
if (!targetConversationId || targetConversationId.startsWith('temp_')) { if (!targetConversationId || targetConversationId.startsWith('temp_')) {
debugLog('没有当前对话ID跳过历史加载'); debugLog('没有当前对话ID跳过历史加载');
@ -1431,6 +1445,20 @@ const appOptions = {
return; 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; const loadSeq = ++this.historyLoadSeq;
this.historyLoading = true; this.historyLoading = true;
this.historyLoadingFor = targetConversationId; this.historyLoadingFor = targetConversationId;
@ -1743,6 +1771,7 @@ const appOptions = {
debugLog(`历史消息渲染完成,共 ${this.messages.length} 条消息`); debugLog(`历史消息渲染完成,共 ${this.messages.length} 条消息`);
this.logMessageState('renderHistoryMessages:after-render'); this.logMessageState('renderHistoryMessages:after-render');
this.lastHistoryLoadedConversationId = this.currentConversationId || null;
// 强制更新视图 // 强制更新视图
this.$forceUpdate(); this.$forceUpdate();

View File

@ -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') { if (!ctx || typeof ctx.fetchAndDisplayHistory !== 'function') {
return; return;
} }
if (!ctx.currentConversationId || ctx.currentConversationId.startsWith('temp_')) {
return;
}
setTimeout(() => { setTimeout(() => {
const targetConversationId = ctx.currentConversationId;
if (!targetConversationId || targetConversationId.startsWith('temp_')) {
return;
}
try { 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') { if (typeof ctx.fetchConversationTokenStatistics === 'function') {
ctx.fetchConversationTokenStatistics(); ctx.fetchConversationTokenStatistics();
} }
@ -565,13 +579,31 @@ export async function initializeLegacySocket(ctx: any) {
}); });
} }
let hasConnectedOnce = false;
// 连接事件 // 连接事件
ctx.socket.on('connect', () => { 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; ctx.isConnected = true;
socketLog('WebSocket已连接'); 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', () => { ctx.socket.on('disconnect', () => {
@ -731,15 +763,30 @@ export async function initializeLegacySocket(ctx: any) {
socketLog('跳过重复的 conversation_loaded 处理'); socketLog('跳过重复的 conversation_loaded 处理');
return; return;
} }
if (data.clear_ui) { const targetConversationId = (data && data.conversation_id) || ctx.currentConversationId;
// 清理当前UI状态准备显示历史内容 const historyLoadingSame =
ctx.resetAllStates('socket:conversation_loaded'); !!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);
// 延迟获取并显示历史对话内容 if (!forceReload && (historyLoadingSame || historyReady)) {
setTimeout(() => { socketLog('conversation_loaded: 历史已加载/加载中,跳过重复刷新');
ctx.fetchAndDisplayHistory(); } else {
}, 300); if (data.clear_ui) {
// 清理当前UI状态准备显示历史内容
ctx.resetAllStates('socket:conversation_loaded');
}
// 延迟获取并显示历史对话内容
setTimeout(() => {
ctx.fetchAndDisplayHistory({ force: forceReload });
}, 300);
}
// 延迟获取Token统计累计+当前上下文) // 延迟获取Token统计累计+当前上下文)
setTimeout(() => { setTimeout(() => {