Files

108 lines
4.2 KiB
JavaScript

import { app } from "../../scripts/app.js";
import { invalidateList } from "./api.js";
import { refreshPresetOptions } from "./preset-modal.js";
// Workflow-local flakes and presets (#437) are staged as "_local/<hash>/<name>".
const LOCAL_PREFIX = /^_local\/[0-9a-f]+\//;
function* graphNodes() {
for (const graph of [app.graph, ...(app.graph.subgraphs?.values() ?? [])]) yield* graph.nodes;
}
function flakeEntries(node) {
const widget = node.widgets?.find(w => w.name === "flakes_json");
return [widget, widget ? JSON.parse(widget.value || "[]") : []];
}
// Point the nodes' flakes and presets at their new names. A name staged from
// an earlier bundle matches by its original name.
function renameInNodes({ flakes, presets }) {
const renamed = (map, name) => map[name] ?? map[name?.replace(LOCAL_PREFIX, "")] ?? name;
for (const node of graphNodes()) {
const [flakesWidget, entries] = flakeEntries(node);
if (entries.some(e => renamed(flakes, e.name) !== e.name)) {
for (const e of entries) e.name = renamed(flakes, e.name);
flakesWidget.value = JSON.stringify(entries);
(node._flakes_render ?? node._combo_render)?.();
}
const presetWidget = node.widgets?.find(w => w.name === "preset");
const combo = node.properties?._combo_presets;
if (presetWidget && [presetWidget.value, ...(combo ?? [])].some(p => renamed(presets, p) !== p)) {
presetWidget.value = renamed(presets, presetWidget.value);
if (combo) {
node.properties._combo_presets = combo.map(p => renamed(presets, p));
node.properties._combo_bypassed = (node.properties._combo_bypassed ?? []).map(p => renamed(presets, p));
}
(node._preset_render ?? node._model_combo_render)?.();
}
}
app.graph.setDirtyCanvas(true, true);
}
function graphUsesLocal() {
for (const node of graphNodes()) {
const names = [...flakeEntries(node)[1].map(e => e.name), node.widgets?.find(w => w.name === "preset")?.value, ...(node.properties?._combo_presets ?? [])];
if (names.some(n => LOCAL_PREFIX.test(n ?? ""))) return true;
}
return false;
}
// "Save to disk" on a local flake or preset: installs it and the flakes it
// links to, then points the workflow at the saved names. The bundle goes once
// nothing local is left.
export async function saveLocalToDisk(name) {
const r = await fetch("/flakes/bundle/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
});
const result = await r.json();
if (!r.ok) {
window.alert(`Failed to save '${name}' to disk: ${result.error}`);
return;
}
invalidateList();
await refreshPresetOptions();
renameInNodes(result);
if (!graphUsesLocal()) delete app.graph.extra?.flake_bundle;
}
// Staging results by bundle JSON: a tab switch reloads a bundle this session
// already staged.
const STAGED = new Map();
function stageBundle(bundle) {
const body = JSON.stringify(bundle);
if (!STAGED.has(body)) {
STAGED.set(body, fetch("/flakes/bundle/stage", {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
}).then(async r => {
const result = await r.json();
if (!r.ok) throw new Error(`[flakes] bundle staging failed: ${result.error}`);
return result;
}).catch(e => {
STAGED.delete(body);
throw e;
}));
}
return STAGED.get(body);
}
// A dropped Flake Generate image carries the flakes and presets it used in
// workflow.extra.flake_bundle (#427). They stay local to the workflow (#437):
// staged in ComfyUI's temp folder, nothing installed on disk. Loading doesn't
// wait for staging; the nodes are renamed once it's done, unless another
// workflow was loaded meanwhile.
app.registerExtension({
name: "comfyui-flakes.Bundle",
afterConfigureGraph() {
const bundle = app.graph.extra?.flake_bundle;
if (!bundle) return;
stageBundle(bundle).then(result => {
if (app.graph.extra?.flake_bundle === bundle) renameInNodes(result);
});
},
});