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 (