Spaces:
Running on Zero
Running on Zero
| """Validate the API workflow connections against the ComfyUI UI workflow. | |
| ComfyUI API exports collapse some UI-only SetNode/GetNode routing pairs. This | |
| validator resolves those pairs before comparing links so the check reflects the | |
| actual graph semantics instead of the visual routing helpers. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from pathlib import Path | |
| from typing import Any | |
| ROOT = Path(__file__).resolve().parents[1] | |
| DEFAULT_API = ROOT / "workflows" / "voicegate_api.json" | |
| DEFAULT_UI = ROOT / "VoiceGate" / "workflows" / "VoiceGate-Workflow.json" | |
| def load_json(path: Path) -> Any: | |
| with path.open("r", encoding="utf-8") as file: | |
| return json.load(file) | |
| def is_connection(value: Any) -> bool: | |
| return ( | |
| isinstance(value, list) | |
| and len(value) == 2 | |
| and isinstance(value[0], str) | |
| and isinstance(value[1], int) | |
| ) | |
| def build_expected_connections(ui_workflow: dict[str, Any]) -> dict[str, dict[str, Any]]: | |
| nodes = ui_workflow["nodes"] | |
| links = ui_workflow["links"] | |
| nodes_by_id = {str(node["id"]): node for node in nodes} | |
| links_by_id = { | |
| link[0]: { | |
| "from": str(link[1]), | |
| "slot": link[2], | |
| "to": str(link[3]), | |
| "to_slot": link[4], | |
| "type": link[5], | |
| } | |
| for link in links | |
| } | |
| set_by_name: dict[str, dict[str, Any]] = {} | |
| for node in nodes: | |
| if node.get("type") != "SetNode": | |
| continue | |
| values = node.get("widgets_values") or [] | |
| inputs = node.get("inputs") or [] | |
| if not values or not inputs: | |
| continue | |
| link_id = inputs[0].get("link") | |
| link = links_by_id.get(link_id) | |
| if link: | |
| set_by_name[values[0]] = { | |
| "from": link["from"], | |
| "slot": link["slot"], | |
| "via_set": str(node["id"]), | |
| } | |
| def resolve_source(from_node: str, slot: int) -> dict[str, Any]: | |
| node = nodes_by_id.get(from_node) | |
| if node and node.get("type") == "GetNode": | |
| values = node.get("widgets_values") or [] | |
| name = values[0] if values else None | |
| if name in set_by_name: | |
| resolved = dict(set_by_name[name]) | |
| resolved["via_get"] = from_node | |
| resolved["name"] = name | |
| return resolved | |
| return {"from": from_node, "slot": slot} | |
| expected: dict[str, dict[str, Any]] = {} | |
| for node in nodes: | |
| for input_def in node.get("inputs") or []: | |
| link_id = input_def.get("link") | |
| if link_id is None: | |
| continue | |
| link = links_by_id.get(link_id) | |
| if not link: | |
| continue | |
| key = f"{node['id']}.{input_def['name']}" | |
| expected[key] = { | |
| **resolve_source(link["from"], link["slot"]), | |
| "type": link["type"], | |
| } | |
| return expected | |
| def validate(api_workflow: dict[str, Any], ui_workflow: dict[str, Any]) -> list[dict[str, Any]]: | |
| expected = build_expected_connections(ui_workflow) | |
| mismatches: list[dict[str, Any]] = [] | |
| for node_id, node in api_workflow.items(): | |
| for input_name, value in (node.get("inputs") or {}).items(): | |
| if not is_connection(value): | |
| continue | |
| actual = {"from": value[0], "slot": value[1]} | |
| expected_connection = expected.get(f"{node_id}.{input_name}") | |
| if ( | |
| not expected_connection | |
| or expected_connection["from"] != actual["from"] | |
| or expected_connection["slot"] != actual["slot"] | |
| ): | |
| mismatches.append( | |
| { | |
| "node": node_id, | |
| "class_type": node.get("class_type"), | |
| "input": input_name, | |
| "actual": actual, | |
| "expected": expected_connection, | |
| } | |
| ) | |
| return mismatches | |
| def parse_args() -> argparse.Namespace: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--api", type=Path, default=DEFAULT_API) | |
| parser.add_argument("--ui", type=Path, default=DEFAULT_UI) | |
| return parser.parse_args() | |
| def main() -> None: | |
| args = parse_args() | |
| api_workflow = load_json(args.api) | |
| ui_workflow = load_json(args.ui) | |
| mismatches = validate(api_workflow, ui_workflow) | |
| checked = sum( | |
| 1 | |
| for node in api_workflow.values() | |
| for value in (node.get("inputs") or {}).values() | |
| if is_connection(value) | |
| ) | |
| print(f"checked_connections={checked}") | |
| print(f"mismatches={len(mismatches)}") | |
| if mismatches: | |
| print(json.dumps(mismatches, ensure_ascii=False, indent=2)) | |
| raise SystemExit(1) | |
| if __name__ == "__main__": | |
| main() | |