feat: 升级至 v0.3.11 — 流式输出性能优化 12 项,解决长内容卡死
针对 AI 流式输出长内容时应用卡死问题,实施 12 项性能优化: 渲染层优化(F1/F3/F7): - F1: React.memo 包裹 MessageItem/AssistantMessage,跳过历史消息重渲染 - F3: 流式时用纯文本渲染,避免 ReactMarkdown 全量重解析 O(n²) - F7: useMemo 缓存 ReactMarkdown 元素,非流式时复用实例 滚动与布局(F2/F4/F10/F12): - F2: 滚动节流 rAF + 100ms throttle + trailing 兜底,避免高频布局 - F4: CSS content-visibility 准虚拟滚动,跳过不可见区域布局 - F10: CSS contain 隔离 markdown 布局计算 - F12: 滚动容器 transform: translateZ(0) GPU 加速 状态与 IPC 批处理(F5/F8/F9): - F5: 渲染进程 text_delta rAF 批处理,合并多次 store 写入 - F8: 主进程 text_delta 32ms 节流合并,降低 IPC 频率 - F9: reasoning_delta traceSteps thought 走 rAF 批处理 代码清理(F11): - F11: 移除 rehypeRaw,节省 raw HTML 解析开销 + 防 AI 输出注入 验证:tsc --noEmit 通过,flush 逻辑在 done/error/cleanup 三处正确执行。
This commit is contained in:
@@ -163,10 +163,73 @@ export function registerAllIPCHandlers(
|
||||
}
|
||||
|
||||
// 监听 Agent Loop 事件
|
||||
const onStreamEvent = (event: MetonaStreamEvent) => {
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('agent:streamEvent', event);
|
||||
// F8: 主进程 text_delta 节流合并
|
||||
// 问题:主进程 1:1 转发所有流式事件,text_delta 频率 30-50 次/秒,
|
||||
// 每次 IPC 调用都有跨进程开销,叠加渲染进程处理成本。
|
||||
// 方案:在主进程聚合 text_delta,32ms 间隔合并转发(IPC 频率降至 ~30 次/秒)。
|
||||
// 非 text_delta 事件立即转发(先 flush 缓冲区保证顺序)。
|
||||
// done/error 时立即 flush,避免最后一段 delta 丢失。
|
||||
// 与 F5 渲染进程 rAF 批处理叠加,总延迟约 48ms(人眼不敏感)。
|
||||
let textDeltaBuffer = '';
|
||||
let lastTextEventMeta: Pick<MetonaStreamEvent, 'requestId' | 'sessionId' | 'iteration' | 'seq' | 'timestamp' | 'runId'> | null = null;
|
||||
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const flushTextDeltaBuffer = () => {
|
||||
flushTimer = null;
|
||||
if (!textDeltaBuffer || !lastTextEventMeta || mainWindow.isDestroyed()) {
|
||||
textDeltaBuffer = '';
|
||||
lastTextEventMeta = null;
|
||||
return;
|
||||
}
|
||||
// 构建合并的 text_delta 事件,保留最后一个 delta 的元数据
|
||||
const mergedEvent: MetonaStreamEvent = {
|
||||
...lastTextEventMeta,
|
||||
type: MetonaStreamEventType.TEXT_DELTA,
|
||||
delta: textDeltaBuffer,
|
||||
seq: lastTextEventMeta.seq, // 使用最后一个 delta 的 seq
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
mainWindow.webContents.send('agent:streamEvent', mergedEvent);
|
||||
textDeltaBuffer = '';
|
||||
lastTextEventMeta = null;
|
||||
};
|
||||
|
||||
const onStreamEvent = (event: MetonaStreamEvent) => {
|
||||
if (mainWindow.isDestroyed()) return;
|
||||
|
||||
// F8: text_delta 聚合,其他事件立即转发(先 flush 保证顺序)
|
||||
if (event.type === MetonaStreamEventType.TEXT_DELTA && event.delta) {
|
||||
if (textDeltaBuffer === '') {
|
||||
lastTextEventMeta = {
|
||||
requestId: event.requestId,
|
||||
sessionId: event.sessionId,
|
||||
iteration: event.iteration,
|
||||
seq: event.seq,
|
||||
timestamp: event.timestamp,
|
||||
runId: event.runId,
|
||||
};
|
||||
} else {
|
||||
// 更新 seq 为最新 delta 的 seq(保证前端 seq 连续性判断)
|
||||
lastTextEventMeta = {
|
||||
...lastTextEventMeta!,
|
||||
seq: event.seq,
|
||||
timestamp: event.timestamp,
|
||||
runId: event.runId,
|
||||
};
|
||||
}
|
||||
textDeltaBuffer += event.delta;
|
||||
if (flushTimer === null) {
|
||||
flushTimer = setTimeout(flushTextDeltaBuffer, 32);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 非 text_delta 事件:先 flush 缓冲区,再立即转发(保证事件顺序)
|
||||
if (flushTimer !== null) {
|
||||
clearTimeout(flushTimer);
|
||||
flushTextDeltaBuffer();
|
||||
}
|
||||
mainWindow.webContents.send('agent:streamEvent', event);
|
||||
};
|
||||
|
||||
const onStateChange = (data: { previous?: string; current?: string; sessionId?: string; iteration?: number; state?: string; runId?: string }) => {
|
||||
@@ -413,6 +476,11 @@ export function registerAllIPCHandlers(
|
||||
|
||||
return { success: false, error: (error as Error).message };
|
||||
} finally {
|
||||
// F8: 注销前 flush 残留的 text_delta 缓冲区,避免丢失最后一段内容
|
||||
if (flushTimer !== null) {
|
||||
clearTimeout(flushTimer);
|
||||
flushTextDeltaBuffer();
|
||||
}
|
||||
agentLoop.off('streamEvent', onStreamEvent);
|
||||
agentLoop.off('stateChange', onStateChange);
|
||||
agentLoop.off('compressed', onCompressed);
|
||||
|
||||
Reference in New Issue
Block a user