diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 2c63e38cd..264e9692f 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -335,11 +335,7 @@ function directCodexProcessDetail({ return '正在记录失败原因'; } if (status === 'streaming') { - const text = accumulatedText?.trim(); - if (text) { - return `正在生成回复:${text}`; - } - return directCodexActivityDetail(activity, status); + return '正在生成回复'; } if (status === 'finalizing') { return directCodexActivityDetail(activity, status); @@ -351,6 +347,24 @@ function directCodexProcessDetail({ return directCodexActivityDetail(activity, status); } +function directCodexTransientReplyText({ + accumulatedText, + status, +}: { + accumulatedText?: string | null; + status: string; +}) { + if ( + status !== 'streaming' && + status !== 'finalizing' && + status !== 'completed' + ) { + return null; + } + const text = accumulatedText?.trim(); + return text || null; +} + function directCodexConversationMessageId( turnId: string, role: ChatMessage['role'], @@ -1305,15 +1319,18 @@ export function App({ setDirectCodexStatus(payload.status); setDirectCodexProgress(processDetail); setDirectCodexProgressUpdatedAt(updatedAt); - setDirectCodexTransientReply(processDetail); + setDirectCodexTransientReply(''); setDirectCodexTransientReplyUpdatedAt(null); return; } setDirectCodexStatus(payload.status); setDirectCodexProgress(processDetail); setDirectCodexProgressUpdatedAt(updatedAt); - setDirectCodexTransientReply(processDetail); - setDirectCodexTransientReplyUpdatedAt(updatedAt); + const transientReply = directCodexTransientReplyText(payload); + if (transientReply !== null) { + setDirectCodexTransientReply(transientReply); + setDirectCodexTransientReplyUpdatedAt(updatedAt); + } }, ) .then((unlisten) => { @@ -1358,7 +1375,6 @@ export function App({ const progressDetail = ensureDirectProcessPrefix(event.payload.message); setDirectCodexStatus('running'); setDirectCodexProgress(progressDetail); - setDirectCodexTransientReply(progressDetail); setDirectCodexProgressUpdatedAt(Date.now()); return; } @@ -5537,7 +5553,7 @@ export function App({ setDirectCodexStatus('accepted'); setDirectCodexProcessKey(`${directProjectPath}\u0000${clientTurnId}`); setDirectCodexProgress('正在等待陶泥儿开始'); - setDirectCodexTransientReply('正在等待陶泥儿开始'); + setDirectCodexTransientReply(''); setDirectCodexProgressUpdatedAt(Date.now()); setDirectCodexTransientReplyUpdatedAt(null); setProjectSupervisorRuntimeError(''); @@ -5609,7 +5625,6 @@ export function App({ ); setDirectCodexStatus('finalizing'); setDirectCodexProgress('正在同步项目文件'); - setDirectCodexTransientReply('正在同步项目文件'); setDirectCodexProgressUpdatedAt(Date.now()); await refreshDirectProjectManifest(directProjectPath); } diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx index 566f18df7..98537ada9 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ProjectSupervisorView.tsx @@ -186,7 +186,7 @@ export function ProjectSupervisorView({

))} {directCodex && - (runtimePanelProps.controlBusy || Boolean(transientReply)) ? ( + (runtimePanelProps.controlBusy || Boolean(directProcessDetail)) ? (
- {(directCodex ? directProcessDetail : transientReply) ? ( + {directProcessDetail ? (

- {directCodex ? directProcessDetail : transientReply} + {directProcessDetail}

- {(directCodex - ? directProcessDetail - : transientReply - ).includes('\n') || - (directCodex ? directProcessDetail : transientReply).length > - 96 ? ( + {directProcessDetail.includes('\n') || + directProcessDetail.length > 96 ? (
- ) : transientReply ? ( + ) : null} + {transientReply ? (

diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css index ecb06d6ad..e4281b392 100644 --- a/apps/ai-game-creator-shell/src/styles.css +++ b/apps/ai-game-creator-shell/src/styles.css @@ -2587,6 +2587,12 @@ textarea { display: block; max-height: 180px; overflow: auto; + scrollbar-color: rgb(47 111 75 / 58%) transparent; +} + +.project-supervisor-process-card p.is-expanded::-webkit-scrollbar-track, +.project-supervisor-process-card p.is-expanded::-webkit-scrollbar-corner { + background: transparent; } .project-supervisor-process-detail { diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index 90558c11f..7eaf924ca 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -5700,7 +5700,10 @@ export function registerProjectSupervisorSurfaceTests() { expect( within(streamingProcessCard).getByLabelText('陶泥儿正在执行的内容') .textContent, - ).toContain('正在生成回复:DIRECT_STREAM:先完成正式客户端玩法拆解'); + ).toBe('正在生成回复'); + expect( + within(supervisorSurface).getByLabelText('陶泥儿实时回复').textContent, + ).toBe('DIRECT_STREAM:先完成正式客户端玩法拆解'); expect(directMessageList.scrollTop).toBe(640); expect(within(supervisorSurface).queryByText(/不能覆盖正文/u)).toBeNull(); expect( @@ -5725,6 +5728,9 @@ export function registerProjectSupervisorSurfaceTests() { expect( within(streamingProcessCard).getByText(/正在执行 npm test/u), ).not.toBeNull(); + expect( + within(supervisorSurface).getByLabelText('陶泥儿实时回复').textContent, + ).toBe('DIRECT_STREAM:先完成正式客户端玩法拆解'); expect( ( within(streamingProcessCard).getByRole('button', { @@ -5808,10 +5814,10 @@ export function registerProjectSupervisorSurfaceTests() { expect( within(failedTurnProcessCard).getByLabelText('陶泥儿正在执行的内容') .textContent, - ).toContain('正在生成回复:即将失败的临时正文'); + ).toBe('正在生成回复'); expect( - within(supervisorSurface).queryByLabelText('陶泥儿实时回复'), - ).toBeNull(); + within(supervisorSurface).getByLabelText('陶泥儿实时回复').textContent, + ).toBe('即将失败的临时正文'); await act(async () => { directTurnUpdateHandler?.({ payload: { diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index b13c9d328..13e60d322 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -18,9 +18,9 @@ ## 2026-09-02 Direct 过程卡按回合阶段状态驱动 - 背景:DirectProject 结果卡把工具活动词、中间文本和真实回复增量都当成“实时回复”,标题随最近一次事件跳动;上游常整包返回正文时还叠加合成打字机,用户看到的是行为名而非当前阶段。 -- 决策:Direct 过程卡顶部标题只由 `GameCreatorDirectTurnUpdateStatus` 决定(accepted=需求已接收 / running=任务执行中 / streaming=回复生成中 / finalizing=结果整理中 / completed=回复已生成 / failed=处理失败),小字只展示当前正在执行的具体内容并统一加“正在”前缀;真实回复增量(AccumulatedText)才标记 streaming,计划、推理、工具输出与 Activity 一律 running。移除合成打字机回放;工具说明/中间文本不再触发 streaming。展开/收起是同一 `project + clientTurnId` 内的持久状态,内容更新不重置,切换新回合才收起。 +- 决策:Direct 过程卡顶部标题只由 `GameCreatorDirectTurnUpdateStatus` 决定(accepted=需求已接收 / running=任务执行中 / streaming=回复生成中 / finalizing=结果整理中 / completed=回复已生成 / failed=处理失败),小字只展示当前正在执行的具体内容并统一加“正在”前缀;真实回复增量(AccumulatedText)才标记 streaming,计划、推理、工具输出与 Activity 一律 running。生成中的累计回复直接作为 assistant 消息气泡在会话列表中原位更新,不再拼进过程卡;进入 finalizing / completed 时保留完整累计回复直到正式消息接管,失败时清除未完成正文。移除合成打字机回放;工具说明/中间文本不再触发 streaming。展开/收起是同一 `project + clientTurnId` 内的持久状态,内容更新不重置,切换新回合才收起;展开详情的滚动条轨道和角落保持透明。 - 影响范围:`apps/ai-game-creator-shell/src-tauri/src/agent/direct_runtime.rs` 的 DirectProject observer、`apps/ai-game-creator-shell/src/App.tsx` 的事件投影、`ProjectSupervisorView` 过程卡渲染与对应 AppSurface 回归。 -- 验证方式:Rust 单测证明只有开启流式时的 AccumulatedText 是 streaming;AppSurface 覆盖接受态、running 长文本展开、streaming 标题与小字、同一回合后续 running 不收起、失败态小字与卡片清理;AGC typecheck、全量 appSurface、rustfmt、`npm run check:encoding`、`git diff --check` 通过。 +- 验证方式:Rust 单测证明只有开启流式时的 AccumulatedText 是 streaming;AppSurface 覆盖接受态、running 长文本展开、streaming 正文进入 assistant 气泡且过程卡只显示阶段、同一回合后续 running 不覆盖正文也不收起、失败后清除未完成正文、正式消息接管不重复;样式核对确认展开详情的滚动条轨道与角落透明;AGC typecheck、全量 appSurface、rustfmt、`npm run check:encoding`、`git diff --check` 通过。 - 关联文档:`docs/technical/【技术方案】Direct回合行为审计账本-2026-08-31.md`、分支 `feat/agc-llm-router-official-chain`。 ---