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}");