From 633a3ff253912f614ed3992d4e34db4e27a04ed2 Mon Sep 17 00:00:00 2001 From: iHeyTang Date: Wed, 13 Aug 2025 17:10:55 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81API=E5=8F=82=E6=95=B0=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E7=AE=80=E5=8C=96=E8=BE=93=E5=85=A5?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E5=AD=97=E6=AE=B5=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E4=BB=A3=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- workflow_service/comfyui_client.py | 190 ++++++++++++++++------------- 1 file changed, 105 insertions(+), 85 deletions(-) diff --git a/workflow_service/comfyui_client.py b/workflow_service/comfyui_client.py index 818870a..823eeb2 100644 --- a/workflow_service/comfyui_client.py +++ b/workflow_service/comfyui_client.py @@ -364,7 +364,8 @@ async def submit_workflow_to_queue( def parse_api_spec(workflow_data: dict) -> Dict[str, Dict[str, Any]]: """ - 解析工作流,并根据规范 '{基础名}_{属性名}_{可选计数}' 生成API参数名。 + 解析工作流,并根据规范生成嵌套结构的API参数名。 + 结构: {'节点名': {'node_id': '节点ID', '字段名': {字段信息}}} """ spec = {"inputs": {}, "outputs": {}} if "nodes" not in workflow_data or not isinstance(workflow_data["nodes"], list): @@ -373,8 +374,6 @@ def parse_api_spec(workflow_data: dict) -> Dict[str, Dict[str, Any]]: ) nodes_map = {str(node["id"]): node for node in workflow_data["nodes"]} - input_name_counter = defaultdict(int) - output_name_counter = defaultdict(int) for node_id, node in nodes_map.items(): title: str = node.get("title") @@ -382,65 +381,77 @@ def parse_api_spec(workflow_data: dict) -> Dict[str, Dict[str, Any]]: continue if title.startswith(API_INPUT_PREFIX): - base_name = title[len(API_INPUT_PREFIX) :].lower() + # 提取节点名(去掉API_INPUT_PREFIX前缀) + node_name = title[len(API_INPUT_PREFIX) :].lower() + + # 如果节点名不在inputs中,创建新的节点条目 + if node_name not in spec["inputs"]: + spec["inputs"][node_name] = { + "node_id": node_id, + "class_type": node.get("type"), + } + if "inputs" in node: for a_input in node.get("inputs", []): if a_input.get("link") is None and "widget" in a_input: widget_name = a_input["widget"]["name"].lower() - # [BUG修复] 构建有意义的基础参数名 - param_name_candidate = f"{base_name}_{widget_name}" + # 特殊处理:隐藏LoadImage节点的upload字段 + if node.get("type") == "LoadImage" and widget_name == "upload": + continue - input_name_counter[param_name_candidate] += 1 - count = input_name_counter[param_name_candidate] - final_param_name = ( - f"{param_name_candidate}_{count}" - if count > 1 - else param_name_candidate - ) + # 确保inputs字段存在 + if "inputs" not in spec["inputs"][node_name]: + spec["inputs"][node_name]["inputs"] = {} - input_type_str = a_input.get("type", "STRING").upper() - param_type = "string" - if "COMBO" in input_type_str: - param_type = "UploadFile" - elif "INT" in input_type_str: - param_type = "int" - elif "FLOAT" in input_type_str: - param_type = "float" - - spec["inputs"][final_param_name] = { - "node_id": node_id, - "type": param_type, + # 直接使用widget_name作为字段名 + spec["inputs"][node_name]["inputs"][widget_name] = { + "type": _get_param_type(a_input.get("type", "STRING")), "widget_name": a_input["widget"]["name"], } elif title.startswith(API_OUTPUT_PREFIX): - base_name = title[len(API_OUTPUT_PREFIX) :].lower() + # 提取节点名(去掉API_OUTPUT_PREFIX前缀) + node_name = title[len(API_OUTPUT_PREFIX) :].lower() + + # 如果节点名不在outputs中,创建新的节点条目 + if node_name not in spec["outputs"]: + spec["outputs"][node_name] = { + "node_id": node_id, + "class_type": node.get("type"), + } + if "outputs" in node: for an_output in node.get("outputs", []): output_name = an_output["name"].lower() - # [BUG修复] 构建有意义的基础参数名 - param_name_candidate = f"{base_name}_{output_name}" + # 确保outputs字段存在 + if "outputs" not in spec["outputs"][node_name]: + spec["outputs"][node_name]["outputs"] = {} - output_name_counter[param_name_candidate] += 1 - count = output_name_counter[param_name_candidate] - final_param_name = ( - f"{param_name_candidate}_{count}" - if count > 1 - else param_name_candidate - ) - - spec["outputs"][final_param_name] = { - "node_id": node_id, - "class_type": node.get("type"), + # 直接使用output_name作为字段名 + spec["outputs"][node_name]["outputs"][output_name] = { "output_name": an_output["name"], - "output_index": node["outputs"].index(an_output), } return spec +def _get_param_type(input_type_str: str) -> str: + """ + 根据输入类型字符串确定参数类型 + """ + input_type_str = input_type_str.upper() + if "COMBO" in input_type_str: + return "UploadFile" + elif "INT" in input_type_str: + return "int" + elif "FLOAT" in input_type_str: + return "float" + else: + return "string" + + async def patch_workflow( workflow_data: dict, api_spec: dict[str, dict[str, Any]], @@ -449,70 +460,79 @@ async def patch_workflow( ) -> dict: """ 将request_data中的参数值,patch到workflow_data中。并返回修改后的workflow_data。 + + request_data结构: {"节点名称": {"字段名称": "字段值"}} """ if "nodes" not in workflow_data: raise ValueError("无效的工作流格式") nodes_map = {str(node["id"]): node for node in workflow_data["nodes"]} - for param_name, value in request_data.items(): - if param_name not in api_spec["inputs"]: + for node_name, node_fields in request_data.items(): + if node_name not in api_spec["inputs"]: continue - spec = api_spec["inputs"][param_name] - node_id = spec["node_id"] + node_spec = api_spec["inputs"][node_name] + node_id = node_spec["node_id"] if node_id not in nodes_map: continue target_node = nodes_map[node_id] - widget_name_to_patch = spec["widget_name"] - # 找到所有属于widget的输入,并确定目标widget的索引 - widget_inputs = [ - inp for inp in target_node.get("inputs", []) if "widget" in inp - ] + # 处理该节点下的所有字段 + for field_name, value in node_fields.items(): + if "inputs" not in node_spec or field_name not in node_spec["inputs"]: + continue - target_widget_index = -1 - for i, widget_input in enumerate(widget_inputs): - # "widget" 字典可能不存在或没有 "name" 键 - if widget_input.get("widget", {}).get("name") == widget_name_to_patch: - target_widget_index = i - break + field_spec = node_spec["inputs"][field_name] + widget_name_to_patch = field_spec["widget_name"] - if target_widget_index == -1: - logger.warning( - f"在节点 {node_id} 中未找到名为 '{widget_name_to_patch}' 的 widget。跳过此参数。" - ) - continue + # 找到所有属于widget的输入,并确定目标widget的索引 + widget_inputs = [ + inp for inp in target_node.get("inputs", []) if "widget" in inp + ] - # 确保 `widgets_values` 存在且为列表 - if "widgets_values" not in target_node or not isinstance( - target_node.get("widgets_values"), list - ): - # 如果不存在或格式错误,根据widget数量创建一个占位符列表 - target_node["widgets_values"] = [None] * len(widget_inputs) + target_widget_index = -1 + for i, widget_input in enumerate(widget_inputs): + # "widget" 字典可能不存在或没有 "name" 键 + if widget_input.get("widget", {}).get("name") == widget_name_to_patch: + target_widget_index = i + break - # 确保 `widgets_values` 列表足够长 - while len(target_node["widgets_values"]) <= target_widget_index: - target_node["widgets_values"].append(None) + if target_widget_index == -1: + logger.warning( + f"在节点 {node_id} 中未找到名为 '{widget_name_to_patch}' 的 widget。跳过此参数。" + ) + continue - # 根据API规范转换数据类型 - target_type = str - if spec["type"] == "int": - target_type = int - elif spec["type"] == "float": - target_type = float + # 确保 `widgets_values` 存在且为列表 + if "widgets_values" not in target_node or not isinstance( + target_node.get("widgets_values"), list + ): + # 如果不存在或格式错误,根据widget数量创建一个占位符列表 + target_node["widgets_values"] = [None] * len(widget_inputs) - # 在正确的位置上更新值 - try: - if target_node.get("type") == "LoadImage": - value = await upload_image_to_comfy(value, server) - target_node["widgets_values"][target_widget_index] = target_type(value) - except (ValueError, TypeError) as e: - logger.warning( - f"无法将参数 '{param_name}' 的值 '{value}' 转换为类型 '{spec['type']}'。错误: {e}" - ) - continue + # 确保 `widgets_values` 列表足够长 + while len(target_node["widgets_values"]) <= target_widget_index: + target_node["widgets_values"].append(None) + + # 根据API规范转换数据类型 + target_type = str + if field_spec["type"] == "int": + target_type = int + elif field_spec["type"] == "float": + target_type = float + + # 在正确的位置上更新值 + try: + if target_node.get("type") == "LoadImage": + value = await upload_image_to_comfy(value, server) + target_node["widgets_values"][target_widget_index] = target_type(value) + except (ValueError, TypeError) as e: + logger.warning( + f"无法将节点 '{node_name}' 的字段 '{field_name}' 的值 '{value}' 转换为类型 '{field_spec['type']}'。错误: {e}" + ) + continue workflow_data["nodes"] = list(nodes_map.values()) return workflow_data