fix(frontend): 修复引导/压缩等运行期注入消息场景下刷新导致运行记录重复显示
刷新恢复运行中任务时,重建判据只看最后一条消息:当运行区间中夹着 运行期注入的 user 消息(运行中引导/通知、多智能体内联消息、压缩续接 引导语)时,引导前的静态 assistant 段未被清空,而事件重放从 0 重建 整个任务内容,导致同一段运行记录显示两遍。 修复:重建前先界定本次运行区间(越过 assistant 与运行期注入 user 消 息,直到任务发起消息),复用区间内第一条 assistant 作为重放容器并 移除区间其余部分(注入消息与后续分段由事件流按原序重建),同时绑定 currentMessageIndex 使容器复用真正生效。
This commit is contained in:
parent
eb037ccb7c
commit
f953127613
@ -401,28 +401,87 @@ export const compressionMethods = {
|
||||
if (needsRebuild) {
|
||||
debugLog('[TaskPolling] 需要从头重建 assistant 响应');
|
||||
|
||||
// 定位「本次运行区间」:从消息末尾向前,越过 assistant 消息与运行期注入的
|
||||
// user 消息(运行中引导/通知、多智能体内联消息、压缩续接引导语),直到发起
|
||||
// 本次任务的 user 消息(或更早的历史消息)为止。
|
||||
//
|
||||
// 背景:事件重放会从 0 重建本次运行的全部 assistant 内容。若运行区间中夹着
|
||||
// 静态的运行期 user 消息(典型:运行中发送了引导对话、运行中触发对话压缩),
|
||||
// 只清空末条 assistant 会让引导/压缩消息之前的静态分段残留,重放又把全部
|
||||
// 内容重建到新 assistant 中,导致同一段运行记录显示两遍。
|
||||
//
|
||||
// 处理:复用运行区间内【第一条】assistant 作为重放容器(清空 actions 并绑定
|
||||
// currentMessageIndex),并移除区间内其后的所有消息——运行期注入的 user
|
||||
// 消息在任务事件流中都有对应 user_message 事件,会与后续 assistant 段一起
|
||||
// 由事件重放按原顺序重建,位置与运行时一致。
|
||||
const isMidRunRuntimeUserMessage = (msg: any): boolean => {
|
||||
if (!msg || msg.role !== 'user') {
|
||||
return false;
|
||||
}
|
||||
const meta = msg.metadata || {};
|
||||
if (meta.runtime_injected === true) {
|
||||
return true;
|
||||
}
|
||||
// 压缩续接引导语走任务递归入口持久化,没有 runtime_injected 标记,
|
||||
// 但它同样是运行中插入、且事件流中有对应 user_message 事件。
|
||||
const src = String(meta.message_source || '').trim().toLowerCase();
|
||||
return src === 'compression' || src === 'compression_handoff';
|
||||
};
|
||||
let runBoundaryIdx = this.messages.length - 1;
|
||||
while (runBoundaryIdx >= 0) {
|
||||
const msg = this.messages[runBoundaryIdx];
|
||||
if (msg && msg.role === 'assistant') {
|
||||
runBoundaryIdx -= 1;
|
||||
continue;
|
||||
}
|
||||
if (isMidRunRuntimeUserMessage(msg)) {
|
||||
runBoundaryIdx -= 1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
let rebuildContainer: any = null;
|
||||
let rebuildContainerIdx = -1;
|
||||
for (let j = Math.max(runBoundaryIdx + 1, 0); j < this.messages.length; j += 1) {
|
||||
if (this.messages[j] && this.messages[j].role === 'assistant') {
|
||||
rebuildContainer = this.messages[j];
|
||||
rebuildContainerIdx = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 关键修复:不要 pop assistant 消息,而是清空其 actions 并设置标记,
|
||||
// 让 handleAiMessageStart 检测到 isRefreshRestore 并复用现有消息容器。
|
||||
// 这样避免视觉闪回(消息不会突然消失)+ 新事件通过轮询增量追加,不会一次性同步处理几百个事件导致卡死。
|
||||
if (isAssistantMessage) {
|
||||
if (rebuildContainer) {
|
||||
debugLog('[TaskPolling] 复用现有 assistant 消息容器,清空 actions 等待事件重建', {
|
||||
hasAssistantContentEvent
|
||||
hasAssistantContentEvent,
|
||||
rebuildContainerIdx,
|
||||
removedAfterContainer: this.messages.length - 1 - rebuildContainerIdx
|
||||
});
|
||||
lastMessage.actions = [];
|
||||
// 移除重建容器之后的运行区间消息(运行期注入的 user 消息 / 后续 assistant
|
||||
// 段),它们会随事件重放按原位置重建;不移除会导致同一段运行记录显示两遍。
|
||||
if (rebuildContainerIdx < this.messages.length - 1) {
|
||||
this.messages.splice(rebuildContainerIdx + 1);
|
||||
}
|
||||
rebuildContainer.actions = [];
|
||||
// 如果事件流中已经有内容事件(tool/thinking/text),说明回复已经开始过,
|
||||
// 重放事件会恢复内容,不显示等待提示。
|
||||
// 如果还没有内容事件,说明回复还没开始,应该显示等待提示。
|
||||
lastMessage.awaitingFirstContent = !hasAssistantContentEvent;
|
||||
lastMessage.generatingLabel = lastMessage.generatingLabel || '思考中...';
|
||||
rebuildContainer.awaitingFirstContent = !hasAssistantContentEvent;
|
||||
rebuildContainer.generatingLabel = rebuildContainer.generatingLabel || '思考中...';
|
||||
// 清理旧的流式标记,确保新事件能正确设置
|
||||
if (typeof lastMessage.streaming === 'boolean') {
|
||||
lastMessage.streaming = true;
|
||||
if (typeof rebuildContainer.streaming === 'boolean') {
|
||||
rebuildContainer.streaming = true;
|
||||
}
|
||||
// 绑定流式写入索引,确保重放事件追加进该容器(否则 ensureAssistantMessage
|
||||
// 会因 currentMessageIndex=-1 新建 assistant,留下空壳并错位)。
|
||||
this.currentMessageIndex = rebuildContainerIdx;
|
||||
}
|
||||
|
||||
this.streamingMessage = true;
|
||||
this.taskInProgress = true;
|
||||
if (!hasAssistantContentEvent && !isAssistantMessage) {
|
||||
if (!hasAssistantContentEvent && !rebuildContainer) {
|
||||
this.ensureRunningAssistantPlaceholder?.(runningTask, 'restore:no-visible-content-yet');
|
||||
}
|
||||
this.$forceUpdate();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user