Files
Runware-ComfyUI-Runware/modules/imageInferenceSettingsStructuredPrompt.py
T

217 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Runware Image Inference Settings Structured Prompt
Builds settings.structuredPrompt for Ideogram 4.0 and compatible models.
Connect the output to Runware Image Inference Settings → structuredPrompt.
Mutually exclusive with positivePrompt at the API level.
"""
import json
from typing import Any, Dict, List, Tuple
class RunwareImageInferenceSettingsStructuredPrompt:
"""Ideogram-style structured JSON prompt for image inference settings."""
_ELEMENT_SLOTS = 8
@classmethod
def INPUT_TYPES(cls):
optional: Dict[str, tuple] = {
"background": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "compositional_deconstruction.background — setting/surface/atmosphere, not discrete subjects.",
}),
"useStyleDescription": ("BOOLEAN", {
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
"tooltip": "Include style_description from JSON below.",
}),
"style_description": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "style_description object as JSON (aesthetics, lighting, photo OR art_style, medium, color_palette). Only when 'Use Style Description' is enabled.",
}),
"useElementsJson": ("BOOLEAN", {
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
"tooltip": "Set elements from a JSON array instead of element slots 1–8.",
}),
"elementsJson": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "JSON array of element objects ({type, desc, text?, bbox?, color_palette?}). Only when 'Use Elements JSON' is enabled.",
}),
}
for i in range(1, cls._ELEMENT_SLOTS + 1):
optional[f"use_element_{i}"] = ("BOOLEAN", {
"default": False,
"label_on": "Enabled",
"label_off": "Disabled",
"tooltip": f"Include element slot {i} in compositional_deconstruction.elements.",
})
optional[f"element_type_{i}"] = (["obj", "text"], {
"default": "obj",
"tooltip": f"Element {i} type: obj (desc interpreted) or text (text rendered literally).",
})
optional[f"element_desc_{i}"] = ("STRING", {
"multiline": True,
"default": "",
"tooltip": f"Element {i} desc — natural language treatment/position notes.",
})
optional[f"element_text_{i}"] = ("STRING", {
"multiline": True,
"default": "",
"tooltip": f"Element {i} text — literal copy for type text only.",
})
optional[f"element_bbox_{i}"] = ("STRING", {
"default": "",
"tooltip": f"Element {i} optional bbox JSON: [y_min, x_min, y_max, x_max] in 0–1000 coords.",
})
optional[f"element_color_palette_{i}"] = ("STRING", {
"default": "",
"tooltip": f"Element {i} optional hex colours, comma-separated (e.g. #1F3B26,#7A5A2F).",
})
required = {
"high_level_description": ("STRING", {
"multiline": True,
"default": "",
"tooltip": "Global scene summary (required).",
}),
}
return {"required": required, "optional": optional}
RETURN_TYPES = ("RUNWAREIMAGEINFERENCESTRUCTUREDPROMPT",)
RETURN_NAMES = ("structuredPrompt",)
FUNCTION = "build_structured_prompt"
CATEGORY = "Runware"
DESCRIPTION = (
"Build settings.structuredPrompt for Ideogram 4.0 (snake_case schema) from "
"high_level_description, background, style_description, and elements. "
"Connect to Runware Image Inference Settings. Do not send positivePrompt in the same request."
)
@staticmethod
def _parse_json_object(raw: str, field_name: str) -> Dict[str, Any]:
text = (raw or "").strip()
if not text:
raise ValueError(f"{field_name} is empty.")
try:
parsed = json.loads(text)
except json.JSONDecodeError as e:
raise ValueError(f"{field_name} must be valid JSON: {e}") from e
if not isinstance(parsed, dict):
raise ValueError(f"{field_name} must be a JSON object.")
return parsed
@staticmethod
def _parse_json_array(raw: str, field_name: str) -> List[Any]:
text = (raw or "").strip()
if not text:
raise ValueError(f"{field_name} is empty.")
try:
parsed = json.loads(text)
except json.JSONDecodeError as e:
raise ValueError(f"{field_name} must be valid JSON: {e}") from e
if not isinstance(parsed, list):
raise ValueError(f"{field_name} must be a JSON array.")
return parsed
@classmethod
def _build_element_from_slot(cls, kwargs: Dict[str, Any], index: int) -> Dict[str, Any]:
el_type = str(kwargs.get(f"element_type_{index}", "obj")).strip() or "obj"
if el_type not in ("obj", "text"):
raise ValueError(f"element_type_{index} must be obj or text.")
element: Dict[str, Any] = {"type": el_type}
desc = str(kwargs.get(f"element_desc_{index}") or "").strip()
if desc:
element["desc"] = desc
if el_type == "text":
text = str(kwargs.get(f"element_text_{index}") or "").strip()
if text:
element["text"] = text
bbox_raw = str(kwargs.get(f"element_bbox_{index}") or "").strip()
if bbox_raw:
try:
bbox = json.loads(bbox_raw)
except json.JSONDecodeError as e:
raise ValueError(f"element_bbox_{index} must be valid JSON: {e}") from e
if not isinstance(bbox, list):
raise ValueError(f"element_bbox_{index} must be a JSON array.")
element["bbox"] = bbox
palette_raw = str(kwargs.get(f"element_color_palette_{index}") or "").strip()
if palette_raw:
palette = [h.strip() for h in palette_raw.split(",") if h.strip()]
if palette:
element["color_palette"] = palette
if el_type == "text" and "text" not in element and not desc:
raise ValueError(
f"Element slot {index}: type text requires element_text_{index} or element_desc_{index}."
)
if el_type == "obj" and not desc:
raise ValueError(f"Element slot {index}: type obj requires element_desc_{index}.")
return element
@classmethod
def _collect_elements(cls, kwargs: Dict[str, Any]) -> List[Dict[str, Any]]:
if kwargs.get("useElementsJson", False):
return cls._parse_json_array(
kwargs.get("elementsJson", ""),
"elementsJson",
)
elements: List[Dict[str, Any]] = []
for i in range(1, cls._ELEMENT_SLOTS + 1):
if not kwargs.get(f"use_element_{i}", False):
continue
elements.append(cls._build_element_from_slot(kwargs, i))
return elements
def build_structured_prompt(self, **kwargs) -> Tuple[Dict[str, Any]]:
hld = str(kwargs.get("high_level_description") or "").strip()
if not hld:
raise ValueError("high_level_description is required.")
structured: Dict[str, Any] = {"high_level_description": hld}
if kwargs.get("useStyleDescription", False):
style_raw = kwargs.get("style_description", "")
structured["style_description"] = self._parse_json_object(
style_raw,
"style_description",
)
comp: Dict[str, Any] = {}
background = str(kwargs.get("background") or "").strip()
if background:
comp["background"] = background
elements = self._collect_elements(kwargs)
if elements:
comp["elements"] = elements
if comp:
structured["compositional_deconstruction"] = comp
return (structured,)
NODE_CLASS_MAPPINGS = {
"RunwareImageInferenceSettingsStructuredPrompt": RunwareImageInferenceSettingsStructuredPrompt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RunwareImageInferenceSettingsStructuredPrompt": "Runware Image Inference Settings Structured Prompt",
}