From 62b0f103de940778468882fd15c17d41901ab567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 17:47:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=8A=20ui-design-doc=20=E7=9A=84=E9=98=BB?= =?UTF-8?q?=E5=A1=9E=20IO=20=E6=8C=AA=E5=87=BA=20async=20=E6=89=A7?= =?UTF-8?q?=E8=A1=8C=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src-tauri/src/agent/runtime_tools/ui_design_doc.rs 的 from-images 与 into-js 观察函数改为 async,核心逻辑用 tokio::task::spawn_blocking 执行:from-images 要逐张 image::open 解码设计图再写 manifest,into-js 要读整份文档、算 SHA-256 再落盘 JS,原先都在 async 执行器线程上同步跑,会和其它已派发的动作抢执行器 阻塞任务未完成(join 失败)时返回 status=error 的观察,沿用仓库既有的 spawn_blocking 失败兜底写法 src-tauri/src/agent/runtime_actions/action_execution.rs 两个分发分支改为 await;ui_design_doc.rs 的两个用例改成 #[tokio::test] 并 await --- .../agent/runtime_actions/action_execution.rs | 6 +- .../src/agent/runtime_tools/ui_design_doc.rs | 70 +++++++++++++------ 2 files changed, 54 insertions(+), 22 deletions(-) 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("输入无效"),