diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs index ad7117320..653595170 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_actions/action_execution.rs @@ -154,13 +154,15 @@ pub(crate) async fn execute_game_creator_agent_runtime_tool_action_with_pending_ } "canvas.asset_import" => observe_agent_runtime_asset_import(root, &action.input).await, "ui-design-doc.from-images" => { - observe_agent_runtime_ui_design_doc_from_images(root, &action.input) + observe_agent_runtime_ui_design_doc_from_images(root, &action.input).await } "ui-design-doc.run-workflow" => { observe_agent_runtime_ui_design_doc_run_workflow(root, agent_id, run_id, &action.input) .await } - "ui-design-doc.into-js" => observe_agent_runtime_ui_design_doc_into_js(root, &action.input), + "ui-design-doc.into-js" => { + observe_agent_runtime_ui_design_doc_into_js(root, &action.input).await + } "project.index" => observe_agent_runtime_project_snapshot_with_lock( root, agent_id, diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/ui_design_doc.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/ui_design_doc.rs index ca9a24160..4017acbc9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/ui_design_doc.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_tools/ui_design_doc.rs @@ -28,7 +28,7 @@ struct DesignDocArguments { design_doc_asset_id: String, } -pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_from_images( +pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_from_images( root: &Path, input: &Value, ) -> AgentRuntimeToolObservation { @@ -44,11 +44,25 @@ pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_from_images( let revision_before = read_game_creator_agent_runtime_project_revision(root) .ok() .map(|snapshot| snapshot.revision); - match create_ui_design_doc_from_images(CreateUiDesignDocFromImagesInput { - project_path: root.to_string_lossy().into_owned(), - expected_project_id, - images: arguments.images, - }) { + // 新建文档要逐张解码设计图并写 manifest:整段阻塞 IO 交给 blocking 线程池, + // 别把 async 执行器(同一个 runtime 还要继续跑其它动作)占住。 + let project_path = root.to_string_lossy().into_owned(); + let images = arguments.images; + let created = match tokio::task::spawn_blocking(move || { + create_ui_design_doc_from_images(CreateUiDesignDocFromImagesInput { + project_path, + expected_project_id, + images, + }) + }) + .await + { + Ok(created) => created, + Err(_) => { + return error_observation(root, tool, "后台创建 UI 设计文档任务未完成".to_string()) + } + }; + match created { Ok(created) => { if revision_before.is_none_or(|before| created.committed_project_revision > before) { emit_game_creator_manifest_invalidated(root, tool); @@ -135,7 +149,7 @@ pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_run_workflow( } } -pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_into_js( +pub(in crate::agent) async fn observe_agent_runtime_ui_design_doc_into_js( root: &Path, input: &Value, ) -> AgentRuntimeToolObservation { @@ -148,11 +162,24 @@ pub(in crate::agent) fn observe_agent_runtime_ui_design_doc_into_js( Ok(project_id) => project_id, Err(error) => return rejected(root, tool, error), }; - match generate_ui_design_code_at(GenerateUiDesignCodeInput { - project_path: root.to_string_lossy().into_owned(), - expected_project_id, - asset_id: arguments.design_doc_asset_id, - }) { + // 生成代码要读整份文档、算 SHA-256 再落盘,同样是阻塞 IO,放到 blocking 线程池。 + let project_path = root.to_string_lossy().into_owned(); + let asset_id = arguments.design_doc_asset_id; + let generated = match tokio::task::spawn_blocking(move || { + generate_ui_design_code_at(GenerateUiDesignCodeInput { + project_path, + expected_project_id, + asset_id, + }) + }) + .await + { + Ok(generated) => generated, + Err(_) => { + return error_observation(root, tool, "后台生成 UI 设计代码任务未完成".to_string()) + } + }; + match generated { Ok(result) => AgentRuntimeToolObservation { tool: tool.to_string(), status: "ok".to_string(), @@ -190,13 +217,14 @@ mod tests { use super::*; use serde_json::json; - #[test] - fn from_images_rejects_unknown_fields_and_accepts_reference_pairs() { + #[tokio::test] + async fn from_images_rejects_unknown_fields_and_accepts_reference_pairs() { let root = Path::new("."); let rejected = observe_agent_runtime_ui_design_doc_from_images( root, &json!({ "images": [{ "assetId": "a", "path": null }], "extra": 1 }), - ); + ) + .await; assert_eq!(rejected.status, "rejected"); assert!(rejected.summary.contains("extra"), "{}", rejected.summary); @@ -204,21 +232,23 @@ mod tests { let invalid_project = observe_agent_runtime_ui_design_doc_from_images( root, &json!({ "images": [{ "assetId": "a", "path": null }] }), - ); + ) + .await; assert_eq!(invalid_project.status, "rejected"); assert!(!invalid_project.summary.contains("输入无效")); } - #[test] - fn design_doc_asset_id_must_be_a_string() { - // run-workflow 与 into-js 共用同一份入参解析,这里解析一次、同步调用一次。 + #[tokio::test] + async fn design_doc_asset_id_must_be_a_string() { + // run-workflow 与 into-js 共用同一份入参解析,这里解析一次、各调用一次。 assert!( serde_json::from_value::(json!({ "designDocAssetId": 1 })).is_err() ); let observation = observe_agent_runtime_ui_design_doc_into_js( Path::new("."), &json!({ "designDocAssetId": 1 }), - ); + ) + .await; assert_eq!(observation.status, "rejected"); assert!( observation.summary.contains("输入无效"),