完善UI自动分离批次处理

复用资源ID安全路径并修正像素坐标

加入标记图、Raw编辑、视觉绑定修复与问题节点继续处理
This commit is contained in:
2026-09-08 18:32:36 +08:00
parent 4cc7f4dd64
commit f796aef7e2
@@ -16,7 +16,6 @@ use platform_llm::{
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fs;
use std::path::{Path, PathBuf};
use ts_rs::TS;
@@ -331,18 +330,10 @@ pub fn separation_sidecar_dir(root: &Path, asset_id: &str) -> Result<PathBuf, St
if asset_id.trim().is_empty() || asset_id.trim() != asset_id {
return Err("UI 资源 ID 无效".to_string());
}
let mut stem = String::new();
for character in asset_id.chars() {
if character.is_ascii_alphanumeric() || matches!(character, '_' | '-') {
stem.push(character);
} else {
stem.push('_');
}
}
let digest = format!("{:x}", Sha256::digest(asset_id.as_bytes()));
let dir = root
.join("ui")
.join(format!(".{stem}-{}-separation", &digest[..16]));
let dir = root.join("ui").join(format!(
".{}-separation",
crate::ui_editor::persistence::generated_file_stem(asset_id)
));
if !dir.starts_with(root) {
return Err("separation sidecar 路径越界".to_string());
}
@@ -437,6 +428,28 @@ pub fn apply_batch_patch(
Ok(())
}
fn mark_batch_problematic(
state: &mut SeparationState,
tree_index: usize,
batch: &[&SeparationNode],
error: String,
) {
let ids = batch
.iter()
.map(|node| node.id.clone())
.collect::<std::collections::HashSet<_>>();
for node in batch {
state.problematic_nodes.push(ProblematicNode {
node_id: node.id.clone(),
problem_description: error.clone(),
rework_count: MAX_REWORK_COUNT,
});
}
if let Some(tree) = state.unprocessed_trees.get_mut(tree_index) {
remove_ids(&mut tree.root, &ids);
}
}
fn increment_rework_count(node: &mut SeparationNode, id: &NodeId, count: u32) {
if node.id == *id {
node.rework_count = count;
@@ -662,22 +675,41 @@ pub(crate) async fn separate_ui_impl(
let Some(current_tree) = separation.unprocessed_trees.get(tree_index) else {
break;
};
let batch = next_leaf_batch(current_tree);
if batch.is_empty() {
let batch_nodes = next_leaf_batch(current_tree)
.into_iter()
.cloned()
.collect::<Vec<_>>();
if batch_nodes.is_empty() {
break;
}
let batch = batch_nodes.iter().collect::<Vec<_>>();
let prompt =
gen_extract_prompt(&batch.iter().map(|n| n.note.clone()).collect::<Vec<_>>());
let marker_path = sidecar.join(format!("marked-{}.png", separation.bound.len()));
let marked_url = build_marked_image(&source_url, &batch, &marker_path)?;
let processed_url = raw_image_edit(
let marked_url = match build_marked_image(&source_url, &batch, &marker_path) {
Ok(value) => value,
Err(error) => {
mark_batch_problematic(&mut separation, tree_index, &batch, error);
write_separation_state(&state_path, &separation)?;
continue;
}
};
let processed_url = match raw_image_edit(
&session,
&marked_url,
&prompt,
image.pixel_size.x as u32,
image.pixel_size.y as u32,
)
.await?;
.await
{
Ok(value) => value,
Err(error) => {
mark_batch_problematic(&mut separation, tree_index, &batch, error);
write_separation_state(&state_path, &separation)?;
continue;
}
};
let processed_path = sidecar.join(format!("processed-{}.png", separation.bound.len()));
let processed_bytes = base64::engine::general_purpose::STANDARD
.decode(
@@ -688,7 +720,14 @@ pub(crate) async fn separate_ui_impl(
)
.map_err(|e| e.to_string())?;
fs::write(&processed_path, processed_bytes).map_err(|e| e.to_string())?;
let binding = visual_binding(source_url.clone(), processed_url, &batch).await?;
let binding = match visual_binding(source_url.clone(), processed_url, &batch).await {
Ok(value) => value,
Err(error) => {
mark_batch_problematic(&mut separation, tree_index, &batch, error);
write_separation_state(&state_path, &separation)?;
continue;
}
};
let mut cut_paths = std::collections::HashMap::new();
for decision in &binding.decisions {
if let BindingDecision::Ok {