diff --git a/apps/ai-game-creator-shell/src/contracts/assetKind.ts b/apps/ai-game-creator-shell/src/contracts/assetKind.ts new file mode 100644 index 000000000..c4ec744e4 --- /dev/null +++ b/apps/ai-game-creator-shell/src/contracts/assetKind.ts @@ -0,0 +1,45 @@ +import type { GameCreationAppAssetKind } from './generated/GameCreationAppAssetKind'; + +export type { GameCreationAppAssetKind } from './generated/GameCreationAppAssetKind'; + +export const GAME_CREATION_APP_ASSET_KINDS = [ + 'image', + 'scene', + 'character', + 'character-animation', + 'icon', + 'icon-spritesheet', + 'icon-spec', + 'ui-design', + 'ui-design-doc', + 'publication-material', + 'spec', + 'video', + 'audio', + 'sound-effect', + 'background-music', + 'font', + 'document', + 'code', + 'unknown', +] as const satisfies readonly GameCreationAppAssetKind[]; + +const KNOWN_ASSET_KINDS = new Set(GAME_CREATION_APP_ASSET_KINDS); + +export function parseGameCreationAppAssetKind( + raw: unknown, + context: string, +): GameCreationAppAssetKind { + if (typeof raw === 'string' && KNOWN_ASSET_KINDS.has(raw)) { + return raw as GameCreationAppAssetKind; + } + + const rawValue = typeof raw === 'string' ? raw : String(raw); + if (rawValue !== 'unknown') { + console.warn('未知的 GameCreationApp 资源 kind,已收口为 unknown', { + rawKind: rawValue, + sourceContext: context, + }); + } + return 'unknown'; +} diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts b/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts index 320fd9a15..f14652124 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts @@ -10,6 +10,10 @@ import { buildGameCreationAppAssetTagLibrary, type GameCreationAppAssetTagLibraryEntry, } from '../../../../../packages/shared/src/contracts/gameCreationAppAssetTagLibrary'; +import { + type GameCreationAppAssetKind, + parseGameCreationAppAssetKind, +} from '../../contracts/assetKind'; export type ResourceReferenceSource = | 'asset-picker' @@ -20,7 +24,7 @@ export type ResourceReferenceSource = export type ResourceReference = { type: 'resource'; resourceId: string; - kind: string; + kind: GameCreationAppAssetKind; mediaType: string; label: string; category: GameCreationAppAssetCategory; @@ -98,7 +102,7 @@ export function resourceReferenceFromAsset( return { type: 'resource', resourceId: asset.id, - kind: asset.kind, + kind: parseGameCreationAppAssetKind(asset.kind, 'resource-reference'), mediaType: asset.mediaType, label: resourceDisplayName(asset), category: gameCreationAppAssetCategory(asset), diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index 7619517cd..d82e2c5d9 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -96,6 +96,7 @@ import { isFloatingOverlayWheelEvent, useImageCanvasFloatingOptionDismiss, } from '../../../../../src/components/image-editor/useImageCanvasFloatingOptionDismiss'; +import { parseGameCreationAppAssetKind } from '../../contracts/assetKind'; import { DesignWorkspacePanel } from '../../features/project-workspace/DesignWorkspacePanel'; import { LocalGamePreviewFrame, @@ -7226,9 +7227,11 @@ export default function ProjectDevelopmentView({ type: 'resource', resourceId: selectedResource.manifestAssetId!, - kind: + kind: parseGameCreationAppAssetKind( selectedResource.subtype || - selectedResource.category, + selectedResource.category, + 'project-development.resource-reference', + ), mediaType: selectedResource.mediaType, label: selectedResource.label, category: diff --git a/apps/ai-game-creator-shell/tests/assetKind.test.ts b/apps/ai-game-creator-shell/tests/assetKind.test.ts new file mode 100644 index 000000000..1d1445d88 --- /dev/null +++ b/apps/ai-game-creator-shell/tests/assetKind.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { + GAME_CREATION_APP_ASSET_KINDS, + parseGameCreationAppAssetKind, +} from '../src/contracts/assetKind'; + +describe('shell generated asset kind boundary', () => { + it('keeps the generated kind values aligned with the manifest vocabulary', () => { + expect(GAME_CREATION_APP_ASSET_KINDS).toContain('font'); + expect(GAME_CREATION_APP_ASSET_KINDS).toContain('audio'); + expect(GAME_CREATION_APP_ASSET_KINDS).toContain('sound-effect'); + expect(GAME_CREATION_APP_ASSET_KINDS).toContain('background-music'); + expect(GAME_CREATION_APP_ASSET_KINDS).toContain('unknown'); + }); + + it('does not alias old values and records unknown input', () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + expect(parseGameCreationAppAssetKind('ui', 'manifest.asset.kind')).toBe( + 'unknown', + ); + expect(warn).toHaveBeenCalledWith( + '未知的 GameCreationApp 资源 kind,已收口为 unknown', + { rawKind: 'ui', sourceContext: 'manifest.asset.kind' }, + ); + warn.mockRestore(); + }); +}); diff --git a/docs/project-memory/plans/【实施计划】AGC资源kindRust枚举与ts-rs绑定-2026-09-15.md b/docs/project-memory/plans/【实施计划】AGC资源kindRust枚举与ts-rs绑定-2026-09-15.md index 8d90b2e62..36ac9c444 100644 --- a/docs/project-memory/plans/【实施计划】AGC资源kindRust枚举与ts-rs绑定-2026-09-15.md +++ b/docs/project-memory/plans/【实施计划】AGC资源kindRust枚举与ts-rs绑定-2026-09-15.md @@ -20,6 +20,7 @@ - `GameCreationAppAssetManifestEntry.kind` 改为 `GameCreationAppAssetKind`;manifest 反序列化未知值收口为 `Unknown`。 - 上传内容推断的字体写入 `Font`,未知上传类型写入 `Unknown`;图集切片 manifest 写入 `Icon`。 +- shell TS 资源引用边界开始消费生成绑定,并为未知值保留原始值与上下文日志。 明确不修改: diff --git a/docs/project-memory/todos/【待办】AGC资源kind枚举化扫描清单-2026-09-15.md b/docs/project-memory/todos/【待办】AGC资源kind枚举化扫描清单-2026-09-15.md index 99d979cd6..e4085b1d4 100644 --- a/docs/project-memory/todos/【待办】AGC资源kind枚举化扫描清单-2026-09-15.md +++ b/docs/project-memory/todos/【待办】AGC资源kind枚举化扫描清单-2026-09-15.md @@ -210,3 +210,11 @@ - `resourceCanvasBottomToolbarModel.ts:243` 的 `art-spritesheet` 和 `:258` 的 `ui-prototype` 当前位于生成入口选项,需先确认该字段是否传入 manifest 登记;若只是 API/生成请求 kind,使用显式转换函数,不把旧值加入 manifest enum。 - `resourceProjectionModel.ts:326`、`uiDesignResourceBridge.ts:87` 是直接比较 manifest asset kind 的消费点,必须跟随生成绑定迁移到 enum。 - `SupervisorChatOnlyView.tsx`、`ProjectSupervisorView.tsx` 等仍直接从 `packages/shared` 导入 manifest 类型,需在删除旧文件前完成 shell 内导入收口。 + +## 已迁移的 TS 边界(增量) + +- `src/contracts/assetKind.ts`:消费 Rust 生成的 `GameCreationAppAssetKind`,未知输入记录 `rawKind` / `sourceContext` 并返回 `unknown`,不做旧 alias 转换。 +- `src/features/project-workspace/resourceReferences.ts`:资源引用 kind 改为生成枚举类型并通过 parser 收口。 +- `src/view/project-development/index.tsx`:画布引用入口通过同一 parser 构造资源引用 kind。 + +其余 `packages/shared` import 和前端 manifest `kind: string` 字段仍未迁移,继续作为后续小切片,不在本次提交中伪装完成。