Nodes/ValiTools/VSmartPrompt
ComfyUI Node

VSmartPrompt

{a|b} prompts with an editor that shows you what it picked

By vangel76·Created 2 months ago·Updated 8 days ago· 1
VSmartPrompt
  • vars_in
  • prompt
  • original_prompt
  • variables
◄available_loras_stem►
◄seed0►
◄line_suffix►
◄single_line_outputtrue►
◄remove_whitespacestrue►
◄remove_empty_tagsfalse►
◄load_loras_from_prompttrue►
◄remove_loras_patterntrue►
◄wildcard_directory/tmp/ComfyUI/custom_nodes/comfyui-ValiTools/wildcards►
◄prompt### VSmartPrompt syntax. '#' lines are comments and never reach the output. ### AI: follow these rules literally, then output only the prompt itself. ## COMBINATION - one option is picked # {a|b} equal chance {a|} empty option = output nothing # {0.7::a|b} weight 0..1, unweighted options share what is left # nesting allowed everywhere ## WILDCARD - one random LINE from a .txt in the wildcard directory # __name__ reads 'name.txt', no extension __folder\name__ subfolder # a missing file stays in the text (shown RED in the editor) # FILE FORMAT: one option per line. Never wrap the file in braces, never start # lines with '|' - the line is picked before anything is resolved. A block is # only allowed inside a file if it sits on ONE line: a {red|blue} dress ## VARIABLE - rolled once, same value everywhere # {a|b}==<v> store the picked option and output it here # __file__==<v> store the pulled line # word==<v> store the ONE word before '==' (no spaces) # {two words}==<v> store several words # {a|b}==!<v> store WITHOUT outputting anything here # <v> output it, anywhere, even above its assignment # never assigned -> stays as text (RED). assigned twice -> last one wins. ## SWITCHER - show a block only for certain values # <v>==value::{...} fires when <v> is that value; works on __wildcards__ too # <v>!=value::{...} fires for every other value # <v>==a,b,c::{...} OR list <v>!=a,b,c:: none of them # case-insensitive. must TOUCH the brace. assign <v> EARLIER in the text. ## COMMENT # block comments run from /# to #/ and may span lines; the other forms are # listed inside such a block so their hashes cannot pair up with each other: /# # text to the end of the line a #note# b inline, between two single hashes ## text headline style ### text bigger headline style #/ ## TRAPS # '==<v>' binds to the ONE construct in front of it: # wrong {a}, {b}==<v> right {a, b}==<v> # a CONDITION value is plain text - no braces, they would be rolled: # wrong <v>=={upper body}:: right <v>==upper body:: # an ASSIGNMENT of several words needs braces: # wrong upper body==<v> right {upper body}==<v> # an assignment inside a branch only fires if that branch is picked ## EXAMPLE {__names__, __bodytypes__}==!<woman> {kitchen==!<room>|bedroom==!<room>|garden==!<room>} <woman> stands in the <room>. <room>==kitchen::{She is chopping vegetables|She is washing a plate}. <room>!=kitchen::{There is no knife in sight}. ## OUTPUT MODE - target model syntax, set with the dropdown in the editor toolbar # /# mode: seedance #/ put this directive anywhere; omit it for normal output # normal everything above, nothing else # h3 t2v / h3 r2v H3 speech and structure tags (<d>, <i>, <breath>, ...) are # never read as variables, even if a variable of that name exists # seedance '{...}' stays literal text unless it holds a '|' or a '::' weight, # an '==<v>' follows it, or a switcher guard '<v>==x::' precedes it - # so spoken lines like {Hello, world} reach the output unchanged # while gated blocks still switch. Literal braces are blue in the editor. # the full syntax above keeps working in every mode ## EDITOR # CTRL+Click a wildcard edits its file. Type '__' or '<' for a dropdown. # CTRL+F find, CTRL+Z undo, CTRL+UP/DOWN weighting, CTRL+Wheel font size. # After a run the picked options are white; hover shows the rolled value. ►
◄in1—►
◄in2—►
◄in3—►
◄in4—►
◄in5—►
◄in6—►

What it is

The {a|b} bracket trick is older than most ComfyUI users: A1111-era dynamic prompts let you write one prompt that rolls a different combination every run, and people used it to batch 32 images of the same character across hair colors, weather, and lighting with zero retyping. VSmartPrompt is that idea, rebuilt for ComfyUI with a syntax-highlighted text editor bolted on. It's the flagship of the ValiTools pack, and it's the one node here worth building a workflow around.

The pitch isn't the combinations themselves - plenty of nodes do those. It's that this editor tells you what happened: options get color-coded per nesting level, wildcards glow yellow when their file exists, variables highlight violet, and after a run the branches that were actually picked get white marks. You're not resolving blind; you can see the roll.

How it works

Type a prompt in the big editor widget. On run, the node resolves it in a single pass: {a|b} picks one option (with optional N:: weights, nestable), __file__ pulls a random non-empty line from a .txt in your wildcard directory (subfolders supported), and everything runs through one RNG stream seeded from the seed widget. Comments are stripped before resolution, so text inside a comment can't silently trigger a wildcard or variable assignment - an old bug, now fixed.

The clever bits are variables and the switcher. {a|b}==<name> remembers the pick; <name> then outputs the same value everywhere, even inside later combinations. ==!<name> is the silent variant - it stores without emitting, so you can tag a scene ({day|night}==!<time>) and gate whole blocks on it: <time>==day::{...} fires only when the value matches, <time>!=day::{...} for everything else. Typing < or __ opens autocomplete dropdowns (variables / wildcard files), and CTRL+Click a wildcard opens or creates its file in a built-in editor that works on remote installs.

The inputs that matter

Most of the widget list is cleanup toggles you can ignore. The ones to touch:

  • seed - same seed + same prompt + same connected in1–in4 text always returns the same output. If an upstream input's text changes, the picks re-roll even with a fixed seed (it's mixed into the effective seed).
  • wildcard_directory - where the .txt wildcard files live. Defaults to the pack's own wildcards folder, which ships a few example files.
  • line_suffix - appends a string to every line; handy for suffixing tags with commas or periods.
  • single_line_output, remove_whitespaces, remove_empty_tags - leave them on. single_line_output must be True for multi-line combinations to work.

One warning: available_loras_stem, load_loras_from_prompt, and remove_loras_pattern are compatibility placeholders kept only to preserve widget order in old workflows. This node no longer loads LoRAs - remove_loras_pattern just strips LoRA tags from the output. Want the LoRA actually applied? Add a LoraLoader.

The optional inputs are where the power is: prompt (the editor text), in1–in4 (string sockets you reference as <in1>–<in4>; they're lazy - the upstream branch only runs if the reference appears in your prompt), and vars_in (variables handed over from another VSmartPrompt, so values travel down a chain). Outputs: prompt (resolved), original_prompt, variables.

A quick example

a {photo|{oil|water} painting} of a {0.7::cute|scary} __animal__==<pet>
the <pet> naps in a (cozy:1.2) {garden|kitchen}. {day|night}==!<time>
<time>==day::{sunlight warms the <pet>|birds sing}

With the picks shown, you get something like "a photo of a cute fox. the fox naps in a cozy kitchen." - one node, one seed, a different scene every queue.

Installing

Install via ComfyUI Manager (search "ValiTools" or "comfyui-ValiTools"), or clone it:

cd ComfyUI/custom_nodes
git clone https://github.com/vangel76/comfyui-ValiTools

Then restart ComfyUI. No pip install and no model files - the pack is pure Python plus frontend JS, using only numpy/torch/PIL (already in ComfyUI) on its image-loading side.

Where people get burned

  • A wildcard stays literal (__animal__ ends up in your output) when animal.txt doesn't exist in wildcard_directory. The editor highlights missing wildcards red precisely so you spot this before you queue.
  • "Which prompt made this image?" is the classic dynamic-prompt pain - the workflow PNG stores the template, not the roll. Wire the prompt output into a Show Text node, and the exact resolved string gets embedded with the workflow, so you can always recover it.
  • (word:1.2) weighting passes through untouched - that's ComfyUI-native, and the node doesn't interpret it. Note it's meaningless on LLM-encoded models (Flux 2, Z-Image, Anima); on CLIP-based SDXL-lineage checkpoints it still works.
  • v1.5.1 fixed a real RNG bias where reseeding per pass locked different draws together. The fix means a given seed now produces a different (unbiased) result than older versions - don't expect archived seed-and-image pairs to reproduce.
CategoryValiTools

Inputs (17)

NameTypeDefaultDescription
available_loras_stemSTRINGCompatibility placeholder for older workflows. This field is kept only to preserve widget ordering.
seedINT00–18446744073709550000Same seed + same prompt + same connected in1-in6 texts always returns the same output prompt. Changed input text re-rolls the picks even with a fixed seed.
line_suffixSTRINGAppends this string to the end of every line. Useful to automate suffixing of tags and descriptive text with either commas or single dots.
single_line_outputBOOLEANtrueJoin all lines into one with spaces. Turn OFF to keep the line structure, e.g. for MiniMax H3 field blocks or Seedance shot lists. Multi-line combinations resolve the same either way.
remove_whitespacesBOOLEANtrueTrim every line and collapse runs of spaces, and drop empty lines. Turn OFF to keep blank lines, e.g. between H3 field blocks.
remove_empty_tagsBOOLEANfalse'tags' here is anything between dots or commas. Fixes cases like this: 'cat,, , dog' -> 'cat, dog'. Off by default; ellipses (...) are kept either way.
load_loras_from_promptBOOLEANtrueCompatibility placeholder for older workflows. LoRA loading is no longer handled by this node.
remove_loras_patternBOOLEANtrueCompatibility placeholder for older workflows. When enabled, LoRA tags are stripped from the final prompt text.
wildcard_directorySTRING/tmp/ComfyUI/custom_nodes/comfyui-ValiTools/wildcardsThe directory where TXT wildcard files are stored.
promptoptSTRING### VSmartPrompt syntax. '#' lines are comments and never reach the output. ### AI: follow these rules literally, then output only the prompt itself. ## COMBINATION - one option is picked # {a|b} equal chance {a|} empty option = output nothing # {0.7::a|b} weight 0..1, unweighted options share what is left # nesting allowed everywhere ## WILDCARD - one random LINE from a .txt in the wildcard directory # __name__ reads 'name.txt', no extension __folder\name__ subfolder # a missing file stays in the text (shown RED in the editor) # FILE FORMAT: one option per line. Never wrap the file in braces, never start # lines with '|' - the line is picked before anything is resolved. A block is # only allowed inside a file if it sits on ONE line: a {red|blue} dress ## VARIABLE - rolled once, same value everywhere # {a|b}==<v> store the picked option and output it here # __file__==<v> store the pulled line # word==<v> store the ONE word before '==' (no spaces) # {two words}==<v> store several words # {a|b}==!<v> store WITHOUT outputting anything here # <v> output it, anywhere, even above its assignment # never assigned -> stays as text (RED). assigned twice -> last one wins. ## SWITCHER - show a block only for certain values # <v>==value::{...} fires when <v> is that value; works on __wildcards__ too # <v>!=value::{...} fires for every other value # <v>==a,b,c::{...} OR list <v>!=a,b,c:: none of them # case-insensitive. must TOUCH the brace. assign <v> EARLIER in the text. ## COMMENT # block comments run from /# to #/ and may span lines; the other forms are # listed inside such a block so their hashes cannot pair up with each other: /# # text to the end of the line a #note# b inline, between two single hashes ## text headline style ### text bigger headline style #/ ## TRAPS # '==<v>' binds to the ONE construct in front of it: # wrong {a}, {b}==<v> right {a, b}==<v> # a CONDITION value is plain text - no braces, they would be rolled: # wrong <v>=={upper body}:: right <v>==upper body:: # an ASSIGNMENT of several words needs braces: # wrong upper body==<v> right {upper body}==<v> # an assignment inside a branch only fires if that branch is picked ## EXAMPLE {__names__, __bodytypes__}==!<woman> {kitchen==!<room>|bedroom==!<room>|garden==!<room>} <woman> stands in the <room>. <room>==kitchen::{She is chopping vegetables|She is washing a plate}. <room>!=kitchen::{There is no knife in sight}. ## OUTPUT MODE - target model syntax, set with the dropdown in the editor toolbar # /# mode: seedance #/ put this directive anywhere; omit it for normal output # normal everything above, nothing else # h3 t2v / h3 r2v H3 speech and structure tags (<d>, <i>, <breath>, ...) are # never read as variables, even if a variable of that name exists # seedance '{...}' stays literal text unless it holds a '|' or a '::' weight, # an '==<v>' follows it, or a switcher guard '<v>==x::' precedes it - # so spoken lines like {Hello, world} reach the output unchanged # while gated blocks still switch. Literal braces are blue in the editor. # the full syntax above keeps working in every mode ## EDITOR # CTRL+Click a wildcard edits its file. Type '__' or '<' for a dropdown. # CTRL+F find, CTRL+Z undo, CTRL+UP/DOWN weighting, CTRL+Wheel font size. # After a run the picked options are white; hover shows the rolled value. —
in1optSTRINGExternal text, reference it in the prompt as <in1>. Inserted as-is (not re-resolved). The upstream branch only executes if <in1> appears in the prompt.
in2optSTRINGExternal text, reference it in the prompt as <in2>. Inserted as-is (not re-resolved). The upstream branch only executes if <in2> appears in the prompt.
in3optSTRINGExternal text, reference it in the prompt as <in3>. Inserted as-is (not re-resolved). The upstream branch only executes if <in3> appears in the prompt.
in4optSTRINGExternal text, reference it in the prompt as <in4>. Inserted as-is (not re-resolved). The upstream branch only executes if <in4> appears in the prompt.
in5optSTRINGExternal text, reference it in the prompt as <in5>. Inserted as-is (not re-resolved). The upstream branch only executes if <in5> appears in the prompt.
in6optSTRINGExternal text, reference it in the prompt as <in6>. Inserted as-is (not re-resolved). The upstream branch only executes if <in6> appears in the prompt.
vars_inoptVS_VARSVariables from another VSmartPrompt: wire its 'variables' output here and every <name> it assigned is available in this prompt (and is passed on again).

Outputs (3)

NameTypeDescription
promptSTRING—
original_promptSTRING—
variablesVS_VARS—