diff --git a/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_template_commands.rs b/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_template_commands.rs index e94a515..293f383 100644 --- a/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_template_commands.rs +++ b/apps/desktop/src-tauri/src/presentation/commands/comfyui_v2_template_commands.rs @@ -16,7 +16,7 @@ use crate::data::models::comfyui::{ // ==================== 请求和响应类型 ==================== -/// 模板创建请求 +/// ComfyUI V2 模板创建请求 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CreateTemplateRequest { pub name: String, diff --git a/cargos/comfyui-sdk/template_hub/ai_model_face_hair_fix.rs b/cargos/comfyui-sdk/template_hub/ai_model_face_hair_fix.rs index 7f0e802..beae7e6 100644 --- a/cargos/comfyui-sdk/template_hub/ai_model_face_hair_fix.rs +++ b/cargos/comfyui-sdk/template_hub/ai_model_face_hair_fix.rs @@ -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") } diff --git a/cargos/comfyui-sdk/types/mod.rs b/cargos/comfyui-sdk/types/mod.rs index 33b1445..fbef945 100644 --- a/cargos/comfyui-sdk/types/mod.rs +++ b/cargos/comfyui-sdk/types/mod.rs @@ -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, @@ -59,11 +70,35 @@ pub struct ParameterSchema { #[serde(skip_serializing_if = "Option::is_none")] pub max: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub step: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub pattern: Option, #[serde(skip_serializing_if = "Option::is_none")] pub items: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, + // Media file related properties + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub accept: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "maxSize")] + #[serde(default)] + pub max_size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub width: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub height: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub duration: Option, + // Workflow node mapping + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub node_mapping: Option, } /// 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, + } + } +} diff --git a/cargos/comfyui-sdk/utils/validation.rs b/cargos/comfyui-sdk/utils/validation.rs index 897e10e..08d068f 100644 --- a/cargos/comfyui-sdk/utils/validation.rs +++ b/cargos/comfyui-sdk/utils/validation.rs @@ -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(); diff --git a/test_template_creation.json b/test_template_creation.json new file mode 100644 index 0000000..16c8cd4 --- /dev/null +++ b/test_template_creation.json @@ -0,0 +1,94 @@ +{ + "request": { + "name": "AI模型头发修复细节", + "category": "", + "description": "", + "template_data": { + "metadata": { + "id": "ai-001", + "name": "AI模型头发修复细节", + "description": "", + "version": "1.0.0", + "author": "", + "tags": [], + "category": "" + }, + "workflow": { + "30": { + "inputs": { + "image": "{{input_image}}" + }, + "class_type": "LoadImage", + "_meta": { + "title": "加载图像" + } + }, + "71": { + "inputs": { + "Number": "{{prompt_strage}}" + }, + "class_type": "Float", + "_meta": { + "title": "Float" + } + } + }, + "parameters": { + "input_image": { + "param_type": "image", + "required": false, + "description": "支持的图片格式:JPG, PNG, WebP, BMP, TIFF", + "default": "", + "node_mapping": { + "node_id": "30", + "input_field": "image" + }, + "accept": ".jpg,.jpeg,.png,.webp,.bmp,.tiff", + "maxSize": 10485760 + }, + "prompt_strage": { + "param_type": "float", + "required": false, + "description": "", + "default": 0.3, + "min": 0, + "max": 1, + "step": 0.01, + "node_mapping": { + "node_id": "71", + "input_field": "Number" + } + } + } + }, + "parameter_schema": { + "input_image": { + "param_type": "image", + "required": false, + "description": "支持的图片格式:JPG, PNG, WebP, BMP, TIFF", + "default": "", + "node_mapping": { + "node_id": "30", + "input_field": "image" + }, + "accept": ".jpg,.jpeg,.png,.webp,.bmp,.tiff", + "maxSize": 10485760 + }, + "prompt_strage": { + "param_type": "float", + "required": false, + "description": "", + "default": 0.3, + "min": 0, + "max": 1, + "step": 0.01, + "node_mapping": { + "node_id": "71", + "input_field": "Number" + } + } + }, + "tags": [], + "author": "" + } +}