fix: 修复ComfyUI V2模板创建参数解析问题并重构工作流创建
主要修改: 1. **扩展ComfyUI SDK参数类型支持** - 在ParameterType枚举中添加Integer、Float、Image、Audio、Video类型 - 在ParameterSchema中添加节点映射和媒体文件相关字段 - 添加step、accept、maxSize、width、height、duration、node_mapping字段 - 为新字段添加serde默认值支持 2. **修复参数解析问题** - 解决comfyui_v2_create_template命令缺少type字段的错误 - 确保前端传递的参数类型与后端ParameterType枚举匹配 - 修复r#enum字段的正确使用(用于JSON Schema兼容性) 3. **重构工作流模板创建方式** - 将ai_model_face_hair_fix.rs中的create_workflow函数重构为create_workflow_from_json - 改为从JSON字符串解析ComfyUIWorkflow,而非手动构建HashMap - 提高可维护性和与实际使用场景的一致性 4. **更新相关测试和验证代码** - 修复validation.rs中的测试用例,添加新字段的默认值 - 确保所有ParameterSchema创建都包含完整字段 5. **添加测试数据** - 创建test_template_creation.json用于测试参数格式 这些修改解决了前端传递的参数格式与后端期望格式不匹配的问题, 使工作流模板创建功能能够正常工作。
This commit is contained in:
@@ -13,7 +13,7 @@ use std::collections::HashMap;
|
||||
use serde_json::json;
|
||||
use crate::types::{
|
||||
WorkflowTemplateData, TemplateMetadata, ParameterSchema, ParameterType,
|
||||
ComfyUIWorkflow, ComfyUINode, NodeMeta
|
||||
ComfyUIWorkflow
|
||||
};
|
||||
|
||||
/// AI Model Face & Hair Detail Fix Template
|
||||
@@ -52,9 +52,16 @@ fn create_ai_model_face_hair_fix_template() -> WorkflowTemplateData {
|
||||
r#enum: None,
|
||||
min: None,
|
||||
max: None,
|
||||
step: None,
|
||||
pattern: None,
|
||||
items: None,
|
||||
properties: None,
|
||||
accept: None,
|
||||
max_size: None,
|
||||
width: None,
|
||||
height: None,
|
||||
duration: None,
|
||||
node_mapping: None,
|
||||
});
|
||||
|
||||
parameters.insert("face_prompt".to_string(), ParameterSchema {
|
||||
@@ -65,9 +72,16 @@ fn create_ai_model_face_hair_fix_template() -> WorkflowTemplateData {
|
||||
r#enum: None,
|
||||
min: None,
|
||||
max: None,
|
||||
step: None,
|
||||
pattern: None,
|
||||
items: None,
|
||||
properties: None,
|
||||
accept: None,
|
||||
max_size: None,
|
||||
width: None,
|
||||
height: None,
|
||||
duration: None,
|
||||
node_mapping: None,
|
||||
});
|
||||
|
||||
parameters.insert("face_denoise".to_string(), ParameterSchema {
|
||||
@@ -78,12 +92,19 @@ fn create_ai_model_face_hair_fix_template() -> WorkflowTemplateData {
|
||||
r#enum: None,
|
||||
min: None,
|
||||
max: None,
|
||||
step: None,
|
||||
pattern: None,
|
||||
items: None,
|
||||
properties: None,
|
||||
accept: None,
|
||||
max_size: None,
|
||||
width: None,
|
||||
height: None,
|
||||
duration: None,
|
||||
node_mapping: None,
|
||||
});
|
||||
|
||||
let workflow = create_workflow();
|
||||
let workflow = create_workflow_from_json();
|
||||
|
||||
WorkflowTemplateData {
|
||||
metadata,
|
||||
@@ -92,344 +113,277 @@ fn create_ai_model_face_hair_fix_template() -> WorkflowTemplateData {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_workflow() -> ComfyUIWorkflow {
|
||||
let mut workflow = HashMap::new();
|
||||
|
||||
// Node 6: 遮罩边缘滑条快速模糊
|
||||
workflow.insert("6".to_string(), ComfyUINode {
|
||||
class_type: "EG_ZZ_MHHT".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("模糊强度".to_string(), json!(3));
|
||||
inputs.insert("mask".to_string(), json!(["53", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("2🐕遮罩边缘滑条快速模糊".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 8: Checkpoint加载器
|
||||
workflow.insert("8".to_string(), ComfyUINode {
|
||||
class_type: "CheckpointLoaderSimple".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("ckpt_name".to_string(), json!("juggernaut_reborn_sd1,5.safetensors"));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("Checkpoint加载器(简易)".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 9: CLIP文本编码 (头发)
|
||||
workflow.insert("9".to_string(), ComfyUINode {
|
||||
class_type: "CLIPTextEncode".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("text".to_string(), json!("Smooth long hair,Hair details, quality hair,Smooth hair"));
|
||||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("CLIP文本编码".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 10: CLIP文本编码 (负面头发)
|
||||
workflow.insert("10".to_string(), ComfyUINode {
|
||||
class_type: "CLIPTextEncode".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("text".to_string(), json!("Frizzy hair, dry hair, split ends,bad quality, poor quality, doll, disfigured, jpg, toy, bad anatomy, missing limbs, missing fingers, 3d, cgi"));
|
||||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("CLIP文本编码".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 13: CLIP文本编码 (脸部)
|
||||
workflow.insert("13".to_string(), ComfyUINode {
|
||||
class_type: "CLIPTextEncode".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("text".to_string(), json!(["59", 0]));
|
||||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("CLIP文本编码".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 14: CLIP文本编码 (负面脸部)
|
||||
workflow.insert("14".to_string(), ComfyUINode {
|
||||
class_type: "CLIPTextEncode".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("text".to_string(), json!("(NSFW:1.2),(worst quality:1.2),(low quality:1.2),(normal quality:1.2),low resolution,watermark,"));
|
||||
inputs.insert("clip".to_string(), json!(["8", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("CLIP文本编码".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 22: Segformer Ultra V2 (头发)
|
||||
workflow.insert("22".to_string(), ComfyUINode {
|
||||
class_type: "LayerMask: SegformerUltraV2".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("detail_method".to_string(), json!("VITMatte(local)"));
|
||||
inputs.insert("detail_erode".to_string(), json!(44));
|
||||
inputs.insert("detail_dilate".to_string(), json!(6));
|
||||
inputs.insert("black_point".to_string(), json!(0.030000000000000006));
|
||||
inputs.insert("white_point".to_string(), json!(0.99));
|
||||
inputs.insert("process_detail".to_string(), json!(true));
|
||||
inputs.insert("device".to_string(), json!("cuda"));
|
||||
inputs.insert("max_megapixels".to_string(), json!(2));
|
||||
inputs.insert("image".to_string(), json!(["30", 0]));
|
||||
inputs.insert("segformer_pipeline".to_string(), json!(["28", 0]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("LayerMask: Segformer Ultra V2".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 25: 局部重绘采样器 (头发)
|
||||
workflow.insert("25".to_string(), ComfyUINode {
|
||||
class_type: "EG_CYQ_JB".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("seed".to_string(), json!(646617836820462i64));
|
||||
inputs.insert("steps".to_string(), json!(30));
|
||||
inputs.insert("cfg".to_string(), json!(4.5200000000000005));
|
||||
inputs.insert("sampler_name".to_string(), json!("dpmpp_2s_ancestral"));
|
||||
inputs.insert("scheduler".to_string(), json!("karras"));
|
||||
inputs.insert("denoise".to_string(), json!(0.4));
|
||||
inputs.insert("重绘模式".to_string(), json!("原图"));
|
||||
inputs.insert("遮罩延展".to_string(), json!(10));
|
||||
inputs.insert("仅局部重绘".to_string(), json!(true));
|
||||
inputs.insert("局部重绘大小".to_string(), json!(768));
|
||||
inputs.insert("重绘区域扩展".to_string(), json!(50));
|
||||
inputs.insert("遮罩羽化".to_string(), json!(5));
|
||||
inputs.insert("TEXT".to_string(), json!("2🐕出品,必出精品"));
|
||||
inputs.insert("model".to_string(), json!(["8", 0]));
|
||||
inputs.insert("image".to_string(), json!(["30", 0]));
|
||||
inputs.insert("vae".to_string(), json!(["8", 2]));
|
||||
inputs.insert("mask".to_string(), json!(["47", 0]));
|
||||
inputs.insert("positive".to_string(), json!(["9", 0]));
|
||||
inputs.insert("negative".to_string(), json!(["10", 0]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("2🐕局部重绘采样器".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 26: 局部重绘采样器 (脸部)
|
||||
workflow.insert("26".to_string(), ComfyUINode {
|
||||
class_type: "EG_CYQ_JB".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("seed".to_string(), json!(969968724502727i64));
|
||||
inputs.insert("steps".to_string(), json!(30));
|
||||
inputs.insert("cfg".to_string(), json!(4.5200000000000005));
|
||||
inputs.insert("sampler_name".to_string(), json!("dpmpp_2s_ancestral"));
|
||||
inputs.insert("scheduler".to_string(), json!("karras"));
|
||||
inputs.insert("denoise".to_string(), json!(["71", 0]));
|
||||
inputs.insert("重绘模式".to_string(), json!("原图"));
|
||||
inputs.insert("遮罩延展".to_string(), json!(10));
|
||||
inputs.insert("仅局部重绘".to_string(), json!(true));
|
||||
inputs.insert("局部重绘大小".to_string(), json!(768));
|
||||
inputs.insert("重绘区域扩展".to_string(), json!(50));
|
||||
inputs.insert("遮罩羽化".to_string(), json!(5));
|
||||
inputs.insert("TEXT".to_string(), json!("2🐕出品,必出精品"));
|
||||
inputs.insert("model".to_string(), json!(["8", 0]));
|
||||
inputs.insert("image".to_string(), json!(["25", 1]));
|
||||
inputs.insert("vae".to_string(), json!(["8", 2]));
|
||||
inputs.insert("mask".to_string(), json!(["6", 0]));
|
||||
inputs.insert("positive".to_string(), json!(["13", 0]));
|
||||
inputs.insert("negative".to_string(), json!(["14", 0]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("2🐕局部重绘采样器".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 28: Segformer Clothes Pipeline (头发)
|
||||
workflow.insert("28".to_string(), ComfyUINode {
|
||||
class_type: "LayerMask: SegformerClothesPipelineLoader".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("model".to_string(), json!("segformer_b3_clothes"));
|
||||
inputs.insert("face".to_string(), json!(false));
|
||||
inputs.insert("hair".to_string(), json!(true));
|
||||
inputs.insert("hat".to_string(), json!(false));
|
||||
inputs.insert("sunglass".to_string(), json!(false));
|
||||
inputs.insert("left_arm".to_string(), json!(false));
|
||||
inputs.insert("right_arm".to_string(), json!(false));
|
||||
inputs.insert("left_leg".to_string(), json!(false));
|
||||
inputs.insert("right_leg".to_string(), json!(false));
|
||||
inputs.insert("left_shoe".to_string(), json!(false));
|
||||
inputs.insert("right_shoe".to_string(), json!(false));
|
||||
inputs.insert("upper_clothes".to_string(), json!(false));
|
||||
inputs.insert("skirt".to_string(), json!(false));
|
||||
inputs.insert("pants".to_string(), json!(false));
|
||||
inputs.insert("dress".to_string(), json!(false));
|
||||
inputs.insert("belt".to_string(), json!(false));
|
||||
inputs.insert("bag".to_string(), json!(false));
|
||||
inputs.insert("scarf".to_string(), json!(false));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("LayerMask: Segformer Clothes Pipeline".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 30: 加载图像
|
||||
workflow.insert("30".to_string(), ComfyUINode {
|
||||
class_type: "LoadImage".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("image".to_string(), json!("{{input_image}}"));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("加载图像".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 44: Mask Fill Holes
|
||||
workflow.insert("44".to_string(), ComfyUINode {
|
||||
class_type: "Mask Fill Holes".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("masks".to_string(), json!(["22", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("Mask Fill Holes".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 47: Grow Mask With Blur
|
||||
workflow.insert("47".to_string(), ComfyUINode {
|
||||
class_type: "GrowMaskWithBlur".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("expand".to_string(), json!(5));
|
||||
inputs.insert("incremental_expandrate".to_string(), json!(5));
|
||||
inputs.insert("tapered_corners".to_string(), json!(true));
|
||||
inputs.insert("flip_input".to_string(), json!(false));
|
||||
inputs.insert("blur_radius".to_string(), json!(9.4));
|
||||
inputs.insert("lerp_alpha".to_string(), json!(0.9900000000000002));
|
||||
inputs.insert("decay_factor".to_string(), json!(1));
|
||||
inputs.insert("fill_holes".to_string(), json!(false));
|
||||
inputs.insert("mask".to_string(), json!(["44", 0]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("Grow Mask With Blur".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 52: Segformer Clothes Pipeline (脸部)
|
||||
workflow.insert("52".to_string(), ComfyUINode {
|
||||
class_type: "LayerMask: SegformerClothesPipelineLoader".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("model".to_string(), json!("segformer_b3_clothes"));
|
||||
inputs.insert("face".to_string(), json!(true));
|
||||
inputs.insert("hair".to_string(), json!(false));
|
||||
inputs.insert("hat".to_string(), json!(false));
|
||||
inputs.insert("sunglass".to_string(), json!(true));
|
||||
inputs.insert("left_arm".to_string(), json!(false));
|
||||
inputs.insert("right_arm".to_string(), json!(false));
|
||||
inputs.insert("left_leg".to_string(), json!(false));
|
||||
inputs.insert("right_leg".to_string(), json!(false));
|
||||
inputs.insert("left_shoe".to_string(), json!(false));
|
||||
inputs.insert("right_shoe".to_string(), json!(false));
|
||||
inputs.insert("upper_clothes".to_string(), json!(false));
|
||||
inputs.insert("skirt".to_string(), json!(false));
|
||||
inputs.insert("pants".to_string(), json!(false));
|
||||
inputs.insert("dress".to_string(), json!(false));
|
||||
inputs.insert("belt".to_string(), json!(false));
|
||||
inputs.insert("bag".to_string(), json!(false));
|
||||
inputs.insert("scarf".to_string(), json!(false));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("LayerMask: Segformer Clothes Pipeline".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 53: Segformer Ultra V2 (脸部)
|
||||
workflow.insert("53".to_string(), ComfyUINode {
|
||||
class_type: "LayerMask: SegformerUltraV2".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("detail_method".to_string(), json!("VITMatte(local)"));
|
||||
inputs.insert("detail_erode".to_string(), json!(6));
|
||||
inputs.insert("detail_dilate".to_string(), json!(4));
|
||||
inputs.insert("black_point".to_string(), json!(0.01));
|
||||
inputs.insert("white_point".to_string(), json!(0.99));
|
||||
inputs.insert("process_detail".to_string(), json!(false));
|
||||
inputs.insert("device".to_string(), json!("cuda"));
|
||||
inputs.insert("max_megapixels".to_string(), json!(2));
|
||||
inputs.insert("image".to_string(), json!(["30", 0]));
|
||||
inputs.insert("segformer_pipeline".to_string(), json!(["52", 0]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("LayerMask: Segformer Ultra V2".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 58: 保存图像
|
||||
workflow.insert("58".to_string(), ComfyUINode {
|
||||
class_type: "SaveImage".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("filename_prefix".to_string(), json!("ComfyUI"));
|
||||
inputs.insert("images".to_string(), json!(["26", 1]));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("保存图像".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 59: String (脸部提示词)
|
||||
workflow.insert("59".to_string(), ComfyUINode {
|
||||
class_type: "String".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("String".to_string(), json!("A girl, slim figure, oval face, beauty, Pink and greasy lips,{{face_prompt}}"));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("String".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
// Node 71: Float (脸部去噪强度)
|
||||
workflow.insert("71".to_string(), ComfyUINode {
|
||||
class_type: "Float".to_string(),
|
||||
inputs: {
|
||||
let mut inputs = HashMap::new();
|
||||
inputs.insert("Number".to_string(), json!("{{face_denoise}}"));
|
||||
inputs
|
||||
},
|
||||
_meta: Some(NodeMeta {
|
||||
title: Some("Float".to_string()),
|
||||
}),
|
||||
});
|
||||
|
||||
workflow
|
||||
fn create_workflow_from_json() -> ComfyUIWorkflow {
|
||||
let workflow_json = r#"
|
||||
{
|
||||
"6": {
|
||||
"inputs": {
|
||||
"模糊强度": 3,
|
||||
"mask": ["53", 1]
|
||||
},
|
||||
"class_type": "EG_ZZ_MHHT",
|
||||
"_meta": {
|
||||
"title": "2🐕遮罩边缘滑条快速模糊"
|
||||
}
|
||||
},
|
||||
"8": {
|
||||
"inputs": {
|
||||
"ckpt_name": "juggernaut_reborn_sd1,5.safetensors"
|
||||
},
|
||||
"class_type": "CheckpointLoaderSimple",
|
||||
"_meta": {
|
||||
"title": "Checkpoint加载器(简易)"
|
||||
}
|
||||
},
|
||||
"9": {
|
||||
"inputs": {
|
||||
"text": "Smooth long hair,Hair details, quality hair,Smooth hair",
|
||||
"clip": ["8", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP文本编码"
|
||||
}
|
||||
},
|
||||
"10": {
|
||||
"inputs": {
|
||||
"text": "Frizzy hair, dry hair, split ends,bad quality, poor quality, doll, disfigured, jpg, toy, bad anatomy, missing limbs, missing fingers, 3d, cgi",
|
||||
"clip": ["8", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP文本编码"
|
||||
}
|
||||
},
|
||||
"13": {
|
||||
"inputs": {
|
||||
"text": ["59", 0],
|
||||
"clip": ["8", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP文本编码"
|
||||
}
|
||||
},
|
||||
"14": {
|
||||
"inputs": {
|
||||
"text": "(NSFW:1.2),(worst quality:1.2),(low quality:1.2),(normal quality:1.2),low resolution,watermark,",
|
||||
"clip": ["8", 1]
|
||||
},
|
||||
"class_type": "CLIPTextEncode",
|
||||
"_meta": {
|
||||
"title": "CLIP文本编码"
|
||||
}
|
||||
},
|
||||
"22": {
|
||||
"inputs": {
|
||||
"detail_method": "VITMatte(local)",
|
||||
"detail_erode": 44,
|
||||
"detail_dilate": 6,
|
||||
"black_point": 0.030000000000000006,
|
||||
"white_point": 0.99,
|
||||
"process_detail": true,
|
||||
"device": "cuda",
|
||||
"max_megapixels": 2,
|
||||
"image": ["30", 0],
|
||||
"segformer_pipeline": ["28", 0]
|
||||
},
|
||||
"class_type": "LayerMask: SegformerUltraV2",
|
||||
"_meta": {
|
||||
"title": "LayerMask: Segformer Ultra V2"
|
||||
}
|
||||
},
|
||||
"25": {
|
||||
"inputs": {
|
||||
"seed": 646617836820462,
|
||||
"steps": 30,
|
||||
"cfg": 4.5200000000000005,
|
||||
"sampler_name": "dpmpp_2s_ancestral",
|
||||
"scheduler": "karras",
|
||||
"denoise": 0.4,
|
||||
"重绘模式": "原图",
|
||||
"遮罩延展": 10,
|
||||
"仅局部重绘": true,
|
||||
"局部重绘大小": 768,
|
||||
"重绘区域扩展": 50,
|
||||
"遮罩羽化": 5,
|
||||
"TEXT": "2🐕出品,必出精品",
|
||||
"model": ["8", 0],
|
||||
"image": ["30", 0],
|
||||
"vae": ["8", 2],
|
||||
"mask": ["47", 0],
|
||||
"positive": ["9", 0],
|
||||
"negative": ["10", 0]
|
||||
},
|
||||
"class_type": "EG_CYQ_JB",
|
||||
"_meta": {
|
||||
"title": "2🐕局部重绘采样器"
|
||||
}
|
||||
},
|
||||
"26": {
|
||||
"inputs": {
|
||||
"seed": 969968724502727,
|
||||
"steps": 30,
|
||||
"cfg": 4.5200000000000005,
|
||||
"sampler_name": "dpmpp_2s_ancestral",
|
||||
"scheduler": "karras",
|
||||
"denoise": ["71", 0],
|
||||
"重绘模式": "原图",
|
||||
"遮罩延展": 10,
|
||||
"仅局部重绘": true,
|
||||
"局部重绘大小": 768,
|
||||
"重绘区域扩展": 50,
|
||||
"遮罩羽化": 5,
|
||||
"TEXT": "2🐕出品,必出精品",
|
||||
"model": ["8", 0],
|
||||
"image": ["25", 1],
|
||||
"vae": ["8", 2],
|
||||
"mask": ["6", 0],
|
||||
"positive": ["13", 0],
|
||||
"negative": ["14", 0]
|
||||
},
|
||||
"class_type": "EG_CYQ_JB",
|
||||
"_meta": {
|
||||
"title": "2🐕局部重绘采样器"
|
||||
}
|
||||
},
|
||||
"28": {
|
||||
"inputs": {
|
||||
"model": "segformer_b3_clothes",
|
||||
"face": false,
|
||||
"hair": true,
|
||||
"hat": false,
|
||||
"sunglass": false,
|
||||
"left_arm": false,
|
||||
"right_arm": false,
|
||||
"left_leg": false,
|
||||
"right_leg": false,
|
||||
"left_shoe": false,
|
||||
"right_shoe": false,
|
||||
"upper_clothes": false,
|
||||
"skirt": false,
|
||||
"pants": false,
|
||||
"dress": false,
|
||||
"belt": false,
|
||||
"bag": false,
|
||||
"scarf": false
|
||||
},
|
||||
"class_type": "LayerMask: SegformerClothesPipelineLoader",
|
||||
"_meta": {
|
||||
"title": "LayerMask: Segformer Clothes Pipeline"
|
||||
}
|
||||
},
|
||||
"30": {
|
||||
"inputs": {
|
||||
"image": "{{input_image}}"
|
||||
},
|
||||
"class_type": "LoadImage",
|
||||
"_meta": {
|
||||
"title": "加载图像"
|
||||
}
|
||||
},
|
||||
"44": {
|
||||
"inputs": {
|
||||
"masks": ["22", 1]
|
||||
},
|
||||
"class_type": "Mask Fill Holes",
|
||||
"_meta": {
|
||||
"title": "Mask Fill Holes"
|
||||
}
|
||||
},
|
||||
"47": {
|
||||
"inputs": {
|
||||
"expand": 5,
|
||||
"incremental_expandrate": 5,
|
||||
"tapered_corners": true,
|
||||
"flip_input": false,
|
||||
"blur_radius": 9.4,
|
||||
"lerp_alpha": 0.9900000000000002,
|
||||
"decay_factor": 1,
|
||||
"fill_holes": false,
|
||||
"mask": ["44", 0]
|
||||
},
|
||||
"class_type": "GrowMaskWithBlur",
|
||||
"_meta": {
|
||||
"title": "Grow Mask With Blur"
|
||||
}
|
||||
},
|
||||
"52": {
|
||||
"inputs": {
|
||||
"model": "segformer_b3_clothes",
|
||||
"face": true,
|
||||
"hair": false,
|
||||
"hat": false,
|
||||
"sunglass": true,
|
||||
"left_arm": false,
|
||||
"right_arm": false,
|
||||
"left_leg": false,
|
||||
"right_leg": false,
|
||||
"left_shoe": false,
|
||||
"right_shoe": false,
|
||||
"upper_clothes": false,
|
||||
"skirt": false,
|
||||
"pants": false,
|
||||
"dress": false,
|
||||
"belt": false,
|
||||
"bag": false,
|
||||
"scarf": false
|
||||
},
|
||||
"class_type": "LayerMask: SegformerClothesPipelineLoader",
|
||||
"_meta": {
|
||||
"title": "LayerMask: Segformer Clothes Pipeline"
|
||||
}
|
||||
},
|
||||
"53": {
|
||||
"inputs": {
|
||||
"detail_method": "VITMatte(local)",
|
||||
"detail_erode": 6,
|
||||
"detail_dilate": 4,
|
||||
"black_point": 0.01,
|
||||
"white_point": 0.99,
|
||||
"process_detail": false,
|
||||
"device": "cuda",
|
||||
"max_megapixels": 2,
|
||||
"image": ["30", 0],
|
||||
"segformer_pipeline": ["52", 0]
|
||||
},
|
||||
"class_type": "LayerMask: SegformerUltraV2",
|
||||
"_meta": {
|
||||
"title": "LayerMask: Segformer Ultra V2"
|
||||
}
|
||||
},
|
||||
"58": {
|
||||
"inputs": {
|
||||
"filename_prefix": "ComfyUI",
|
||||
"images": ["26", 1]
|
||||
},
|
||||
"class_type": "SaveImage",
|
||||
"_meta": {
|
||||
"title": "保存图像"
|
||||
}
|
||||
},
|
||||
"59": {
|
||||
"inputs": {
|
||||
"String": "{{face_prompt}}"
|
||||
},
|
||||
"class_type": "String",
|
||||
"_meta": {
|
||||
"title": "String"
|
||||
}
|
||||
},
|
||||
"71": {
|
||||
"inputs": {
|
||||
"Number": "{{face_denoise}}"
|
||||
},
|
||||
"class_type": "Float",
|
||||
"_meta": {
|
||||
"title": "Float"
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
// 解析 JSON 字符串为 ComfyUIWorkflow
|
||||
serde_json::from_str(workflow_json)
|
||||
.expect("Failed to parse workflow JSON")
|
||||
}
|
||||
|
||||
@@ -39,12 +39,23 @@ pub enum ParameterType {
|
||||
Boolean,
|
||||
Array,
|
||||
Object,
|
||||
Integer,
|
||||
Float,
|
||||
Image,
|
||||
Audio,
|
||||
Video,
|
||||
}
|
||||
|
||||
/// Node mapping configuration for parameter-to-workflow binding
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NodeMapping {
|
||||
pub node_id: String,
|
||||
pub input_field: String,
|
||||
}
|
||||
|
||||
/// Parameter schema definition
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ParameterSchema {
|
||||
#[serde(rename = "type")]
|
||||
pub param_type: ParameterType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub required: Option<bool>,
|
||||
@@ -59,11 +70,35 @@ pub struct ParameterSchema {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub max: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub step: Option<f64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub pattern: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub items: Option<Box<ParameterSchema>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub properties: Option<HashMap<String, ParameterSchema>>,
|
||||
// Media file related properties
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub accept: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(rename = "maxSize")]
|
||||
#[serde(default)]
|
||||
pub max_size: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub width: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub height: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub duration: Option<f64>,
|
||||
// Workflow node mapping
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(default)]
|
||||
pub node_mapping: Option<NodeMapping>,
|
||||
}
|
||||
|
||||
/// Template metadata
|
||||
@@ -190,3 +225,28 @@ impl ValidationError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterSchema {
|
||||
/// Creates a new ParameterSchema with the given type and default values for other fields
|
||||
pub fn new(param_type: ParameterType) -> Self {
|
||||
Self {
|
||||
param_type,
|
||||
required: None,
|
||||
default: None,
|
||||
description: None,
|
||||
r#enum: None,
|
||||
min: None,
|
||||
max: None,
|
||||
step: None,
|
||||
pattern: None,
|
||||
items: None,
|
||||
properties: None,
|
||||
accept: None,
|
||||
max_size: None,
|
||||
width: None,
|
||||
height: None,
|
||||
duration: None,
|
||||
node_mapping: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,9 +235,16 @@ mod tests {
|
||||
r#enum: None,
|
||||
min: None,
|
||||
max: None,
|
||||
step: None,
|
||||
pattern: None,
|
||||
items: None,
|
||||
properties: None,
|
||||
accept: None,
|
||||
max_size: None,
|
||||
width: None,
|
||||
height: None,
|
||||
duration: None,
|
||||
node_mapping: None,
|
||||
});
|
||||
|
||||
let parameters = HashMap::new();
|
||||
@@ -259,9 +266,16 @@ mod tests {
|
||||
r#enum: None,
|
||||
min: Some(3.0),
|
||||
max: Some(10.0),
|
||||
step: None,
|
||||
pattern: None,
|
||||
items: None,
|
||||
properties: None,
|
||||
accept: None,
|
||||
max_size: None,
|
||||
width: None,
|
||||
height: None,
|
||||
duration: None,
|
||||
node_mapping: None,
|
||||
});
|
||||
|
||||
let mut parameters = HashMap::new();
|
||||
|
||||
Reference in New Issue
Block a user