补充本地化文案聊天入口

新增 /localization 本地化与文案检查摘要

补充 /next 与 /help 的本地化入口覆盖

验证 /localization 不触发文件读取、运行、预览、导出或写入

同步 AI 游戏创作 App 技术方案和决策记录
This commit is contained in:
AIGameCreator App
2026-07-04 13:55:52 +08:00
parent 6d2e1ffb3a
commit 14e92e477f
4 changed files with 266 additions and 2 deletions
+122
View File
@@ -1878,6 +1878,7 @@ const chatCommandHelp = [
'/mobile:查看移动试玩检查',
'/compatibility:准备兼容性说明',
'/accessibility:查看可读性与无障碍检查',
'/localization:查看本地化与文案检查',
'/performance:查看性能与加载检查',
'/polish:查看试玩前打磨清单',
'/risks:查看当前项目风险',
@@ -2846,6 +2847,106 @@ function summarizeProjectAccessibilityGuide(
};
}
function summarizeProjectLocalizationChecklist(
nextManifest: GameCreationAppManifest,
trace: GameCreationAgentRunTrace | null,
) {
const preview = nextManifest.preview;
const previewRunning = preview?.status === 'running' && preview.url;
const previewSummary = previewRunning
? `运行中 ${preview.url}`
: preview
? previewStatusLabels[preview.status]
: '未启动';
const tracePassed = isAgentRunTracePassed(trace);
const goal =
nextManifest.goal?.trim() ||
trace?.goal?.trim() ||
trace?.taskGraph.goal?.trim() ||
'暂无';
const manifestTasks = taskRowsFromManifest(nextManifest);
const traceTasks = trace?.taskGraph.tasks ?? [];
const tasks = manifestTasks.map(
(task) => traceTasks.find((traceTask) => traceTask.id === task.id) ?? task,
);
const relevantTasks = [
'design-foundation',
'code-prototype',
'quality-review',
'publish-package',
]
.map((taskId) => tasks.find((task) => task.id === taskId))
.filter((task): task is NonNullable<typeof task> => Boolean(task));
const taskLines = relevantTasks.map(
(task) =>
`- ${task.id}:${taskGroupLabels[task.group]} / ${task.role} ${task.title} · ${taskStatusLabels[task.status]}`,
);
const hasPublishArtifact =
trace?.artifacts.some((artifact) => artifact.path === 'exports/README.md') ??
false;
const latestCopyStep =
trace?.steps
.filter(
(step) =>
step.group === 'design' ||
step.group === 'code' ||
step.group === 'publishing' ||
step.phase === 'evaluation' ||
step.taskId === 'design-foundation' ||
step.taskId === 'quality-review' ||
step.taskId === 'publish-package',
)
.slice(-1)[0] ?? null;
let draftCommand = '/next';
let draftCommandLabel = '查看下一步';
if (trace && !tracePassed) {
draftCommand = '/review';
draftCommandLabel = '查看评审';
} else if (hasPublishArtifact) {
draftCommand = '/read exports/README.md';
draftCommandLabel = '读发布说明';
} else if (tracePassed) {
draftCommand =
'/agent-resume 本地化与文案:统一标题、按钮、状态提示、失败胜利文案、发布简介';
draftCommandLabel = '补充文案';
}
return {
text: [
'本地化与文案:',
`- 项目:${nextManifest.name}`,
`- 目标:${goal}`,
'- 默认语言:简体中文;首版不承诺多语言',
'- 文案范围:标题;目标提示;操作按钮;状态反馈;失败/胜利;重开;发布简介',
`- 当前证据:${
tracePassed
? `最近 run 已通过${trace?.runId ? ` ${trace.runId}` : ''}`
: trace
? `最近 run 未通过 ${trace.status} / ${trace.stopReason}`
: '暂无最近 run'
};预览 ${previewSummary};发布说明 ${
hasPublishArtifact ? '已生成' : '未见 trace 产物'
}`,
taskLines.length > 0
? `- 关联任务:\n${taskLines.join('\n')}`
: '- 关联任务:暂无',
'- 检查口径:短句优先;动词一致;玩家术语统一;错误提示可复现;UI 文案避免开发解释',
'- 暂不做:英日韩等多语言包;自动翻译;地区化素材;语音本地化;商店长文案 A/B',
`- 最近文案相关步骤:${
latestCopyStep
? `${latestCopyStep.agent} #${latestCopyStep.pass} · ${latestCopyStep.status} · ${latestCopyStep.summary}`
: '暂无'
}`,
'- 参考:/rules;/tutorial;/listing;/faq;/known-issues',
'- 边界:只整理本地化与文案检查;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目',
`- 建议:${draftCommand}`,
].join('\n'),
draftCommand,
draftCommandLabel,
};
}
function summarizeProjectPerformanceCheck(
nextManifest: GameCreationAppManifest,
trace: GameCreationAgentRunTrace | null,
@@ -6067,6 +6168,7 @@ function summarizeNextProjectActions(
addSuggestion('查看移动试玩检查', '/mobile');
addSuggestion('准备兼容性说明', '/compatibility');
addSuggestion('查看可读性与无障碍检查', '/accessibility');
addSuggestion('查看本地化与文案检查', '/localization');
addSuggestion('查看性能与加载检查', '/performance');
addSuggestion('查看试玩前打磨清单', '/polish');
addSuggestion('查看数值与难度口径', '/balance');
@@ -9952,6 +10054,26 @@ export function App() {
return;
}
if (prompt === '/localization') {
if (!requireChatProjectForUserAction()) {
return;
}
const summary = summarizeProjectLocalizationChecklist(
manifest,
agentRunTrace,
);
setMessages((current) => [
...current,
{
role: 'assistant',
text: summary.text,
draftCommand: summary.draftCommand,
draftCommandLabel: summary.draftCommandLabel,
},
]);
return;
}
if (prompt === '/performance') {
if (!requireChatProjectForUserAction()) {
return;
@@ -3196,6 +3196,139 @@ describe('AI 游戏创作 App 界面边界', () => {
invoke.mock.calls.filter(([command]) => command === 'control_agent_run'),
).toHaveLength(commandCountsBeforeAccessibility.controlAgentRun);
const commandCountsBeforeLocalization = {
manifestRead: invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
).length,
fileRead: invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length,
fileList: invoke.mock.calls.filter(
([command]) => command === 'list_local_project_files',
).length,
runLocal: invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length,
startPreview: invoke.mock.calls.filter(
([command]) => command === 'start_local_game_preview',
).length,
openPreview: invoke.mock.calls.filter(
([command]) => command === 'open_local_game_preview',
).length,
exportPackage: invoke.mock.calls.filter(
([command]) => command === 'export_local_project_package',
).length,
exportList: invoke.mock.calls.filter(
([command]) => command === 'list_local_project_export_packages',
).length,
controlAgentRun: invoke.mock.calls.filter(
([command]) => command === 'control_agent_run',
).length,
};
submitChat('/localization');
const localizationMessages =
await screen.findAllByText(/本地化与文案:/);
const localizationMessage =
localizationMessages[localizationMessages.length - 1];
expect(localizationMessage.textContent).toContain('项目:未命名游戏原型');
expect(localizationMessage.textContent).toContain(
'目标:做一个厨房弹幕游戏',
);
expect(localizationMessage.textContent).toContain(
'默认语言:简体中文;首版不承诺多语言',
);
expect(localizationMessage.textContent).toContain(
'文案范围:标题;目标提示;操作按钮;状态反馈;失败/胜利;重开;发布简介',
);
expect(localizationMessage.textContent).toContain(
'当前证据:最近 run 已通过 run-main-shortcut-trace;预览 未启动;发布说明 已生成',
);
expect(localizationMessage.textContent).toContain(
'design-foundation:策划组 / Gameplay 确定玩法规格 · 待处理',
);
expect(localizationMessage.textContent).toContain(
'code-prototype:程序组 / Code 生成可运行原型 · 待处理',
);
expect(localizationMessage.textContent).toContain(
'quality-review:程序组 / Review 执行质量评审 · 待处理',
);
expect(localizationMessage.textContent).toContain(
'publish-package:运营组 / Publish 整理发布包装 · 待处理',
);
expect(localizationMessage.textContent).toContain(
'检查口径:短句优先;动词一致;玩家术语统一;错误提示可复现;UI 文案避免开发解释',
);
expect(localizationMessage.textContent).toContain(
'暂不做:英日韩等多语言包;自动翻译;地区化素材;语音本地化;商店长文案 A/B',
);
expect(localizationMessage.textContent).toContain(
'最近文案相关步骤:Evaluator #1 · passed · 质量评审通过',
);
expect(localizationMessage.textContent).toContain(
'参考:/rules;/tutorial;/listing;/faq;/known-issues',
);
expect(localizationMessage.textContent).toContain(
'边界:只整理本地化与文案检查;不读取文件;不启动或打开预览;不导出试玩包;不上传云端;不发布作品;不写项目',
);
expect(localizationMessage.textContent).toContain(
'建议:/read exports/README.md',
);
const localizationMessageList = document.querySelector('.message-list');
expect(localizationMessageList).not.toBeNull();
const localizationDraftButtons = within(
localizationMessageList as HTMLElement,
).getAllByRole('button', { name: '读发布说明' });
fireEvent.click(
localizationDraftButtons[localizationDraftButtons.length - 1],
);
expect(screen.getByLabelText('创作想法')).toHaveProperty(
'value',
'/read exports/README.md',
);
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
),
).toHaveLength(commandCountsBeforeLocalization.manifestRead);
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
),
).toHaveLength(commandCountsBeforeLocalization.fileRead);
expect(
invoke.mock.calls.filter(
([command]) => command === 'list_local_project_files',
),
).toHaveLength(commandCountsBeforeLocalization.fileList);
expect(
invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
),
).toHaveLength(commandCountsBeforeLocalization.runLocal);
expect(
invoke.mock.calls.filter(
([command]) => command === 'start_local_game_preview',
),
).toHaveLength(commandCountsBeforeLocalization.startPreview);
expect(
invoke.mock.calls.filter(
([command]) => command === 'open_local_game_preview',
),
).toHaveLength(commandCountsBeforeLocalization.openPreview);
expect(
invoke.mock.calls.filter(
([command]) => command === 'export_local_project_package',
),
).toHaveLength(commandCountsBeforeLocalization.exportPackage);
expect(
invoke.mock.calls.filter(
([command]) => command === 'list_local_project_export_packages',
),
).toHaveLength(commandCountsBeforeLocalization.exportList);
expect(
invoke.mock.calls.filter(([command]) => command === 'control_agent_run'),
).toHaveLength(commandCountsBeforeLocalization.controlAgentRun);
const commandCountsBeforePerformance = {
manifestRead: invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
@@ -4847,6 +4980,9 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(
screen.getByText(/查看可读性与无障碍检查:\/accessibility/),
).not.toBeNull();
expect(
screen.getByText(/查看本地化与文案检查:\/localization/),
).not.toBeNull();
expect(
screen.getByText(/查看性能与加载检查:\/performance/),
).not.toBeNull();
@@ -12588,6 +12724,9 @@ describe('AI 游戏创作 App 界面边界', () => {
expect(
screen.getByText(/\/accessibility:查看可读性与无障碍检查/),
).not.toBeNull();
expect(
screen.getByText(/\/localization:查看本地化与文案检查/),
).not.toBeNull();
expect(
screen.getByText(/\/performance:查看性能与加载检查/),
).not.toBeNull();