修复 AGC 前端资源 kind 判据与用例的 canonical 残留
Project CI / AI game creator shell Rust shard 4/4 (pull_request) Successful in 7m1s
Project CI / AI game creator shell Rust shard 3/4 (pull_request) Successful in 7m6s
Project CI / AI game creator shell Rust shard 1/4 (pull_request) Successful in 7m14s
Project CI / AI game creator shell Rust shard 2/4 (pull_request) Successful in 7m52s
Project CI / AI game creator shell Rust crates (pull_request) Successful in 1m31s
Project CI / AI game creator shell Rust smoke (pull_request) Successful in 1m50s
Project CI / Repository checks (pull_request) Successful in 4m44s
Project CI / Frontend tests (pull_request) Successful in 6m20s
Project CI / Backend tests (pull_request) Successful in 8m27s
Project CI / Native shell tests (pull_request) Successful in 8m15s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m10s

- resource-canvas/resourceCanvasAssetGenerationReferenceModel.ts:「图集不接受用户参考」判据改用 canonical icon-spritesheet,删除残留平台词的字符串清单。
- view/project-development/index.tsx:美术摘要的图片产出判据显式排除 ui-design,候选 UI 原型图不再顶掉「仅完成计划」。
- tests/appSurface/project-development.suite.ts:generateCall 支持按素材名定位,规范 / 图集 / UI 设计图入口断言改用 canonical kind。
- tests/appSurface/project-assets.suite.ts:/asset-register 与开发面板用例改用 canonical kind,面板默认值按 unknown 断言。
- tests/resourceCanvasAssetGenerationReferences.test.tsx:夹具与提交断言改用 icon-spritesheet / ui-design。
- docs/technical/【AGC】栏目画布底部工具栏入口矩阵-2026-09-13.md:载荷表与规范通道说明改用 canonical kind,并写明平台请求词汇只留在 source.generationKind。
- docs/project-memory/shared-memory/pitfalls.md:记录前端 kind 判据与 fixture 必须一起按 canonical 成员重写。
This commit is contained in:
2026-09-19 03:21:37 +08:00
parent 603650fbf4
commit 47f7cb2bf1
7 changed files with 72 additions and 45 deletions
@@ -23,21 +23,17 @@ export const RESOURCE_CANVAS_ASSET_GENERATION_MAX_USER_REFERENCES_WITH_SPEC = 4;
/**
* 不接受用户参考的生成类型。
*
* 目前只有 `art-spritesheet`(图集)按合同只接受单张规范引用:这类入口不呈现用户参考选择器,
* 原生提交也会显式拒绝额外参考(不是静默丢弃)。
* 目前只有 `icon-spritesheet`(图集)按合同只接受单张规范引用:这类入口不呈现用户参考选择器,
* 原生提交也会显式拒绝额外参考(不是静默丢弃)。判据直接用 manifest 的 canonical kind
* 与 Rust 侧 `platform_art_asset_kind_accepts_user_reference_assets` 同一个词。
*
* 其它入口一律支持用户参考,包括 `icon-spec`(图标规范):它虽然产出权威规范图,但生成时同样
* 可以带参考图,上限与普通生成一致。
*/
const RESOURCE_CANVAS_ASSET_GENERATION_REFERENCE_FREE_KINDS: readonly string[] =
['art-spritesheet'];
export function resourceCanvasAssetGenerationAcceptsReferences(
action: ResourceCanvasAssetToolAction,
): boolean {
return !RESOURCE_CANVAS_ASSET_GENERATION_REFERENCE_FREE_KINDS.includes(
action.assetKind,
);
return action.assetKind !== 'icon-spritesheet';
}
/**
@@ -3938,10 +3938,17 @@ export default function ProjectDevelopmentView({
resourceRenameAsset,
],
);
/**
* Agent = UI
*
* `ui-design`
*
*/
const hasRegisteredArtImageAssets = manifest.assets.some(
(asset) =>
asset.mediaType.startsWith('image/') &&
isGameCreationAppAssetVisualKind(asset.kind),
isGameCreationAppAssetVisualKind(asset.kind) &&
asset.kind !== GAME_CREATION_APP_UI_DESIGN_ASSET_KIND,
);
const allAgentSummaries = [
summarizeAgent(manifest, 'design', '设计实现 Agent'),
@@ -396,14 +396,14 @@ export function registerProjectAssetTests() {
).not.toBeNull();
invoke.mockClear();
await submitChat('/asset-register assets/hero.png sprite image/png');
await submitChat('/asset-register assets/hero.png image image/png');
expect(
await screen.findByText('asset.register · assets/hero.png'),
).not.toBeNull();
expect(
screen.getByText(
'登记 /tmp/authorized-game/assets/hero.png · sprite · image/png',
'登记 /tmp/authorized-game/assets/hero.png · image · image/png',
),
).not.toBeNull();
expect(invoke).not.toHaveBeenCalledWith(
@@ -419,7 +419,7 @@ export function registerProjectAssetTests() {
expect(invoke).toHaveBeenCalledWith('register_local_asset', {
projectPath: '/tmp/authorized-game',
localPath: 'assets/hero.png',
kind: 'sprite',
kind: 'image',
mediaType: 'image/png',
sourceKind: 'generated',
canvasProjectId: '',
@@ -547,7 +547,7 @@ export function registerProjectAssetTests() {
).not.toBeNull();
invoke.mockClear();
await submitChat('/asset-register assets/hero.png sprite image/png');
await submitChat('/asset-register assets/hero.png image image/png');
expect(
await screen.findByText('准备登记项目资产:assets/hero.png'),
@@ -571,7 +571,7 @@ export function registerProjectAssetTests() {
expect(invoke).toHaveBeenCalledWith('register_local_asset', {
projectPath: '/tmp/authorized-game',
localPath: 'assets/hero.png',
kind: 'sprite',
kind: 'image',
mediaType: 'image/png',
sourceKind: 'generated',
canvasProjectId: '',
@@ -675,7 +675,9 @@ export function registerProjectAssetTests() {
expect(invoke).toHaveBeenCalledWith('register_local_asset', {
projectPath: '/tmp/genarrative-ai-game-draft',
localPath: 'assets/hero.png',
kind: 'asset',
// 面板默认「资产类型」是 `unknown`(待归类):写入口径允许先落 Unknown,
// 由后续归类决定怎么处理,不在这里编一个非 canonical 的别名。
kind: 'unknown',
mediaType: 'application/octet-stream',
sourceKind: 'generated',
canvasProjectId: '',
@@ -2,6 +2,7 @@ import userEvent from '@testing-library/user-event';
import { createRef } from 'react';
import type {
GameCreationAppAssetKind,
ProjectResourceCanvasLayout,
ProjectResourceCanvasPosition,
} from '../../../../packages/shared/src/contracts/gameCreationApp';
@@ -370,16 +371,27 @@ async function submitBottomToolbarPanel(
*
* `start_local_project_asset_generation` +
* Rust
*
* canonical kind IPC kind
* `assetName` kind
*/
function generateCall(calls: readonly BottomToolbarInvokeCall[], kind: string) {
function generateCall(
calls: readonly BottomToolbarInvokeCall[],
kind: GameCreationAppAssetKind,
assetName?: string,
) {
const call = calls.find(
(entry) =>
entry.command === 'start_local_project_asset_generation' &&
invokeStringField(entry.args, 'kind') === kind,
invokeStringField(entry.args, 'kind') === kind &&
(assetName === undefined ||
invokeStringField(entry.args, 'assetName') === assetName),
);
if (!call?.args) {
throw new Error(
`missing start_local_project_asset_generation call for ${kind}`,
`missing start_local_project_asset_generation call for ${
assetName ?? kind
}`,
);
}
return call.args;
@@ -12101,7 +12113,7 @@ export function registerProjectAgentStatusTests() {
menu: '生成规范',
prompt: '像素月光厨房统一视觉规范',
});
expect(generateCall(calls, 'icon-spec')).toMatchObject({
expect(generateCall(calls, 'icon-spec', '图标规范')).toMatchObject({
kind: 'icon-spec',
prompt: '像素月光厨房统一视觉规范',
aspectRatio: '1:1',
@@ -12110,13 +12122,14 @@ export function registerProjectAgentStatusTests() {
outputPath: 'assets/art-spec.png',
});
// 3) 生成规范 → 自定义规范:规格档固定展示 1:1·1K,不带比例选择器
// 3) 生成规范 → 自定义规范:规格档固定展示 1:1·1K,不带比例选择器;与上面那条
// 共用 `icon-spec` 通道,只有素材名能区分。
await submitBottomToolbarPanel('自定义规范', {
menu: '生成规范',
prompt: '场景与道具的统一色彩规范',
});
expect(generateCall(calls, 'spec')).toMatchObject({
kind: 'spec',
expect(generateCall(calls, 'icon-spec', '自定义规范')).toMatchObject({
kind: 'icon-spec',
prompt: '场景与道具的统一色彩规范',
aspectRatio: '1:1',
imageSize: '1K',
@@ -12191,7 +12204,7 @@ export function registerProjectAgentStatusTests() {
await submitBottomToolbarPanel('生成图标素材', {
prompt: '各种敌人头像:骷髅 哥布林 强盗',
});
expect(generateCall(calls, 'art-spritesheet')).toMatchObject({
expect(generateCall(calls, 'icon-spritesheet')).toMatchObject({
kind: 'icon-spritesheet',
prompt: '各种敌人头像:骷髅 哥布林 强盗',
aspectRatio: '1:1',
@@ -12204,7 +12217,7 @@ export function registerProjectAgentStatusTests() {
await submitBottomToolbarPanel('生成 UI 设计图', {
prompt: '横屏单屏界面,含 HUD 与操作控件',
});
expect(generateCall(calls, 'ui-prototype')).toMatchObject({
expect(generateCall(calls, 'ui-design')).toMatchObject({
kind: 'ui-design',
prompt: '横屏单屏界面,含 HUD 与操作控件',
aspectRatio: '16:9',
@@ -12219,7 +12232,7 @@ export function registerProjectAgentStatusTests() {
menu: '生成规范',
prompt: '第二版图标规范',
});
expect(generateCall(calls, 'icon-spec')).toMatchObject({
expect(generateCall(calls, 'icon-spec', '图标规范')).toMatchObject({
kind: 'icon-spec',
assetName: '图标规范',
outputPath: null,
@@ -12241,10 +12254,10 @@ export function registerProjectAgentStatusTests() {
menu: '生成规范',
prompt: '三角色头身比与服装规范',
});
// 本地 IPC 只有一条规范通道(spec),没有 specType 入参:角色规范与自定义规范共用它,
// 靠素材名与提示词区分。
expect(generateCall(calls, 'spec')).toMatchObject({
kind: 'spec',
// 本地 IPC 只有一条规范通道(`icon-spec`),没有 specType 入参:角色规范与自定义规范
// 共用它,靠素材名与提示词区分。
expect(generateCall(calls, 'icon-spec', '角色规范')).toMatchObject({
kind: 'icon-spec',
prompt: '三角色头身比与服装规范',
assetName: '角色规范',
outputPath: null,
@@ -62,13 +62,13 @@ const iconSpecAction = action({
const uiPrototypeAction = action({
id: 'generate-ui-prototype',
label: '生成 UI 设计图',
assetKind: 'ui-prototype',
assetKind: 'ui-design',
requiresIconSpecReference: true,
});
const spritesheetAction = action({
id: 'generate-icon-spritesheet',
label: '生成图标素材',
assetKind: 'art-spritesheet',
assetKind: 'icon-spritesheet',
requiresIconSpecReference: true,
});
@@ -295,7 +295,7 @@ describe('生成面板的参考接线', () => {
);
expect(onSubmit).toHaveBeenCalledTimes(1);
expect(onSubmit.mock.calls[0]?.[0]).toMatchObject({
kind: 'art-spritesheet',
kind: 'icon-spritesheet',
references: [],
});
});