- ComfyUI custom nodes: BridgeInput (11 types) + BridgeOutput
- AnyType wildcard for universal output capture
- Custom server route: GET /comfybridge/results/{prompt_id}
- Python async client (ComfyBridge) + sync wrapper (ComfyBridgeSync)
- Workflow JSON parser and patcher (deepcopy, original unchanged)
- WebSocket completion detection
- BridgeResult container with PIL/bytes helpers
- Examples: basic_text_to_image.py, agent_loop.py
- Unit tests for workflow parser and client
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
"""워크플로우 JSON 파서 및 BridgeInput 값 패처."""
|
|
|
|
import copy
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from .types import convert_value
|
|
|
|
BRIDGE_INPUT_PREFIX = "BridgeInput"
|
|
|
|
|
|
def load_workflow(source: str | dict | Path) -> dict:
|
|
"""워크플로우를 파일 경로 또는 dict로 로드한다."""
|
|
if isinstance(source, dict):
|
|
return source
|
|
path = Path(source)
|
|
with path.open("r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def find_bridge_inputs(workflow: dict) -> dict[str, tuple[str, str]]:
|
|
"""
|
|
워크플로우에서 BridgeInput 노드를 탐색한다.
|
|
|
|
Returns:
|
|
{ "input_name": (node_id, type_name) }
|
|
예: { "prompt": ("1", "STRING"), "steps": ("3", "INT") }
|
|
"""
|
|
result: dict[str, tuple[str, str]] = {}
|
|
for node_id, node in workflow.items():
|
|
class_type = node.get("class_type", "")
|
|
if class_type.startswith(BRIDGE_INPUT_PREFIX):
|
|
name = node.get("inputs", {}).get("name")
|
|
type_name = class_type[len(BRIDGE_INPUT_PREFIX):]
|
|
if name:
|
|
result[name] = (node_id, type_name)
|
|
return result
|
|
|
|
|
|
def patch_inputs(workflow: dict, inputs: dict[str, Any]) -> dict:
|
|
"""
|
|
원본 워크플로우를 deepcopy 후 BridgeInput 노드의 value를 패치한다.
|
|
원본 파일은 절대 수정하지 않는다.
|
|
|
|
Args:
|
|
workflow: ComfyUI API 포맷 워크플로우 dict
|
|
inputs: { "name": value } 주입할 값 딕셔너리
|
|
|
|
Raises:
|
|
ValueError: 워크플로우에 해당 이름의 BridgeInput 노드가 없을 때
|
|
"""
|
|
patched = copy.deepcopy(workflow)
|
|
bridge_map = find_bridge_inputs(patched)
|
|
|
|
for name, value in inputs.items():
|
|
if name not in bridge_map:
|
|
available = list(bridge_map.keys())
|
|
raise ValueError(
|
|
f"BridgeInput '{name}' not found in workflow. "
|
|
f"Available inputs: {available}"
|
|
)
|
|
node_id, type_name = bridge_map[name]
|
|
converted = convert_value(value, type_name)
|
|
patched[node_id]["inputs"]["value"] = converted
|
|
|
|
return patched
|