Files
2026-06-25 15:45:15 -04:00

102 lines
4.0 KiB
Python

import os
import json
from PIL import Image
import folder_paths
# Field separator for the flat wire format. Must be a char that never appears
# in node IDs, keys, or class names — subgraph node IDs contain ':' (e.g.
# "974:345"), so ':' can't be the delimiter. \x1f (ASCII unit separator) is safe.
SEP = "\x1f"
def _esc(val):
# Newlines are the wire-format line separator, so escape them inside values.
# Backslash first so the unescaping in JS is unambiguous.
return (
str(val)
.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
)
class WorkflowInspectorNode:
@classmethod
def INPUT_TYPES(cls):
input_dir = folder_paths.get_input_directory()
files = [
f for f in os.listdir(input_dir)
if os.path.isfile(os.path.join(input_dir, f))
]
return {
"required": {
"image": (sorted(files), {"image_upload": True}),
},
"optional": {
"selected_leaf": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("selected_text", "tree_summary")
FUNCTION = "inspect"
CATEGORY = "utils"
OUTPUT_NODE = True
@classmethod
def IS_CHANGED(cls, image, selected_leaf=""):
image_path = folder_paths.get_annotated_filepath(image)
try:
return os.path.getmtime(image_path)
except OSError:
return float("nan")
def _result(self, selected_leaf, tree_summary):
# ui dict is what ComfyUI sends to the browser (picked up by node.onExecuted).
# result tuple is what flows to downstream nodes via RETURN_TYPES.
return {"ui": {"tree_summary": [tree_summary]}, "result": (selected_leaf, tree_summary)}
def inspect(self, image, selected_leaf=""):
image_path = folder_paths.get_annotated_filepath(image)
try:
img = Image.open(image_path)
prompt_str = img.info.get("prompt") or img.info.get("parameters")
if not prompt_str:
return self._result(selected_leaf, "ERROR:No ComfyUI metadata found in this image")
prompt = json.loads(prompt_str)
except json.JSONDecodeError as e:
return self._result(selected_leaf, f"ERROR:Invalid JSON in metadata: {e}")
except Exception as e:
return self._result(selected_leaf, f"ERROR:{e}")
lines = []
try:
# Numeric IDs sort first (by value), non-numeric keys after (by string).
# Returning a uniform (group, value) tuple avoids comparing int to str,
# which blows up when the metadata mixes numeric and non-numeric keys.
node_ids = sorted(
prompt.keys(),
key=lambda x: (0, int(x), "") if str(x).isdigit() else (1, 0, str(x)),
)
for node_id in node_ids:
node_data = prompt[node_id]
class_type = node_data.get("class_type", "Unknown")
# Newer ComfyUI stores the user-assigned title here; fall back to class.
title = node_data.get("_meta", {}).get("title", class_type)
lines.append(SEP.join(["NODE", node_id, class_type, _esc(title)]))
for key, val in node_data.get("inputs", {}).items():
# ComfyUI link reference: [node_id, output_index]
if isinstance(val, list) and len(val) == 2 and isinstance(val[1], int):
lines.append(SEP.join(["LINK", node_id, key, f"→{val[0]}"]))
elif isinstance(val, list):
lines.append(SEP.join(["LEAF", node_id, key, _esc(json.dumps(val))]))
else:
lines.append(SEP.join(["LEAF", node_id, key, _esc(val)]))
except Exception as e:
return self._result(selected_leaf, f"ERROR:Tree building failed: {e}")
return self._result(selected_leaf, "\n".join(lines))