f5be04a5cb
Widget-type inputs in the optional section render as connection sockets (not editable boxes) in some ComfyUI frontends. Moved all widgets (report_dir, run_tag, reference_description, system_prompt, user_prompt, keep_loaded, auto_download) into required; only generated_image (a real node-to-node wire) stays optional. Same fix for the receptor's source_file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.6 KiB
Python
66 lines
2.6 KiB
Python
"""
|
|
Calibrator Prompt Receptor node.
|
|
|
|
The injection point for the external CLI-agent controller. The agent overrides
|
|
this node's widget values per queue via the ComfyUI HTTP API (`POST /prompt`,
|
|
override by node id), or — as a fallback — points `source_file` at a JSON file
|
|
the agent writes. Its outputs feed the T2I sampler in place of a static prompt.
|
|
|
|
This is the "receptor in ComfyUI" in the loop:
|
|
agent -> (sets prompt here) -> T2I -> Qwen3-VL Judge -> analysis -> agent
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
|
|
|
|
class CalibratorPromptReceptor:
|
|
CATEGORY = "prompt_calibrator"
|
|
FUNCTION = "emit"
|
|
RETURN_TYPES = ("STRING", "STRING", "INT")
|
|
RETURN_NAMES = ("prompt", "negative", "seed")
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"prompt": ("STRING", {"default": "", "multiline": True}),
|
|
"negative": ("STRING", {"default": "", "multiline": True}),
|
|
"seed": ("INT", {"default": 0, "min": 0, "max": 0x7FFFFFFFFFFFFFFF}),
|
|
# If set and present, a JSON file {prompt, negative, seed} overrides
|
|
# the widgets above. Lets the agent drive the loop file-first if it
|
|
# prefers that to the HTTP API. (Kept in `required` so it renders as an
|
|
# editable field, not an input socket.)
|
|
"source_file": ("STRING", {"default": ""}),
|
|
},
|
|
}
|
|
|
|
@classmethod
|
|
def IS_CHANGED(cls, prompt, negative, seed, source_file=""):
|
|
# Re-run whenever the effective inputs change: widget values (API override)
|
|
# OR the source file's mtime (file-driven mode).
|
|
mtime = ""
|
|
if source_file and os.path.isfile(source_file):
|
|
mtime = str(os.path.getmtime(source_file))
|
|
return f"{prompt}|{negative}|{seed}|{source_file}|{mtime}"
|
|
|
|
def emit(self, prompt, negative, seed, source_file=""):
|
|
if source_file and os.path.isfile(source_file):
|
|
try:
|
|
with open(source_file, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
prompt = data.get("prompt", prompt)
|
|
negative = data.get("negative", negative)
|
|
seed = int(data.get("seed", seed))
|
|
except (OSError, ValueError, json.JSONDecodeError) as e:
|
|
print(f"[CalibratorPromptReceptor] could not read {source_file}: {e}")
|
|
return (prompt, negative, int(seed))
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {"CalibratorPromptReceptor": CalibratorPromptReceptor}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"CalibratorPromptReceptor": "SxCP External Prompt (Receptor)"
|
|
}
|