补充项目风险命令入口
新增聊天侧risks命令基于当前状态生成风险摘要 补充主窗口风险入口和帮助命令测试 同步AI游戏创作App技术方案和共享决策记录
This commit is contained in:
@@ -1826,6 +1826,7 @@ const chatCommandHelp = [
|
||||
'/audit:审计当前项目的 Agent 能力证据',
|
||||
'/status:查看项目状态',
|
||||
'/brief:生成当前项目简报',
|
||||
'/risks:查看当前项目风险',
|
||||
'/next:查看下一步建议',
|
||||
'/open-project:在系统文件管理器中显示项目目录',
|
||||
'/switch-project:回到项目启动器切换工作区',
|
||||
@@ -2020,6 +2021,111 @@ function summarizeProjectBrief(
|
||||
}`;
|
||||
}
|
||||
|
||||
function summarizeProjectRisks(
|
||||
nextManifest: GameCreationAppManifest,
|
||||
trace: GameCreationAgentRunTrace | null,
|
||||
) {
|
||||
const risks: Array<{ text: string; command?: string }> = [];
|
||||
const addRisk = (text: string, command?: string) => {
|
||||
if (command && risks.some((risk) => risk.command === command)) {
|
||||
return;
|
||||
}
|
||||
risks.push({ text, command });
|
||||
};
|
||||
|
||||
const tasks = taskRowsFromManifest(nextManifest);
|
||||
const failedTasks = tasks.filter((task) => task.status === 'failed');
|
||||
const readyTasks = selectGameCreationAppReadyTasks({ tasks });
|
||||
const commandRuns = nextManifest.commandRuns ?? [];
|
||||
const latestCommandRun = commandRuns[commandRuns.length - 1];
|
||||
const preview = nextManifest.preview;
|
||||
const tracePassed =
|
||||
trace?.status === 'passed' ||
|
||||
trace?.status === 'artifacts-written' ||
|
||||
trace?.stopReason === 'evaluator-passed';
|
||||
const hasCanvasImageAsset = nextManifest.assets.some(
|
||||
(asset) =>
|
||||
asset.source.kind === 'canvas' &&
|
||||
(asset.mediaType.startsWith('image/') ||
|
||||
asset.mediaType === 'application/vnd.genarrative.image-sequence'),
|
||||
);
|
||||
|
||||
if (!trace) {
|
||||
addRisk(
|
||||
'暂无最近 Agent run,当前项目还缺少生成闭环证据。',
|
||||
'/next',
|
||||
);
|
||||
} else if (
|
||||
trace.lifecycleStatus === 'killed' ||
|
||||
trace.status === 'failed' ||
|
||||
trace.stopReason === 'max-passes-exhausted'
|
||||
) {
|
||||
addRisk(
|
||||
`最近 run 未完成:${trace.status} / ${trace.stopReason}。`,
|
||||
'/agent-resume ',
|
||||
);
|
||||
} else if (!tracePassed) {
|
||||
addRisk(
|
||||
`最近 run 尚未通过 Evaluator:${trace.status} / ${trace.stopReason}。`,
|
||||
'/trace',
|
||||
);
|
||||
}
|
||||
|
||||
if (failedTasks.length > 0) {
|
||||
addRisk(
|
||||
`有 ${failedTasks.length} 个任务处于失败状态。`,
|
||||
'/tasks',
|
||||
);
|
||||
}
|
||||
|
||||
if (latestCommandRun?.status === 'failed') {
|
||||
addRisk(
|
||||
`最近命令 ${latestCommandRun.commandId} 失败。`,
|
||||
'/logs',
|
||||
);
|
||||
}
|
||||
|
||||
if (tracePassed && !(preview?.status === 'running' && preview.url)) {
|
||||
addRisk(
|
||||
'最近 run 已通过,但当前本地预览未运行。',
|
||||
'/run',
|
||||
);
|
||||
}
|
||||
|
||||
if (readyTasks.length > 0) {
|
||||
addRisk(
|
||||
`还有 ${readyTasks.length} 个 ready 任务等待处理。`,
|
||||
'/tasks',
|
||||
);
|
||||
}
|
||||
|
||||
if (nextManifest.assets.length === 0) {
|
||||
addRisk(
|
||||
'暂无本地资产,首版原型可能缺少可复用素材。',
|
||||
'/asset-register assets/hero.png image image/png',
|
||||
);
|
||||
} else if (!hasCanvasImageAsset) {
|
||||
addRisk(
|
||||
'暂无画板来源图片资产,美术组可能只能先使用占位素材。',
|
||||
'/sync-canvas-project ',
|
||||
);
|
||||
}
|
||||
|
||||
const firstAction = risks.find((risk) => risk.command);
|
||||
return {
|
||||
text:
|
||||
risks.length > 0
|
||||
? `项目风险:\n${risks
|
||||
.map(
|
||||
(risk) => `- ${risk.text}${risk.command ? ` 建议:${risk.command}` : ''}`,
|
||||
)
|
||||
.join('\n')}`
|
||||
: '项目风险:\n- 暂未发现需要立即处理的风险。',
|
||||
draftCommand: firstAction?.command,
|
||||
draftCommandLabel: firstAction ? '处理首个风险' : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeNextProjectActions(
|
||||
nextManifest: GameCreationAppManifest,
|
||||
trace: GameCreationAgentRunTrace | null,
|
||||
@@ -5352,6 +5458,23 @@ export function App() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prompt === '/risks') {
|
||||
if (!requireChatProjectForUserAction()) {
|
||||
return;
|
||||
}
|
||||
const summary = summarizeProjectRisks(manifest, agentRunTrace);
|
||||
setMessages((current) => [
|
||||
...current,
|
||||
{
|
||||
role: 'assistant',
|
||||
text: summary.text,
|
||||
draftCommand: summary.draftCommand,
|
||||
draftCommandLabel: summary.draftCommandLabel,
|
||||
},
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (prompt === '/next') {
|
||||
if (!requireChatProjectForUserAction()) {
|
||||
return;
|
||||
|
||||
@@ -2134,6 +2134,40 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
),
|
||||
).toHaveLength(manifestReadCountBeforeBrief);
|
||||
|
||||
const manifestReadCountBeforeRisks = invoke.mock.calls.filter(
|
||||
([command]) => command === 'get_local_game_manifest',
|
||||
).length;
|
||||
const fileReadCountBeforeRisks = invoke.mock.calls.filter(
|
||||
([command]) => command === 'read_local_project_file',
|
||||
).length;
|
||||
const runLocalCountBeforeRisks = invoke.mock.calls.filter(
|
||||
([command]) => command === 'run_limited_local_command',
|
||||
).length;
|
||||
submitChat('/risks');
|
||||
expect(await screen.findByText(/项目风险:/)).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(/最近 run 已通过,但当前本地预览未运行。 建议:\/run/),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText(/还有 1 个 ready 任务等待处理。 建议:\/tasks/))
|
||||
.not.toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: '处理首个风险' }));
|
||||
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run');
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command]) => command === 'get_local_game_manifest',
|
||||
),
|
||||
).toHaveLength(manifestReadCountBeforeRisks);
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command]) => command === 'read_local_project_file',
|
||||
),
|
||||
).toHaveLength(fileReadCountBeforeRisks);
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command]) => command === 'run_limited_local_command',
|
||||
),
|
||||
).toHaveLength(runLocalCountBeforeRisks);
|
||||
|
||||
const runLocalCountBeforeNext = invoke.mock.calls.filter(
|
||||
([command]) => command === 'run_limited_local_command',
|
||||
).length;
|
||||
@@ -7712,6 +7746,7 @@ describe('AI 游戏创作 App 界面边界', () => {
|
||||
screen.getByText(/\/switch-project:回到项目启动器切换工作区/),
|
||||
).not.toBeNull();
|
||||
expect(screen.getByText(/\/brief:生成当前项目简报/)).not.toBeNull();
|
||||
expect(screen.getByText(/\/risks:查看当前项目风险/)).not.toBeNull();
|
||||
expect(screen.getByText(/\/next:查看下一步建议/)).not.toBeNull();
|
||||
expect(
|
||||
screen.getByText(
|
||||
|
||||
Reference in New Issue
Block a user