fix: views redraw when a project's pipeline goes in; project switches wait for edits in flight and load one at a time; modifier settings hide when the pipeline cannot install them; refusals name the setting the person sees; older SD1.x files are recognised; stale load retries are cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
608b4dd566
commit
ef84df4286
+1
-1
@@ -22,7 +22,7 @@ import { composer as c } from "./composer/composer.js";
|
||||
const heard = new Set();
|
||||
const say = (what) => heard.forEach((fn) => { try { fn(what); } catch (err) { console.error(err); } });
|
||||
const on = (fn) => { heard.add(fn); return () => heard.delete(fn); };
|
||||
const project = createProject({ keepConsistent: syncSeparated, onChange: () => say("change"), onOpen: () => say("open"), onError: (err) => console.warn(err) });
|
||||
const project = createProject({ keepConsistent: syncSeparated, onChange: () => say("change"), onOpen: () => say("open"), onError: (err) => console.warn(err), beforeSwitch: () => pipeline.settled() });
|
||||
const pipeline = createPipelineState(api);
|
||||
const generate = createGenerate({ pipeline });
|
||||
linkPipeline({ project, pipeline, onOpen: (fn) => on((what) => { if (what === "open") fn(); }), say: (text) => c.toast.warn({ text }) });
|
||||
|
||||
@@ -5,7 +5,7 @@ export function linkPipeline({ project, pipeline, onOpen, say = () => {} }) {
|
||||
// it is written. A project opened while an open or a save is still in flight must never receive the other's pipeline.
|
||||
// An id, not the object: Undo puts a copy of the same project in place.
|
||||
let owner = null;
|
||||
let retries = 0, opens = 0;
|
||||
let retries = 0, opens = 0, retry = null;
|
||||
// Only a real change is written: opening a project must not rewrite its file.
|
||||
const store = (base, slots, opening) => {
|
||||
const next = { ...base, slots: JSON.parse(JSON.stringify(slots || [])), removed: pipeline.removedIds(), unwired: pipeline.unwiredMap(), whole: pipeline.whole() };
|
||||
@@ -14,6 +14,7 @@ export function linkPipeline({ project, pipeline, onOpen, say = () => {} }) {
|
||||
};
|
||||
|
||||
async function adopt() {
|
||||
clearTimeout(retry); // a retry still waiting is for a project that may no longer be open
|
||||
owner = null;
|
||||
const mine = ++opens, target = project.project && project.project.id;
|
||||
const saved = project.project && project.project.models || {};
|
||||
@@ -30,7 +31,7 @@ export function linkPipeline({ project, pipeline, onOpen, say = () => {} }) {
|
||||
// An old project with nothing saved runs the default and stays as it is on disk; a new one keeps what it took.
|
||||
if (own || project.fresh) store(saved, pipeline.slots(), !project.fresh); // a new project's whole flag is a fact, not a guess
|
||||
} else if (retries++ < 3) {
|
||||
setTimeout(adopt, 5000);
|
||||
retry = setTimeout(adopt, 5000);
|
||||
} else {
|
||||
say("This project's saved pipeline could not be loaded; the default is in use.");
|
||||
}
|
||||
@@ -40,6 +41,6 @@ export function linkPipeline({ project, pipeline, onOpen, say = () => {} }) {
|
||||
if (!project.project || project.project.id !== owner) return;
|
||||
store(project.project.models || {}, slots);
|
||||
});
|
||||
onOpen(adopt);
|
||||
onOpen(() => { retries = 0; return adopt(); }); // each project opened gets its own retries
|
||||
return { adopt };
|
||||
}
|
||||
|
||||
@@ -301,12 +301,18 @@ export function createPipelineState(API) {
|
||||
}
|
||||
function isQuarantined(id) { return !!(controlState[id] && controlState[id].quarantine); }
|
||||
// Whether anything would act on a module's settings now: its own node is in the pipeline (when it has one),
|
||||
// and what it serves (the taste store) has a user that is on and not set to a manual value.
|
||||
function useful(m) {
|
||||
const values = currentValues();
|
||||
const working = (u) => { const v = values[u.id] || {}; return (!("enabled" in (u.settings || {})) || v.enabled) && v.mode !== "manual"; };
|
||||
const inPipeline = !(m.nodes || []).length || !slots || m.nodes.some((n) => slots.some((s) => s.node === n));
|
||||
const served = !(m.serves || []).length || activeModules().some((u) => working(u) && (u.uses || []).some((cap) => m.serves.includes(cap)));
|
||||
// and what it serves (the modifier loader, the taste store) has a user that is on, not set to a manual value,
|
||||
// has every node it needs in the pipeline, and is itself acted on (a taste learner the loader never installs is not).
|
||||
function useful(m, values = currentValues(), seen = new Set([m.id])) {
|
||||
const has = (n) => !slots || slots.some((s) => s.node === n);
|
||||
const working = (u) => {
|
||||
const v = values[u.id] || {};
|
||||
return !seen.has(u.id) && (!("enabled" in (u.settings || {})) || v.enabled) && v.mode !== "manual"
|
||||
&& (u.nodes || []).every(has) && useful(u, values, new Set([...seen, u.id]));
|
||||
};
|
||||
const inPipeline = !(m.nodes || []).length || m.nodes.some(has);
|
||||
// Every capability it offers must reach a working user: the taste key is installed by the loader AND read by a learner.
|
||||
const served = (m.serves || []).every((cap) => activeModules().some((u) => (u.uses || []).includes(cap) && working(u)));
|
||||
return inPipeline && served;
|
||||
}
|
||||
|
||||
@@ -338,7 +344,8 @@ export function createPipelineState(API) {
|
||||
// get that from this promise as written -- it would need save() itself
|
||||
// reworked to resolve each queued caller at ITS OWN write's landing, not
|
||||
// at whichever write happens to be in flight when the queue drains.
|
||||
function setModuleValue(moduleId, name, value) {
|
||||
async function setModuleValue(moduleId, name, value) {
|
||||
if (adoptGate) await adoptGate; // the values tree must be the pipeline this lands on, not the one going out
|
||||
pendingValues[moduleId] = { ...(pendingValues[moduleId] || {}), [name]: value };
|
||||
const values = currentValues();
|
||||
mirrorValues(values);
|
||||
@@ -465,14 +472,23 @@ export function createPipelineState(API) {
|
||||
// files, node values, the module-settings blob), group and bypass win on every slot the
|
||||
// default still has; slots they added are kept. The server's own defaults fill in whatever
|
||||
// the saved copy predates, so an update's new settings are not lost to an old project.
|
||||
// Not announced as a change: it IS the project's own copy.
|
||||
// Announced once it is in, so views redraw; the project link writes nothing while a project is going in.
|
||||
// True when the project's pipeline is in (or it had none to put in); false when it could not be.
|
||||
// `inherit`: a project with nothing saved keeps the live pipeline (a new one); otherwise it gets the default.
|
||||
async function adopt(saved, removedIds, unwiredMap, wholeSaved, inherit = true) {
|
||||
let open;
|
||||
const gate = new Promise((r) => { open = r; });
|
||||
const gate = new Promise((r) => { open = r; }), before = adoptGate;
|
||||
adoptGate = gate; // before anything awaits: an edit right behind this waits too
|
||||
try { await ensureLoaded(); return await _adopt(saved, removedIds, unwiredMap, wholeSaved, inherit); } finally { if (adoptGate === gate) adoptGate = null; open(); }
|
||||
try {
|
||||
if (before) await before; // one at a time: a project opened behind another is put in after it, never under it
|
||||
await ensureLoaded();
|
||||
return await _adopt(saved, removedIds, unwiredMap, wholeSaved, inherit);
|
||||
} finally { if (adoptGate === gate) adoptGate = null; open(); _changed(); }
|
||||
}
|
||||
|
||||
/** Resolves once no edit is on its way and no project's pipeline is going in (at most ~10 s). */
|
||||
async function settled() {
|
||||
for (let waited = 0; (saving || adoptGate) && waited < 10000; waited += 20) await new Promise((r) => setTimeout(r, 20));
|
||||
}
|
||||
|
||||
async function _adopt(saved, removedIds, unwiredMap, wholeSaved, inherit) {
|
||||
@@ -561,7 +577,7 @@ export function createPipelineState(API) {
|
||||
|
||||
return {
|
||||
frozenInputs,
|
||||
ensureLoaded, save, edit, restore, snapshot, setGroup, adopt, subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }, valuesAlreadyPlaced, currentValues, setModuleValue,
|
||||
ensureLoaded, save, edit, restore, snapshot, setGroup, adopt, settled, subscribe: (fn) => { listeners.add(fn); return () => listeners.delete(fn); }, valuesAlreadyPlaced, currentValues, setModuleValue,
|
||||
modulesById: () => modulesById,
|
||||
activeModules, useful, isOff, setOff, allOff, setAllOff, refreshControl, control: () => controlState,
|
||||
removedIds: () => [...removed],
|
||||
|
||||
@@ -55,7 +55,7 @@ const put = (project) => json("PUT", `${BASE}/${encodeURIComponent(project.id)}`
|
||||
const TYPING = 1200; // ms after the last keystroke that still counts as typing
|
||||
const KEPT = ["editor_settings", "models"];
|
||||
|
||||
export function createProject({ onChange, onError, onOpen, keepConsistent } = {}) {
|
||||
export function createProject({ onChange, onError, onOpen, keepConsistent, beforeSwitch = async () => {} } = {}) {
|
||||
let project = null;
|
||||
let selected = null;
|
||||
// Every project this browser knows about, for the File menu. Read once at
|
||||
@@ -216,7 +216,7 @@ export function createProject({ onChange, onError, onOpen, keepConsistent } = {}
|
||||
|
||||
async open(id) {
|
||||
const mine = ++generation;
|
||||
await flush();
|
||||
await beforeSwitch(); await flush(); // an edit still landing elsewhere (the pipeline) reaches THIS project first
|
||||
const found = await read(id);
|
||||
// A newer switch already won this race (another open/new/import started
|
||||
// after this one and has already landed) -- applying this one now would
|
||||
@@ -232,7 +232,7 @@ export function createProject({ onChange, onError, onOpen, keepConsistent } = {}
|
||||
|
||||
async newProject(name) {
|
||||
const mine = ++generation;
|
||||
await flush();
|
||||
await beforeSwitch(); await flush();
|
||||
const made = await create(name);
|
||||
if (mine !== generation) {
|
||||
// Superseded by a newer switch -- this one already created a real
|
||||
@@ -264,7 +264,7 @@ export function createProject({ onChange, onError, onOpen, keepConsistent } = {}
|
||||
*/
|
||||
async importProject(data) {
|
||||
const mine = ++generation;
|
||||
await flush();
|
||||
await beforeSwitch(); await flush();
|
||||
const imported = await json("POST", `${BASE}/import`, data);
|
||||
if (mine !== generation) {
|
||||
remove(imported.id).catch(() => {}); // superseded -- see newProject()
|
||||
|
||||
@@ -92,3 +92,19 @@ test("a new project keeps the preset pipeline it inherits as whole, so it reopen
|
||||
await opened();
|
||||
assert.equal(writes[0].whole, true);
|
||||
});
|
||||
|
||||
test("a retry left over from a project that failed to load never reloads the one opened after it", async (t) => {
|
||||
t.mock.timers.enable({ apis: ["setTimeout"] });
|
||||
let opened, calls = 0, fail = true;
|
||||
const project = { project: { id: "a", models: { slots, removed: [], unwired: {}, whole: false } }, setField: () => {} };
|
||||
const pipeline = { adopt: async () => { calls += 1; return !fail; }, slots: () => slots, removedIds: () => [], unwiredMap: () => ({}), whole: () => false, subscribe: () => {} };
|
||||
linkPipeline({ project, pipeline, onOpen: (fn) => { opened = fn; } });
|
||||
await opened(); // A fails: a retry is waiting
|
||||
fail = false;
|
||||
project.project = { id: "b", models: { slots, removed: [], unwired: {}, whole: false } };
|
||||
await opened(); // B opens and loads
|
||||
const before = calls;
|
||||
t.mock.timers.tick(6000);
|
||||
await new Promise((r) => setImmediate(r));
|
||||
assert.equal(calls, before);
|
||||
});
|
||||
|
||||
@@ -38,15 +38,15 @@ test("a project's saved pipeline lays its values over the server's default, keep
|
||||
assert.ok(by.extra);
|
||||
});
|
||||
|
||||
test("adopting is the project's own copy, so it is not announced as an edit; a real edit is", async () => {
|
||||
test("a project's pipeline going in is announced once it is in, so views redraw; a real edit is announced too", async () => {
|
||||
const posts = [];
|
||||
const PS = load(posts);
|
||||
let heard = 0;
|
||||
PS.subscribe(() => { heard++; });
|
||||
let heard = [];
|
||||
PS.subscribe((s) => { heard.push(s.find((x) => x.id === "model").inputs.file); });
|
||||
await PS.adopt([{ id: "model", node: "Loader", inputs: { file: "a" } }]);
|
||||
assert.strictEqual(heard, 0);
|
||||
assert.deepStrictEqual(heard, ["a"]);
|
||||
await PS.save({ inputs: { model: { file: "b" } } });
|
||||
assert.strictEqual(heard, 1);
|
||||
assert.deepStrictEqual(heard, ["a", "b"]);
|
||||
});
|
||||
|
||||
test("nothing saved leaves the session's pipeline as it is", async () => {
|
||||
@@ -370,3 +370,53 @@ test("a save reaches listeners before the model probe answers, so a project swit
|
||||
answer({ detected: true, traits: [] });
|
||||
await saving;
|
||||
});
|
||||
|
||||
const sink = (blob) => ({ id: "settings", node: "Sink", inputs: { settings: JSON.stringify(blob) } });
|
||||
|
||||
test("a setting changed while another project's pipeline goes in is built on that project's values", async () => {
|
||||
const posts = [];
|
||||
const PS = load(posts, { delay: 30 });
|
||||
lastApi.modules = async () => ({ modules: [{ id: "sharpen", settings: { enabled: { type: "bool", default: false }, amount: { type: "float", default: 0.5 } } }] });
|
||||
const edit = lastApi.editPipeline;
|
||||
lastApi.editPipeline = (body) => { // the server writes the whole values blob into the sink, as core's place() does
|
||||
if (body.values) body.slots.filter((x) => x.node === "Sink").forEach((x) => { x.inputs = { settings: JSON.stringify(body.values) }; });
|
||||
return edit(body);
|
||||
};
|
||||
await PS.adopt([sink({ sharpen: { enabled: true, amount: 0.9 } })]);
|
||||
const opening = PS.adopt([sink({ sharpen: { enabled: false, amount: 0.1 } })]);
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
await PS.setModuleValue("sharpen", "amount", 0.5);
|
||||
await opening;
|
||||
assert.deepStrictEqual(PS.currentValues().sharpen, { enabled: false, amount: 0.5 });
|
||||
});
|
||||
|
||||
test("a new project made while another project's pipeline goes in inherits it, never a half-replaced one", async () => {
|
||||
const posts = [];
|
||||
const PS = load(posts, { delay: 30 });
|
||||
await PS.ensureLoaded();
|
||||
const opening = PS.adopt([{ id: "model", node: "Loader", inputs: { file: "A" } }]);
|
||||
const fresh = PS.adopt([], [], {}, undefined, true);
|
||||
let seenByNew = null;
|
||||
await fresh.then(() => { seenByNew = PS.slots().find((s) => s.id === "model").inputs.file; });
|
||||
await opening;
|
||||
assert.strictEqual(seenByNew, "A");
|
||||
});
|
||||
|
||||
test("a modifier's settings count only when the pipeline has both the settings node and the loader that installs it", async () => {
|
||||
const loader = { id: "mods", nodes: ["FunPackModifierSettings", "FunPackLoadModifiers"], uses: ["modifier"] };
|
||||
const sharpen = { id: "sharpen", serves: ["modifier"], settings: { amount: { default: 0.5 } } };
|
||||
const learner = { id: "q", serves: ["modifier"], uses: ["taste_store"], settings: { enabled: { type: "bool", default: true } } };
|
||||
const taste = { id: "taste", serves: ["modifier", "taste_store"], settings: { key: { default: "" } } };
|
||||
const check = async (nodes, mods = [loader, sharpen, learner, taste]) => {
|
||||
const PS = load([]);
|
||||
lastApi.pipeline = async () => ({ slots: nodes.map((node, i) => ({ id: `s${i}`, node, inputs: {} })), incomplete: [], refused: [], queueable: true });
|
||||
lastApi.modules = async () => ({ modules: mods });
|
||||
await PS.ensureLoaded();
|
||||
const by = PS.modulesById();
|
||||
return [PS.useful(by.sharpen), PS.useful(by.taste)];
|
||||
};
|
||||
assert.deepStrictEqual(await check(["FunPackModifierSettings", "FunPackLoadModifiers"]), [true, true]);
|
||||
assert.deepStrictEqual(await check(["FunPackModifierSettings"]), [false, false]); // loader removed: nothing installs them, so no learner reads taste
|
||||
assert.deepStrictEqual(await check(["CheckpointLoaderSimple", "KSampler"]), [false, false]); // a plain imported workflow
|
||||
assert.deepStrictEqual(await check(["FunPackModifierSettings", "FunPackLoadModifiers"], [loader, sharpen, taste]), [true, false]); // the key installed, but no learner reads it
|
||||
});
|
||||
|
||||
@@ -307,7 +307,7 @@ test("an edit made WHILE an import is in flight is still saved, against the old
|
||||
};
|
||||
|
||||
const importing = p.importProject({ name: "Imported", scenes: [] });
|
||||
await Promise.resolve(); await Promise.resolve(); await Promise.resolve();
|
||||
await new Promise((r) => setTimeout(r, 0)); await Promise.resolve(); await Promise.resolve();
|
||||
assert.equal(p.project.id, "abcdef012345", "the import switched before the edit -- this test proves nothing");
|
||||
|
||||
p.project.editor_settings = { autosave_sec: 0.3 };
|
||||
@@ -342,9 +342,9 @@ test("two imports racing land on whichever was clicked last, not whichever answe
|
||||
};
|
||||
|
||||
const first = p.importProject({ name: "A", scenes: [] }); // clicked first
|
||||
await Promise.resolve();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const second = p.importProject({ name: "B", scenes: [] }); // clicked second, while A is still in flight
|
||||
await Promise.resolve();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
// A answers LAST, after B -- a real, ordinary network race, not a
|
||||
// contrived one.
|
||||
@@ -377,9 +377,9 @@ test("a superseded import deletes its own orphaned project instead of leaving it
|
||||
const p = createProject({});
|
||||
await p.start();
|
||||
const first = p.importProject({ name: "A", scenes: [] });
|
||||
await Promise.resolve();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
const second = p.importProject({ name: "B", scenes: [] });
|
||||
await Promise.resolve();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
pending.B(); await second;
|
||||
pending.A(); await first;
|
||||
@@ -407,7 +407,7 @@ test("start() losing a race to a switch the user already made does not revert it
|
||||
|
||||
const p = createProject({});
|
||||
const starting = p.start(); // in flight -- list() has not answered yet
|
||||
await Promise.resolve();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
|
||||
await p.newProject("MyNewThing"); // the user's own action completes first
|
||||
assert.equal(p.project.name, "MyNewThing");
|
||||
@@ -662,3 +662,18 @@ test("an undo keeps the chosen pipeline too", async () => {
|
||||
p.undo();
|
||||
assert.deepEqual(p.project.models, { slots: ["B"] });
|
||||
});
|
||||
|
||||
test("a switch waits for edits still landing elsewhere before it reads the next project", async () => {
|
||||
const order = [];
|
||||
let release;
|
||||
const p = createProject({ beforeSwitch: () => new Promise((r) => { order.push("wait"); release = () => { order.push("settled"); r(); }; }) });
|
||||
await p.start();
|
||||
globalThis.fetch = async (path) => { order.push(`fetch ${String(path).split("/").pop()}`); return { ok: true, status: 200, json: async () => ({ id: "b", name: "B", scenes: [], video: {}, updated_at: 1 }) }; };
|
||||
const opening = p.open("b");
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
assert.deepStrictEqual(order, ["wait"]);
|
||||
release();
|
||||
await opening;
|
||||
assert.deepStrictEqual(order.slice(0, 2), ["wait", "settled"]);
|
||||
assert.equal(p.project.name, "B");
|
||||
});
|
||||
|
||||
+4
-1
@@ -293,7 +293,10 @@ def build(slots: Sequence[dict], schemas: Optional[Schemas] = None) -> Tuple[dic
|
||||
else:
|
||||
problem = unacceptable(name, value, limits.get(name))
|
||||
if problem:
|
||||
problems.append(f"{slot_id}.{problem}")
|
||||
# An input the app writes under a name the person sees (Length, Width) is refused by that name.
|
||||
label = next((r.get("label") for r in slot.get("roles") or []
|
||||
if isinstance(r, dict) and r.get("input") == name and r.get("label")), None)
|
||||
problems.append(unacceptable(label, value, limits.get(name)) if label else f"{slot_id}.{problem}")
|
||||
continue
|
||||
inputs[name] = value
|
||||
for name in schemas.required(class_type):
|
||||
|
||||
@@ -296,6 +296,14 @@ def test_a_number_outside_what_the_node_takes_is_refused():
|
||||
assert any("above the largest" in p for p in problems), problems
|
||||
|
||||
|
||||
def test_a_value_the_app_writes_is_refused_by_the_name_the_person_sees():
|
||||
_prompt, problems = graph.build(
|
||||
[{"id": "model", "node": "Loader", "roles": [{"at": "project.video", "input": "steps", "label": "Length"}],
|
||||
"inputs": {"ckpt_name": "a.safetensors", "steps": 1000}}],
|
||||
_schemas_with_limits())
|
||||
assert any(p.startswith("Length is 1000, above the largest") for p in problems), problems
|
||||
|
||||
|
||||
def test_a_bool_is_not_measured_against_a_number_bound():
|
||||
"""True is 1 in Python, so a naive comparison lets a checkbox be judged
|
||||
against a step count -- and quietly passes it."""
|
||||
|
||||
@@ -287,7 +287,7 @@ def test_the_taste_key_serves_the_store_and_every_module_reading_it_says_so(comf
|
||||
from pathlib import Path
|
||||
from core import routes
|
||||
found = {m["id"]: m for m in routes.manifest()["modules"]}
|
||||
assert found["taste"]["serves"] == ["taste_store"]
|
||||
assert found["taste"]["serves"] == ["modifier", "taste_store"]
|
||||
root = Path(__file__).resolve().parents[2] / "modules"
|
||||
readers = {p.parent.name for p in root.rglob("*.py") if "/tests/" not in str(p)
|
||||
and re.search(r'ask\("taste_store"', p.read_text())}
|
||||
|
||||
@@ -18,13 +18,15 @@ STAGE = "load"
|
||||
CATEGORY = "system"
|
||||
STATUS = "experimental"
|
||||
|
||||
_SIGNATURE_KEYS = ("model.diffusion_model.input_blocks.0.0.weight",
|
||||
"cond_stage_model.transformer.text_model.embeddings.token_embedding.weight")
|
||||
_UNET = "model.diffusion_model.input_blocks.0.0.weight"
|
||||
# Older SD1.x files name the CLIP-L embedding without `text_model.`; ComfyUI renames it on load.
|
||||
_CLIP_L = ("cond_stage_model.transformer.text_model.embeddings.token_embedding.weight",
|
||||
"cond_stage_model.transformer.embeddings.token_embedding.weight")
|
||||
|
||||
|
||||
def detect(keys) -> bool:
|
||||
keyset = set(keys)
|
||||
return all(key in keyset for key in _SIGNATURE_KEYS)
|
||||
return _UNET in keyset and any(key in keyset for key in _CLIP_L)
|
||||
|
||||
|
||||
def probe_traits(keys) -> list:
|
||||
|
||||
@@ -25,6 +25,7 @@ def test_sd1_is_recognised_and_sd2_sdxl_h3_are_not(comfyui):
|
||||
from modules.models import sd15
|
||||
assert sd15.detect({UNET, CLIP_L})
|
||||
assert sd15.probe_traits({UNET, CLIP_L}) == ["spatial_latent", "predict_eps"]
|
||||
assert sd15.detect({UNET, "cond_stage_model.transformer.embeddings.token_embedding.weight"}) # an older SD1.x file's CLIP naming
|
||||
assert not sd15.detect({UNET, "cond_stage_model.model.token_embedding.weight"}) # SD2 (OpenCLIP)
|
||||
assert not sd15.detect({UNET, "conditioner.embedders.0.transformer.text_model.embeddings.token_embedding.weight"}) # SDXL
|
||||
assert not sd15.detect({"video_patch_proj.weight", "audio_patch_proj.weight"}) # H3
|
||||
|
||||
@@ -9,6 +9,7 @@ CATEGORY = "sampling"
|
||||
STATUS = "proven"
|
||||
|
||||
NODES = [FunPackModifierSettings, FunPackLoadModifiers]
|
||||
USES = ["modifier"] # the modifiers it installs act only when both its nodes are in the pipeline
|
||||
|
||||
|
||||
def settings_sink():
|
||||
|
||||
Reference in New Issue
Block a user