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(),
// 当前任务是否仍在进行中(用于保持输入区的“停止”状态)
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();

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') {
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(() => {