接通 DirectProject 中间阶段进度

订阅 game-creator-agent-progress 并按项目过滤

在执行过程卡显示真实阶段文案并补充回归测试
This commit is contained in:
kdletters
2026-10-06 12:05:23 +08:00
parent c0c9179d5c
commit 2c285fbd99
4 changed files with 71 additions and 2 deletions
@@ -13,6 +13,11 @@ import type { PendingUiConfirmation } from '../../../app/types';
import { projectNameFromPath } from '../../../features/agent-runtime';
import { registerActiveChatComposer } from '../../../features/project-workspace/activeChatComposer';
import { userItemFromContent } from '../../../features/project-workspace/resourceReferences';
import { visibleClientErrorMessage } from '../../../services/errorReporting';
import {
canSubscribeTauriEvents,
subscribeTauriEvent,
} from '../../../services/tauriEventSubscription';
import { type ApprovalMode, approvalModeLabel } from '../approvalMode';
import { ApprovalModeDialog } from '../ApprovalModeDialog';
import { DirectProjectChatHeader } from './components/DirectProjectChatHeader/DirectProjectChatHeader';
@@ -115,6 +120,7 @@ export function DirectProjectChatView({
} = useDirectProjectManifest(projectPath);
const composerHandleRef = useRef<DirectProjectComposerHandle | null>(null);
const [runtimeNotice, setRuntimeNotice] = useState('');
const [agentProgress, setAgentProgress] = useState('');
const [settingsOpen, setSettingsOpen] = useState(false);
const [approvalOpen, setApprovalOpen] = useState(false);
const [approvalMode, setApprovalMode] = useState<ApprovalMode>('strict');
@@ -123,6 +129,39 @@ export function DirectProjectChatView({
const handleRuntimeError = useCallback((message: string) => {
setRuntimeNotice(message);
}, []);
useEffect(() => {
setAgentProgress('');
if (!projectPath || !canSubscribeTauriEvents()) return undefined;
let disposed = false;
let unsubscribe: (() => void) | null = null;
void subscribeTauriEvent<{
project_path?: string;
projectPath?: string;
message?: string;
}>('game-creator-agent-progress', (event) => {
if (disposed) return;
const eventProjectPath =
event.payload.project_path ?? event.payload.projectPath ?? '';
if (eventProjectPath !== projectPath) return;
const message = event.payload.message?.trim();
if (message) setAgentProgress(message);
})
.then((release) => {
if (disposed) release();
else unsubscribe = release;
})
.catch((error) => {
if (!disposed) {
setRuntimeNotice(
`执行过程进度订阅失败:${visibleClientErrorMessage(error)}`,
);
}
});
return () => {
disposed = true;
unsubscribe?.();
};
}, [projectPath]);
const chat = useDirectProjectChatController({
enabled: Boolean(projectPath),
ensureConversationReadAllowed,
@@ -156,6 +195,13 @@ export function DirectProjectChatView({
turnCancelling,
uploadFiles,
} = chat;
useEffect(() => {
// 阶段文案属于当前回合;清掉上一轮的最后阶段,避免下一轮尚未收到首个进度事件时
// 继续显示旧的“正在验证/正在生成”。
if (commandInFlight || (!directTurnRunning && pendingTurns.length === 0)) {
setAgentProgress('');
}
}, [commandInFlight, directTurnRunning, pendingTurns.length]);
// 这份投影要参与下游 memo 的判据,必须自己先缓存:不缓存的话每次渲染都是一份新数组,
// `useDirectProjectTurnStatus` 里以 `turns` 为判据的 `useMemo` 永远命中不了,那层 memo
// 就成了死代码,读代码的人还会误以为 `turnStatus` 是引用稳定的。
@@ -265,6 +311,7 @@ export function DirectProjectChatView({
historyLoading={historyLoading}
historyError={historyError}
turnInFlight={turnStatus.displayBusy}
activeProgress={agentProgress}
activeTurnStartedAt={activeTurnStartedAt}
onLoadEarlierHistory={() => void loadEarlierHistory()}
onRetryEarlierHistory={() => void retryEarlierHistory()}
@@ -26,6 +26,7 @@ export function DirectProjectConversation({
historyLoading,
historyError,
turnInFlight,
activeProgress = '',
activeTurnStartedAt,
onLoadEarlierHistory,
onRetryEarlierHistory,
@@ -46,6 +47,8 @@ export function DirectProjectConversation({
* `activeTurnStartedAt` 还是 0,卡片只报"正在处理";宿主开始事件一到就开始读秒。
*/
turnInFlight: boolean;
/** Rust `game-creator-agent-progress` 的当前阶段;没有事件时保留通用忙态。 */
activeProgress?: string;
activeTurnStartedAt: number;
onLoadEarlierHistory: () => void;
onRetryEarlierHistory: () => void;
@@ -105,7 +108,7 @@ export function DirectProjectConversation({
>
<header>
<span aria-hidden="true" />
<strong>陶泥儿正在处理</strong>
<strong>{activeProgress || '陶泥儿正在处理'}</strong>
<DirectTurnElapsed startedAt={activeTurnStartedAt} />
</header>
</AgentMessageContent>
@@ -77,3 +77,22 @@ test('没有运行中的回合时不订阅时钟', () => {
vi.useRealTimers();
}
});
test('宿主阶段进度会显示在运行中过程卡,而不是只显示通用忙态', () => {
const view = render(
<DirectProjectConversation
conversationKey="direct-process-progress"
turns={[]}
historyHasMore={false}
historyLoading={false}
historyError={null}
turnInFlight
activeProgress="正在验证游戏"
activeTurnStartedAt={0}
onLoadEarlierHistory={() => undefined}
onRetryEarlierHistory={() => undefined}
/>,
);
expect(view.getByText('正在验证游戏')).toBeTruthy();
expect(view.queryByText('陶泥儿正在处理')).toBeNull();
});