While queueing the cartesian product of jobs, each FlakeCombo and FlakeModelCombo node highlights (blue border + glow) the flake/preset being used for the current combination via a transient _combo_generating_index, cleared when generation finishes.
150 lines
5.2 KiB
JavaScript
150 lines
5.2 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
|
|
export function getComboFlakes(node) {
|
|
return node.properties?._combo_flakes || [];
|
|
}
|
|
|
|
export function getComboPresets(node) {
|
|
return node.properties?._combo_presets || [];
|
|
}
|
|
|
|
// Active (non-bypassed) flakes for a FlakeCombo node.
|
|
function activeComboFlakes(node) {
|
|
return getComboFlakes(node).filter(f => !f.bypassed);
|
|
}
|
|
|
|
// Number of prompts a generation will queue: the product of each
|
|
// FlakeCombo's active-flake count and each FlakeModelCombo's preset count.
|
|
// Returns 1 when there are combo nodes but a single combination, or 1 when
|
|
// there are no combo nodes at all (a normal single queue).
|
|
export function computeJobCount(graph) {
|
|
const g = graph || app.graph;
|
|
if (!g) return 1;
|
|
let count = 1;
|
|
for (const node of g.nodes) {
|
|
if (node.type === "FlakeCombo") {
|
|
const n = activeComboFlakes(node).length;
|
|
if (n > 0) count *= n;
|
|
} else if (node.type === "FlakeModelCombo") {
|
|
const n = getComboPresets(node).length;
|
|
if (n > 0) count *= n;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
export function cartesianProduct(arrays) {
|
|
if (arrays.length === 0) return [[]];
|
|
const result = [];
|
|
const head = arrays[0];
|
|
const tail = cartesianProduct(arrays.slice(1));
|
|
for (const h of head) {
|
|
for (const t of tail) {
|
|
result.push([h, ...t]);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const _originalQueuePrompt = app.queuePrompt;
|
|
app.queuePrompt = async function(number, batchCount = 1) {
|
|
const comboNodes = app.graph.nodes.filter(n => n.type === "FlakeCombo");
|
|
const modelComboNodes = app.graph.nodes.filter(n => n.type === "FlakeModelCombo");
|
|
|
|
if (comboNodes.length === 0 && modelComboNodes.length === 0) {
|
|
return _originalQueuePrompt.call(this, number, batchCount);
|
|
}
|
|
|
|
const optionsArrays = [];
|
|
|
|
for (const node of comboNodes) {
|
|
const flakes = getComboFlakes(node);
|
|
if (flakes.length === 0) {
|
|
window.alert("FlakeCombo node has no flakes selected.");
|
|
return;
|
|
}
|
|
// Bypassed (crossed-out) flakes are excluded from the combinatorial
|
|
// job set — only active flakes contribute to the queue.
|
|
const activeFlakes = flakes
|
|
.map((flake, i) => ({ flake, i }))
|
|
.filter(({ flake }) => !flake.bypassed);
|
|
if (activeFlakes.length === 0) {
|
|
window.alert("FlakeCombo node has no active (non-bypassed) flakes.");
|
|
return;
|
|
}
|
|
optionsArrays.push(activeFlakes.map(({ flake, i }) => ({
|
|
node,
|
|
type: "combo",
|
|
value: flake,
|
|
index: i,
|
|
})));
|
|
}
|
|
|
|
for (const node of modelComboNodes) {
|
|
const presets = getComboPresets(node);
|
|
if (presets.length === 0) {
|
|
window.alert("FlakeModelCombo node has no presets selected.");
|
|
return;
|
|
}
|
|
optionsArrays.push(presets.map((preset, i) => ({
|
|
node,
|
|
type: "model_combo",
|
|
value: preset,
|
|
index: i,
|
|
})));
|
|
}
|
|
|
|
const combinations = cartesianProduct(optionsArrays);
|
|
if (combinations.length === 0) {
|
|
return _originalQueuePrompt.call(this, number, batchCount);
|
|
}
|
|
|
|
// Save original widget values (once per unique node)
|
|
const nodeOriginals = new Map();
|
|
for (const item of combinations[0]) {
|
|
if (!nodeOriginals.has(item.node.id)) {
|
|
if (item.type === "combo") {
|
|
const w = item.node.widgets?.find(w => w.name === "flakes_json");
|
|
nodeOriginals.set(item.node.id, { node: item.node, widget: w, value: w?.value });
|
|
} else {
|
|
const w = item.node.widgets?.find(w => w.name === "preset");
|
|
nodeOriginals.set(item.node.id, { node: item.node, widget: w, value: w?.value });
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
for (const combination of combinations) {
|
|
for (const item of combination) {
|
|
if (item.type === "combo") {
|
|
const w = item.node.widgets?.find(w => w.name === "flakes_json");
|
|
if (w) w.value = JSON.stringify([item.value]);
|
|
// Highlight the flake being generated on its combo node
|
|
item.node._combo_generating_index = item.index;
|
|
item.node._combo_render?.();
|
|
} else {
|
|
const w = item.node.widgets?.find(w => w.name === "preset");
|
|
if (w) w.value = item.value;
|
|
item.node._model_combo_generating_index = item.index;
|
|
item.node._model_combo_render?.();
|
|
}
|
|
}
|
|
await _originalQueuePrompt.call(this, number, 1);
|
|
}
|
|
} finally {
|
|
for (const orig of nodeOriginals.values()) {
|
|
if (orig.widget) orig.widget.value = orig.value;
|
|
}
|
|
for (const n of app.graph.nodes) {
|
|
if (n.type === "FlakeCombo") {
|
|
delete n._combo_generating_index;
|
|
n._combo_render?.();
|
|
}
|
|
if (n.type === "FlakeModelCombo") {
|
|
delete n._model_combo_generating_index;
|
|
n._model_combo_render?.();
|
|
}
|
|
}
|
|
}
|
|
};
|