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.
69 lines
3.1 KiB
Python
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)
|