Steps, Sampler and Scheduler are on the main window -- the three v4 buried several clicks deep in Engine Settings. No new UI: the sampler slot declares roles the way the prompt and the size already do. What made that nearly free is that the controls now come from the NODE's own description, through the settingFor/rendererFor pair the pipeline window has always used. So a combo is a dropdown of the real choices and a number obeys the real bounds, and prompt.js lost both of its hand-rolled branches: it no longer decides what a control looks like, it asks. File: New project, and every project listed with the open one ticked. The store has been able to do this since the day it arrived and there was no way in -- 20 lines of menu. Three faults found on the way, all in the shared input helper: * ENTER COULD NOT BE TYPED IN A TEXTAREA. `wire()` bound Enter to commit for every text control, textareas included, so a multi-line value was impossible -- the prompt above all, which is the longest multi-line thing in the app. It has been that way in the pipeline window since it was written; nobody had typed a second line into one. * A control committed on blur alone, so autofill, a password manager and any script setting a value went unheard. It listens to `change` as well now, and commit() only acts on a real difference so the pair costs nothing. * A control commits on blur, which means text still being typed is not in the project yet -- and a tab closing is exactly when that is lost. boot blurs before it saves.
87 lines
4.4 KiB
Python
87 lines
4.4 KiB
Python
"""The default pipeline, as data.
|
|
|
|
Every slot here can be pointed at a different node or taken out entirely, and
|
|
that is only true because this is a list rather than a function. v4's equivalent
|
|
was a `CORE` dict inside the builder, so "remove this node" had nothing to
|
|
delete and "use a different node" had nothing to change -- the pipeline was code,
|
|
and code is not a thing a user can edit.
|
|
|
|
The rule for what belongs here: enough to generate with nothing else wired. A
|
|
run with no overrides at all has to produce a picture, because that is what makes
|
|
the UI an override layer rather than a requirement.
|
|
|
|
Nothing about these slots is privileged. Core does not know this list exists; it
|
|
is handed one and builds it.
|
|
|
|
`roles` is how a slot puts one of its inputs on the app's own surface: the
|
|
prompt is a slot input like any other, and typing it into a box beside the
|
|
picture rather than digging for the node it belongs to is the whole difference
|
|
between a pipeline and an app. The name is a mount point, so a role naming a
|
|
place this shell does not offer is simply not shown.
|
|
|
|
`group` is the pipeline's own idea of how it reads to a person -- the cards the
|
|
app shows are these names, in the order they first appear. It travels on the
|
|
slot rather than living in the app, so a pipeline that replaced every slot keeps
|
|
its own arrangement, and a group is created by a slot claiming a name nobody
|
|
used yet rather than by registering it anywhere.
|
|
"""
|
|
|
|
ID = "pipeline"
|
|
TITLE = "Pipeline"
|
|
STAGE = "load"
|
|
STATUS = "proven"
|
|
|
|
# Slot ids are stable: they are how an override says which slot it means, and
|
|
# they end up as node ids in the queued prompt.
|
|
DEFAULT = [
|
|
{"id": "model", "group": "Loaders", "node": "FunPackDiffusionModelLoader", "inputs": {
|
|
"weight_dtype": "default", "compute_dtype": "default", "attention": "default"}},
|
|
{"id": "clip", "group": "Loaders", "node": "FunPackCLIPLoader", "inputs": {"type": "stable_diffusion"}},
|
|
{"id": "vae", "group": "Loaders", "node": "FunPackVAELoader", "inputs": {"dtype": "bfloat16"}},
|
|
|
|
{"id": "positive", "group": "Preparation", "node": "CLIPTextEncode",
|
|
"roles": [{"at": "generation.prompt", "input": "text", "label": "Prompt"}],
|
|
"inputs": {"clip": ["clip", 0], "text": ""}},
|
|
{"id": "negative", "group": "Preparation", "node": "CLIPTextEncode",
|
|
"roles": [{"at": "generation.prompt", "input": "text", "label": "Negative"}],
|
|
"inputs": {"clip": ["clip", 0], "text": ""}},
|
|
|
|
{"id": "latent", "group": "Preparation", "node": "FunPackEmptyLatent",
|
|
"roles": [{"at": "project.video", "input": "width", "label": "Width"},
|
|
{"at": "project.video", "input": "height", "label": "Height"},
|
|
{"at": "project.video", "input": "length", "label": "Length"}],
|
|
"inputs": {
|
|
"model": ["model", 0], "width": 512, "height": 512, "length": 1, "batch_size": 1}},
|
|
|
|
{"id": "settings", "group": "Preparation", "node": "FunPackModifierSettings", "inputs": {"settings": "{}"}},
|
|
{"id": "modifiers", "group": "Preparation", "node": "FunPackLoadModifiers", "inputs": {
|
|
"model": ["model", 0], "settings": ["settings", 0]}},
|
|
|
|
{"id": "sampler", "group": "Sampling", "node": "FunPackSampler",
|
|
# The three dials that get reached for constantly. v4 buried them several
|
|
# clicks deep in Engine Settings; here they are on the main window because
|
|
# the slot says they belong there.
|
|
"roles": [{"at": "generation.sampling", "input": "steps", "label": "Steps"},
|
|
{"at": "generation.sampling", "input": "sampler_name", "label": "Sampler"},
|
|
{"at": "generation.sampling", "input": "scheduler", "label": "Scheduler"}],
|
|
"inputs": {
|
|
"model": ["modifiers", 0], "positive": ["positive", 0], "negative": ["negative", 0],
|
|
"latent": ["latent", 0], "settings": ["settings", 0],
|
|
"seed": 0, "steps": 20, "cfg": 7.0,
|
|
"sampler_name": "euler", "scheduler": "normal", "denoise": 1.0}},
|
|
|
|
{"id": "decode", "group": "Render", "node": "FunPackDecode", "inputs": {
|
|
"samples": ["sampler", 0], "vae": ["vae", 0], "model": ["model", 0]}},
|
|
|
|
{"id": "save", "group": "Render", "node": "SaveImage", "inputs": {
|
|
"images": ["decode", 0], "filename_prefix": "FunPack"}},
|
|
]
|
|
|
|
|
|
def default():
|
|
"""A fresh copy: a caller that edits the pipeline must not edit everyone's."""
|
|
return [dict(slot, inputs=dict(slot.get("inputs") or {})) for slot in DEFAULT]
|
|
|
|
|
|
PROVIDES = {"default_pipeline": default}
|