fix(frontend): 修复对话压缩后页面抽搐、重复显示与滚动重置

三管齐下,消除 in-place 压缩触发的多余重载:

1. handleTaskEvent: _rebuildingFromScratch 重放时跳过
   compression_finished/compression_state/shallow_compression,
   打破 restoreTaskState 重放 → 再次触发压缩完成的无限循环。

2. handleCompressionFinished + compressConversation:
   in-place 压缩(对话 id 不变)跳过 loadConversation +
   restoreTaskState + loadConversationsList,
   避免 resetAllStates 重置滚动状态和清空消息列表。

3. useLegacySocket conversation_loaded:
   同对话 clear_ui 跳过 resetAllStates + fetchAndDisplayHistory,
   避免后端 Socket.IO 推送触发二次重置。
This commit is contained in:
JOJO 2026-06-08 02:27:37 +08:00
parent 66261e91c6
commit 0d4f6c9e56
3 changed files with 30 additions and 15 deletions

View File

@ -1073,9 +1073,11 @@ export const messageMethods = {
if (response.ok && result.success) { if (response.ok && result.success) {
this.compressionStage = 'switching'; this.compressionStage = 'switching';
// in-place 压缩:对话 id 不变,重新加载以刷新“已压缩历史”展示。
const newId = result.compressed_conversation_id; const newId = result.compressed_conversation_id;
if (newId) { const isInPlace = newId && newId === this.currentConversationId;
// in-place 压缩:对话 id 不变,不重新加载(避免 resetAllStates
// 重置滚动状态 + fetchAndDisplayHistory 清空重渲染导致闪烁和锁死)。
if (newId && !isInPlace) {
await this.loadConversation(newId, { force: true }); await this.loadConversation(newId, { force: true });
} }
const guideMessage = (result.guide_message || '').trim(); const guideMessage = (result.guide_message || '').trim();
@ -1099,15 +1101,16 @@ export const messageMethods = {
} }
} else if (compressBehavior === 'wait') { } else if (compressBehavior === 'wait') {
// 等待用户:后端已把引导语作为 user 消息追加进历史,这里仅刷新展示,不触发请求。 // 等待用户:后端已把引导语作为 user 消息追加进历史,这里仅刷新展示,不触发请求。
if (newId) { if (newId && !isInPlace) {
await this.loadConversation(newId, { force: true }); await this.loadConversation(newId, { force: true });
} }
} else if (guideMessage) { } else if (guideMessage) {
await this.sendAutoUserMessage(guideMessage); await this.sendAutoUserMessage(guideMessage);
} }
this.conversationsOffset = 0; if (!isInPlace) {
await this.loadConversationsList(); await this.loadConversationsList();
}
debugLog('对话压缩完成:', result); debugLog('对话压缩完成:', result);
this.uiPushToast({ this.uiPushToast({

View File

@ -756,12 +756,18 @@ export const taskPollingMethods = {
this.handleConversationResolved(eventData, eventIdx); this.handleConversationResolved(eventData, eventIdx);
break; break;
case 'compression_state': case 'compression_state':
// 重放历史事件时不处理管理类事件,避免触发副作用(如 toast 闪烁 / 状态回退)。
if (this._rebuildingFromScratch) break;
this.handleCompressionState(eventData, eventIdx); this.handleCompressionState(eventData, eventIdx);
break; break;
case 'compression_finished': case 'compression_finished':
// 重放历史事件时不处理管理类事件,避免 restoreTaskState → 重放 → 再次触发 compression_finished 的无限循环。
if (this._rebuildingFromScratch) break;
this.handleCompressionFinished(eventData, eventIdx); this.handleCompressionFinished(eventData, eventIdx);
break; break;
case 'shallow_compression': case 'shallow_compression':
// 同上。
if (this._rebuildingFromScratch) break;
this.handleShallowCompression(eventData, eventIdx); this.handleShallowCompression(eventData, eventIdx);
break; break;
@ -2431,19 +2437,21 @@ export const taskPollingMethods = {
this.compressionMode = ''; this.compressionMode = '';
this.compressionStage = ''; this.compressionStage = '';
this.compressionError = ''; this.compressionError = '';
// in-place 压缩:对话 id 不变。重新加载该对话以刷新“已压缩历史”的展示;
// 若返回的 id 与当前不一致(兼容旧行为)也按其加载。
const newId = data?.conversation_id; const newId = data?.conversation_id;
if (newId) { const isInPlace = newId && newId === this.currentConversationId;
if (newId && !isInPlace) {
// 旧行为兼容:压缩产生了新对话 id非 in-place需要切换并重新加载。
await this.loadConversation(newId, { force: true }); await this.loadConversation(newId, { force: true });
this.conversationsOffset = 0;
if (typeof this.loadConversationsList === 'function') {
await this.loadConversationsList();
}
await this.refreshRunningWorkspaceTasks?.();
await this.restoreTaskState?.();
} }
this.conversationsOffset = 0; // in-place 压缩:对话 id 不变,不重新加载对话,也不触发 restoreTaskState。
if (typeof this.loadConversationsList === 'function') { // loadConversation 会 clearTask 停止轮询才需要 restorein-place 跳过了
await this.loadConversationsList(); // loadConversation轮询仍在运行restoreTaskState 的 rebuild 反而会
}
await this.refreshRunningWorkspaceTasks?.();
// 续接任务仍在同一对话内运行,恢复轮询以持续显示进度。
await this.restoreTaskState?.();
this.uiPushToast({ this.uiPushToast({
title: '压缩完成', title: '压缩完成',
message: '已压缩较早的对话内容', message: '已压缩较早的对话内容',

View File

@ -841,6 +841,10 @@ export async function initializeLegacySocket(ctx: any) {
if (!forceReload && (historyLoadingSame || historyReady)) { if (!forceReload && (historyLoadingSame || historyReady)) {
socketLog('conversation_loaded: 历史已加载/加载中,跳过重复刷新'); socketLog('conversation_loaded: 历史已加载/加载中,跳过重复刷新');
} else if (data.clear_ui && targetConversationId === ctx.currentConversationId) {
// in-place 压缩等同一对话内的刷新:不 resetAllStates避免重置滚动状态
// 导致锁死),不重新 fetchAndDisplayHistory避免消息闪烁
socketLog('conversation_loaded: 同对话 clear_ui跳过重置以保持滚动状态');
} else { } else {
if (data.clear_ui) { if (data.clear_ui) {
// 清理当前UI状态准备显示历史内容 // 清理当前UI状态准备显示历史内容