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) {
this.compressionStage = 'switching';
// in-place 压缩:对话 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 });
}
const guideMessage = (result.guide_message || '').trim();
@ -1099,15 +1101,16 @@ export const messageMethods = {
}
} else if (compressBehavior === 'wait') {
// 等待用户:后端已把引导语作为 user 消息追加进历史,这里仅刷新展示,不触发请求。
if (newId) {
if (newId && !isInPlace) {
await this.loadConversation(newId, { force: true });
}
} else if (guideMessage) {
await this.sendAutoUserMessage(guideMessage);
}
this.conversationsOffset = 0;
await this.loadConversationsList();
if (!isInPlace) {
await this.loadConversationsList();
}
debugLog('对话压缩完成:', result);
this.uiPushToast({

View File

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

View File

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