new list utility nodes (#13)

* add List-related nodes for Checkpoints / LoRAs / Samplers/ Schedulers
* wiki: reorg XY Plot 3rd tutorial and add example
* bump version
This commit is contained in:
Bug, Ltd.
2025-01-29 11:18:14 +01:00
committed by GitHub
parent 1c6034bbcb
commit 9dcf6660de
45 changed files with 1613 additions and 400 deletions
+30 -25
View File
@@ -82,6 +82,16 @@ const customLabels = {
nb_elements: 'nb of elements',
},
},
ListCheckpoints: {
widget: {
with_extension: 'with file extension?',
},
},
ListLoras: {
widget: {
with_extension: 'with file extension?',
},
},
OutputConfigBackend: {
widget: {
file_path: 'JSON or YAML file (backend)',
@@ -95,31 +105,6 @@ const customLabels = {
show_config: 'show config',
},
},
OutputConfigAuto: {
widget: {
auto_connect: 'auto-connect?',
multi_nodes: 'if multiple nodes found',
error_display: 'display errors',
show_config: 'show config',
},
},
NodeConfigApplyLocal: {
widget: {
error_display: 'display errors',
check_level: 'reject on',
},
},
MultiConfigFetch: {
widget: {
node_by: 'identify nodes by',
},
},
MultiConfigApplyLocal: {
widget: {
error_display: 'display errors',
check_level: 'reject on',
},
},
GenericQueue: {
input: {
input_list: 'input list',
@@ -238,6 +223,26 @@ const customLooks = {
list: { shape: 'grid' },
},
},
ListCheckpoints: {
output: {
list: { shape: 'grid' },
},
},
ListLoras: {
output: {
list: { shape: 'grid' },
},
},
ListSamplers: {
output: {
list: { shape: 'grid' },
},
},
ListSchedulers: {
output: {
list: { shape: 'grid' },
},
},
};
function setNodeLabels(nodeType, nodeData) {
const nodeName = nodeData.name;
+76
View File
@@ -1,4 +1,5 @@
import { findWidget, loadTextFile } from '../shared/utils.js';
import { makeComboWidget } from '../widgets/factories.js';
export function ListFromMultiline(nodeType) {
const original_onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function (...args) {
@@ -13,3 +14,78 @@ export function ListFromMultiline(nodeType) {
};
};
}
export function ListFromSelection(nodeType, selection_type) {
const isCombo = (widget) => widget.name.startsWith(`${selection_type}_`.toLowerCase());
const findCombos = (node) => node.widgets.filter((w) => isCombo(w));
const addCombo = (node, selectionWidget) => {
const index = findCombos(node).length;
const width = node.size[0];
const widget = makeComboWidget(node, `${selection_type}_${index}`.toLowerCase(), ['none', ...selectionWidget.all], 0);
node.setSize([width, node.size[1]]);
widget.label = `${selection_type} #${index + 1}`;
widget.options.serialize = false;
return widget;
};
const refreshSelection = (node, selectionWidget) => {
const selected = [];
let widgetIndex = 0;
let comboIndex = 0;
while (widgetIndex < node.widgets.length) {
const widget = node.widgets[widgetIndex];
if (!isCombo(widget)) {
widgetIndex += 1;
continue;
}
if (widget.selected === 0) {
node.widgets.splice(widgetIndex, 1);
continue;
}
widget.name = `${selection_type}_${comboIndex}`.toLowerCase();
widget.label = `${selection_type} #${comboIndex + 1}`;
selected.push(widget.value);
widgetIndex += 1;
comboIndex += 1;
}
const last = addCombo(node, selectionWidget);
last.onSelected = () => {
refreshSelection(node, selectionWidget);
};
selectionWidget.value = { selected };
};
const refreshFromSelection = (node, selectionWidget) => {
let widgetIndex = 0;
while (widgetIndex < node.widgets.length) {
const widget = node.widgets[widgetIndex];
if (isCombo(widget)) {
node.widgets.splice(widgetIndex, 1);
continue;
}
widgetIndex += 1;
}
const selected = selectionWidget.value.selected;
for (const value of selected) {
const widget = addCombo(node, selectionWidget);
widget.value = value;
widget.callback(widget.value);
widget.onSelected = () => {
refreshSelection(node, selectionWidget);
};
}
const last = addCombo(node, selectionWidget);
last.onSelected = () => {
refreshSelection(node, selectionWidget);
};
};
const original_onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function (...args) {
original_onNodeCreated?.apply(this, ...args);
const selectionWidget = findWidget(this, 'selection');
refreshFromSelection(this, selectionWidget);
};
const original_onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function (...args) {
original_onConfigure?.apply(this, ...args);
const selectionWidget = findWidget(this, 'selection');
refreshFromSelection(this, selectionWidget);
};
}
+18 -5
View File
@@ -3,9 +3,9 @@ import { CATEGORY } from './shared/common.js';
import { customizeNode } from './custom.js';
import { setupDynamicConnections } from './shared/dynamic_connections.js';
import { QUEUE_STATUS } from './widgets/queue.js';
import { HIDDEN, BTN, ERROR_DISPLAY } from './widgets/misc.js';
import { HIDDEN, BTN, ERROR_DISPLAY, SELECTION_LIST } from './widgets/misc.js';
import { OutputConfigBackend, OutputConfigLocal } from './nodes/config.js';
import { ListFromMultiline } from './nodes/list.js';
import { ListFromMultiline, ListFromSelection } from './nodes/list.js';
import { InputMultiline } from './nodes/input.js';
import { DebugJSONYAML, DebugWidgetVisibility } from './nodes/debug.js';
globalThis[CATEGORY] = { log: 'debug' };
@@ -28,6 +28,21 @@ app.registerExtension({
start_index: 1,
});
break;
case 'ListFromMultiline':
ListFromMultiline(nodeType);
break;
case 'ListCheckpoints':
ListFromSelection(nodeType, 'checkpoint');
break;
case 'ListLoras':
ListFromSelection(nodeType, 'LoRA');
break;
case 'ListSamplers':
ListFromSelection(nodeType, 'sampler');
break;
case 'ListSchedulers':
ListFromSelection(nodeType, 'scheduler');
break;
case 'InputMultiline':
InputMultiline(nodeType);
break;
@@ -38,9 +53,6 @@ app.registerExtension({
start_index: 0,
});
break;
case 'ListFromMultiline':
ListFromMultiline(nodeType);
break;
case 'DebugJSONYAML':
DebugJSONYAML(nodeType);
break;
@@ -63,6 +75,7 @@ app.registerExtension({
HIDDEN,
BTN,
ERROR_DISPLAY,
SELECTION_LIST,
};
},
});
+12 -1
View File
@@ -1,6 +1,7 @@
import { makeButtonWidget, makeComboWidget, } from '../widgets/factories.js';
import { makeWidgetAsync } from '../shared/utils.js';
export function HIDDEN(node, inputName, inputData, _app) {
import { log } from '../shared/common.js';
export function HIDDEN(node, inputName, inputData) {
const initialValue = inputData[1].value;
const widget = {
type: 'HIDDEN',
@@ -69,3 +70,13 @@ export function ERROR_DISPLAY(node, name = 'error_display') {
const widget = makeComboWidget(node, name, ERROR_DISPLAY_CHOICES, 'dialog', () => { }, 'how to display detected errors');
return widget;
}
export function SELECTION_LIST(node, inputName, inputData) {
inputData[1].value = { selected: [] };
if (!Array.isArray(inputData[1].all) || !inputData[1].all) {
log.error("SELECTION_LIST: 'all is invalid");
inputData[1].all = [];
}
const widget = HIDDEN(node, inputName, inputData);
widget.all = inputData[1].all;
return widget;
}