Updated TextRandomReplacerNode and TextMultiLineRandomReplacerNode to
use BaseTextRandomReplacerNode, added a Log to Workspace to view the generated prompts
This commit is contained in:
+22
-6
@@ -1,18 +1,29 @@
|
||||
WEB_DIRECTORY = "./web"
|
||||
import os
|
||||
|
||||
# Import all independent node files safely
|
||||
from .console_log import ConsoleLogNode
|
||||
from .console_log import ConsoleLogNode, LogToWorkspace, LogToWorkspaceAdv
|
||||
from .execution_delay import ExecutionDelayNode
|
||||
from .file_loader import WildcardFileLoaderNode
|
||||
from .randomizer import MultilineRandomChanceNode
|
||||
from .text_concatenator import TextConcatenator4Node, TextConcatenator8Node
|
||||
from .text_tag_replacer import TextRandomReplacerNode, TextMultiLineRandomReplacerNode
|
||||
from .text_tag_replacer import TextRandomReplacerNode, TextMultiLineRandomReplacerNode, TextChainReplacerNode
|
||||
|
||||
# Get the exact absolute directory path of this __init__.py file
|
||||
NODE_ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# Point ComfyUI safely to your web folder
|
||||
WEB_DIRECTORY = os.path.join(NODE_ROOT, "web")
|
||||
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"MultilineRandomChanceNode": MultilineRandomChanceNode,
|
||||
"ConsoleLogNode": ConsoleLogNode,
|
||||
"LogToWorkspace": LogToWorkspace,
|
||||
"LogToWorkspaceAdv": LogToWorkspaceAdv,
|
||||
"TextRandomReplacerNode": TextRandomReplacerNode,
|
||||
"TextMultiLineRandomReplacerNode": TextMultiLineRandomReplacerNode,
|
||||
"TextChainReplacerNode": TextChainReplacerNode,
|
||||
"WildcardFileLoaderNode": WildcardFileLoaderNode,
|
||||
"ExecutionDelayNode": ExecutionDelayNode,
|
||||
"TextConcatenator4Node": TextConcatenator4Node,
|
||||
@@ -21,13 +32,18 @@ NODE_CLASS_MAPPINGS = {
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"MultilineRandomChanceNode": "🎲 Weighted Multiline Randomizer",
|
||||
"ConsoleLogNode": "🖥️ Log to Console",
|
||||
"TextRandomReplacerNode": "🏷️ Text Random Replacer",
|
||||
"TextMultiLineRandomReplacerNode": "🏷️ Text Random Replacer (MultiLine)",
|
||||
"TextChainReplacerNode": "🏷️ Text Random Replacer Chain",
|
||||
"WildcardFileLoaderNode": "📂 Wildcard File Loader",
|
||||
"ExecutionDelayNode": "⏳ Execution Delay",
|
||||
|
||||
"TextConcatenator4Node": "🧩 Text Concatenator (4-Way)",
|
||||
"TextConcatenator8Node": "🧱 Text Concatenator (8-Way)", # New visual display label
|
||||
"TextConcatenator8Node": "🧱 Text Concatenator (8-Way)",
|
||||
"ConsoleLogNode": "🖥️ Log to Console",
|
||||
"LogToWorkspace": "🖥️ Log to Workspace",
|
||||
"LogToWorkspaceAdv": "🖥️ Log to Workspace Advanced",
|
||||
|
||||
"ExecutionDelayNode": "⏳ Execution Delay",
|
||||
}
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
class ConsoleLogNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -30,3 +33,92 @@ class ConsoleLogNode:
|
||||
print(f'{yellow_start}"{text}"{color_reset}\n')
|
||||
|
||||
return (text,)
|
||||
|
||||
|
||||
|
||||
class LogToWorkspace:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}), # Accepts any incoming string
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "display_text"
|
||||
OUTPUT_NODE = True # Necessary so ComfyUI runs it even without downstream outputs
|
||||
CATEGORY = "TextRandomizer/Debug"
|
||||
|
||||
def display_text(self, text):
|
||||
# Normalize: ensure text is wrapped into a clean, uniform list structure
|
||||
if isinstance(text, list):
|
||||
ui_text_list = [str(item) for item in text]
|
||||
# Convert list back to a string format for the node's standard execution pipeline output
|
||||
result_text = "\n".join(ui_text_list)
|
||||
else:
|
||||
ui_text_list = [str(text)]
|
||||
result_text = str(text)
|
||||
|
||||
# ComfyUI's UI engine expects a uniform array of strings to iterate over on the frontend canvas
|
||||
return {"ui": {"text": ui_text_list}, "result": (result_text,)}
|
||||
|
||||
|
||||
|
||||
class LogToWorkspaceAdv:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
# Toggles to control clean up and sanitization pipelines
|
||||
"remove_empty_commas": ("BOOLEAN", {"default": True}),
|
||||
"remove_empty_lines": ("BOOLEAN", {"default": True}),
|
||||
"collapse_spaces": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "display_text"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "TextRandomizer/Debug"
|
||||
|
||||
def display_text(self, text, remove_empty_commas, remove_empty_lines, collapse_spaces):
|
||||
# 1. Standardize processing into a unified text block string
|
||||
if isinstance(text, list):
|
||||
raw_text = "\n".join([str(item) for item in text])
|
||||
else:
|
||||
raw_text = str(text)
|
||||
|
||||
# 2. RUN SANITIZATION FILTERS
|
||||
|
||||
# Scrub redundant, broken commas left behind by empty wildcards (e.g., ", ,", ",,")
|
||||
if remove_empty_commas:
|
||||
# Replaces multiple sequential commas (with optional spacing) with a single comma
|
||||
raw_text = re.sub(r',\s*,+', ',', raw_text)
|
||||
# Cleans up floating leading or trailing commas inside line strings
|
||||
raw_text = re.sub(r'^\s*,\s*|\s*,\s*$', '', raw_text, flags=re.MULTILINE)
|
||||
|
||||
# Clean repeating whitespace blocks down to tight single space blocks
|
||||
if collapse_spaces:
|
||||
raw_text = re.sub(r'[ \t]+', ' ', raw_text)
|
||||
|
||||
# Remove ghost breaks or stacked blank line carriage returns
|
||||
if remove_empty_lines:
|
||||
# Splitting lines to inspect individual strings safely
|
||||
lines = [line.strip() for line in raw_text.splitlines()]
|
||||
# Filter away completely empty arrays or spacer elements
|
||||
ui_text_list = [line for line in lines if line]
|
||||
result_text = "\n".join(ui_text_list)
|
||||
else:
|
||||
ui_text_list = raw_text.splitlines()
|
||||
result_text = raw_text
|
||||
|
||||
# Fallback safeguard layout value if everything is wiped clean
|
||||
if not ui_text_list:
|
||||
ui_text_list = [""]
|
||||
result_text = ""
|
||||
|
||||
return {"ui": {"text": ui_text_list}, "result": (result_text,)}
|
||||
|
||||
@@ -37,8 +37,6 @@ class TextConcatenator4Node:
|
||||
return ("",)
|
||||
|
||||
combined_output = separator.join(active_fragments)
|
||||
combined_output = re.sub(r"\s+", " ", combined_output)
|
||||
combined_output = re.sub(r",\s*,", ", ", combined_output)
|
||||
combined_output = combined_output.strip().rstrip(",")
|
||||
|
||||
return (combined_output.strip(),)
|
||||
@@ -106,8 +104,6 @@ class TextConcatenator8Node:
|
||||
|
||||
# 3. Assemble and apply standard punctuation/spacing cleanup passes
|
||||
combined_output = separator.join(active_fragments)
|
||||
combined_output = re.sub(r"\s+", " ", combined_output)
|
||||
combined_output = re.sub(r",\s*,", ", ", combined_output)
|
||||
combined_output = combined_output.strip().rstrip(",")
|
||||
|
||||
return (combined_output.strip(),)
|
||||
|
||||
+22
-81
@@ -2,21 +2,16 @@ import re
|
||||
|
||||
from .randomizer import MultilineRandomChanceNode
|
||||
|
||||
|
||||
class TextRandomReplacerNode:
|
||||
class TextReplacerNodeBase:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": False,
|
||||
"default": "#TAG"
|
||||
# "default": "A {professional photo|2d sketch} of a #CHARACTER in a #LOCATION, cinematic lighting.",
|
||||
},
|
||||
{"multiline": cls.MULTILINE, "default": ""}
|
||||
# "default": "A {professional photo|2d sketch} of a #CHARACTER in a #LOCATION, cinematic lighting.",
|
||||
),
|
||||
#"target_tag": ("STRING", {"default": "#CHARACTER"}),
|
||||
"target_tag": ("STRING", {"default": "#TAG"}),
|
||||
"replacement_options": (
|
||||
"STRING",
|
||||
@@ -26,7 +21,7 @@ class TextRandomReplacerNode:
|
||||
# "default": "ancient elven {wizard|mage}\ncyberpunk {hacker|netrunner} :: 2",
|
||||
},
|
||||
),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
|
||||
"seed": ("INT", {"default": 1, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,20 +93,21 @@ class TextRandomReplacerNode:
|
||||
return (final_batch_output,)
|
||||
|
||||
|
||||
class TextMultiLineRandomReplacerNode:
|
||||
class TextRandomReplacerNode(TextReplacerNodeBase):
|
||||
MULTILINE = False
|
||||
|
||||
|
||||
class TextMultiLineRandomReplacerNode(TextReplacerNodeBase):
|
||||
MULTILINE = True
|
||||
|
||||
|
||||
class TextChainReplacerNode(TextReplacerNodeBase):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": (
|
||||
"STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": ""
|
||||
# "default": "A {professional photo|2d sketch} of a #CHARACTER in a #LOCATION, cinematic lighting.",
|
||||
},
|
||||
),
|
||||
#"target_tag": ("STRING", {"default": "#CHARACTER"}),
|
||||
"input_chain": ("STRING", {"multiline": False, "default": ""}),
|
||||
"text": ("STRING", {"multiline": True, "default": ""}),
|
||||
"target_tag": ("STRING", {"default": "#TAG"}),
|
||||
"replacement_options": (
|
||||
"STRING",
|
||||
@@ -127,67 +123,12 @@ class TextMultiLineRandomReplacerNode:
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("completed_prompt",)
|
||||
FUNCTION = "replace_tag_with_random"
|
||||
FUNCTION = "chain_texts"
|
||||
CATEGORY = "TextRandomizer/Text"
|
||||
|
||||
def _process_single_string(
|
||||
self, single_text, target_tag, chosen_replacement, randomizer
|
||||
):
|
||||
"""Helper to swap the tag and expand any curly brackets in a single prompt string."""
|
||||
if target_tag and target_tag in single_text:
|
||||
text_with_tag_swapped = single_text.replace(target_tag, chosen_replacement)
|
||||
else:
|
||||
text_with_tag_swapped = single_text
|
||||
|
||||
bracket_pattern = re.compile(r"\{([^{}]+)\}")
|
||||
final_prompt = text_with_tag_swapped
|
||||
|
||||
loop_guard = 0
|
||||
while bracket_pattern.search(final_prompt) and loop_guard < 10:
|
||||
final_prompt = bracket_pattern.sub(
|
||||
randomizer._replace_brackets, final_prompt
|
||||
)
|
||||
loop_guard += 1
|
||||
|
||||
return final_prompt
|
||||
|
||||
def replace_tag_with_random(self, text, target_tag, replacement_options, seed):
|
||||
randomizer = MultilineRandomChanceNode()
|
||||
|
||||
# 1. Standardise the replacement options into a flat list of pure strings
|
||||
if isinstance(replacement_options, list):
|
||||
options_list = replacement_options
|
||||
else:
|
||||
# FIX: We unpack index [0] to extract the pure string from the randomizer tuple
|
||||
chosen_tuple = randomizer.process_random_text(replacement_options, seed)
|
||||
options_list = [chosen_tuple[0]]
|
||||
|
||||
# 2. Standardise the input template text into a list
|
||||
if isinstance(text, list):
|
||||
text_list = text
|
||||
else:
|
||||
text_list = [text]
|
||||
|
||||
final_batch_output = []
|
||||
|
||||
# 3. Zip or loop through the text templates and your batch of options
|
||||
max_batch = max(len(text_list), len(options_list))
|
||||
|
||||
for i in range(max_batch):
|
||||
single_text_item = text_list[i % len(text_list)]
|
||||
chosen_replacement = options_list[i % len(options_list)]
|
||||
|
||||
processed_item = self._process_single_string(
|
||||
single_text_item, target_tag, chosen_replacement, randomizer
|
||||
)
|
||||
final_batch_output.append(processed_item)
|
||||
|
||||
# 4. Clean up the output format for ComfyUI pipeline compliance
|
||||
if (
|
||||
len(final_batch_output) == 1
|
||||
and not isinstance(text, list)
|
||||
and not isinstance(replacement_options, list)
|
||||
):
|
||||
return (final_batch_output[0],)
|
||||
else:
|
||||
return (final_batch_output,)
|
||||
def chain_texts(self, input_chain, text, target_tag, replacement_options, seed):
|
||||
"""Helper to concatenate the input chain with the new text and replace tags."""
|
||||
# Process the input chain
|
||||
processed_chain = self.replace_tag_with_random(text, target_tag, replacement_options, seed)
|
||||
# Concatenate the processed chain and the new text
|
||||
return (f"{input_chain} {processed_chain[0]}",)
|
||||
|
||||
Reference in New Issue
Block a user