From 185649323e9769c8f9cb1bad520554b3c7f22054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 22 Sep 2026 19:55:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20DirectProject=20=E6=97=A7?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E9=87=8D=E8=A7=A3=E6=9E=90=E5=AF=BC=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E4=BC=9A=E8=AF=9D=E5=8C=BA=E5=8D=A1=E9=A1=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 ChatMarkdownMessage 加 memo,旧消息文本不变时不再重新解析 Markdown 与语法高亮 流式期间跳过语法高亮,定稿后再补,避免每个 chunk 重跑一遍 highlight.js 为 AgentReasoning 加 memo,内容未变的思考块整块跳过重渲染 把 AgentReasoning 折叠态预览的 Markdown AST 解析收进 useMemo --- .../components/ChatMarkdownMessage/index.tsx | 18 ++++++++++++++++-- .../AgentReasoning/AgentReasoning.tsx | 13 ++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx index d8acce153..543351e25 100644 --- a/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx +++ b/apps/ai-game-creator-shell/src/components/ChatMarkdownMessage/index.tsx @@ -6,6 +6,7 @@ import { Component, createContext, isValidElement, + memo, useContext, } from 'react'; import ReactMarkdown, { type Components } from 'react-markdown'; @@ -302,7 +303,15 @@ const streamingMarkdownComponents: Components = { p: StreamingMarkdownParagraph, }; -export function ChatMarkdownMessage({ +/** + * 解析器的输入只有 `text` / `role` / `streaming` / `preserveBlankLines` 这几个标量,所以 + * 内容没变的旧消息在父级重渲染时可以直接跳过:Markdown 解析与语法高亮是这个组件里最贵的 + * 两件事(`react-markdown` 每次渲染都会重建 `unified()` 处理器并重跑全部插件),而旧消息 + * 的文本永远不会再改。 + */ +export const ChatMarkdownMessage = memo(ChatMarkdownMessageImpl); + +function ChatMarkdownMessageImpl({ text, role, streaming = false, @@ -317,8 +326,13 @@ export function ChatMarkdownMessage({ MAX_HIGHLIGHT_CHARACTERS + ? [] + : [rehypeHighlight] } components={ streaming ? streamingMarkdownComponents : markdownComponents diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/components/AgentReasoning/AgentReasoning.tsx b/apps/ai-game-creator-shell/src/view/project-development/chat/components/AgentReasoning/AgentReasoning.tsx index f2d836663..c3d7da397 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/components/AgentReasoning/AgentReasoning.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/components/AgentReasoning/AgentReasoning.tsx @@ -1,5 +1,5 @@ import { ChevronDown, Lightbulb } from 'lucide-react'; -import { useState } from 'react'; +import { memo, useMemo, useState } from 'react'; import { AgentMessageContent } from '../../../../../../../../packages/shared/src/components/AgentMessageContent'; import { AgentProcessSummary } from '../../../../../../../../packages/shared/src/components/AgentProcessSummary'; @@ -12,7 +12,9 @@ import { agentProcessPreview } from '../../../../../features/project-workspace/a * 折叠态是单行纯文本预览(Markdown 只取可见文字,符号不进预览);展开态复用助手正文的 * 安全 Markdown 链路。这里只有表现与展开态,思考内容本身由两条产品路径各自的事实源提供。 */ -export function AgentReasoning({ +export const AgentReasoning = memo(AgentReasoningImpl); + +function AgentReasoningImpl({ text, label = '思考过程', testId, @@ -22,7 +24,12 @@ export function AgentReasoning({ testId?: string; }) { const [expanded, setExpanded] = useState(false); - const preview = agentProcessPreview(text); + /* + 折叠态那一行预览是**完整 Markdown AST 解析**(`remark-parse` + `unified`),和展开态 + `react-markdown` 那次解析是两笔开销;上面那层 `memo` 让内容没变的思考块整个跳过, + 这里的 `useMemo` 再兜一层,避免同一文本在真正重渲染时被重算。 + */ + const preview = useMemo(() => agentProcessPreview(text), [text]); return (