Filter sampler presets by the connected model
This commit is contained in:
@@ -102,7 +102,7 @@ KSampler, KSampler (Advanced) and custom sigmas in one node, built on `comfy.sam
|
||||
- **Advanced**: `start_at_step`, `end_at_step`, `add_noise`.
|
||||
- **Recommended settings**: press `Use recommended settings` with a Loader's PIPE connected to fill sampler, scheduler, steps and cfg (nothing changes when you only connect the pipe). They come from the model's sidecar (the most common sampler, scheduler, steps and cfg of the author's example images; prompts are never read), then name hints (turbo / distilled -> 8 steps, cfg 1), then family defaults from community documentation (Krea 2: res_multistep/simple; MiniMax H3: euler/simple; LTX: the LTXV schedule). These are starting points, not official values.
|
||||
- **Step cache**: `step_cache` (off / auto / easycache / lazycache) wraps ComfyUI's own EasyCache and LazyCache, which skip model calls on steps where the output barely changes and work across architectures (including audio+video models). `auto` turns on easycache from 15 steps up; distilled 4-8 step runs should stay off. `cache_threshold` (higher = faster, softer), `cache_start` and `cache_end` set when it may skip. Measured on Anima, 30 steps, RTX 5080: easycache 0.1 skipped 15/30 steps (1.8x, similarity to uncached 0.991), 0.2 skipped 17 (2.05x, 0.978), 0.4 skipped 19 (2.4x, 0.971); lazycache 0.2 gave 2.05x at 0.988. Results vary by model.
|
||||
- **Presets**: save, apply and delete named presets (steps, cfg, sampler, scheduler, denoise, runs, step cache). They are stored in ComfyUI's user folder under `promptforge/sampler_presets.json`.
|
||||
- **Presets**: save, apply and delete named presets (steps, cfg, sampler, scheduler, denoise, runs, step cache). They are stored in ComfyUI's user folder under `promptforge/sampler_presets.json`. Connect the Loader's PIPE to the sampler and the preset list shows only presets for that model family and model, plus generic presets; `show all presets` reveals the rest. A saved preset records the connected family and model, so it keeps matching that model and does not clutter other models.
|
||||
|
||||
Tested starting points at about 1.5 MP (identity tests used a source picture through the edit graphs; every row was rendered and checked):
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ComfyUI-PromptForge"
|
||||
description = "ComfyUI prompt enhancement suite: model/config node with GPU-aware auto settings, prompt enhancer with a preset catalog, and a standalone config finder."
|
||||
version = "1.28.11"
|
||||
version = "1.28.12"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = [
|
||||
"transformers",
|
||||
|
||||
@@ -61,6 +61,10 @@ async def _save_preset_route(request):
|
||||
presets.pop(name, None)
|
||||
else:
|
||||
presets[name] = {field: body["settings"][field] for field in PRESET_FIELDS if field in body.get("settings", {})}
|
||||
for key in ("family", "model"):
|
||||
value = str(body.get(key, "")).strip()
|
||||
if value:
|
||||
presets[name][key] = value
|
||||
_preset_file().parent.mkdir(parents=True, exist_ok=True)
|
||||
_preset_file().write_text(json.dumps(presets, indent=1), encoding="utf-8")
|
||||
return web.json_response(presets)
|
||||
|
||||
@@ -14,7 +14,6 @@ app.registerExtension({
|
||||
onCreated?.apply(this, arguments);
|
||||
const node = this;
|
||||
const widget = (name) => node.widgets.find((item) => item.name === name);
|
||||
let presets = {};
|
||||
|
||||
const apply = (settings) => {
|
||||
for (const [name, value] of Object.entries(settings)) {
|
||||
@@ -26,19 +25,37 @@ app.registerExtension({
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
const loaderNode = () => {
|
||||
const link = app.graph.links[node.inputs?.find((input) => input.name === "pipe")?.link];
|
||||
return app.graph.getNodeById(link?.origin_id);
|
||||
};
|
||||
const model = () => loaderNode()?.promptforgeModelInfo?.();
|
||||
let showAll = false;
|
||||
const presetWidget = node.addWidget("combo", "preset", NO_PRESET, (name) => name !== NO_PRESET && apply(presets[name] ?? {}), { values: [NO_PRESET] });
|
||||
presetWidget.serialize = false;
|
||||
let presets = {};
|
||||
const fits = (entry) => {
|
||||
const current = model();
|
||||
if (!current || !entry.family) return true;
|
||||
return entry.family === current.family && (!entry.model || entry.model === current.model);
|
||||
};
|
||||
const reload = (data) => {
|
||||
presets = data;
|
||||
presetWidget.options.values = [NO_PRESET, ...Object.keys(presets)];
|
||||
const names = Object.keys(presets).filter((name) => showAll || fits(presets[name]));
|
||||
presetWidget.options.values = [NO_PRESET, ...names];
|
||||
if (presetWidget.value !== NO_PRESET && !names.includes(presetWidget.value)) presetWidget.value = NO_PRESET;
|
||||
};
|
||||
api.fetchApi("/promptforge/sampler_presets").then((response) => response.json()).then(reload);
|
||||
const all = node.addWidget("toggle", "show all presets", false, (value) => { showAll = value; reload(presets); }, {});
|
||||
node.promptforgeOnPipe = () => { showAll = false; all.value = false; api.fetchApi("/promptforge/sampler_presets").then((response) => response.json()).then(reload); };
|
||||
all.serialize = false;
|
||||
|
||||
const send = async (body) => reload(await (await api.fetchApi("/promptforge/sampler_presets", { method: "POST", body: JSON.stringify(body) })).json());
|
||||
const save = node.addWidget("button", "Save as preset", null, async () => {
|
||||
const name = window.prompt("Preset name", presetWidget.value !== NO_PRESET ? presetWidget.value : "");
|
||||
if (!name) return;
|
||||
await send({ name, settings: Object.fromEntries(PRESET_FIELDS.map((field) => [field, widget(field).value])) });
|
||||
const current = model() || {};
|
||||
await send({ name, family: current.family || "", model: current.model || "",
|
||||
settings: Object.fromEntries(PRESET_FIELDS.map((field) => [field, widget(field).value])) });
|
||||
presetWidget.value = name;
|
||||
});
|
||||
const remove = node.addWidget("button", "Delete preset", null, async () => {
|
||||
@@ -47,10 +64,7 @@ app.registerExtension({
|
||||
presetWidget.value = NO_PRESET;
|
||||
});
|
||||
// only ever runs when the button is pressed; the loader is whatever is wired to the pipe input right now
|
||||
const loader = () => {
|
||||
const link = app.graph.links[node.inputs?.find((input) => input.name === "pipe")?.link];
|
||||
return app.graph.getNodeById(link?.origin_id);
|
||||
};
|
||||
const loader = loaderNode;
|
||||
const say = (summary, detail) => {
|
||||
const toast = app.extensionManager?.toast;
|
||||
if (toast) toast.add({ severity: "info", summary, detail, life: 4000 });
|
||||
|
||||
Reference in New Issue
Block a user