Files

203 lines
6.3 KiB
Python

"""
ComfyUI Prompt Iterator Custom Nodes
This module provides two custom nodes for iterating through prompts and templates:
1. PromptIteratorNode - Iterates through multi-line prompts
2. PromptTemplateIteratorNode - Iterates through keywords and fills templates
"""
# Global counter shared between all iterator nodes
_iteration_counter = 0
# Track last content to detect changes
_last_prompt_content = ""
_last_keyword_content = ""
_last_template_content = ""
def get_counter():
"""Get the current iteration counter value."""
global _iteration_counter
return _iteration_counter
def increment_counter():
"""Increment the global iteration counter."""
global _iteration_counter
_iteration_counter += 1
return _iteration_counter
def reset_counter():
"""Reset the global iteration counter to 0."""
global _iteration_counter
_iteration_counter = 0
return _iteration_counter
def check_and_reset_if_content_changed(content, content_type="prompt"):
"""Check if content changed and reset counter if needed."""
global _iteration_counter, _last_prompt_content, _last_keyword_content, _last_template_content
if content_type == "prompt":
if content != _last_prompt_content:
_last_prompt_content = content
_iteration_counter = 0
elif content_type == "keyword":
if content != _last_keyword_content:
_last_keyword_content = content
_iteration_counter = 0
elif content_type == "template":
if content != _last_template_content:
_last_template_content = content
_iteration_counter = 0
return _iteration_counter
class PromptIteratorNode:
"""
A node that iterates through multi-line prompts, outputting one line per execution.
Uses a global counter to track the current position.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompts": ("STRING", {
"multiline": True,
"default": "prompt 1\nprompt 2\nprompt 3"
}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("prompt", "index")
FUNCTION = "iterate"
CATEGORY = "utils"
OUTPUT_NODE = False
@classmethod
def IS_CHANGED(cls, prompts):
"""Force execution on every run by returning the current counter and content."""
return (get_counter(), prompts)
def iterate(self, prompts):
"""
Iterate through prompts and return the current one based on global counter.
Counter resets automatically when prompt content changes.
Args:
prompts: Multi-line string containing prompts (one per line)
Returns:
tuple: (current_prompt, index_info)
"""
# Reset counter if prompt content changed
check_and_reset_if_content_changed(prompts, "prompt")
# Split prompts by newlines and filter out empty lines
prompt_lines = [line.strip() for line in prompts.split('\n') if line.strip()]
if not prompt_lines:
return ("", "0/0")
# Get current counter value
counter = get_counter()
# Calculate current index with wraparound
current_index = counter % len(prompt_lines)
current_prompt = prompt_lines[current_index]
# Increment counter for next execution
increment_counter()
# Return the prompt and index info
index_info = f"{current_index+1}/{len(prompt_lines)}"
return (current_prompt, index_info)
class PromptTemplateIteratorNode:
"""
A node that iterates through keywords and fills a template string.
Uses the same global counter as PromptIteratorNode.
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"keywords": ("STRING", {
"multiline": True,
"default": "cat\ndog\nbird"
}),
"template": ("STRING", {
"multiline": False,
"default": "A photo of a {keyword}"
}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("prompt", "index")
FUNCTION = "iterate"
CATEGORY = "utils"
OUTPUT_NODE = False
@classmethod
def IS_CHANGED(cls, keywords, template):
"""Force execution on every run by returning the current counter and content."""
return (get_counter(), keywords, template)
def iterate(self, keywords, template):
"""
Iterate through keywords and fill the template with the current keyword.
Counter resets automatically when keyword or template content changes.
Args:
keywords: Multi-line string containing keywords (one per line)
template: Template string with {keyword} placeholder
Returns:
tuple: (filled_template, index_info)
"""
# Reset counter if keyword or template content changed
check_and_reset_if_content_changed(keywords, "keyword")
check_and_reset_if_content_changed(template, "template")
# Split keywords by newlines and filter out empty lines
keyword_lines = [line.strip() for line in keywords.split('\n') if line.strip()]
if not keyword_lines:
return ("", "0/0")
# Get current counter value
counter = get_counter()
# Calculate current index with wraparound
current_index = counter % len(keyword_lines)
current_keyword = keyword_lines[current_index]
# Fill template with current keyword
output = template.replace("{keyword}", current_keyword)
# Increment counter for next execution
increment_counter()
# Return the filled template and index info
index_info = f"{current_index+1}/{len(keyword_lines)}"
return (output, index_info)
# Node class mappings for ComfyUI
NODE_CLASS_MAPPINGS = {
"PromptIterator": PromptIteratorNode,
"PromptTemplateIterator": PromptTemplateIteratorNode,
}
# Display name mappings for ComfyUI UI
NODE_DISPLAY_NAME_MAPPINGS = {
"PromptIterator": "Prompt Iterator",
"PromptTemplateIterator": "Prompt Template Iterator",
}