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] =?UTF-8?q?=E6=8E=A5=E5=85=A5=20UI=20=E6=96=87=E6=A1=A3?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E4=BB=A3=E7=A0=81=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=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();