From 410322cbc0f608cc3aa720e7adb16c47ba1372a8 Mon Sep 17 00:00:00 2001 From: lili Date: Wed, 24 Jun 2026 18:01:56 -0700 Subject: [PATCH] =?UTF-8?q?fix(studio):=20AIPanel=20onDone=20=E6=94=B9?= =?UTF-8?q?=E7=94=A8=20ref=20=E8=AF=BB=E6=B5=81=E5=BC=8F=E6=AD=A3=E6=96=87?= =?UTF-8?q?=EF=BC=8C=E6=B6=88=E9=99=A4=E6=B8=B2=E6=9F=93=E6=9C=9F=E8=B7=A8?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=20setState?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AIPanel onDone 此前在 setStreamContent 更新函数里调父组件 onCandidateGenerated→AIPanel 渲染期触发 WorkspacePage setState(React warn "Cannot update a component while rendering a different component")。改:streamContentRef 在 onChunk 与 streamContent 同步累计,onDone 直接读 ref 调回调,不再渲染期 setState。ai-generation e2e 再跑 React warning grep=0(消失)、仍 passed(26.2s)。顺带修 AgentPage.contract.test 多余 import React(JSX automatic runtime 不需→TS6133;此前写 test 只跑 vitest 漏跑 tsc、commit 4c069d4 带入)。tsc 全量 0、vitest 102/102。 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/features/editor/components/AIPanel.tsx | 13 +++++++++---- muse-studio/src/pages/AgentPage.contract.test.tsx | 1 - 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/muse-studio/src/features/editor/components/AIPanel.tsx b/muse-studio/src/features/editor/components/AIPanel.tsx index 85dd7f7b..72cdf8e8 100644 --- a/muse-studio/src/features/editor/components/AIPanel.tsx +++ b/muse-studio/src/features/editor/components/AIPanel.tsx @@ -42,6 +42,10 @@ export default function AIPanel({ // 维护中止控制器的引用以管理流生命周期 const abortControllerRef = React.useRef(null); + // 流式正文最新值镜像:onDone 需读累计正文回传上层。用 ref 而非在 setStreamContent 更新函数里调父组件回调—— + // 后者会在 AIPanel 渲染期触发 WorkspacePage setState(React 警告 "Cannot update a component while rendering + // a different component")。ref 与 streamContent 在 onChunk 同步累计,onDone 直接读 ref 即为最新值。 + const streamContentRef = React.useRef(''); // 1. 发起 AI 生成请求 const handleGenerate = async () => { @@ -54,6 +58,7 @@ export default function AIPanel({ setIsGenerating(true); setStreamContent(''); + streamContentRef.current = ''; setErrorMsg(''); try { @@ -82,6 +87,7 @@ export default function AIPanel({ const controller = connectAIStream(`/app-api/muse/ai/tasks/${task.taskId}/stream`, { onChunk: (data) => { + streamContentRef.current += data.content; setStreamContent((prev) => prev + data.content); }, onDone: (data) => { @@ -90,10 +96,9 @@ export default function AIPanel({ // 缺失时回传 null(上层据此禁用采纳按钮,只允许预览)。 const suggestionId = data?.suggestionId ?? null; // 通过回调将生成正文与候选 ID 一并提交给上层,以渲染候选 Diff 视图并支持采纳。 - setStreamContent((current) => { - onCandidateGenerated(current, suggestionId); - return current; - }); + // 直接读 streamContentRef(onChunk 已同步累计),避免在 setStreamContent 更新函数里调父组件回调、 + // 在 AIPanel 渲染期触发 WorkspacePage 跨组件 setState。 + onCandidateGenerated(streamContentRef.current, suggestionId); abortControllerRef.current = null; }, onError: (err) => { diff --git a/muse-studio/src/pages/AgentPage.contract.test.tsx b/muse-studio/src/pages/AgentPage.contract.test.tsx index 9598eea7..3cf4c4e3 100644 --- a/muse-studio/src/pages/AgentPage.contract.test.tsx +++ b/muse-studio/src/pages/AgentPage.contract.test.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { http, HttpResponse } from 'msw'; import { describe, expect, it, beforeEach } from 'vitest'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';