Merge remote-tracking branch 'origin/main' into feature-Pruna-p-video

This commit is contained in:
Sirshendu Ganguly
2026-06-04 23:19:41 +05:30
8 changed files with 350 additions and 5 deletions
+17 -1
View File
@@ -225,7 +225,23 @@ class txt2img:
def VALIDATE_INPUTS(cls, **kwargs):
positivePrompt = kwargs.get("positivePrompt", "")
negativePrompt = kwargs.get("negativePrompt", "")
settings = kwargs.get("settings")
structured_prompt = None
if isinstance(settings, dict):
structured_prompt = settings.get("structuredPrompt")
has_structured_prompt = (
isinstance(structured_prompt, dict) and len(structured_prompt) > 0
)
has_positive_prompt = bool(
positivePrompt is not None and str(positivePrompt) != ""
)
if has_structured_prompt and has_positive_prompt:
return (
"settings.structuredPrompt and positivePrompt are mutually exclusive. "
"Clear positivePrompt or disconnect Structured Prompt from Settings."
)
# Only validate positivePrompt length if it's provided
if positivePrompt and len(positivePrompt) > 10000:
return "Positive Prompt is too long. Maximum length is 10000 characters."
@@ -0,0 +1,216 @@
"""
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",
}
+14 -3
View File
@@ -4,8 +4,8 @@ Provides settings for image generation including temperature, systemPrompt, topP
quality, background, style, search, promptExtend, editRegions, thinking (boolean),
thinkingLevel (high/medium/low), sequential, renderingSpeed (TURBO/DEFAULT/QUALITY),
magicPrompt (AUTO/ON/OFF), autoCrop, dilatePixels, creativity (raw/low/medium/high), colorPalette,
and moodboards
(from Runware Image Inference Settings Color Palette).
moodboards (from Runware Image Inference Settings Moodboards),
and structuredPrompt (from Runware Image Inference Settings Structured Prompt; Ideogram 4.0).
"""
import json
@@ -205,6 +205,9 @@ class RunwareSettings:
"moodboards": ("RUNWAREIMAGEINFERENCEMOODBOARDS", {
"tooltip": "Connect Runware Image Inference Settings Moodboards. When connected with at least one entry, it is merged into settings.moodboards.",
}),
"structuredPrompt": ("RUNWAREIMAGEINFERENCESTRUCTUREDPROMPT", {
"tooltip": "Connect Runware Image Inference Settings Structured Prompt (Ideogram 4.0). Merged into settings.structuredPrompt. Mutually exclusive with positivePrompt on the same API request.",
}),
}
}
@@ -216,7 +219,7 @@ class RunwareSettings:
"Configure general settings for image generation: temperature, system prompt, top-p, layers, quality, background, style, search, "
"promptExtend, editRegions (JSON), thinking (boolean), thinkingLevel (high/medium/low), sequential, "
"renderingSpeed (TURBO/DEFAULT/QUALITY), magicPrompt (AUTO/ON/OFF), autoCrop, dilatePixels, "
"creativity (raw/low/medium/high), and optional colorPalette and moodboards from dedicated settings nodes."
"creativity (raw/low/medium/high), and optional colorPalette, moodboards, and structuredPrompt from dedicated settings nodes."
)
def createSettings(self, **kwargs) -> tuple[Dict[str, Any]]:
@@ -309,6 +312,14 @@ class RunwareSettings:
if moodboards is not None and isinstance(moodboards, list) and len(moodboards) > 0:
settings["moodboards"] = moodboards
structured_prompt: Optional[Dict[str, Any]] = kwargs.get("structuredPrompt")
if (
structured_prompt is not None
and isinstance(structured_prompt, dict)
and len(structured_prompt) > 0
):
settings["structuredPrompt"] = structured_prompt
if useRenderingSpeed:
settings["renderingSpeed"] = str(renderingSpeed)
if useMagicPrompt: