From 69e2740c6b3a3876bac6d916f76b3b463cb78a9b Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sat, 16 May 2026 01:17:55 +0200 Subject: [PATCH 01/31] Change addon to v2 with more inputs --- src/nodes/prompt_preset_selector.py | 206 +++++++++++++++++++++------- 1 file changed, 159 insertions(+), 47 deletions(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index 88fa8e0..ad1c753 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -5,11 +5,15 @@ from .. import config class PromptPresetSelector: """ - Selects a variant from inline prompt blocks of the form: - {% option1 | option2 | option3 %} + Selects a variant from inline prompt blocks. - The selected option is injected based on the chosen preset index. - Preset indices start at 1. + Syntax: {open_tag} option_a | option_b | {close_tag} + Default: {% option_a | option_b %} + + Rules: + - All blocks must have the same number of options. + - Empty presets are allowed: {% | something %} -> ("", "something") + - Index is 0-based. """ CATEGORY = config.NODE_CATEGORY @@ -18,72 +22,180 @@ class PromptPresetSelector: def INPUT_TYPES(cls): return { "required": { - "separator": ( + "text": ( "STRING", { - "default": "|", - "tooltip": "Preset separator character", + "multiline": True, + "tooltip": "Text with preset blocks.", }, ), "preset_index": ( "INT", { - "default": 1, - "min": 1, - "max": 50, + "default": 0, + "min": 0, + "max": 99, "step": 1, + "tooltip": "0-based index of the preset to select.", + }, + ), + "open_tag": ( + "STRING", + { + "default": "{%", + "tooltip": "Opening tag for preset blocks.", + }, + ), + "close_tag": ( + "STRING", + { + "default": "%}", + "tooltip": "Closing tag for preset blocks.", + }, + ), + "separator": ( + "STRING", + { + "default": "|", + "tooltip": "Separator between presets. Spaces around it are mandatory.", + }, + ), + "on_error": ( + ["strict", "clamp", "empty"], + { + "default": "strict", + "tooltip": ( + "strict: stops the workflow on error. " + "clamp: uses the last available preset. " + "empty: replaces the block with an empty string." + ), + }, + ), + "cleanup": ( + "BOOLEAN", + { + "default": True, + "tooltip": "Clean up double commas and blank lines left by empty presets.", }, ), - "cleanup": ("BOOLEAN", {"default": True}), - "text": ("STRING", {"multiline": True}), }, } - RETURN_TYPES = ("STRING", "INT") - RETURN_NAMES = ("prompt", "preset_index") + RETURN_TYPES = ("STRING", "INT", "INT") + RETURN_NAMES = ("text", "preset_index", "preset_count") FUNCTION = "process" - TEMPLATE_PATTERN = r"{%(.*?)%}" - @classmethod - def VALIDATE_INPUTS(cls, separator, preset_index, cleanup, text): - if not separator: - return "Separator cannot be empty" - - blocks = re.findall(cls.TEMPLATE_PATTERN, text, re.DOTALL) - - expected_count = None - for block in blocks: - parts = [p.strip() for p in block.split(separator)] - if expected_count is None: - expected_count = len(parts) - elif len(parts) != expected_count: - return "All preset blocks must have the same number of options" - - if preset_index > len(parts): - return f"Preset index {preset_index} out of range (max {len(parts)})" - + def VALIDATE_INPUTS(cls, open_tag, close_tag, separator, **kwargs): + if not open_tag or not open_tag.strip(): + return "open_tag cannot be empty" + if not close_tag or not close_tag.strip(): + return "close_tag cannot be empty" + if not separator or not separator.strip(): + return "separator cannot be empty" + if open_tag == close_tag: + return "open_tag and close_tag must be different" return True - def process(self, separator, preset_index, cleanup, text): - def replace_block(match: re.Match[str]) -> str: - content = match.group(1) - parts = [p.strip() for p in content.split(separator)] + def _build_pattern(self, open_tag: str, close_tag: str) -> str: + """Build the regex pattern from the configured tags.""" + return re.escape(open_tag) + r"\s*(.*?)\s*" + re.escape(close_tag) - # index is 1-based - return parts[preset_index - 1] + def _parse_blocks(self, text: str, pattern: str, separator: str) -> list[list[str]]: + """Return all blocks as a list of option lists.""" + raw_blocks = re.findall(pattern, text, re.DOTALL) + return [ + [p.strip() for p in block.strip().split(separator)] for block in raw_blocks + ] - def clean_prompt(text: str) -> str: - text = re.sub(r"\s+,", ",", text) # Delete spaces before comas - text = re.sub(r",\s*,", ",", text) # Delete double or empty comas - text = re.sub(r"\n\s*\n", "\n", text) # Delete empty lines - return text.strip() + def _validate( + self, + blocks: list[list[str]], + preset_index: int, + on_error: str, + ) -> str | None: + """ + Validate blocks consistency. + Returns an error message string if strict mode and an error is found, else None. + """ + if not blocks: + return None + + counts = [len(b) for b in blocks] + min_count = min(counts) + max_count = max(counts) + + if min_count != max_count: + msg = ( + f"PromptPresetSelector: blocks have inconsistent option counts " + f"(found between {min_count} and {max_count})." + ) + if on_error == "strict": + raise ValueError(msg) + + if preset_index >= max_count: + msg = ( + f"PromptPresetSelector: preset_index {preset_index} is out of range " + f"(max index is {max_count - 1})." + ) + if on_error == "strict": + raise ValueError(msg) + + return None + + def _replace_blocks( + self, + text: str, + pattern: str, + separator: str, + preset_index: int, + on_error: str, + ) -> str: + """Replace each block with the selected option.""" + + def replace_block(match: re.Match) -> str: + parts = [opt.strip() for opt in match.group(1).split(separator)] + count = len(parts) + + if preset_index >= count: + if on_error == "clamp": + return parts[-1] + else: # empty + return "" + + return parts[preset_index] + + return re.sub(pattern, replace_block, text, flags=re.DOTALL) + + def _clean_prompt(self, text: str) -> str: + text = re.sub(r" +,", ",", text) # spaces before commas + text = re.sub(r",\s*,", ",", text) # double or empty commas + text = re.sub(r"\n\s*\n", "\n", text) # empty lines + return text.strip() + + def process( + self, + text: str, + preset_index: int, + open_tag: str, + close_tag: str, + separator: str, + on_error: str, + cleanup: bool, + ) -> tuple[str, int, int]: + pattern = self._build_pattern(open_tag, close_tag) + blocks = self._parse_blocks(text, pattern, separator) + + self._validate(blocks, preset_index, on_error) + + preset_count = max((len(b) for b in blocks), default=0) + + result = self._replace_blocks(text, pattern, separator, preset_index, on_error) - result = re.sub(self.TEMPLATE_PATTERN, replace_block, text, flags=re.DOTALL) if cleanup: - result = clean_prompt(result) + result = self._clean_prompt(result) - return (result, preset_index) + return (result, preset_index, preset_count) NODE_CLASS_MAPPINGS = { From 9b6c53e7aff5600ce1ec0ee2b6c8ca8cb285fa39 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sat, 16 May 2026 01:18:06 +0200 Subject: [PATCH 02/31] Add tests --- tests/__init__.py | 0 tests/test_prompt_preset_selector.py | 149 +++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/test_prompt_preset_selector.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_prompt_preset_selector.py b/tests/test_prompt_preset_selector.py new file mode 100644 index 0000000..546aa06 --- /dev/null +++ b/tests/test_prompt_preset_selector.py @@ -0,0 +1,149 @@ +import pytest +from src.nodes.prompt_preset_selector import PromptPresetSelector + + +@pytest.fixture +def node(): + return PromptPresetSelector() + + +def run(node, text, index=0, on_error="strict", cleanup=False): + return node.process( + text=text, + preset_index=index, + open_tag="{%", + close_tag="%}", + separator="|", + on_error=on_error, + cleanup=cleanup, + ) + + +# --- Base cases --- + + +def test_simple_index_0(node): + text, idx, count = run(node, "I am {% happy | sad %} today") + assert text == "I am happy today" + assert idx == 0 + assert count == 2 + + +def test_simple_index_1(node): + text, _, _ = run(node, "I am {% happy | sad %} today", index=1) + assert text == "I am sad today" + + +def test_empty_preset_first(node): + text, _, count = run(node, "I am {% | sad %} today", index=0) + assert text == "I am today" + assert count == 2 + + +def test_empty_preset_second(node): + text, _, _ = run(node, "I am {% happy | %} today", index=1) + assert text == "I am today" + + +def test_multiple_blocks_same_index(node): + text, _, count = run(node, "{% a | b %} and {% c | d %}", index=1) + assert text == "b and d" + assert count == 2 + + +def test_no_blocks(node): + text, idx, count = run(node, "clean text") + assert text == "clean text" + assert count == 0 + + +def test_three_presets(node): + text, _, count = run(node, "{% a | b | c %}", index=2) + assert text == "c" + assert count == 3 + + +# --- Mode strict --- + + +def test_strict_index_out_of_range(node): + with pytest.raises(ValueError, match="out of range"): + run(node, "{% a | b %}", index=5, on_error="strict") + + +def test_strict_inconsistent_counts(node): + with pytest.raises(ValueError, match="inconsistent"): + run(node, "{% a | b %} and {% c | d | e %}", on_error="strict") + + +# --- Mode clamp --- + + +def test_clamp_index_out_of_range(node): + text, _, _ = run(node, "{% a | b %}", index=5, on_error="clamp") + assert text == "b" + + +def test_clamp_inconsistent_uses_last(node): + # bloc 1 with 2 options, bloc 2 with 3 — index 2 clamp on bloc 1 + text, _, _ = run(node, "{% a | b %} and {% c | d | e %}", index=2, on_error="clamp") + assert text == "b and e" + + +# --- Mode empty --- + + +def test_empty_index_out_of_range(node): + text, _, _ = run(node, "{% a | b %}", index=5, on_error="empty") + assert text == "" + + +# --- Custom tags / separator --- + + +def test_custom_tags(node): + result, _, _ = node.process( + text="I am << happy / sad >> today", + preset_index=1, + open_tag="<<", + close_tag=">>", + separator="/", + on_error="strict", + cleanup=False, + ) + assert result == "I am sad today" + + +# --- Cleanup --- + + +def test_cleanup_double_comma(node): + text, _, _ = run(node, "tag1, {% a | %}, tag2", index=1, cleanup=True) + assert text == "tag1, tag2" + + +# --- VALIDATE_INPUTS --- + + +def test_validate_empty_separator(): + result = PromptPresetSelector.VALIDATE_INPUTS( + open_tag="{%", + close_tag="%}", + separator="", + on_error="strict", + cleanup=True, + text="", + ) + assert result != True + + +def test_validate_same_tags(): + result = PromptPresetSelector.VALIDATE_INPUTS( + open_tag="%%", + close_tag="%%", + separator="|", + on_error="strict", + cleanup=True, + text="", + ) + assert result != True From 7834e074d8ef2d44bdf40ca8937704d2ba7627e0 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 00:52:34 +0200 Subject: [PATCH 03/31] Simplify node by grouping open_tag, separator, and close_tag under syntax ; remove on_error, strict mode is inforced --- src/nodes/prompt_preset_selector.py | 104 ++++++++++----------------- tests/test_prompt_preset_selector.py | 62 +++------------- 2 files changed, 46 insertions(+), 120 deletions(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index ad1c753..855ff0e 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -22,6 +22,13 @@ class PromptPresetSelector: def INPUT_TYPES(cls): return { "required": { + "syntax": ( + "STRING", + { + "default": "{% | %}", + "tooltip": "Format: open_tag separator close_tag. Ex: {% | %}", + }, + ), "text": ( "STRING", { @@ -39,38 +46,6 @@ class PromptPresetSelector: "tooltip": "0-based index of the preset to select.", }, ), - "open_tag": ( - "STRING", - { - "default": "{%", - "tooltip": "Opening tag for preset blocks.", - }, - ), - "close_tag": ( - "STRING", - { - "default": "%}", - "tooltip": "Closing tag for preset blocks.", - }, - ), - "separator": ( - "STRING", - { - "default": "|", - "tooltip": "Separator between presets. Spaces around it are mandatory.", - }, - ), - "on_error": ( - ["strict", "clamp", "empty"], - { - "default": "strict", - "tooltip": ( - "strict: stops the workflow on error. " - "clamp: uses the last available preset. " - "empty: replaces the block with an empty string." - ), - }, - ), "cleanup": ( "BOOLEAN", { @@ -86,17 +61,30 @@ class PromptPresetSelector: FUNCTION = "process" @classmethod - def VALIDATE_INPUTS(cls, open_tag, close_tag, separator, **kwargs): - if not open_tag or not open_tag.strip(): - return "open_tag cannot be empty" - if not close_tag or not close_tag.strip(): - return "close_tag cannot be empty" - if not separator or not separator.strip(): - return "separator cannot be empty" + def VALIDATE_INPUTS(cls, syntax, **kwargs): + parts = syntax.split() + if len(parts) != 3: + return ( + f"Invalid syntax '{syntax}'. Expected: 'open_tag separator close_tag'" + ) + open_tag, _, close_tag = parts if open_tag == close_tag: return "open_tag and close_tag must be different" return True + def _parse_syntax(self, syntax: str) -> tuple[str, str, str]: + """ + Parse the syntax string into (open_tag, separator, close_tag). + Expected format: 'open_tag separator close_tag', ex: '{% | %}' + """ + parts = syntax.split() + if len(parts) != 3: + raise ValueError( + f"PromptPresetSelector: invalid syntax '{syntax}'. " + "Expected format: 'open_tag separator close_tag', ex: '{% | %}'" + ) + return parts[0], parts[1], parts[2] + def _build_pattern(self, open_tag: str, close_tag: str) -> str: """Build the regex pattern from the configured tags.""" return re.escape(open_tag) + r"\s*(.*?)\s*" + re.escape(close_tag) @@ -108,15 +96,10 @@ class PromptPresetSelector: [p.strip() for p in block.strip().split(separator)] for block in raw_blocks ] - def _validate( - self, - blocks: list[list[str]], - preset_index: int, - on_error: str, - ) -> str | None: + def _validate(self, blocks: list[list[str]], preset_index: int) -> str | None: """ Validate blocks consistency. - Returns an error message string if strict mode and an error is found, else None. + Raises an error message string if an error is found, else None. """ if not blocks: return None @@ -126,20 +109,16 @@ class PromptPresetSelector: max_count = max(counts) if min_count != max_count: - msg = ( + raise ValueError( f"PromptPresetSelector: blocks have inconsistent option counts " f"(found between {min_count} and {max_count})." ) - if on_error == "strict": - raise ValueError(msg) if preset_index >= max_count: - msg = ( + raise ValueError( f"PromptPresetSelector: preset_index {preset_index} is out of range " f"(max index is {max_count - 1})." ) - if on_error == "strict": - raise ValueError(msg) return None @@ -149,20 +128,11 @@ class PromptPresetSelector: pattern: str, separator: str, preset_index: int, - on_error: str, ) -> str: """Replace each block with the selected option.""" def replace_block(match: re.Match) -> str: parts = [opt.strip() for opt in match.group(1).split(separator)] - count = len(parts) - - if preset_index >= count: - if on_error == "clamp": - return parts[-1] - else: # empty - return "" - return parts[preset_index] return re.sub(pattern, replace_block, text, flags=re.DOTALL) @@ -175,22 +145,20 @@ class PromptPresetSelector: def process( self, + syntax: str, text: str, preset_index: int, - open_tag: str, - close_tag: str, - separator: str, - on_error: str, cleanup: bool, ) -> tuple[str, int, int]: + open_tag, separator, close_tag = self._parse_syntax(syntax) + pattern = self._build_pattern(open_tag, close_tag) blocks = self._parse_blocks(text, pattern, separator) - self._validate(blocks, preset_index, on_error) + self._validate(blocks, preset_index) preset_count = max((len(b) for b in blocks), default=0) - - result = self._replace_blocks(text, pattern, separator, preset_index, on_error) + result = self._replace_blocks(text, pattern, separator, preset_index) if cleanup: result = self._clean_prompt(result) diff --git a/tests/test_prompt_preset_selector.py b/tests/test_prompt_preset_selector.py index 546aa06..9bdc36d 100644 --- a/tests/test_prompt_preset_selector.py +++ b/tests/test_prompt_preset_selector.py @@ -7,14 +7,11 @@ def node(): return PromptPresetSelector() -def run(node, text, index=0, on_error="strict", cleanup=False): +def run(node, text, index=0, cleanup=False): return node.process( + syntax="{% | %}", text=text, preset_index=index, - open_tag="{%", - close_tag="%}", - separator="|", - on_error=on_error, cleanup=cleanup, ) @@ -63,39 +60,17 @@ def test_three_presets(node): assert count == 3 -# --- Mode strict --- +# --- Error raised --- -def test_strict_index_out_of_range(node): +def test_index_out_of_range(node): with pytest.raises(ValueError, match="out of range"): - run(node, "{% a | b %}", index=5, on_error="strict") + run(node, "{% a | b %}", index=5) -def test_strict_inconsistent_counts(node): +def test_inconsistent_counts(node): with pytest.raises(ValueError, match="inconsistent"): - run(node, "{% a | b %} and {% c | d | e %}", on_error="strict") - - -# --- Mode clamp --- - - -def test_clamp_index_out_of_range(node): - text, _, _ = run(node, "{% a | b %}", index=5, on_error="clamp") - assert text == "b" - - -def test_clamp_inconsistent_uses_last(node): - # bloc 1 with 2 options, bloc 2 with 3 — index 2 clamp on bloc 1 - text, _, _ = run(node, "{% a | b %} and {% c | d | e %}", index=2, on_error="clamp") - assert text == "b and e" - - -# --- Mode empty --- - - -def test_empty_index_out_of_range(node): - text, _, _ = run(node, "{% a | b %}", index=5, on_error="empty") - assert text == "" + run(node, "{% a | b %} and {% c | d | e %}") # --- Custom tags / separator --- @@ -103,12 +78,9 @@ def test_empty_index_out_of_range(node): def test_custom_tags(node): result, _, _ = node.process( + syntax="<< / >>", text="I am << happy / sad >> today", preset_index=1, - open_tag="<<", - close_tag=">>", - separator="/", - on_error="strict", cleanup=False, ) assert result == "I am sad today" @@ -126,24 +98,10 @@ def test_cleanup_double_comma(node): def test_validate_empty_separator(): - result = PromptPresetSelector.VALIDATE_INPUTS( - open_tag="{%", - close_tag="%}", - separator="", - on_error="strict", - cleanup=True, - text="", - ) + result = PromptPresetSelector.VALIDATE_INPUTS(syntax="{% %}") assert result != True def test_validate_same_tags(): - result = PromptPresetSelector.VALIDATE_INPUTS( - open_tag="%%", - close_tag="%%", - separator="|", - on_error="strict", - cleanup=True, - text="", - ) + result = PromptPresetSelector.VALIDATE_INPUTS(syntax="% | %") assert result != True From ca0456272d219edfd2ced3f2b92f7bd75c6c241c Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 13:41:18 +0200 Subject: [PATCH 04/31] Rework js file with new version --- web/prompt_color_editor.js | 145 ------------------------ web/prompt_preset_selector.js | 206 ++++++++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+), 145 deletions(-) delete mode 100644 web/prompt_color_editor.js create mode 100644 web/prompt_preset_selector.js diff --git a/web/prompt_color_editor.js b/web/prompt_color_editor.js deleted file mode 100644 index 8814b87..0000000 --- a/web/prompt_color_editor.js +++ /dev/null @@ -1,145 +0,0 @@ -import { app } from "/scripts/app.js"; - -// Escape HTML to prevent injections -const escapeHtml = (str) => - str.replace(/[&<>"]/g, c => ({ - "&": "&", - "<": "<", - ">": ">", - '"': """ - }[c])); - -// Hook into ComfyUI widget callback (reliable change detection) -const hookWidget = (widget, onChange) => { - if (!widget) return; - - const originalCallback = widget.callback; - widget.callback = function (value) { - if (originalCallback) { - originalCallback.call(this, value); - } - onChange(value); - }; -}; - -// Main function to attach the custom editor to the node -function attachEditor(node) { - // Only apply to PromptPresetSelector custom node - if (node.type !== "PromptPresetSelector") return; - - const textWidget = node.widgets?.find(w => w.name === "text"); - const separatorWidget = node.widgets?.find(w => w.name === "separator"); - const presetWidget = node.widgets?.find(w => w.name === "preset_index"); - if (!textWidget) return; - - const textarea = textWidget.inputEl || textWidget.element; - if (!textarea || !textarea.parentNode) return; - - // Prevent double initialization - if (textarea._presetEditorAttached) return; - textarea._presetEditorAttached = true; - - // Hide the original textarea - textarea.style.display = "none"; - - // Create a contentEditable div for rich coloring - const editor = document.createElement("div"); - editor.contentEditable = true; - editor.style.cssText = ` - height: 100%; - padding: 6px; - border: 1px solid #444; - background: #111; - color: #eee; - font-family: monospace; - font-size: 10px; - white-space: pre-wrap; - overflow-y: auto; - outline: none; - box-sizing: border-box; - `; - textarea.parentNode.appendChild(editor); - editor.innerText = textarea.value; - - // Used colors - const tagColor = "#ff9800"; - const inactiveColor = "#777"; - - let lastRawText = textarea.value; - - // Text recolor function - const recolor = (force = false) => { - const raw = editor.innerText; - // Skip if no tags found - if (!raw.includes("{%")) return; - - // Prevent caret jump while typing - if (!force && document.activeElement === editor) return; - if (!force && raw !== lastRawText) return; - - const separator = separatorWidget?.value || "|"; - const presetIndex = Math.max(1, presetWidget?.value || 1) - 1; - - const safe = escapeHtml(raw); - - editor.innerHTML = safe.replace(/\{%(.*?)%\}/gs, (_, content) => { - const rawParts = content.split(separator); - - const coloredParts = rawParts.map((part, i) => { - const color = i === presetIndex ? "inherit" : inactiveColor; - return `${escapeHtml(part)}`; - }); - - const sepHtml = `${escapeHtml(separator)}`; - - return `{%` - + coloredParts.join(sepHtml) - + `%}` - ; - }); - }; - - // Sync editor with textarea - editor.addEventListener("input", () => { - const raw = editor.innerText; - textarea.value = raw; - lastRawText = raw; - textarea.dispatchEvent(new Event("input", { bubbles: true })); - }); - - // Raw text while editing - editor.addEventListener("focus", () => { - editor.innerText = textarea.value; - }); - - // Colored mode when leaving editor - editor.addEventListener("blur", () => recolor(true)); - - // React instantly to widget changes - hookWidget(separatorWidget, () => recolor(true)); - hookWidget(presetWidget, () => recolor(true)); - - // Initial render - recolor(true); -} - -// Register the extension -app.registerExtension({ - name: "PromptPresetColorEditor", - beforeRegisterNodeDef(nodeType) { - nodeType.prototype.onNodeCreated = function () { - const node = this; - - const tryAttach = () => { - attachEditor(node); - - // Retry until widgets DOM is ready - if (!node.widgets?.find(w => w.name === "text")?.inputEl?.parentNode) { - requestAnimationFrame(tryAttach); - } - }; - - requestAnimationFrame(tryAttach); - }; - } -}); diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js new file mode 100644 index 0000000..885eda5 --- /dev/null +++ b/web/prompt_preset_selector.js @@ -0,0 +1,206 @@ +import { app } from "/scripts/app.js"; + +// --- Helpers --- + +// Escape HTML to prevent injections +const escapeHtml = (str) => + str.replace( + /[&<>"]/g, + (c) => + ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + })[c], + ); + +const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + +// Hook into ComfyUI widget callback (reliable change detection) +const hookWidget = (widget, onChange) => { + if (!widget) return; + const original = widget.callback; + widget.callback = function (value) { + if (original) original.call(this, value); + onChange(value); + }; +}; + +// Parse the syntax param to { openTag, separator, closeTag } +const parseSyntax = (syntax) => { + const parts = (syntax || "{% | %}").split(/\s+/); + if (parts.length !== 3) + return { openTag: "{%", separator: "|", closeTag: "%}" }; + return { openTag: parts[0], separator: parts[1], closeTag: parts[2] }; +}; + +// --- Colors --- + +const COLORS = { + tag: "#ff9800", + sep: "#ff9800", + active: "#e0e0e0", + inactive: "#555", +}; + +// --- Editor --- + +function buildHighlightedHtml(raw, syntax, presetIndex) { + const { openTag, separator, closeTag } = parseSyntax(syntax); + const pattern = new RegExp( + escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), + "gs", + ); + + let result = ""; + let lastIndex = 0; + + for (const match of raw.matchAll(pattern)) { + // Text before the match, escaped + result += escapeHtml(raw.slice(lastIndex, match.index)); + + const parts = match[1].split(separator); + + const coloredParts = parts.map((part, i) => { + const color = i === presetIndex ? COLORS.active : COLORS.inactive; + return `${escapeHtml(part)}`; + }); + + const sepHtml = `${escapeHtml(separator)}`; + + result += + `${escapeHtml(openTag)} ` + + coloredParts.join(sepHtml) + + ` ${escapeHtml(closeTag)}`; + + lastIndex = match.index + match[0].length; + } + + // Remaining text after last match + result += escapeHtml(raw.slice(lastIndex)); + + return result; +} + +// Main function to attach the custom editor to the node +function attachEditor(node) { + // Only apply to PromptPresetSelector custom node + if (node.type !== "PromptPresetSelector") return; + + const textWidget = node.widgets?.find((w) => w.name === "text"); + const syntaxWidget = node.widgets?.find((w) => w.name === "syntax"); + const presetWidget = node.widgets?.find((w) => w.name === "preset_index"); + + if (!textWidget) return; + + const textarea = textWidget.inputEl || textWidget.element; + if (!textarea?.parentNode) return; + + // Prevent double initialization + if (textarea._editorAttached) return; + textarea._editorAttached = true; + + // Hide native textarea + textarea.style.display = "none"; + + // Create contentEditable div + const editor = document.createElement("div"); + editor.contentEditable = "true"; + editor.spellcheck = false; + editor.style.cssText = ` + width: 100%; + height: 100%; + padding: 6px; + border: 1px solid #444; + border-radius: 4px; + background: #111; + color: ${COLORS.active}; + font-family: monospace; + font-size: 10px; + white-space: pre-wrap; + overflow-y: auto; + outline: none; + box-sizing: border-box; + cursor: text; + line-height: 1.5; + `; + + textarea.parentNode.appendChild(editor); + + // State helpers + const getPresetIndex = () => presetWidget?.value ?? 0; + const getSyntax = () => syntaxWidget?.value ?? "{% | %}"; + + // Render modes + const renderColored = () => { + const raw = textarea.value; + editor.innerHTML = buildHighlightedHtml( + raw, + getSyntax(), + getPresetIndex(), + ); + }; + + const renderRaw = () => { + editor.innerText = textarea.value; + }; + + // --- Event listener - Input: Sync editor -> textarea --- + editor.addEventListener("input", () => { + textarea.value = editor.innerText; + textarea.dispatchEvent(new Event("input", { bubbles: true })); + }); + + // --- Event listener - Focus: Switch to raw text for editing --- + editor.addEventListener("focus", () => { + renderRaw(); + // Place caret at end + const range = document.createRange(); + range.selectNodeContents(editor); + range.collapse(false); + const sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + }); + + // --- Event listener - Blur: Switch to colored view --- + editor.addEventListener("blur", () => renderColored()); + + // React to widget changes + hookWidget(syntaxWidget, () => { + if (document.activeElement !== editor) renderColored(); + }); + hookWidget(presetWidget, () => { + if (document.activeElement !== editor) renderColored(); + }); + + // Initial render + renderColored(); +} + +// --- Registration --- + +app.registerExtension({ + name: "PromptPresetSelector", + beforeRegisterNodeDef(nodeType, nodeData) { + if (nodeData.name !== "PromptPresetSelector") return; + + const original = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + if (original) original.call(this); + + const node = this; + // Retry until widgets DOM is ready + const tryAttach = () => { + const textWidget = node.widgets?.find((w) => w.name === "text"); + if (textWidget?.inputEl?.parentNode) { + attachEditor(node); + } else { + requestAnimationFrame(tryAttach); + } + }; + requestAnimationFrame(tryAttach); + }; + }, +}); From 37863c8560b7d9ee3f5b96dde7d4043610b81695 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 13:41:34 +0200 Subject: [PATCH 05/31] Move preset_index field above text --- src/nodes/prompt_preset_selector.py | 16 ++++++++-------- tests/test_prompt_preset_selector.py | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index 855ff0e..8317087 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -29,13 +29,6 @@ class PromptPresetSelector: "tooltip": "Format: open_tag separator close_tag. Ex: {% | %}", }, ), - "text": ( - "STRING", - { - "multiline": True, - "tooltip": "Text with preset blocks.", - }, - ), "preset_index": ( "INT", { @@ -46,6 +39,13 @@ class PromptPresetSelector: "tooltip": "0-based index of the preset to select.", }, ), + "text": ( + "STRING", + { + "multiline": True, + "tooltip": "Text with preset blocks.", + }, + ), "cleanup": ( "BOOLEAN", { @@ -146,8 +146,8 @@ class PromptPresetSelector: def process( self, syntax: str, - text: str, preset_index: int, + text: str, cleanup: bool, ) -> tuple[str, int, int]: open_tag, separator, close_tag = self._parse_syntax(syntax) diff --git a/tests/test_prompt_preset_selector.py b/tests/test_prompt_preset_selector.py index 9bdc36d..0b13033 100644 --- a/tests/test_prompt_preset_selector.py +++ b/tests/test_prompt_preset_selector.py @@ -10,8 +10,8 @@ def node(): def run(node, text, index=0, cleanup=False): return node.process( syntax="{% | %}", - text=text, preset_index=index, + text=text, cleanup=cleanup, ) @@ -79,8 +79,8 @@ def test_inconsistent_counts(node): def test_custom_tags(node): result, _, _ = node.process( syntax="<< / >>", - text="I am << happy / sad >> today", preset_index=1, + text="I am << happy / sad >> today", cleanup=False, ) assert result == "I am sad today" From 3edaff5d21c9d0cac4188a7cc51b505afe3d5a0c Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 13:41:50 +0200 Subject: [PATCH 06/31] Update README with v2 --- README.md | 81 ++++++++++++++---------- docs/images/PromptPresetSelector_v2.png | Bin 0 -> 89395 bytes 2 files changed, 49 insertions(+), 32 deletions(-) create mode 100644 docs/images/PromptPresetSelector_v2.png diff --git a/README.md b/README.md index f814dfe..3f648cc 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ A collection of custom nodes for ComfyUI. > [!NOTE] > This project was inspired by the [cookiecutter](https://github.com/Comfy-Org/cookiecutter-comfy-extension) template, though the full architecture is a bit different. - ## Quickstart 1. Install [ComfyUI](https://docs.comfy.org/get_started). @@ -13,60 +12,78 @@ A collection of custom nodes for ComfyUI. 3. Look up this extension in ComfyUI-Manager. If you are installing manually, clone this repository under `ComfyUI/custom_nodes`. 4. Restart ComfyUI. - ## Features ### PromptPresetSelector node -![PromptPresetSelector v1](docs/images/PromptPresetSelector_v1.png) +![PromptPresetSelector v2](docs/images/PromptPresetSelector_v2.png) -Input: -- `separator` (str), default to | -- `preset_index` (int) in range 1 to 50, default to 1 -- `text` (str), a multiline textarea -- `cleanup` (bool), default to **True** +#### Inputs -Output: -- `prompt` (str), the formatted prompt -- `preset_index` (int), the selected index +- `syntax` (str), default to `{% | %}` — defines the opening tag, separator, and closing tag as a single field, space-separated +- `preset_index` (int), 0-based, default to `0` +- `text` (str), multiline textarea supporting preset blocks +- `cleanup` (bool), default to `True` -This node helps introducing variations to a text, usually a prompt, by supporting templates. Basically, you can include a template like this `{% %}` in your `text`, that will hold various options, separated by the `separator`. +#### Outputs -Depending on the chosen `preset_index`, the correct preset option will be parsed and add to the text. +- `text` (str) — the formatted text, with preset blocks resolved +- `preset_index` (int) — the selected index +- `preset_count` (int) — the number of presets found across blocks + +#### Usage + +Include preset blocks in your `text` using the syntax defined in the `syntax` field. Each block holds options separated by the separator. Depending on `preset_index`, the matching option is injected and the block markers are removed. -> Example: -> ``` -This is {% an example | a text | a text %} for {% the PromptPresetSelector | the README | %} +This is {% an example | a text | another text %} for {% the PromptPresetSelector | the README | %} ``` -- With `preset_index` == 1: This is an example for the PromptPresetSelector -- With `preset_index` == 2: This is an text for the README -- With `preset_index` == 3: This is an text for +- `preset_index` = `0` -> `This is an example for the PromptPresetSelector` +- `preset_index` = `1` -> `This is a text for the README` +- `preset_index` = `2` -> `This is another text for` -The node validates that the `separator` is filled, that you registered the same amount of options in all of the templates, and that you selected a `preset_index` within the length of the configured presets. +Empty presets are allowed — the block is simply replaced by an empty string. -> Example: +#### Custom syntax -If we fix `preset_index` to 3: +The `syntax` field allows changing the opening tag, separator and closing tag. Format is always `open_tag separator close_tag`, space-separated. -- *This is **{% a text | a message %}** for **{% the PromptPresetSelector %}*** => Error, first template with 2 presets while the second only got 1 -- *This is **{% a text | a message %}** for **{% the PromptPresetSelector | the README %}*** => Error, only 2 presets were configured +``` +<< / >> +``` -A `cleanup` option is also provided, to clean spaces before comas, double or empty comas and empty lines, that may result from the preset application. +Would match blocks like `<< option_a / option_b >>`. +#### Validation -### Web folder +The node runs the following checks and stops the workflow on error: -#### PromptColorEditor +- `syntax` must contain exactly 3 space-separated parts +- `open_tag` and `close_tag` must be different +- All preset blocks in `text` must have the same number of options +- `preset_index` must be within range -This extension adds a `contentEditable` div on top of the `text` textarea of the **PromptPresetSelector** to display the tags with some color. The colors will be displayed when the textarea loses focus, and will disappear when focused. +#### Cleanup -Currently, tags and separators will have the same color, and the unselected presets will be grayed out. The colors are also updated live when the `selector` or `preset_index` values are changed +When `cleanup` is enabled, the following are removed from the output: +- Spaces before commas +- Double or empty commas +- Empty lines + +### Web — PromptPresetSelector editor + +The `text` widget is replaced by a `contentEditable` div that provides syntax highlighting: + +- Tags and separators are highlighted in orange +- The active preset is shown in full brightness +- Inactive presets are grayed out +- Colors are displayed when the field loses focus, and revert to plain text while editing +- Highlighting updates live when `syntax` or `preset_index` are changed ## TODO -- Add JS to transform the preset_index in a dynamic dropdown, based on the configured presets -- Add field to name the different presets -- Add field to easily concatenate text at the end of the prompt +- Add JS to transform `preset_index` into a dynamic dropdown based on named presets +- Add a field to name the different presets +- Add a field to easily concatenate text at the end of the output \ No newline at end of file diff --git a/docs/images/PromptPresetSelector_v2.png b/docs/images/PromptPresetSelector_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..74ec24cee483789d1c026015f7a27f492b6624f8 GIT binary patch literal 89395 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

qxUw!&1-Cm22z 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 literal 0 HcmV?d00001 From 9a2551e0b165323c60ed963ae0f15ae8e9c76223 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 13:58:58 +0200 Subject: [PATCH 07/31] Add looping over preset_index values --- web/prompt_preset_selector.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 885eda5..26f4725 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -171,7 +171,29 @@ function attachEditor(node) { hookWidget(syntaxWidget, () => { if (document.activeElement !== editor) renderColored(); }); - hookWidget(presetWidget, () => { + hookWidget(presetWidget, (value) => { + const { openTag, separator, closeTag } = parseSyntax(getSyntax()); + const pattern = new RegExp( + escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), + "gs", + ); + const matches = [...textarea.value.matchAll(pattern)]; + if (matches.length === 0) return; + + const presetCount = Math.max( + ...matches.map((m) => m[1].split(separator).length), + ); + if (presetCount === 0) return; + + // Loop both ways on preset_index + let newValue = value % presetCount; + if (newValue < 0) newValue += presetCount; + + if (newValue !== value) { + presetWidget.value = newValue; + if (node.graph) node.graph.setDirtyCanvas(true, true); + } + if (document.activeElement !== editor) renderColored(); }); From 1375c97acbaebb52cf9825b6c8cceb43109521a4 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 13:59:27 +0200 Subject: [PATCH 08/31] Update min preset_index to -1 to unblock left looping --- src/nodes/prompt_preset_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index 8317087..5dd810d 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -33,7 +33,7 @@ class PromptPresetSelector: "INT", { "default": 0, - "min": 0, + "min": -1, "max": 99, "step": 1, "tooltip": "0-based index of the preset to select.", From 6c52b981b68ce0d882e70eb777cb03159a1a2303 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 13:59:46 +0200 Subject: [PATCH 09/31] Update README --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3f648cc..70cd9cd 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,9 @@ When `cleanup` is enabled, the following are removed from the output: - Double or empty commas - Empty lines -### Web — PromptPresetSelector editor +### Web + +#### PromptPresetSelector editor The `text` widget is replaced by a `contentEditable` div that provides syntax highlighting: @@ -82,8 +84,13 @@ The `text` widget is replaced by a `contentEditable` div that provides syntax hi - Colors are displayed when the field loses focus, and revert to plain text while editing - Highlighting updates live when `syntax` or `preset_index` are changed +#### Field tweaks + +- `preset_index` loops over the available indexes if possible to avoid some out-of-bounds errors + ## TODO - Add JS to transform `preset_index` into a dynamic dropdown based on named presets - Add a field to name the different presets -- Add a field to easily concatenate text at the end of the output \ No newline at end of file +- Add a field to easily concatenate text at the end of the output +- Add color even when in edit mode \ No newline at end of file From 50e367a6f3a9df5c6d0df0b47f64077a8e1206fe Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 22:06:07 +0200 Subject: [PATCH 10/31] Add preset_names for presets --- src/nodes/prompt_preset_selector.py | 37 +++++++++++++++++++++++++--- tests/test_prompt_preset_selector.py | 37 +++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index 5dd810d..d31933b 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -39,6 +39,17 @@ class PromptPresetSelector: "tooltip": "0-based index of the preset to select.", }, ), + "preset_names": ( + "STRING", + { + "default": "", + "tooltip": ( + "Optional. Name each preset separated by commas. " + "Ex: 'neutral, happy, sad'. " + "If filled, must match the number of options in all blocks." + ), + }, + ), "text": ( "STRING", { @@ -61,7 +72,7 @@ class PromptPresetSelector: FUNCTION = "process" @classmethod - def VALIDATE_INPUTS(cls, syntax, **kwargs): + def VALIDATE_INPUTS(cls, syntax, preset_names, **kwargs): parts = syntax.split() if len(parts) != 3: return ( @@ -70,6 +81,12 @@ class PromptPresetSelector: open_tag, _, close_tag = parts if open_tag == close_tag: return "open_tag and close_tag must be different" + + if preset_names.strip(): + names = [n.strip() for n in preset_names.split(",")] + if len(names) < 1: + return "preset_names must contain at least 1 entry separated by commas" + return True def _parse_syntax(self, syntax: str) -> tuple[str, str, str]: @@ -96,7 +113,12 @@ class PromptPresetSelector: [p.strip() for p in block.strip().split(separator)] for block in raw_blocks ] - def _validate(self, blocks: list[list[str]], preset_index: int) -> str | None: + def _validate( + self, + blocks: list[list[str]], + preset_index: int, + preset_names: str, + ) -> str | None: """ Validate blocks consistency. Raises an error message string if an error is found, else None. @@ -120,6 +142,14 @@ class PromptPresetSelector: f"(max index is {max_count - 1})." ) + if preset_names.strip(): + names = [n.strip() for n in preset_names.split(",")] + if len(names) != max_count: + raise ValueError( + f"PromptPresetSelector: preset_names has {len(names)} entries " + f"but blocks have {max_count} options." + ) + return None def _replace_blocks( @@ -147,6 +177,7 @@ class PromptPresetSelector: self, syntax: str, preset_index: int, + preset_names: str, text: str, cleanup: bool, ) -> tuple[str, int, int]: @@ -155,7 +186,7 @@ class PromptPresetSelector: pattern = self._build_pattern(open_tag, close_tag) blocks = self._parse_blocks(text, pattern, separator) - self._validate(blocks, preset_index) + self._validate(blocks, preset_index, preset_names) preset_count = max((len(b) for b in blocks), default=0) result = self._replace_blocks(text, pattern, separator, preset_index) diff --git a/tests/test_prompt_preset_selector.py b/tests/test_prompt_preset_selector.py index 0b13033..1a45038 100644 --- a/tests/test_prompt_preset_selector.py +++ b/tests/test_prompt_preset_selector.py @@ -7,10 +7,11 @@ def node(): return PromptPresetSelector() -def run(node, text, index=0, cleanup=False): +def run(node, text, index=0, preset_names="", cleanup=False): return node.process( syntax="{% | %}", preset_index=index, + preset_names=preset_names, text=text, cleanup=cleanup, ) @@ -80,12 +81,31 @@ def test_custom_tags(node): result, _, _ = node.process( syntax="<< / >>", preset_index=1, + preset_names="", text="I am << happy / sad >> today", cleanup=False, ) assert result == "I am sad today" +# --- Preset names --- + + +def test_preset_names_valid_count(node): + text, _, count = run(node, "{% a | b | c %}", preset_names="neutral, happy, sad") + assert count == 3 + + +def test_preset_names_mismatch(node): + with pytest.raises(ValueError, match="preset_names"): + run(node, "{% a | b %}", preset_names="neutral, happy, sad") + + +def test_preset_names_empty_ignored(node): + text, _, _ = run(node, "{% a | b %}", preset_names="") + assert text == "a" + + # --- Cleanup --- @@ -98,10 +118,21 @@ def test_cleanup_double_comma(node): def test_validate_empty_separator(): - result = PromptPresetSelector.VALIDATE_INPUTS(syntax="{% %}") + result = PromptPresetSelector.VALIDATE_INPUTS(syntax="{% %}", preset_names="") assert result != True def test_validate_same_tags(): - result = PromptPresetSelector.VALIDATE_INPUTS(syntax="% | %") + result = PromptPresetSelector.VALIDATE_INPUTS(syntax="% | %", preset_names="") assert result != True + + +def test_validate_preset_names_wrong_separator(): + result = PromptPresetSelector.VALIDATE_INPUTS( + syntax="{% | %}", + preset_index=0, + preset_names="neutral", # Only one element + text="", + cleanup=False, + ) + assert result == True From 30b84fac6431cc56daf18073e44c77c0627df50b Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 22:11:22 +0200 Subject: [PATCH 11/31] Remove enforcing preset_names length equals to preset_index --- src/nodes/prompt_preset_selector.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index d31933b..dad6201 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -83,7 +83,7 @@ class PromptPresetSelector: return "open_tag and close_tag must be different" if preset_names.strip(): - names = [n.strip() for n in preset_names.split(",")] + names = [n.strip() for n in preset_names.split(",") if n.strip()] if len(names) < 1: return "preset_names must contain at least 1 entry separated by commas" @@ -113,12 +113,7 @@ class PromptPresetSelector: [p.strip() for p in block.strip().split(separator)] for block in raw_blocks ] - def _validate( - self, - blocks: list[list[str]], - preset_index: int, - preset_names: str, - ) -> str | None: + def _validate(self, blocks: list[list[str]], preset_index: int) -> str | None: """ Validate blocks consistency. Raises an error message string if an error is found, else None. @@ -142,14 +137,6 @@ class PromptPresetSelector: f"(max index is {max_count - 1})." ) - if preset_names.strip(): - names = [n.strip() for n in preset_names.split(",")] - if len(names) != max_count: - raise ValueError( - f"PromptPresetSelector: preset_names has {len(names)} entries " - f"but blocks have {max_count} options." - ) - return None def _replace_blocks( @@ -186,7 +173,7 @@ class PromptPresetSelector: pattern = self._build_pattern(open_tag, close_tag) blocks = self._parse_blocks(text, pattern, separator) - self._validate(blocks, preset_index, preset_names) + self._validate(blocks, preset_index) preset_count = max((len(b) for b in blocks), default=0) result = self._replace_blocks(text, pattern, separator, preset_index) From 3d2fe2788d51f8591a92c6ab40b5192e0934c5a9 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Sun, 17 May 2026 22:11:36 +0200 Subject: [PATCH 12/31] Update tests for preset_names --- tests/test_prompt_preset_selector.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/test_prompt_preset_selector.py b/tests/test_prompt_preset_selector.py index 1a45038..488ea06 100644 --- a/tests/test_prompt_preset_selector.py +++ b/tests/test_prompt_preset_selector.py @@ -96,16 +96,21 @@ def test_preset_names_valid_count(node): assert count == 3 -def test_preset_names_mismatch(node): - with pytest.raises(ValueError, match="preset_names"): - run(node, "{% a | b %}", preset_names="neutral, happy, sad") - - def test_preset_names_empty_ignored(node): text, _, _ = run(node, "{% a | b %}", preset_names="") assert text == "a" +def test_preset_names_mismatch_ignored(node): + text, _, _ = run(node, "{% a | b %}", preset_names="neutral, happy, sad") + assert text == "a" + + +def test_preset_names_single(node): + text, _, _ = run(node, "{% a | b %}", preset_names="neutral") + assert text == "a" + + # --- Cleanup --- @@ -127,7 +132,7 @@ def test_validate_same_tags(): assert result != True -def test_validate_preset_names_wrong_separator(): +def test_validate_preset_names_single_entry_valid(): result = PromptPresetSelector.VALIDATE_INPUTS( syntax="{% | %}", preset_index=0, From af633d5688e30628450eb0d518f0e30d48fe855f Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 00:01:46 +0200 Subject: [PATCH 13/31] Add preset_name field --- src/nodes/prompt_preset_selector.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index dad6201..4f7c14d 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -39,6 +39,13 @@ class PromptPresetSelector: "tooltip": "0-based index of the preset to select.", }, ), + "preset_name": ( + [""], + { + "default": "", + "tooltip": "Active when preset_names is filled.", + }, + ), "preset_names": ( "STRING", { From 826a60ad7271aa44ab5a605dd2bd48e681ab5454 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 00:02:30 +0200 Subject: [PATCH 14/31] Replace preset_names with kwargs in process --- src/nodes/prompt_preset_selector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index 4f7c14d..b68217b 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -171,9 +171,9 @@ class PromptPresetSelector: self, syntax: str, preset_index: int, - preset_names: str, text: str, cleanup: bool, + **kwargs, ) -> tuple[str, int, int]: open_tag, separator, close_tag = self._parse_syntax(syntax) From 7523609f47a43cbb7462242a80ebeff757b5c8dc Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 00:19:11 +0200 Subject: [PATCH 15/31] Update preset_names to fill a dropdown preset_name in place of preset_index --- web/prompt_preset_selector.js | 107 ++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 26f4725..3d4f551 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -35,6 +35,21 @@ const parseSyntax = (syntax) => { return { openTag: parts[0], separator: parts[1], closeTag: parts[2] }; }; +// Build preset names, using placeholders if less names than presets are found +const buildNames = (names, presetCount) => { + const result = [...names]; + while (result.length < presetCount) { + result.push(`preset_${result.length}`); + } + return result.slice(0, presetCount); +}; + +// Hide ComfyUI widget component +const hideWidget = (widgetName) => { + widgetName.type = "hidden"; + widgetName.computeSize = () => [0, -4]; // -4 to cancel ComfyUI padding +}; + // --- Colors --- const COLORS = { @@ -91,9 +106,17 @@ function attachEditor(node) { const textWidget = node.widgets?.find((w) => w.name === "text"); const syntaxWidget = node.widgets?.find((w) => w.name === "syntax"); const presetWidget = node.widgets?.find((w) => w.name === "preset_index"); + const comboWidget = node.widgets?.find((w) => w.name === "preset_name"); + const namesWidget = node.widgets?.find((w) => w.name === "preset_names"); + + // Saving widget type + const originalPresetType = presetWidget.type; if (!textWidget) return; + // Hide preset_name + hideWidget(comboWidget); + const textarea = textWidget.inputEl || textWidget.element; if (!textarea?.parentNode) return; @@ -146,10 +169,77 @@ function attachEditor(node) { editor.innerText = textarea.value; }; + // --- Preset names / combo --- + + const destroyCombo = () => { + // Hide preset_name + hideWidget(comboWidget); + + presetWidget.type = originalPresetType; + presetWidget.computeSize = null; + if (node.graph) node.graph.setDirtyCanvas(true, true); + }; + + const buildCombo = (names) => { + // Hide preset_index + hideWidget(presetWidget); + + comboWidget.options.values = names; + comboWidget.value = names[0]; + comboWidget.type = "combo"; + comboWidget.computeSize = null; + + comboWidget.callback = (value) => { + presetWidget.value = names.indexOf(value); + if (node.graph) node.graph.setDirtyCanvas(true, true); + if (document.activeElement !== editor) renderColored(); + }; + + presetWidget.value = 0; + if (node.graph) node.graph.setDirtyCanvas(true, true); + }; + + const parseNames = () => { + const raw = namesWidget?.value?.trim() ?? ""; + if (!raw) return null; + const names = raw + .split(",") + .map((n) => n.trim()) + .filter(Boolean); + return names.length >= 1 ? names : null; + }; + + const refreshCombo = () => { + const names = parseNames(); + if (!names) { + destroyCombo(); + return; + } + + const { separator, openTag, closeTag } = parseSyntax(getSyntax()); + const pattern = new RegExp( + escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), + "gs", + ); + const matches = [...textarea.value.matchAll(pattern)]; + const presetCount = matches.length + ? Math.max(...matches.map((m) => m[1].split(separator).length)) + : 0; + + if (presetCount === 0) { + destroyCombo(); + return; + } + + const finalNames = buildNames(names, presetCount); + buildCombo(finalNames); + }; + // --- Event listener - Input: Sync editor -> textarea --- editor.addEventListener("input", () => { textarea.value = editor.innerText; textarea.dispatchEvent(new Event("input", { bubbles: true })); + refreshCombo(); }); // --- Event listener - Focus: Switch to raw text for editing --- @@ -167,10 +257,8 @@ function attachEditor(node) { // --- Event listener - Blur: Switch to colored view --- editor.addEventListener("blur", () => renderColored()); - // React to widget changes - hookWidget(syntaxWidget, () => { - if (document.activeElement !== editor) renderColored(); - }); + // --- React to widget changes --- + hookWidget(presetWidget, (value) => { const { openTag, separator, closeTag } = parseSyntax(getSyntax()); const pattern = new RegExp( @@ -197,7 +285,18 @@ function attachEditor(node) { if (document.activeElement !== editor) renderColored(); }); + hookWidget(syntaxWidget, () => { + refreshCombo(); + if (document.activeElement !== editor) renderColored(); + }); + + hookWidget(namesWidget, () => { + refreshCombo(); + if (document.activeElement !== editor) renderColored(); + }); + // Initial render + refreshCombo(); renderColored(); } From e60afc28c6e21fd70b339f63fc1f015f064432bb Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 00:19:42 +0200 Subject: [PATCH 16/31] Update README and todos --- README.md | 30 ++++++++++++---------- docs/images/PromptPresetSelector_v2.1.png | Bin 0 -> 96120 bytes 2 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 docs/images/PromptPresetSelector_v2.1.png diff --git a/README.md b/README.md index 70cd9cd..9546ab9 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,14 @@ A collection of custom nodes for ComfyUI. ### PromptPresetSelector node -![PromptPresetSelector v2](docs/images/PromptPresetSelector_v2.png) +![PromptPresetSelector v2.1](docs/images/PromptPresetSelector_v2.1.png) #### Inputs - `syntax` (str), default to `{% | %}` — defines the opening tag, separator, and closing tag as a single field, space-separated -- `preset_index` (int), 0-based, default to `0` +- `preset_index` (int), 0-based, default to `0` — loops both ways within range +- `preset_name` (combo) — dynamic dropdown, active when `preset_names` is filled +- `preset_names` (str), optional — comma-separated list of preset names. Ex: `neutral, happy, sad`. If fewer names than presets, remaining are auto-named (`preset_2`, `preset_3`...). If more, extra names are ignored. - `text` (str), multiline textarea supporting preset blocks - `cleanup` (bool), default to `True` @@ -72,25 +74,25 @@ When `cleanup` is enabled, the following are removed from the output: - Double or empty commas - Empty lines -### Web +### Web — PromptPresetSelector editor -#### PromptPresetSelector editor - -The `text` widget is replaced by a `contentEditable` div that provides syntax highlighting: +The `text` widget is replaced by a `contentEditable` div that provides +syntax highlighting: - Tags and separators are highlighted in orange - The active preset is shown in full brightness - Inactive presets are grayed out -- Colors are displayed when the field loses focus, and revert to plain text while editing -- Highlighting updates live when `syntax` or `preset_index` are changed +- Colors are displayed when the field loses focus, and revert to plain + text while editing +- Highlighting updates live when `syntax`, `preset_index` or `preset_name` + are changed -#### Field tweaks - -- `preset_index` loops over the available indexes if possible to avoid some out-of-bounds errors +When `preset_names` is filled, `preset_index` is replaced by a dynamic +dropdown (`preset_name`) built from the comma-separated names. The dropdown +resets to the first preset when activated. ## TODO -- Add JS to transform `preset_index` into a dynamic dropdown based on named presets -- Add a field to name the different presets - Add a field to easily concatenate text at the end of the output -- Add color even when in edit mode \ No newline at end of file +- Add color even when in edit mode +- Wildcard mode for presets (randomly selected, index to -1 or -2?) \ No newline at end of file diff --git a/docs/images/PromptPresetSelector_v2.1.png b/docs/images/PromptPresetSelector_v2.1.png new file mode 100644 index 0000000000000000000000000000000000000000..20b99b73a552052a51d3db3bf1fce0139d3dbb89 GIT binary patch literal 96120 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 Hrh)$->x>zZ literal 0 HcmV?d00001 From 8a1dfe0c897d240ad7da0097fbaacee257baa6e0 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 00:28:36 +0200 Subject: [PATCH 17/31] Extract pattern build and preset count in helpers --- web/prompt_preset_selector.js | 50 +++++++++++++++-------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 3d4f551..4aa6230 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -44,6 +44,23 @@ const buildNames = (names, presetCount) => { return result.slice(0, presetCount); }; +// Build pattern from syntax +const buildPattern = (syntax) => { + const { openTag, closeTag } = parseSyntax(syntax); + return new RegExp( + escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), + "gs", + ); +}; + +// Get number of presets found +const getPresetCount = (text, syntax) => { + const { separator } = parseSyntax(syntax); + const matches = [...text.matchAll(buildPattern(syntax))]; + if (!matches.length) return 0; + return Math.max(...matches.map((m) => m[1].split(separator).length)); +}; + // Hide ComfyUI widget component const hideWidget = (widgetName) => { widgetName.type = "hidden"; @@ -62,12 +79,7 @@ const COLORS = { // --- Editor --- function buildHighlightedHtml(raw, syntax, presetIndex) { - const { openTag, separator, closeTag } = parseSyntax(syntax); - const pattern = new RegExp( - escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), - "gs", - ); - + const pattern = buildPattern(syntax); let result = ""; let lastIndex = 0; @@ -216,23 +228,13 @@ function attachEditor(node) { return; } - const { separator, openTag, closeTag } = parseSyntax(getSyntax()); - const pattern = new RegExp( - escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), - "gs", - ); - const matches = [...textarea.value.matchAll(pattern)]; - const presetCount = matches.length - ? Math.max(...matches.map((m) => m[1].split(separator).length)) - : 0; - + const presetCount = getPresetCount(textarea.value, getSyntax()); if (presetCount === 0) { destroyCombo(); return; } - const finalNames = buildNames(names, presetCount); - buildCombo(finalNames); + buildCombo(buildNames(names, presetCount)); }; // --- Event listener - Input: Sync editor -> textarea --- @@ -260,17 +262,7 @@ function attachEditor(node) { // --- React to widget changes --- hookWidget(presetWidget, (value) => { - const { openTag, separator, closeTag } = parseSyntax(getSyntax()); - const pattern = new RegExp( - escapeRegex(openTag) + "\\s*(.*?)\\s*" + escapeRegex(closeTag), - "gs", - ); - const matches = [...textarea.value.matchAll(pattern)]; - if (matches.length === 0) return; - - const presetCount = Math.max( - ...matches.map((m) => m[1].split(separator).length), - ); + const presetCount = getPresetCount(textarea.value, getSyntax()); if (presetCount === 0) return; // Loop both ways on preset_index From 466fb88f6ba2244d4e43d862759a2007dca8e206 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 01:15:51 +0200 Subject: [PATCH 18/31] Fix openTag, separator and closeTag undeclared --- web/prompt_preset_selector.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 4aa6230..3f88051 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -79,6 +79,7 @@ const COLORS = { // --- Editor --- function buildHighlightedHtml(raw, syntax, presetIndex) { + const { openTag, separator, closeTag } = parseSyntax(syntax); const pattern = buildPattern(syntax); let result = ""; let lastIndex = 0; From 94213cd80af5eccbea8d90310816ca322325cad7 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 01:21:26 +0200 Subject: [PATCH 19/31] Add guards on parseNames against bootup namesWidget values --- web/prompt_preset_selector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 3f88051..a18965c 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -213,8 +213,8 @@ function attachEditor(node) { }; const parseNames = () => { - const raw = namesWidget?.value?.trim() ?? ""; - if (!raw) return null; + const raw = namesWidget?.value; + if (!raw || typeof raw !== "string") return null; const names = raw .split(",") .map((n) => n.trim()) From b5e6da05899eea03553d596f97741ff2e78f462d Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 01:32:51 +0200 Subject: [PATCH 20/31] Add all-time coloration in div --- web/prompt_preset_selector.js | 65 +++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 19 deletions(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index a18965c..798d812 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -67,6 +67,49 @@ const hideWidget = (widgetName) => { widgetName.computeSize = () => [0, -4]; // -4 to cancel ComfyUI padding }; +// Save current caret position in div +const saveCaretPosition = (el) => { + const sel = window.getSelection(); + if (!sel.rangeCount) return 0; + + const range = sel.getRangeAt(0); + const preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(el); + preCaretRange.setEnd(range.endContainer, range.endOffset); + return preCaretRange.toString().length; +}; + +// Restore caret position in div +const restoreCaretPosition = (el, offset) => { + const range = document.createRange(); + const sel = window.getSelection(); + let charCount = 0; + let found = false; + + const walk = (node) => { + if (found) return; + if (node.nodeType === Node.TEXT_NODE) { + const next = charCount + node.length; + if (next >= offset) { + range.setStart(node, offset - charCount); + range.collapse(true); + found = true; + } + charCount = next; + } else { + for (const child of node.childNodes) walk(child); + } + }; + + walk(el); + if (!found) { + range.selectNodeContents(el); + range.collapse(false); + } + sel.removeAllRanges(); + sel.addRange(range); +}; + // --- Colors --- const COLORS = { @@ -178,10 +221,6 @@ function attachEditor(node) { ); }; - const renderRaw = () => { - editor.innerText = textarea.value; - }; - // --- Preset names / combo --- const destroyCombo = () => { @@ -240,26 +279,14 @@ function attachEditor(node) { // --- Event listener - Input: Sync editor -> textarea --- editor.addEventListener("input", () => { + const pos = saveCaretPosition(editor); textarea.value = editor.innerText; textarea.dispatchEvent(new Event("input", { bubbles: true })); refreshCombo(); + renderColored(); + restoreCaretPosition(editor, pos); }); - // --- Event listener - Focus: Switch to raw text for editing --- - editor.addEventListener("focus", () => { - renderRaw(); - // Place caret at end - const range = document.createRange(); - range.selectNodeContents(editor); - range.collapse(false); - const sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - }); - - // --- Event listener - Blur: Switch to colored view --- - editor.addEventListener("blur", () => renderColored()); - // --- React to widget changes --- hookWidget(presetWidget, (value) => { From 69412337b098c01cbb3863210c17031bb89e5540 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Mon, 18 May 2026 01:36:10 +0200 Subject: [PATCH 21/31] Restore saved preset on load --- web/prompt_preset_selector.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 798d812..d5137a5 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -236,8 +236,11 @@ function attachEditor(node) { // Hide preset_index hideWidget(presetWidget); + const savedIndex = presetWidget.value ?? 0; + const safeIndex = savedIndex < names.length ? savedIndex : 0; + comboWidget.options.values = names; - comboWidget.value = names[0]; + comboWidget.value = names[safeIndex]; comboWidget.type = "combo"; comboWidget.computeSize = null; @@ -247,7 +250,7 @@ function attachEditor(node) { if (document.activeElement !== editor) renderColored(); }; - presetWidget.value = 0; + presetWidget.value = safeIndex; if (node.graph) node.graph.setDirtyCanvas(true, true); }; From 9427250ea1680b554925fb7f8ddf634d8da55144 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Tue, 19 May 2026 23:22:00 +0200 Subject: [PATCH 22/31] Add feature to references previous presets with --- src/nodes/prompt_preset_selector.py | 41 ++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/nodes/prompt_preset_selector.py b/src/nodes/prompt_preset_selector.py index b68217b..f13c3a0 100644 --- a/src/nodes/prompt_preset_selector.py +++ b/src/nodes/prompt_preset_selector.py @@ -146,6 +146,45 @@ class PromptPresetSelector: return None + def _resolve_references(self, parts: list[str], current_index: int) -> str: + """ + Resolve $N references in the selected preset. + Only presets with index < current_index can be referenced. + """ + selected = parts[current_index] + resolving = set() + + def resolve(text: str, depth: int = 0) -> str: + if depth > len(parts): + raise ValueError("PromptPresetSelector: circular reference detected.") + + def replace_ref(match: re.Match) -> str: + ref_index = int(match.group(1)) + + if ref_index >= len(parts): + raise ValueError( + f"PromptPresetSelector: reference ${{ref_index}} is out of range " + f"(max index is {len(parts) - 1})." + ) + if ref_index >= current_index: + raise ValueError( + f"PromptPresetSelector: reference ${{ref_index}} is not yet declared " + f"(current preset is {current_index})." + ) + if ref_index in resolving: + raise ValueError( + f"PromptPresetSelector: circular reference detected on ${ref_index}." + ) + + resolving.add(ref_index) + result = resolve(parts[ref_index], depth + 1) + resolving.discard(ref_index) + return result + + return re.sub(r"\$(\d+)", replace_ref, text) + + return resolve(selected) + def _replace_blocks( self, text: str, @@ -157,7 +196,7 @@ class PromptPresetSelector: def replace_block(match: re.Match) -> str: parts = [opt.strip() for opt in match.group(1).split(separator)] - return parts[preset_index] + return self._resolve_references(parts, preset_index) return re.sub(pattern, replace_block, text, flags=re.DOTALL) From b05da19827ac9bf5e3e7465b80ce31d5859ef47d Mon Sep 17 00:00:00 2001 From: relhamdi Date: Tue, 19 May 2026 23:22:26 +0200 Subject: [PATCH 23/31] Add tests for preset references --- tests/test_prompt_preset_selector.py | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/tests/test_prompt_preset_selector.py b/tests/test_prompt_preset_selector.py index 488ea06..5cb3e71 100644 --- a/tests/test_prompt_preset_selector.py +++ b/tests/test_prompt_preset_selector.py @@ -111,6 +111,46 @@ def test_preset_names_single(node): assert text == "a" +# --- References --- + + +def test_reference_basic(node): + # Index 1, resolve reference + text, _, _ = run(node, "{% hello | $0 world %}", index=1) + assert text == "hello world" + + +def test_reference_index_0(node): + # Index 0, nothing to resolve + text, _, _ = run(node, "{% hello | $0 world %}", index=0) + assert text == "hello" + + +def test_reference_out_of_range(node): + with pytest.raises(ValueError, match="out of range"): + run(node, "{% a | b | $5 %}", index=2) + + +def test_reference_not_yet_declared(node): + with pytest.raises(ValueError, match="not yet declared"): + run(node, "{% $1 | b %}", index=0) + + +def test_reference_circular(node): + with pytest.raises(ValueError, match="circular"): + run(node, "{% $0 | $0 %}", index=1) + + +def test_reference_chained(node): + text, _, _ = run(node, "{% base | $0 mid | $1 top %}", index=2) + assert text == "base mid top" + + +def test_reference_empty_preset(node): + text, _, _ = run(node, "{% | $0 suffix %}", index=1) + assert text == " suffix" + + # --- Cleanup --- From 27aff8c0fc40261e9af035798ea7ed1970b559e4 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Tue, 19 May 2026 23:22:43 +0200 Subject: [PATCH 24/31] Add color for preset references --- web/prompt_preset_selector.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index d5137a5..d664514 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -117,6 +117,7 @@ const COLORS = { sep: "#ff9800", active: "#e0e0e0", inactive: "#555", + ref: "#64b5f6", }; // --- Editor --- @@ -135,7 +136,14 @@ function buildHighlightedHtml(raw, syntax, presetIndex) { const coloredParts = parts.map((part, i) => { const color = i === presetIndex ? COLORS.active : COLORS.inactive; - return `${escapeHtml(part)}`; + const content = + i === presetIndex + ? escapeHtml(part).replace( + /\$(\d+)/g, + `$$$1`, + ) + : escapeHtml(part); + return `${content}`; }); const sepHtml = `${escapeHtml(separator)}`; From 6b4d7a4f7051b1709b9f64a6cf52733b39381fea Mon Sep 17 00:00:00 2001 From: relhamdi Date: Tue, 19 May 2026 23:26:05 +0200 Subject: [PATCH 25/31] Extract preset_names into own category --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9546ab9..09dc800 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,12 @@ The `syntax` field allows changing the opening tag, separator and closing tag. F Would match blocks like `<< option_a / option_b >>`. +#### Preset names + +When `preset_names` is filled, `preset_index` is replaced by a dynamic dropdown (`preset_name`) built from the comma-separated names. The dropdown resets to the first preset when activated. + +If fewer names than presets are defined, remaining entries are auto-named (`preset_2`, `preset_3`...). If more names than presets, extra names are ignored. + #### Validation The node runs the following checks and stops the workflow on error: @@ -87,12 +93,10 @@ syntax highlighting: - Highlighting updates live when `syntax`, `preset_index` or `preset_name` are changed -When `preset_names` is filled, `preset_index` is replaced by a dynamic -dropdown (`preset_name`) built from the comma-separated names. The dropdown +When `preset_names` is filled, and `preset_name` replaces `preset_index`, the dropdown resets to the first preset when activated. ## TODO - Add a field to easily concatenate text at the end of the output -- Add color even when in edit mode - Wildcard mode for presets (randomly selected, index to -1 or -2?) \ No newline at end of file From efee258829700732ed142b3999abee719a1bfb13 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Tue, 19 May 2026 23:27:46 +0200 Subject: [PATCH 26/31] Update README with preset references --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 09dc800..84c6aed 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,25 @@ When `preset_names` is filled, `preset_index` is replaced by a dynamic dropdown If fewer names than presets are defined, remaining entries are auto-named (`preset_2`, `preset_3`...). If more names than presets, extra names are ignored. +#### Preset references + +Inside a preset, you can reference a previously declared preset using `$N` (0-based index). + +``` +{% base style | $0, hat | $1, jewelry %} +``` + +- `preset_index` = `0` → `base style` +- `preset_index` = `1` → `base style, hat` +- `preset_index` = `2` → `base style, hat, jewelry` + +References are resolved recursively — `$1` in preset 2 resolves preset 1, which may itself reference preset 0. + +Rules: +- `$N` can only reference a preset with a lower index (forward references are not allowed) +- `$N` must be within range +- Circular references raise an error + #### Validation The node runs the following checks and stops the workflow on error: From c6a76223ab281dcd14e28f3a324f38fb048d0aa6 Mon Sep 17 00:00:00 2001 From: relhamdi Date: Tue, 19 May 2026 23:38:23 +0200 Subject: [PATCH 27/31] Add guard against html injection with paste that could disturb html coloring --- web/prompt_preset_selector.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index d664514..d98ba8e 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -298,6 +298,13 @@ function attachEditor(node) { restoreCaretPosition(editor, pos); }); + // --- Event listener - Paste: Sanitize text pasting to prevent errors with HTML coloration --- + editor.addEventListener("paste", (e) => { + e.preventDefault(); + const text = e.clipboardData.getData("text/plain"); + document.execCommand("insertText", false, text); + }); + // --- React to widget changes --- hookWidget(presetWidget, (value) => { From 73a0b8eb9d04840793d8489dadbc67406de9bf8f Mon Sep 17 00:00:00 2001 From: relhamdi Date: Wed, 20 May 2026 11:08:29 +0200 Subject: [PATCH 28/31] Add keyboard shortcuts to adjust selected text weight --- README.md | 5 +- web/prompt_preset_selector.js | 145 ++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 84c6aed..119c1cb 100644 --- a/README.md +++ b/README.md @@ -107,8 +107,9 @@ syntax highlighting: - Tags and separators are highlighted in orange - The active preset is shown in full brightness - Inactive presets are grayed out -- Colors are displayed when the field loses focus, and revert to plain - text while editing +- `$N` references are highlighted in blue on the active preset +- Colors update in real time while editing +- Pasting always inserts plain text, stripping any HTML formatting - Highlighting updates live when `syntax`, `preset_index` or `preset_name` are changed diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index d98ba8e..0874a0a 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -110,6 +110,90 @@ const restoreCaretPosition = (el, offset) => { sel.addRange(range); }; +// Get selected word or at caret position +const getSelectedOrWordAtCaret = (editor) => { + const sel = window.getSelection(); + if (!sel.rangeCount) return null; + + const range = sel.getRangeAt(0); + + // First cae: Selected text + if (!range.collapsed) { + return { + text: range.toString().trim(), + range: range.cloneRange(), + }; + } + + // Second case: No selection + const fullText = editor.innerText; + const caretPos = saveCaretPosition(editor); + + // Look for delimiters (commas, to avoid multi word prompts) + const delimiters = /[,\n]/; + let start = caretPos; + let end = caretPos; + + while (start > 0 && !delimiters.test(fullText[start - 1])) start--; + while (end < fullText.length && !delimiters.test(fullText[end])) end++; + + // Trimming spaces + let word = fullText.slice(start, end); + const leftTrim = word.length - word.trimStart().length; + const rightTrim = word.length - word.trimEnd().length; + start += leftTrim; + end -= rightTrim; + word = word.trim(); + + if (!word) return null; + + // Rebuild the range on the word + const wordRange = document.createRange(); + let charCount = 0; + let found = { start: false, end: false }; + + const walk = (node) => { + if (found.start && found.end) return; + if (node.nodeType === Node.TEXT_NODE) { + const next = charCount + node.length; + if (!found.start && next > start) { + wordRange.setStart(node, start - charCount); + found.start = true; + } + if (found.start && next >= end) { + wordRange.setEnd(node, end - charCount); + found.end = true; + } + charCount = next; + } else { + for (const child of node.childNodes) walk(child); + } + }; + + walk(editor); + if (!found.start || !found.end) return null; + + return { text: word, range: wordRange }; +}; + +// Adjust weight on selected word +const adjustWeight = (text, delta) => { + const trimmed = text.trim(); + if (!trimmed) return text; + + const weighted = trimmed.match(/^\((.+):(-?\d+(?:\.\d+)?)\)$/s); + if (weighted) { + const tag = weighted[1]; + const weight = Math.round((parseFloat(weighted[2]) + delta) * 10) / 10; + if (weight === 1.0) return text.replace(trimmed, tag); + return text.replace(trimmed, `(${tag}:${weight.toFixed(1)})`); + } + + const weight = Math.round((1.0 + delta) * 10) / 10; + if (weight === 1.0) return text; + return text.replace(trimmed, `(${trimmed}:${weight.toFixed(1)})`); +}; + // --- Colors --- const COLORS = { @@ -305,6 +389,67 @@ function attachEditor(node) { document.execCommand("insertText", false, text); }); + // --- Event listener - Keydown: Look for keyboard shortcuts --- + editor.addEventListener("keydown", (e) => { + // Ctrl+Up/Down - Adjust weight on selected text + if (!e.ctrlKey || (e.key !== "ArrowUp" && e.key !== "ArrowDown")) + return; + e.preventDefault(); + + const delta = e.key === "ArrowUp" ? 0.1 : -0.1; + const selection = getSelectedOrWordAtCaret(editor); + if (!selection) return; + + const adjusted = adjustWeight(selection.text, delta); + if (adjusted === selection.text) return; + + // Save selected text length + const adjustedLength = adjusted.length; + + const sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(selection.range); + document.execCommand("insertText", false, adjusted); + + // Reselect inserted text + const pos = saveCaretPosition(editor); + const newRange = document.createRange(); + const start = pos - adjustedLength; + + let charCount = 0; + let startNode = null; + let startOffset = 0; + let endNode = null; + let endOffset = 0; + + const walk = (node) => { + if (startNode && endNode) return; + if (node.nodeType === Node.TEXT_NODE) { + const next = charCount + node.length; + if (!startNode && next > start) { + startNode = node; + startOffset = start - charCount; + } + if (startNode && next >= pos) { + endNode = node; + endOffset = pos - charCount; + } + charCount = next; + } else { + for (const child of node.childNodes) walk(child); + } + }; + + walk(editor); + + if (startNode && endNode) { + newRange.setStart(startNode, startOffset); + newRange.setEnd(endNode, endOffset); + sel.removeAllRanges(); + sel.addRange(newRange); + } + }); + // --- React to widget changes --- hookWidget(presetWidget, (value) => { From 87223ab6276dae92096a211640e570851fe98ffa Mon Sep 17 00:00:00 2001 From: relhamdi Date: Wed, 20 May 2026 11:21:55 +0200 Subject: [PATCH 29/31] Extract helpers for walking over text nodes --- web/prompt_preset_selector.js | 125 +++++++++++++++------------------- 1 file changed, 55 insertions(+), 70 deletions(-) diff --git a/web/prompt_preset_selector.js b/web/prompt_preset_selector.js index 0874a0a..8a5bf39 100644 --- a/web/prompt_preset_selector.js +++ b/web/prompt_preset_selector.js @@ -79,6 +79,44 @@ const saveCaretPosition = (el) => { return preCaretRange.toString().length; }; +// Loop over text nodes in the DOM +const walkTextNodes = (root, callback) => { + const walk = (node) => { + if (node.nodeType === Node.TEXT_NODE) { + if (callback(node) === false) return false; + } else { + for (const child of node.childNodes) { + if (walk(child) === false) return false; + } + } + }; + walk(root); +}; + +// Build range from offset on text node +const buildRangeFromOffsets = (root, start, end) => { + const range = document.createRange(); + let charCount = 0; + let startFound = false; + let complete = false; + + walkTextNodes(root, (node) => { + const next = charCount + node.length; + if (!startFound && next > start) { + range.setStart(node, start - charCount); + startFound = true; + } + if (startFound && next >= end) { + range.setEnd(node, end - charCount); + complete = true; + return false; + } + charCount = next; + }); + + return complete ? range : null; +}; + // Restore caret position in div const restoreCaretPosition = (el, offset) => { const range = document.createRange(); @@ -86,22 +124,17 @@ const restoreCaretPosition = (el, offset) => { let charCount = 0; let found = false; - const walk = (node) => { - if (found) return; - if (node.nodeType === Node.TEXT_NODE) { - const next = charCount + node.length; - if (next >= offset) { - range.setStart(node, offset - charCount); - range.collapse(true); - found = true; - } - charCount = next; - } else { - for (const child of node.childNodes) walk(child); + walkTextNodes(el, (node) => { + const next = charCount + node.length; + if (next >= offset) { + range.setStart(node, offset - charCount); + range.collapse(true); + found = true; + return false; } - }; + charCount = next; + }); - walk(el); if (!found) { range.selectNodeContents(el); range.collapse(false); @@ -148,30 +181,8 @@ const getSelectedOrWordAtCaret = (editor) => { if (!word) return null; // Rebuild the range on the word - const wordRange = document.createRange(); - let charCount = 0; - let found = { start: false, end: false }; - - const walk = (node) => { - if (found.start && found.end) return; - if (node.nodeType === Node.TEXT_NODE) { - const next = charCount + node.length; - if (!found.start && next > start) { - wordRange.setStart(node, start - charCount); - found.start = true; - } - if (found.start && next >= end) { - wordRange.setEnd(node, end - charCount); - found.end = true; - } - charCount = next; - } else { - for (const child of node.childNodes) walk(child); - } - }; - - walk(editor); - if (!found.start || !found.end) return null; + const wordRange = buildRangeFromOffsets(editor, start, end); + if (!wordRange) return null; return { text: word, range: wordRange }; }; @@ -413,38 +424,12 @@ function attachEditor(node) { // Reselect inserted text const pos = saveCaretPosition(editor); - const newRange = document.createRange(); - const start = pos - adjustedLength; - - let charCount = 0; - let startNode = null; - let startOffset = 0; - let endNode = null; - let endOffset = 0; - - const walk = (node) => { - if (startNode && endNode) return; - if (node.nodeType === Node.TEXT_NODE) { - const next = charCount + node.length; - if (!startNode && next > start) { - startNode = node; - startOffset = start - charCount; - } - if (startNode && next >= pos) { - endNode = node; - endOffset = pos - charCount; - } - charCount = next; - } else { - for (const child of node.childNodes) walk(child); - } - }; - - walk(editor); - - if (startNode && endNode) { - newRange.setStart(startNode, startOffset); - newRange.setEnd(endNode, endOffset); + const newRange = buildRangeFromOffsets( + editor, + pos - adjustedLength, + pos, + ); + if (newRange) { sel.removeAllRanges(); sel.addRange(newRange); } From c3b83d8bac0bcaf033570fe52ad5d77e572382fd Mon Sep 17 00:00:00 2001 From: relhamdi Date: Wed, 20 May 2026 11:22:12 +0200 Subject: [PATCH 30/31] Update README with keyboard shortcuts --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 119c1cb..fe91ae4 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,9 @@ When `cleanup` is enabled, the following are removed from the output: - Double or empty commas - Empty lines -### Web — PromptPresetSelector editor +### Web + +#### PromptPresetSelector editor The `text` widget is replaced by a `contentEditable` div that provides syntax highlighting: @@ -116,6 +118,15 @@ syntax highlighting: When `preset_names` is filled, and `preset_name` replaces `preset_index`, the dropdown resets to the first preset when activated. +#### Keyboard shortcuts + +| Shortcut | Action | +| ----------- | ---------------------------------------------------------------------------------------------------------- | +| `Ctrl+Up` | Increase weight of selected text or word under caret by `0.1`. Ex: `tag` → `(tag:1.1)` | +| `Ctrl+Down` | Decrease weight of selected text or word under caret by `0.1`. Ex: `(tag:1.1)` → `tag` when reaching `1.0` | + +The selection is preserved after each keypress, consistent with ComfyUI native behavior. + ## TODO - Add a field to easily concatenate text at the end of the output From a5c601cfacc5a2cfcfd530f9e2324464d84f7aca Mon Sep 17 00:00:00 2001 From: relhamdi Date: Wed, 20 May 2026 11:23:24 +0200 Subject: [PATCH 31/31] Update version to 0.2.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd993b8..90527af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ComfyUI-RE-CustomUtils" -version = "0.1.0" +version = "0.2.0" description = "A collection of custom nodes for ComfyUI." license = { file = "LICENSE" } readme = "README.md"