From 470e85c0ffe3706b29c1d4e69df88c85f91d6b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 18:30:28 +0800 Subject: [PATCH 01/75] Add UI-specific prompt rendering for design assets in resource references --- .../src/agent/direct_codex_references.rs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs index f1b78b1ca..80e29e1e6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs @@ -133,6 +133,23 @@ fn validate_resource_reference_id(value: &str) -> Result { Ok(resource_id.to_string()) } +/// Render the prompt context for a UI design asset. +/// +/// Keep this separate from the generic resource renderer so UI-specific +/// instructions/metadata can evolve without changing other asset kinds. +fn render_ui_design_reference_line( + asset: &GameCreationAppAssetManifestEntry, + resource_id: &str, + label: &str, + local_path: &str, + source: &str, +) -> String { + format!( + "- 素材 ID:{resource_id};名称:{label};类型:{};媒体类型:{};项目路径:{local_path};来源:{source}", + asset.kind, asset.media_type + ) +} + fn render_resource_reference_line( manifest: &GameCreationAppManifest, reference: &DirectCodexResourceReference, @@ -149,6 +166,19 @@ fn render_resource_reference_line( .unwrap_or_else(|| asset_display_label(asset)); let source = sanitize_reference_source(reference.source.as_deref()) .unwrap_or_else(|| "unknown".to_string()); + let is_ui_design = matches!( + asset.kind.to_ascii_lowercase().as_str(), + "ui" | "ui-design" | "ui-prototype" + ); + if is_ui_design { + return Ok(render_ui_design_reference_line( + asset, + &resource_id, + &label, + &local_path, + &source, + )); + } Ok(format!( "- 素材 ID:{resource_id};名称:{label};类型:{};媒体类型:{};项目路径:{local_path};来源:{source}", asset.kind, asset.media_type From 9cd1a94369f1079120bcd163abcde167a8b494b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 20:33:13 +0800 Subject: [PATCH 02/75] =?UTF-8?q?=E6=8E=A5=E5=85=A5=20UI=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=BC=95=E7=94=A8=E4=BB=A3=E7=A0=81=E4=B8=8A=E4=B8=8B?= =?UTF-8?q?=E6=96=87=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Direct Codex 引用 ui-design-doc 时生成带文档的 JS 文件 生成成功或失败信息追加到当前 prompt 补充成功、失败与 ui-prototype 隔离测试 --- .../src/agent/direct_codex_references.rs | 150 +++++++++++++++++- 1 file changed, 144 insertions(+), 6 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs index 80e29e1e6..4b84b47e9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_references.rs @@ -1,4 +1,8 @@ use super::*; +use crate::ui_editor::persistence::{ + generate_ui_design_code_at, GenerateUiDesignCodeInput, UI_DESIGN_DOC_ASSET_KIND, + UI_DESIGN_DOC_MEDIA_TYPE, +}; pub(crate) const MAX_DIRECT_CODEX_REFERENCES: usize = 32; const MAX_DIRECT_CODEX_REFERENCE_ID_CHARS: usize = 200; @@ -138,19 +142,30 @@ fn validate_resource_reference_id(value: &str) -> Result { /// Keep this separate from the generic resource renderer so UI-specific /// instructions/metadata can evolve without changing other asset kinds. fn render_ui_design_reference_line( + root: &Path, + manifest: &GameCreationAppManifest, asset: &GameCreationAppAssetManifestEntry, resource_id: &str, label: &str, local_path: &str, source: &str, ) -> String { + let context = match generate_ui_design_code_at(GenerateUiDesignCodeInput { + project_path: root.to_string_lossy().into_owned(), + expected_project_id: manifest.project_id.clone(), + asset_id: resource_id.to_string(), + }) { + Ok(result) => format!("请先阅读生成的带有文档的代码片段: {}", result.relative_path), + Err(error) => format!("生成代码遇到错误{error}"), + }; format!( "- 素材 ID:{resource_id};名称:{label};类型:{};媒体类型:{};项目路径:{local_path};来源:{source}", asset.kind, asset.media_type - ) + ) + "\n" + &context } fn render_resource_reference_line( + root: &Path, manifest: &GameCreationAppManifest, reference: &DirectCodexResourceReference, ) -> Result { @@ -166,12 +181,12 @@ fn render_resource_reference_line( .unwrap_or_else(|| asset_display_label(asset)); let source = sanitize_reference_source(reference.source.as_deref()) .unwrap_or_else(|| "unknown".to_string()); - let is_ui_design = matches!( - asset.kind.to_ascii_lowercase().as_str(), - "ui" | "ui-design" | "ui-prototype" - ); + let is_ui_design = + asset.kind == UI_DESIGN_DOC_ASSET_KIND && asset.media_type == UI_DESIGN_DOC_MEDIA_TYPE; if is_ui_design { return Ok(render_ui_design_reference_line( + root, + manifest, asset, &resource_id, &label, @@ -264,7 +279,7 @@ pub(crate) fn render_direct_codex_references_section( for reference in references { lines.push(match reference { DirectCodexTurnReference::Resource(reference) => { - render_resource_reference_line(&manifest, reference)? + render_resource_reference_line(root, &manifest, reference)? } DirectCodexTurnReference::RuntimeRegion(reference) => { render_runtime_region_reference_line(&manifest, reference)? @@ -282,6 +297,7 @@ pub(crate) fn render_direct_codex_references_section( #[cfg(test)] mod tests { use super::*; + use crate::ui_editor::persistence::initialize_ui_design_state_at; fn fixture_project() -> tempfile::TempDir { let directory = tempfile::tempdir().expect("temp project"); @@ -314,6 +330,128 @@ mod tests { directory } + fn add_ui_design_doc(project: &tempfile::TempDir) -> String { + let root = project.path(); + let relative_path = "ui/design.json"; + let absolute_path = root.join(relative_path); + std::fs::create_dir_all(absolute_path.parent().expect("ui parent")) + .expect("create ui parent"); + std::fs::write(&absolute_path, b"").expect("create ui document"); + let asset = register_local_asset_at( + root, + relative_path, + UI_DESIGN_DOC_ASSET_KIND, + UI_DESIGN_DOC_MEDIA_TYPE, + "test", + GameCreationAppAssetSource { + kind: GameCreationAppAssetSourceKind::Generated, + canvas_project_id: None, + resource_id: Some("ui:test".to_string()), + asset_object_id: None, + task_id: None, + prompt: None, + model: None, + generation_route: None, + generation_kind: None, + reference_resource_ids: Vec::new(), + }, + ) + .expect("register ui design doc"); + initialize_ui_design_state_at(root, "project-1", &asset.id) + .expect("initialize ui design doc"); + asset.id + } + + #[test] + fn ui_design_doc_reference_generates_documented_code_context() { + let project = fixture_project(); + let asset_id = add_ui_design_doc(&project); + let reference = DirectCodexTurnReference::Resource(DirectCodexResourceReference { + resource_id: asset_id, + label: Some("界面设计".to_string()), + source: Some("asset-picker".to_string()), + }); + let section = render_direct_codex_references_section(project.path(), &[reference]) + .expect("render") + .expect("section"); + assert!(section.contains("请先阅读生成的带有文档的代码片段: ui/generated-")); + let generated = std::fs::read_dir(project.path().join("ui")) + .expect("read ui dir") + .filter_map(Result::ok) + .find(|entry| { + entry + .file_name() + .to_string_lossy() + .starts_with("generated-") + }) + .expect("generated code"); + let code = std::fs::read_to_string(generated.path()).expect("read generated code"); + assert!(code.contains("这是从用户手动调整过的UI设计文档生成的html片段")); + } + + #[test] + fn ui_design_doc_generation_errors_stay_in_prompt_and_do_not_abort_section() { + let project = fixture_project(); + let asset_id = add_ui_design_doc(&project); + let manifest = read_manifest_for_project(project.path()).expect("read manifest"); + let asset = manifest + .assets + .iter() + .find(|asset| asset.id == asset_id) + .unwrap(); + std::fs::write(project.path().join(&asset.local_path), b"not-json") + .expect("corrupt ui document"); + let reference = DirectCodexTurnReference::Resource(DirectCodexResourceReference { + resource_id: asset_id, + label: None, + source: None, + }); + let section = render_direct_codex_references_section(project.path(), &[reference]) + .expect("render") + .expect("section"); + assert!(section.contains("素材 ID:")); + assert!(section.contains("生成代码遇到错误")); + } + + #[test] + fn ui_prototype_image_does_not_trigger_code_generation() { + let project = fixture_project(); + let root = project.path(); + let relative_path = "assets/ui-prototype.png"; + std::fs::create_dir_all(root.join("assets")).expect("assets dir"); + std::fs::write(root.join(relative_path), b"png").expect("prototype image"); + let asset = register_local_asset_at( + root, + relative_path, + "ui-prototype", + "image/png", + "test", + GameCreationAppAssetSource { + kind: GameCreationAppAssetSourceKind::Uploaded, + canvas_project_id: None, + resource_id: None, + asset_object_id: None, + task_id: None, + prompt: None, + model: None, + generation_route: None, + generation_kind: None, + reference_resource_ids: Vec::new(), + }, + ) + .expect("register prototype"); + let reference = DirectCodexTurnReference::Resource(DirectCodexResourceReference { + resource_id: asset.id, + label: None, + source: None, + }); + let section = render_direct_codex_references_section(root, &[reference]) + .expect("render") + .expect("section"); + assert!(!section.contains("请先阅读生成的带有文档的代码片段")); + assert!(!root.join("ui").exists()); + } + #[test] fn resource_reference_uses_manifest_identity_and_never_accepts_client_paths() { let project = fixture_project(); From ee7d00c0b169fe710684988c9c94a3d5b3058de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 20:33:46 +0800 Subject: [PATCH 03/75] =?UTF-8?q?=E7=BB=9F=E4=B8=80=20UI=20=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E6=96=87=E6=A1=A3=E8=B5=84=E4=BA=A7=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 ui-design-doc 与 application/json 常量 创建、桥接、工作流和持久化统一使用严格资产类型 --- .../src-tauri/src/commands.rs | 9 ++++-- .../src-tauri/src/ui_editor/persistence.rs | 8 ++++-- .../src/ui_editor/resource_bridge.rs | 28 +++++++++++-------- .../src-tauri/src/ui_editor/workflow.rs | 11 ++++---- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/commands.rs b/apps/ai-game-creator-shell/src-tauri/src/commands.rs index c81dd2663..989f16689 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/commands.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/commands.rs @@ -2144,7 +2144,10 @@ pub(crate) fn create_ui_design_resource( let next_index = manifest .assets .iter() - .filter(|asset| asset.kind == "UI") + .filter(|asset| { + asset.kind == crate::ui_editor::persistence::UI_DESIGN_DOC_ASSET_KIND + && asset.media_type == crate::ui_editor::persistence::UI_DESIGN_DOC_MEDIA_TYPE + }) .count() + 1; let resource_name = format!("UI 设计 {next_index}"); @@ -2178,8 +2181,8 @@ pub(crate) fn create_ui_design_resource( let asset = match register_local_asset_at( root, &relative_path, - "UI", - "application/json", + crate::ui_editor::persistence::UI_DESIGN_DOC_ASSET_KIND, + crate::ui_editor::persistence::UI_DESIGN_DOC_MEDIA_TYPE, "generated", GameCreationAppAssetSource { kind: GameCreationAppAssetSourceKind::Generated, diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs index 73b55d9ce..7755cdbd3 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs @@ -19,6 +19,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use typed_floats::tf32::StrictlyPositiveFinite; const UI_DESIGN_STATE_SCHEMA_VERSION: &str = "game-creator-ui-design-state.v1"; +pub(crate) const UI_DESIGN_DOC_ASSET_KIND: &str = "ui-design-doc"; +pub(crate) const UI_DESIGN_DOC_MEDIA_TYPE: &str = "application/json"; const UI_DESIGN_STATE_MAX_BYTES: usize = 2 * 1024 * 1024; const UI_DESIGN_CODE_MAX_BYTES: usize = UI_DESIGN_STATE_MAX_BYTES * 8; const UI_DESIGN_STATE_MAX_IMAGES: usize = 4; @@ -321,7 +323,7 @@ fn ui_design_asset( .into_iter() .find(|asset| asset.id == asset_id) .ok_or_else(|| "UI 设计资源不存在".to_string())?; - if asset.kind != "UI" || asset.media_type != "application/json" { + if asset.kind != UI_DESIGN_DOC_ASSET_KIND || asset.media_type != UI_DESIGN_DOC_MEDIA_TYPE { return Err("目标资源不是 UI 设计 JSON 资产".to_string()); } normalize_relative_path(&asset.local_path)?; @@ -815,8 +817,8 @@ mod tests { let asset = register_local_asset_at( directory.path(), relative_path, - "UI", - "application/json", + UI_DESIGN_DOC_ASSET_KIND, + UI_DESIGN_DOC_MEDIA_TYPE, "test", GameCreationAppAssetSource { kind: GameCreationAppAssetSourceKind::Generated, diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs index 410746488..fa3954105 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs @@ -1,4 +1,7 @@ -use crate::ui_editor::persistence::initialize_ui_design_state_with_source_image_at; +use crate::ui_editor::persistence::{ + initialize_ui_design_state_with_source_image_at, UI_DESIGN_DOC_ASSET_KIND, + UI_DESIGN_DOC_MEDIA_TYPE, +}; use crate::{ acquire_project_write_lock, advance_agent_runtime_project_revision_locked, enforce_project_permission_policy, read_existing_manifest_for_project, @@ -89,8 +92,8 @@ pub(crate) fn ensure_ui_design_resource_for_prototype( } if let Some(asset) = manifest.assets.iter().find(|asset| { - asset.kind == "UI" - && asset.media_type == "application/json" + asset.kind == UI_DESIGN_DOC_ASSET_KIND + && asset.media_type == UI_DESIGN_DOC_MEDIA_TYPE && asset.source.reference_resource_ids.iter().any(|reference| { source_reference_ids .iter() @@ -118,8 +121,8 @@ pub(crate) fn ensure_ui_design_resource_for_prototype( let asset = match register_local_asset_at( root, &relative_path, - "UI", - "application/json", + UI_DESIGN_DOC_ASSET_KIND, + UI_DESIGN_DOC_MEDIA_TYPE, "generated", GameCreationAppAssetSource { kind: GameCreationAppAssetSourceKind::Generated, @@ -193,7 +196,9 @@ fn next_ui_design_path( let mut index = manifest .assets .iter() - .filter(|asset| asset.kind == "UI") + .filter(|asset| { + asset.kind == UI_DESIGN_DOC_ASSET_KIND && asset.media_type == UI_DESIGN_DOC_MEDIA_TYPE + }) .count() + 1; loop { @@ -281,11 +286,9 @@ mod tests { }; let first = ensure_ui_design_resource_for_prototype(input.clone()).expect("bridge"); assert!(first.created); - assert_eq!(first.asset.kind, "UI"); + assert_eq!(first.asset.kind, UI_DESIGN_DOC_ASSET_KIND); // 写侧 → 分类的端到端断言:这条路径走的是与 - // `workflow.rs` / `persistence.rs` 完全相同的 `register_local_asset_at(..., "UI", ...)`。 - // 别名表漏掉大写 `UI` 时,这里会落 unclassified(真机 8 条 UI 资产的表现), - // 且派生值本身就是 unclassified,读时自愈也救不回来。 + // 写侧与 persistence/workflow 共用 UI 文档 kind/media 常量。 assert_eq!( first.asset.category, GameCreationAppAssetCategory::UiInteraction @@ -322,7 +325,10 @@ mod tests { .manifest .assets .iter() - .filter(|asset| asset.kind == "UI") + .filter(|asset| { + asset.kind == UI_DESIGN_DOC_ASSET_KIND + && asset.media_type == UI_DESIGN_DOC_MEDIA_TYPE + }) .count(), 1 ); diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs index 3c7d9a4f1..22de573c6 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/workflow.rs @@ -7,6 +7,7 @@ use crate::ui_editor::layout::node::{Node, StageStatus}; use crate::ui_editor::persistence::{ initialize_ui_design_state_at, load_ui_design_state_at, save_ui_design_state_at, LoadUiDesignStateInput, SaveUiDesignStateInput, SaveUiDesignStateResult, + UI_DESIGN_DOC_ASSET_KIND, UI_DESIGN_DOC_MEDIA_TYPE, }; use crate::ui_editor::resource::font::FontAsset; use crate::ui_editor::resource::sprite::{SpriteAsset, SpriteAssetMetadata, SpriteBorder}; @@ -683,8 +684,8 @@ fn find_page_ui_resource( let Some(asset) = matches.into_iter().next() else { return Ok(None); }; - if asset.kind != "UI" - || asset.media_type != "application/json" + if asset.kind != UI_DESIGN_DOC_ASSET_KIND + || asset.media_type != UI_DESIGN_DOC_MEDIA_TYPE || asset.local_path != workflow_relative_path(source, &page.page_id) { return Err(format!("页面 {} 的 UI workflow 资源身份冲突", page.page_id)); @@ -748,8 +749,8 @@ fn ensure_page_ui_resource( let registered = register_local_asset_at( root, &relative_path, - "UI", - "application/json", + UI_DESIGN_DOC_ASSET_KIND, + UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", GameCreationAppAssetSource { kind: GameCreationAppAssetSourceKind::Generated, @@ -1179,7 +1180,7 @@ fn update_page_manifest_stage(root: &Path, asset_id: &str, stage: &str) -> Resul .iter_mut() .find(|asset| asset.id == asset_id) .ok_or_else(|| format!("UI workflow 资源 {} 未登记", asset_id))?; - if asset.kind != "UI" || asset.media_type != "application/json" { + if asset.kind != UI_DESIGN_DOC_ASSET_KIND || asset.media_type != UI_DESIGN_DOC_MEDIA_TYPE { return Err(format!("UI workflow 资源 {} 类型不匹配", asset_id)); } let next_kind = format!("ui-workflow.{stage}"); From 18654b68062487b4109a4bfce7d2153de4791017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 20:34:23 +0800 Subject: [PATCH 04/75] =?UTF-8?q?=E5=90=8C=E6=AD=A5=20UI=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E8=B5=84=E4=BA=A7=E5=85=B1=E4=BA=AB=E5=A5=91=E7=BA=A6?= =?UTF-8?q?=E4=B8=8E=E5=89=8D=E7=AB=AF=E8=AF=86=E5=88=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 共享契约新增 ui-design-doc 分类及中英文映射 前端编辑器与资源桥接严格识别文档资产 更新技术方案、实施计划和长期决策记录 --- .../ui-editor/uiDesignResourceBridge.ts | 2 +- .../src/view/project-development/index.tsx | 4 +- .../tests/assetKindCanonicalMapping.test.ts | 17 ++++--- ...计划】UI设计文档引用代码上下文-2026-09-14.md | 51 +++++++++++++++++++ ...程碑】UI设计文档引用代码上下文-2026-09-14.md | 49 ++++++++++++++++++ .../shared-memory/decision-log.md | 6 +++ ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 2 +- ...】UI工作流资源桥接与Runtime执行-2026-08-24.md | 30 +++++++++-- .../shared/src/contracts/gameCreationApp.ts | 15 +++--- .../shared-contracts/src/game_creation_app.rs | 12 ++--- 10 files changed, 160 insertions(+), 28 deletions(-) create mode 100644 docs/project-memory/plans/【实施计划】UI设计文档引用代码上下文-2026-09-14.md create mode 100644 docs/project-memory/plans/【里程碑】UI设计文档引用代码上下文-2026-09-14.md diff --git a/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts b/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts index 1c3e016d4..232ca97bf 100644 --- a/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts +++ b/apps/ai-game-creator-shell/src/features/ui-editor/uiDesignResourceBridge.ts @@ -48,7 +48,7 @@ export function findLinkedUiDesignResource( manifest.assets .filter( (asset) => - asset.kind === 'UI' && + asset.kind === 'ui-design-doc' && asset.mediaType === 'application/json' && asset.source.referenceResourceIds?.some((reference) => referenceIds.has(reference), 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 15fdad3a4..0d1266d82 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 @@ -5040,7 +5040,7 @@ export default function ProjectDevelopmentView({ if (canvasOpenEpochRef.current !== openEpoch) return; if ( result.manifest.projectId !== manifest.projectId || - result.asset.kind !== 'UI' || + result.asset.kind !== 'ui-design-doc' || result.asset.mediaType !== 'application/json' ) { throw new Error('UI 编辑资源结果与当前项目不一致'); @@ -5112,7 +5112,7 @@ export default function ProjectDevelopmentView({ if (uiEditorRoute) return; const completed = manifest.assets.find( (asset) => - asset.kind === 'UI' && + asset.kind === 'ui-design-doc' && asset.mediaType === 'application/json' && asset.source.generationKind === 'ui-workflow.completed', ); diff --git a/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts b/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts index 40308e594..bb076aa9c 100644 --- a/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts +++ b/apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts @@ -219,8 +219,13 @@ describe('真机出现的 kind 归类口径', () => { canonical: 'icon', category: 'ui-interaction', }, - // 现役写入侧的大写字面量与字体 kind,两者都必须落进明确栏目。 + // UI 文档与字体 kind,两者都必须落进明确栏目。 { kind: 'UI', canonical: 'ui-design', category: 'ui-interaction' }, + { + kind: 'ui-design-doc', + canonical: 'ui-design-doc', + category: 'ui-interaction', + }, { kind: 'font', canonical: 'document', category: 'document' }, // `Object.prototype` 上的键不是别名:TS 侧必须用 `Object.hasOwn` 挡住对象字面量的 // 原型命中,Rust 侧 match 字面量本来就落 `image`;这类极端输入两侧也必须一致。 @@ -277,13 +282,13 @@ describe('写侧 kind 字面量 → 分类的端到端口径', () => { return kinds; } - test('UI 设计写点仍直接写 `"UI"`,且它落 UI 交互而不是待归类', () => { - // 8 条真机 UI 资产是 `kind:"UI"` + `mediaType:"application/json"`: - // 该 kind 不在别名表里时派生结果也是 unclassified,读时自愈同样救不回来。 + test('UI 设计写点统一使用 ui-design-doc 常量,且它落 UI 交互而不是待归类', () => { for (const file of UI_EDITOR_WRITE_SITES) { - expect(registeredKindLiterals(file)).toContain('UI'); + expect(readFileSync(file, 'utf8')).toContain('UI_DESIGN_DOC_ASSET_KIND'); } - expect(gameCreationAppAssetCategoryForKind('UI')).toBe('ui-interaction'); + expect(gameCreationAppAssetCategoryForKind('ui-design-doc')).toBe( + 'ui-interaction', + ); }); test('UI 编辑器写点写出的每个 kind 都落明确栏目', () => { diff --git a/docs/project-memory/plans/【实施计划】UI设计文档引用代码上下文-2026-09-14.md b/docs/project-memory/plans/【实施计划】UI设计文档引用代码上下文-2026-09-14.md new file mode 100644 index 000000000..969437609 --- /dev/null +++ b/docs/project-memory/plans/【实施计划】UI设计文档引用代码上下文-2026-09-14.md @@ -0,0 +1,51 @@ +# 【实施计划】UI设计文档引用代码上下文 + +| 字段 | 值 | +| --- | --- | +| Version | 1 | +| Status | ready | +| Owner | Codex | +| Milestone | `docs/project-memory/plans/【里程碑】UI设计文档引用代码上下文-2026-09-14.md` | + +## 修改边界 + +允许修改: + +- UI Editor persistence/resource bridge/workflow/command 中的 UI JSON 文档 kind 常量与校验。 +- `packages/shared` 的 canonical kind 与 Rust 对应映射。 +- `agent/direct_codex_references.rs` 的 UI 引用 prompt 生成。 +- 相关 Rust、TypeScript 测试和当前 UI workflow/AGC 文档。 +- `decision-log.md` 的长期决策记录。 + +明确不修改: + +- `ui-prototype` 图片生成和图片 workflow 语义。 +- UI State schema、`render_ui_design_state_js` 输出格式和 `ui/generated-*.js` 路径规则。 +- SpacetimeDB schema、External v1 OpenAPI、非 Direct Codex Supervisor 引用行为。 +- 用户已有 `.env` 未提交修改。 + +## 实现顺序 + +1. 更新 UI workflow 主规范与项目决策,明确 `ui-design-doc` 与 `ui-prototype` 的身份边界。 +2. 在 persistence 提取并导出 UI 文档 kind/media 常量,替换 Rust UI 文档校验。 +3. 同步所有 UI JSON 资源生产者、workflow 校验、命令筛选、shared contract 与前端 bridge。 +4. 在 Direct Codex 引用渲染中调用 `generate_ui_design_code_at`;成功追加生成文件相对路径,失败追加原始错误并继续发送。 +5. 补充旧 kind、图片、成功、文档错误、多引用和 renderer 输出的测试。 +6. 执行定向验证,复核 diff 中无 fallback、迁移或无关 `.env` 修改。 + +## 验证命令 + +1. `cargo fmt --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml -- --check` +2. `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml ui_editor::persistence` +3. `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml direct_codex_references` +4. 相关 shared contract Vitest 测试与前端类型检查 +5. `npm run check:doc-index` +6. `npm run check:encoding` +7. `git diff --check` + +## 风险与回滚点 + +- `ui-prototype` 与 UI JSON 文档共用部分分类展示逻辑,需确保分类映射不会让图片进入文档分支。 +- `generate_ui_design_code_at` 会持有项目写锁;多引用必须顺序调用,不能并行写同一项目。 +- 生成失败继续发送是已确认语义;测试必须证明错误文本进入 prompt 而不是被转成聊天失败。 +- 若发现生产者仍写入旧 `UI`,按开发阶段合同直接修生产者和 fixture,不增加运行时兼容分支。 diff --git a/docs/project-memory/plans/【里程碑】UI设计文档引用代码上下文-2026-09-14.md b/docs/project-memory/plans/【里程碑】UI设计文档引用代码上下文-2026-09-14.md new file mode 100644 index 000000000..8e368d3aa --- /dev/null +++ b/docs/project-memory/plans/【里程碑】UI设计文档引用代码上下文-2026-09-14.md @@ -0,0 +1,49 @@ +# 【里程碑】UI设计文档引用代码上下文 + +| 字段 | 值 | +| --- | --- | +| Version | 1 | +| Status | in-progress | +| Date | 2026-09-14 | +| Parent Spec | `docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md` | + +## 目标 + +将可编辑 UI JSON 文档统一识别为 `ui-design-doc`,并在 Direct Codex 聊天引用该文档时复用 UI Editor 现有代码导出流程,为 LLM 提供生成的、带文档注释的 JS 代码路径或原始生成错误。 + +## 范围 + +- UI JSON 文档资产身份固定为 `kind=ui-design-doc`、`mediaType=application/json`。 +- 复用 `ui_editor::persistence::generate_ui_design_code_at` 读取并校验文档、调用 `render_ui_design_state_js`、写入 `ui/generated-*.js`。 +- Direct Codex 引用文本保留稳定 manifest 元数据,并追加代码路径或 `生成代码遇到错误{error}`。 +- 同步 Rust/TypeScript 的 UI 文档生产者、消费者、分类映射和测试。 +- `ui-prototype` 图片继续作为独立源图片类型。 + +## 不在范围内 + +- 不接受旧 `UI`、`ui`、`ui-design` 作为 UI 文档类型。 +- 不增加兼容 fallback、数据迁移或旧数据转换逻辑。 +- 不把 `ui-prototype` 图片当作 UI 文档,不从图片生成替代 JSON 文档代码。 +- 不改变 UI State schema、renderer 输出格式、项目 revision 或 manifest 阶段。 +- 不改变旧 Supervisor 非 Direct Codex 引用链路。 + +## 依赖与前置条件 + +- `generate_ui_design_code_at` 已存在并返回 `relative_path`。 +- `render_ui_design_state_js` 已生成包含文档注释的 JS 模块。 +- Direct Codex 结构化引用已传递 `resourceId`,manifest 是资源身份权威。 + +## 验收标准 + +- [ ] UI 编辑器 JSON 资源创建、加载、保存、workflow 校验和引用分支均只接受 `ui-design-doc + application/json`。 +- [ ] Direct Codex 成功引用 UI 文档时,prompt 追加 `请先阅读生成的带有文档的代码片段: {relative_path}`。 +- [ ] UI 文档生成失败时,prompt 保留原 metadata 并追加 `生成代码遇到错误{error}`,不走图片或其它文件 fallback。 +- [ ] `ui-prototype` 图片引用不触发 UI 文档代码生成。 +- [ ] 多个引用按顺序处理,单个 UI 文档失败不丢失其它引用。 +- [ ] Rust/TypeScript kind 映射一致,旧 kind 不被隐式迁移。 + +## 证据要求 + +- 自动化:persistence、direct_codex_references、shared contract 定向测试;`cargo fmt --check`、`npm run check:encoding`、`git diff --check`。 +- 运行时:不要求真实 Provider;测试验证生成文件路径、renderer 文档注释和 prompt 注入结果。 +- 边界:旧 kind、图片 kind、损坏 JSON、renderer 错误、多引用和路径来源均有测试。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 97f0d6d93..d7a696525 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -8677,3 +8677,9 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 决策:复用判据收窄为**同一条写调用链(同一线程)重入**——按锁路径登记真实持锁线程,只有当前线程就是持锁线程时才返回 advisory guard;本进程其它线程的争用继续走有界等待与终态占用。自主游戏构建流水线的并行专家动作豁免保持不变;跨进程占用、残留回收、权限分类、等待预算和错误文案不变。 - 边界:锁定这些不变量的既有用例(`project_tools` / `command_runtime` / `parallel_actions` / `runtime_state` / `response_stream` / `direct_tool_bridge` / `ui_editor::persistence`)不得为了让锁语义通过而改写;用「同线程自持锁」模拟「另一个写者」的两条用例改为**在另一条线程持锁**,断言语义不变。同进程跨线程重入(持锁链在 `await` / `spawn_blocking` 后于其它线程再取锁)仍会等满预算,出现现场时按 2026-08-27 的既有处置改用 `*_locked` 入口,不放宽判据。 - 关联文档:[项目客户端占用锁收敛里程碑](../plans/【里程碑】项目客户端占用锁收敛-2026-09-14.md)、[踩坑记录](pitfalls.md)。 + +## 2026-09-14 Direct Codex 引用 UI 设计文档生成代码上下文 + +- 决策:UI Editor JSON 文档资产唯一使用 `kind:"ui-design-doc"` 且 `mediaType:"application/json"`;`ui-prototype` 保持图片语义,旧 `UI` / `ui` / `ui-design` 不作为该文档分支输入,不做 fallback 或迁移。 +- 决策:Direct Codex 结构化资源引用命中该 kind 时,顺序调用 UI Editor persistence 的 `generate_ui_design_code_at`,生成 `ui/generated-*.js`,并把 `请先阅读生成的带有文档的代码片段: {relative_path}` 追加到当前 prompt。生成失败不阻断本轮引用,追加原始 `生成代码遇到错误{error}`,其它引用继续处理。 +- 原因:复用 `html_renderer/mod.rs` 统一产物,确保 LLM 读取的代码包含 UI 节点元数据和文档注释;严格 kind + mediaType 判定避免图片资产误走代码生成。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index e08a22eb3..3917ebd68 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -1221,7 +1221,7 @@ game-project/ ## 2026-08-17 UI Editor 项目资源 State 持久化 -- UI 编辑器复用现有 manifest `kind: "UI"`、`mediaType: "application/json"` 资源,不增加平行 asset kind。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope:`projectId`、`assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets` 与 `Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。 +- UI 编辑器使用唯一的 manifest `kind: "ui-design-doc"`、`mediaType: "application/json"` 资源,不接受旧 `UI` / `ui` / `ui-design`,也不增加 fallback 或迁移。资源文件固定为严格 `game-creator-ui-design-state.v1` JSON envelope:`projectId`、`assetId`、每资源 `revision` 和 Rust 唯一源 `State`;旧空对象、未知字段、身份错配、超限、无效内部引用和不安全相对路径均失败关闭。内部引用校验同时覆盖 `Image.target_graphic -> sprite_assets` 与 `Text.font -> font_assets`,可选引用非空时必须命中同一 State 内已登记资源。 - Tauri 专用 load/save command 只接受项目路径、期望项目 ID、manifest asset ID 和(保存时)资源 revision;Rust 按 manifest 解析受控本地路径并在项目写锁内做 CAS。相同 `State` 返回 unchanged 且不推进 project revision;不同内容安装并回读一致后才推进 revision,后续推进失败返回 `reconciliation-required`,不伪装为完整保存。 - UI 编辑器代码导出仅写入用户项目目录下的 `ui/generated-*.js` 派生文件,绝不推进项目 revision、UI State revision、manifest 阶段或 Runtime 验证门;写入失败只返回生成错误,不得把生成文件写入冒充为项目 mutation。 - UI State 原子安装保留最近一个可解析、canonical 的 `.previous` 恢复候选,作为最佳努力恢复来源;写入主文件前不把完整 State 语义校验重复执行一遍。主文件损坏时,恢复候选仍必须通过同一严格 schema、project/asset identity、revision、引用和 State 校验后才能安装;恢复安装与保存共用项目写锁,并在持锁后重新读取主文件,已有并发保存的有效新版本时直接返回而不安装旧副本。任一候选均不可信则停在加载错误,前端禁编辑和保存。新建 UI 资源先登记并安装合法 envelope,任一步失败补偿 manifest/文件,避免把空 JSON 留给资源卡。 diff --git a/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md b/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md index f618c73f0..847d62b75 100644 --- a/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md +++ b/docs/【技术方案】UI工作流资源桥接与Runtime执行-2026-08-24.md @@ -2,10 +2,12 @@ ## 目标 -`ui-prototype` 与 `UI` 是两种不同资源,不能通过修改投影 `subtype` 混为一种资源: +`ui-prototype` 与 `ui-design-doc` 是两种不同资源,不能通过修改投影 `subtype` 混为一种资源: - `ui-prototype`:Agent 生成并登记到画布的界面设计图片。 -- `UI`:UI 编辑器使用的 JSON 资源,保存界面图、UI 树、组件绑定和 State revision。 +- `ui-design-doc`:UI 编辑器使用的 `application/json` 资源,保存界面图、UI 树、组件绑定和 State revision。 + +UI 设计文档资产必须严格满足 `kind=ui-design-doc` 与 `mediaType=application/json`;旧 `UI`、`ui`、`ui-design` 不作为兼容输入,也不提供迁移或图片 fallback。 自然语言生成链路必须把前者桥接为后者,并持续让 manifest 成为客户端资源投影的权威来源。游戏场景的页面清单由 Runtime 自动发现,Agent 不得凭空猜测页面。 @@ -35,7 +37,7 @@ Agent 通过白名单工具 `ui.workflow.run` 发起工作流。项目路径由 处理规则: -1. `prepare` 为每个页面创建确定性的 `kind=UI` JSON 资源,引用源 `ui-prototype` 和页面设计图,载入设计图尺寸与相对路径到 `ui_design_images`,并保存 State。 +1. `prepare` 为每个页面创建确定性的 `kind=ui-design-doc` JSON 资源,引用源 `ui-prototype` 和页面设计图,载入设计图尺寸与相对路径到 `ui_design_images`,并保存 State。 2. `recognize` 依次执行 Provider 多模态结构识别、现有多树合并器、最多每批 5 项的图片/图标组件绑定,并把已登记字体的安全元数据提供给绑定器;阶段分别持久化为 `structure-ready`、`merge-ready`、`binding-ready`,重复执行从最近真实阶段恢复。 3. `status` 只回读 State、页面阶段和 blockers,不推进项目 revision。 4. `finalize` 只接受 `game/` 下的真实 UTF-8 文件,写入与 UI State revision 绑定的应用标记;所有页面通过应用门禁后才返回 `asset-separation` 最终阶段路由。缺少页面、资源、组件或应用标记时拒绝伪造完成。 @@ -61,15 +63,33 @@ UI 编辑器的分离提示会对节点描述和返工备注做长度与控制 真实 Provider 鉴权失败时,Codex app-server 可能只返回 `codexErrorInfo=other`,而把上游 `401/403` 放在错误正文中。Runtime 必须从受控错误字段识别为 `codex-app-server-error:unauthorized`(公共摘要为 `codex-app-server-unauthorized`),只向公共运行记录暴露错误类别和指纹,不记录 Token 或上游原文。此错误不能伪造为 UI 工作流阶段完成;修复凭据后应从原有 run 的恢复边界重新执行。 +## UI 设计文档代码引用 + +Direct Codex 聊天引用 `ui-design-doc` 时,复用 `ui_editor::persistence::generate_ui_design_code_at`。该入口只读取被引用的 JSON 文档,调用 `render_ui_design_state_js`,并写入既有 `ui/generated-*.js` 派生文件。 + +成功时在稳定 manifest 元数据后追加: + +```text +请先阅读生成的带有文档的代码片段: {relative_path} +``` + +JSON 文档读取、State 校验或 renderer 失败时,在同一引用后追加原始错误: + +```text +生成代码遇到错误{error} +``` + +错误不阻断本轮 Direct Codex prompt,其它引用继续按顺序处理;不从图片或其它文件 fallback,不把生成文件登记为正式 manifest 资产,也不推进项目 revision。 + ## 画布跳转与客户端更新 点击画布中的 `ui-prototype` 时,工作台调用 `ensure_ui_design_resource_for_prototype`: - 按 manifest asset id、`source.resourceId`、`source.assetObjectId` 识别已有关联,避免重复创建。 -- 没有关联时原子创建 `ui/UI 设计 N.json`,登记 `kind=UI`、`application/json`,并把原型图作为首张页面设计图载入 State。 +- 没有关联时原子创建 `ui/UI 设计 N.json`,登记 `kind=ui-design-doc`、`application/json`,并把原型图作为首张页面设计图载入 State。 - 成功后通过 `onManifestChange` 更新客户端资源投影,再打开 UI 编辑器;普通桥接从 `reference-analysis` 开始。 -点击已有 `UI` 资源直接打开 UI 编辑器。若 manifest 阶段为 `ui-workflow.completed`,工作台自动打开该资源的 `asset-separation` 阶段(最远步骤为 2),交给用户做最终检查和手动调整。 +点击已有 `ui-design-doc` 资源直接打开 UI 编辑器。若 manifest 阶段为 `ui-workflow.completed`,工作台自动打开该资源的 `asset-separation` 阶段(最远步骤为 2),交给用户做最终检查和手动调整。 自动切分达到返工上限的 problematic 节点会随分离 DTO 返回每节点的 `problem_history`,并由编辑器回写为 `component_status = NeedReview(...)`。`SeparationOverview` 只读取 UI State 中的状态来计数和定位;该结果仍按“已尽力完成”报告成功并执行既有 finalize,剩余节点由用户在概览定位后手动处理或再次发起分离。 diff --git a/packages/shared/src/contracts/gameCreationApp.ts b/packages/shared/src/contracts/gameCreationApp.ts index a6f568836..6b9a6abfb 100644 --- a/packages/shared/src/contracts/gameCreationApp.ts +++ b/packages/shared/src/contracts/gameCreationApp.ts @@ -480,6 +480,12 @@ export interface GameCreationAppAssetManifestEntry { tags?: string[]; } +/** UI Editor JSON 文档资产的唯一 kind 与媒体类型。 */ +export const GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND = + 'ui-design-doc' as const; +export const GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE = + 'application/json' as const; + export const GAME_CREATION_APP_CANONICAL_ASSET_KINDS = [ 'image', 'scene', @@ -489,6 +495,7 @@ export const GAME_CREATION_APP_CANONICAL_ASSET_KINDS = [ 'icon-spritesheet', 'icon-spec', 'ui-design', + GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, 'publication-material', 'spec', 'video', @@ -542,12 +549,7 @@ const GAME_CREATION_APP_LEGACY_ASSET_KINDS: Record< export function canonicalGameCreationAppAssetKind( value: string, ): GameCreationAppCanonicalAssetKind { - /** - * kind 词汇大小写不敏感:UI 设计资产的现役写入侧写的是**大写** `"UI"` - * (`src-tauri/src/ui_editor/resource_bridge.rs`、`workflow.rs`、`persistence.rs`)。 - * 只按小写收口会让它落 `image` 兜底、再经 `image → unclassified` 永远停在「待归类」, - * 且派生值本身就是 `unclassified`,读时自愈也救不回来。 - */ + /** kind 词汇大小写不敏感;UI Editor 文档写入侧统一使用 `ui-design-doc`。 */ const normalized = value.trim().toLowerCase(); /** * `kind` 是外部输入,可能正好是 `Object.prototype` 上的键(`constructor` / `__proto__` / @@ -594,6 +596,7 @@ export const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: Record< 'icon-spritesheet': 'ui-interaction', 'icon-spec': 'ui-interaction', 'ui-design': 'ui-interaction', + 'ui-design-doc': 'ui-interaction', 'publication-material': 'unclassified', spec: 'document', video: 'unclassified', diff --git a/server-rs/crates/shared-contracts/src/game_creation_app.rs b/server-rs/crates/shared-contracts/src/game_creation_app.rs index 9c8a6b62d..6d9fb6ddd 100644 --- a/server-rs/crates/shared-contracts/src/game_creation_app.rs +++ b/server-rs/crates/shared-contracts/src/game_creation_app.rs @@ -565,7 +565,7 @@ impl<'de> Deserialize<'de> for GameCreationAppAssetManifestEntry { } } -pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 16] = [ +pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 17] = [ "image", "scene", "character", @@ -574,6 +574,7 @@ pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 16] = [ "icon-spritesheet", "icon-spec", "ui-design", + "ui-design-doc", "publication-material", "spec", "video", @@ -585,11 +586,7 @@ pub const GAME_CREATION_APP_CANONICAL_ASSET_KINDS: [&str; 16] = [ ]; pub fn canonical_game_creation_app_asset_kind(value: &str) -> &'static str { - // kind 词汇大小写不敏感:UI 设计资产的现役写入侧写的是**大写** `"UI"` - // (`apps/ai-game-creator-shell/src-tauri/src/ui_editor/resource_bridge.rs` 的 - // `register_local_asset_at(..., "UI", ...)`,`workflow.rs` / `persistence.rs` 同), - // 只按小写收口会让它落到 `image` 兜底、再经 `image -> unclassified` 永远停在 - // 「待归类」;且派生值本身就是 unclassified,读时自愈也救不回来。 + // kind 词汇大小写不敏感;UI Editor 文档写入侧统一使用 `ui-design-doc`。 // TS 侧 `canonicalGameCreationAppAssetKind` 用同一口径,由 // `apps/ai-game-creator-shell/tests/assetKindCanonicalMapping.test.ts` 交叉钉住。 let normalized = value.trim().to_lowercase(); @@ -640,7 +637,7 @@ pub const GAME_CREATION_APP_ASSET_CATEGORIES: [GameCreationAppAssetCategory; 6] ]; /// canonical kind 到功能分类的默认映射,必须穷举 `GAME_CREATION_APP_CANONICAL_ASSET_KINDS`。 -pub const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: [(&str, GameCreationAppAssetCategory); 16] = [ +pub const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: [(&str, GameCreationAppAssetCategory); 17] = [ ("image", GameCreationAppAssetCategory::Unclassified), ("scene", GameCreationAppAssetCategory::Scene), ("character", GameCreationAppAssetCategory::Character), @@ -655,6 +652,7 @@ pub const GAME_CREATION_APP_ASSET_CATEGORY_BY_KIND: [(&str, GameCreationAppAsset ), ("icon-spec", GameCreationAppAssetCategory::UiInteraction), ("ui-design", GameCreationAppAssetCategory::UiInteraction), + ("ui-design-doc", GameCreationAppAssetCategory::UiInteraction), ( "publication-material", GameCreationAppAssetCategory::Unclassified, From fe4e952853cf75248cda10564555a179aed9d8ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 21:04:08 +0800 Subject: [PATCH 05/75] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20UI=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E8=B5=84=E6=BA=90=E7=BC=96=E8=BE=91=E5=99=A8=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 ui-design-doc 资源纳入选中资源的 UI 编辑器按钮判定 保留 ui-prototype 图片资源的编辑器桥接入口 --- .../src/view/project-development/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0d1266d82..7ab94d191 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 @@ -6812,7 +6812,7 @@ export default function ProjectDevelopmentView({ [manifest, selectedResource], ); const selectedResourceOpensUiEditor = - selectedResource?.subtype === 'UI' || + selectedResource?.subtype === 'ui-design-doc' || selectedResource?.subtype === 'ui-prototype'; const selectedToolbarStyle = selectedResourceLayer From 2e4a1996c88bc6d7e6a7c74faba13b069888f788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 21:12:03 +0800 Subject: [PATCH 06/75] =?UTF-8?q?=E5=90=8C=E6=AD=A5=20UI=20=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=99=A8=E5=85=A5=E5=8F=A3=E6=B5=8B=E8=AF=95=E8=B5=84?= =?UTF-8?q?=E4=BA=A7=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将项目开发 UI 编辑器回程测试 fixture 更新为 ui-design-doc --- .../tests/appSurface/project-development.suite.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts index c5288b270..51da64e3c 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/project-development.suite.ts @@ -5908,7 +5908,7 @@ export function registerProjectWorkbenchFoundationTests() { manifest.assets = [ { id: 'ui-design-resource', - kind: 'UI', + kind: 'ui-design-doc', mediaType: 'application/json', localPath: 'assets/ui-design.json', source: { kind: 'generated' }, From b7e3dac6619bdf5e2f1e5257989f83d5d3044031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Mon, 14 Sep 2026 21:36:43 +0800 Subject: [PATCH 07/75] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=BC=95=E7=94=A8=20UI?= =?UTF-8?q?=20=E6=96=87=E6=A1=A3=E8=B5=84=E4=BA=A7=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 前端入口、资源桥接与引用预览统一使用共享常量 Rust 持久化与测试复用共享契约常量 补齐 UI 编辑器入口 fixture 的新资产类型 --- .../src-tauri/src/tests/project.rs | 25 +++++++++------ .../src-tauri/src/ui_editor/persistence.rs | 6 ++-- .../ResourceReferenceInput.tsx | 6 +++- .../ui-editor/uiDesignResourceBridge.ts | 8 +++-- .../src/view/project-development/index.tsx | 14 +++++--- .../appSurface/project-development.suite.ts | 8 +++-- .../tests/assetKindCanonicalMapping.test.ts | 13 +++++--- .../tests/uiDesignResourceBridge.test.ts | 32 +++++++++++-------- .../shared/src/contracts/gameCreationApp.ts | 4 +-- .../shared-contracts/src/game_creation_app.rs | 9 ++++-- 10 files changed, 81 insertions(+), 44 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs index 52ad3d47d..839132da7 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/tests/project.rs @@ -2031,8 +2031,8 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() { let registered = register_local_asset_at( &root, "ui/UI 设计 1.json", - "UI", - "application/json", + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", source(), ) @@ -2053,8 +2053,8 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() { register_local_asset_at( &root, "ui/UI 设计 1.json", - "UI", - "application/json", + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", source(), ) @@ -2063,7 +2063,10 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() { let manifest: Value = serde_json::from_str(&fs::read_to_string(root.join(".agent/manifest.json")).unwrap()) .expect("manifest json"); - assert_eq!(manifest["assets"][0]["kind"], "UI"); + assert_eq!( + manifest["assets"][0]["kind"], + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND + ); assert_eq!(manifest["assets"][0]["category"], "audio"); assert_eq!(manifest["assets"][0]["tags"], serde_json::json!(["界面"])); @@ -2074,7 +2077,7 @@ fn register_local_asset_keeps_explicit_category_when_kind_is_unchanged() { /// /// 这里的字面量与写入侧逐字一致:UI 设计资产是 /// `ui_editor/resource_bridge.rs` / `workflow.rs` / `persistence.rs` 的 -/// `register_local_asset_at(root, path, "UI", "application/json", ...)`, +/// `register_local_asset_at` 使用 UI 文档 kind/media 常量, /// 字体是 `commands.rs` 字体上传的 `register_local_asset_entry(root, path, "font", ...)`。 /// 只要别名表漏掉它们,真机资产就会永远停在「待归类」且读时自愈也救不回来。 #[test] @@ -2099,8 +2102,8 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() { register_local_asset_at( &root, "ui/UI 设计 1.json", - "UI", - "application/json", + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND, + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, "ui-workflow", source(), ) @@ -2133,7 +2136,11 @@ fn register_local_asset_derives_category_from_real_write_side_kinds() { assert_eq!( categories, vec![ - ("UI".to_string(), "ui-interaction".to_string()), + ( + shared_contracts::game_creation_app::GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND + .to_string(), + "ui-interaction".to_string(), + ), ("font".to_string(), "document".to_string()), ] ); diff --git a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs index 7755cdbd3..f9f1a018a 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/ui_editor/persistence.rs @@ -19,8 +19,10 @@ use std::time::{SystemTime, UNIX_EPOCH}; use typed_floats::tf32::StrictlyPositiveFinite; const UI_DESIGN_STATE_SCHEMA_VERSION: &str = "game-creator-ui-design-state.v1"; -pub(crate) const UI_DESIGN_DOC_ASSET_KIND: &str = "ui-design-doc"; -pub(crate) const UI_DESIGN_DOC_MEDIA_TYPE: &str = "application/json"; +pub(crate) use shared_contracts::game_creation_app::{ + GAME_CREATION_APP_UI_DESIGN_DOC_ASSET_KIND as UI_DESIGN_DOC_ASSET_KIND, + GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE as UI_DESIGN_DOC_MEDIA_TYPE, +}; const UI_DESIGN_STATE_MAX_BYTES: usize = 2 * 1024 * 1024; const UI_DESIGN_CODE_MAX_BYTES: usize = UI_DESIGN_STATE_MAX_BYTES * 8; const UI_DESIGN_STATE_MAX_IMAGES: usize = 4; diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx index 1971660b5..591cc3830 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx @@ -53,6 +53,7 @@ import { createPortal, flushSync } from 'react-dom'; import { PlatformResourceFilterBar } from '../../../../../packages/shared/src/components/PlatformResourceFilterBar'; import { PlatformSegmentedTabs } from '../../../../../packages/shared/src/components/PlatformSegmentedTabs'; import { + GAME_CREATION_APP_UI_DESIGN_DOC_MEDIA_TYPE, type GameCreationAppAssetManifestEntry, gameCreationAppAssetTags, type GameIterationVersion, @@ -1253,7 +1254,10 @@ function ResourcePickerThumbnail({ if (mediaType.startsWith('image/')) { return