comfy_graph_bind is a visual input mapper for ComfyUI API-format workflows. It provides a Qt-based editor UI that lets you wire a configurable Start node to workflow inputs, defining how your application communicates with ComfyUI workflows.
This library supports only the ComfyUI LiteGraph API format.
- Displays the nodes and links of a ComfyUI workflow JSON.
- Nodes are read-only; only the special Start node can be the source of new links (and only its outputs are connectable).
- The Start node title is configurable via
EditorConfig.start_node_title(defaults to"Start"). - Inputs that are wired in the source JSON are visible on each node and can be the target of a drag-to-link from the Start node.
- Drag an output (left dot) to an input (right dot) to create a rope-like bezier link.
- Remove a link by dragging from a Start output and releasing over empty space — the existing link from that output is removed.
- Zoom with
Ctrl + wheel(or the dialog toolbar buttons), pan with the middle mouse button, and reset the view (positions + zoom) with the toolbarResetbutton or theRkey. - Modal
QDialogwrapper (GraphEditorDialog) that hosts the editor with its own toolbar and Ok/Cancel buttons. The dialog can be reopened with a previously obtained result to preload the Start-node links.
git clone https://github.com/dacert/comfy_graph_bind.git
cd comfy_graph_bind
pip install -e .import json
from PyQt5.QtWidgets import QApplication
from comfy_graph_bind import (
EditorConfig,
GraphEditorDialog,
StartOutputConfig,
parse_workflow_json,
)
app = QApplication([])
graph = parse_workflow_json("path/to/workflow.json") # returns a WorkflowGraph
dialog = GraphEditorDialog(
graph,
EditorConfig(
workflow_name="My workflow",
start_node_title="Inputs", # optional, defaults to "Start"
start_outputs=[
StartOutputConfig("prompt", "STRING"),
StartOutputConfig("seed", "INT"),
StartOutputConfig("image", "IMAGE"),
],
),
)
dialog.exec_()
result = dialog.editor_result() # None if the user cancelled
if result is not None:
print(json.dumps(result, indent=2))
# {
# "workflow_name": "My workflow",
# "inputs": {
# "prompt": {"node_id": "97", "property": "prompt"},
# "seed": {"node_id": "3", "property": "seed"},
# "image": {"node_id": "109","property": "image"},
# },
# }Alternatively, use from_api_workflow with a raw API dict parsed from any
source (file, clipboard, network, …):
import json
from pathlib import Path
from PyQt5.QtWidgets import QApplication
from comfy_graph_bind import EditorConfig, GraphEditorDialog, StartOutputConfig
app = QApplication([])
raw_api = json.loads(Path("path/to/workflow.json").read_text())
dialog = GraphEditorDialog.from_api_workflow(
raw_api,
EditorConfig(
workflow_name="My workflow",
start_outputs=[StartOutputConfig("prompt", "STRING")],
),
)
dialog.exec_()
result = dialog.editor_result()GraphEditor is also available as a headless widget (no top-level
window) you can embed in your own QMainWindow, QDialog or any other
container.
import json
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QPlainTextEdit,
QPushButton,
QVBoxLayout,
QWidget,
)
from comfy_graph_bind import EditorConfig, GraphEditorDialog, StartOutputConfig
class Host(QMainWindow):
def __init__(self, path: str) -> None:
super().__init__()
self._path = path
self._cfg = EditorConfig(
workflow_name=path.rsplit("/", 1)[-1],
start_outputs=[
StartOutputConfig("prompt", "STRING"),
StartOutputConfig("seed", "INT"),
],
)
central = QWidget(self)
self.setCentralWidget(central)
layout = QVBoxLayout(central)
self._button = QPushButton("Open graph editor\u2026", central)
self._textarea = QPlainTextEdit(central)
self._textarea.setReadOnly(True)
layout.addWidget(self._button)
layout.addWidget(self._textarea, 1)
self._button.clicked.connect(self._open)
def _open(self) -> None:
text = self._textarea.toPlainText().strip()
initial = json.loads(text) if text else None
with open(self._path) as f:
raw_api = json.load(f)
dialog = GraphEditorDialog.from_api_workflow(
raw_api,
self._cfg,
initial_result=initial,
parent=self,
)
dialog.exec_()
result = dialog.editor_result() # None if the user cancelled
if result is not None:
self._textarea.setPlainText(json.dumps(result, indent=2))
app = QApplication([])
window = Host("path/to/workflow.json")
window.show()
app.exec_()When initial_result is provided, the dialog preloads the Start-node links
so the user can immediately see the existing wiring. Any entry that does
not resolve to a known node/property is silently ignored.
A runnable version of this example lives at
examples/dialog_demo.py.
Used in the krita_comfyui plugin https://github.com/dacert/krita-comfyui
