fix(studio): AIPanel onDone 改用 ref 读流式正文,消除渲染期跨组件 setState

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) <noreply@anthropic.com>
This commit is contained in:
lili 2026-06-24 18:01:56 -07:00
parent 494c9e9b89
commit 410322cbc0
2 changed files with 9 additions and 5 deletions

View File

@ -42,6 +42,10 @@ export default function AIPanel({
// 维护中止控制器的引用以管理流生命周期
const abortControllerRef = React.useRef<AbortController | null>(null);
// 流式正文最新值镜像onDone 需读累计正文回传上层。用 ref 而非在 setStreamContent 更新函数里调父组件回调——
// 后者会在 AIPanel 渲染期触发 WorkspacePage setStateReact 警告 "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;
});
// 直接读 streamContentRefonChunk 已同步累计),避免在 setStreamContent 更新函数里调父组件回调、
// 在 AIPanel 渲染期触发 WorkspacePage 跨组件 setState。
onCandidateGenerated(streamContentRef.current, suggestionId);
abortControllerRef.current = null;
},
onError: (err) => {

View File

@ -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';