Files
DumiFlex-ComfyUI-Wildcard-P…/wp_nodes/assembler_node.py
T
DumiFlex ccbf38af99 feat(nodes): add wp_seedlist + split loop config types + wp_ prefix sweep + tooltips
Three coordinated changes to the WP_* node family.

1. New WP_SeedList node
   Emits a list of N derived seeds for a downstream loop (is_output_list).
   Three independent override toggles (override_seed / override_count /
   override_strategy) each mirror the wired Loop's base_seed / count /
   strategy. Connecting loop_config auto-flips override_count +
   override_strategy ON so the natural intent of wiring in a loop gets
   the defaults right; override_seed stays explicit opt-in.
   All emitted seeds are masked to 50 bits via the new MAX_SAFE_SEED
   constant in engine.seed_derive so values sit inside ComfyUI's
   frontend randomize range and copy-paste reproduces identically in a
   standalone sampler.

2. Context Loop type split + downstream Context seed widget lock
   Split WP_CONTEXT_LOOP_CONFIG (wire payload) from a new
   WP_CONTEXT_LOOP_WIDGET (DOM-widget-only TYPE). Loop's own widget
   mounts on the widget type; the wire type has no factory so consumers
   like WP_SeedList render their loop_config socket as a plain input
   instead of being hidden by a fallback-undefined widget.
   New graph walker hasUpstreamLoopOverridingSeed: WP_Context's widget
   glue greys out the local seed stock widget when an upstream Loop has
   override_seed ON, so users read "this value is dead while the Loop
   drives it" without inspecting the Loop config separately.

3. wp_ prefix sweep + tooltips across every WP_* node input
   Custom DOM widget input names now carry the wp_ prefix so canvas
   socket labels read as branded WP widgets: modules -> wp_modules,
   rows -> wp_rows, viewer -> wp_viewer, cleaner -> wp_cleaner,
   context_loop_config -> wp_context_loop_config. Every stock input
   across the node family gets a tooltip describing what it does and
   when to wire it from another node. Tests + comfy_api stub updated.
   CLAUDE.md notes the wp_ convention for future custom widgets.

Bundle size budget bumped 388 -> 395 KB to fit the SeedList Vue chunk
(~4.9 KB net gzip) + override lock UI; entry stays at ~4 KB gzipped.
2026-05-28 12:40:44 +03:00

69 lines
3.1 KiB
Python

"""WP_PromptAssembler — fills $var placeholders from a PipelineContext."""
from comfy_api.latest import io # pyright: ignore[reportMissingImports]
from engine.context import strip_internals
from engine.template import resolve_variables
from wp_nodes.types import PipelineContext
class WPPromptAssembler(io.ComfyNode):
"""Template-fills $var placeholders using the incoming PipelineContext."""
@classmethod
def define_schema(cls):
return io.Schema(
node_id="WP_PromptAssembler",
display_name="WP Prompt Assembler",
category="wildcard-pipeline",
inputs=[
PipelineContext.Input(
"context",
tooltip=(
"The resolved $variable context from any upstream "
"WP Context / Loop / Injector chain. Required — "
"without it the template can't resolve any $vars."
),
),
io.String.Input(
"template",
multiline=True,
default="",
# Placeholder example shown only while the textarea
# is empty — the workflow JSON stores no template
# by default, so re-creating the node leaves it
# blank instead of pre-populating prose users
# immediately have to delete.
placeholder="A $style portrait of $subject",
tooltip=(
"Your prompt template. Type free text and insert "
"$variable names where the upstream Context's "
"values should appear. Inline {a|b|c} picks render "
"verbatim here — produce randomness in a seeded "
"module instead."
),
),
],
outputs=[io.String.Output("prompt")],
)
@classmethod
def execute(cls, context, template):
# Build the render context from the socket payload. `context.context`
# holds user-named vars including those flagged internal (the
# PIPELINE_CONTEXT socket now propagates internal vars across
# nodes so Combine / Derivation downstream of an internal var
# can still read it). `__wp_internal_flags__` rides in
# `context.internals` as a cross-node-survivor so this assembler
# can re-apply the user's "hide from prompt" intent. Merge the
# flag map back into the render dict, then strip_internals
# drops both engine `__` keys AND user-flagged internal vars
# before resolution — net effect: `$var` for an internal var
# never substitutes in the rendered prompt.
render_ctx = dict(context.context)
flags = (context.internals or {}).get("__wp_internal_flags__")
if isinstance(flags, dict):
render_ctx["__wp_internal_flags__"] = flags
resolved = resolve_variables(template, strip_internals(render_ctx))
return io.NodeOutput(resolved)