Files
StableLlama-ComfyUI-basic_d…/web/js/dynamicnode.js
T
StableLlama 0487dca3e3 Fix dynamic widget removal corrupting create DICT nodes (#65)
Removing an empty dynamic input pair spliced node.widgets directly, which
left the old entries in ComfyUI's widget value store. renumberDynamicInputs
then renamed the inputs, but renaming the widgets silently failed because
the target id was still taken by the orphaned entry. Input names and widget
names drifted apart: the prompt was built from the stale widget names
(key_1..value_2), so DictCreate.create() never found key_0 and returned {},
and the connected input link was drawn next to the wrong widget row.

Remove widgets through node.removeWidget() so the store entry is deleted
too, falling back to an array splice on older litegraph builds.

Verified in ComfyUI 0.43 (frontend 1.57.4): clearing a pair now keeps
inputs and widgets in sync, the link stays on its own row and the node
returns the remaining pairs.
2026-11-03 23:10:11 +01:00

682 lines
31 KiB
JavaScript

/**
* ComfyUI basic data handling.
* Copyright (C) 2025 StableLlama
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Purpose: Manage dynamic input nodes in ComfyUI.
* Inspired by cozy_ex_dynamic
*/
import { app } from "../../../scripts/app.js";
const TypeSlot = {
Input: 1,
Output: 2
};
const TypeSlotEvent = {
Connect: true,
Disconnect: false
};
// Helper for escaping strings to be used in RegExp
const escapeRegExp = (string) => {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
};
app.registerExtension({
name: 'Basic data handling: dynamic input',
async beforeRegisterNodeDef(nodeType, nodeData, appInstance) {
if (!nodeType.comfyClass.startsWith('Basic data handling:')) {
return;
}
const combinedInputData = {
...(nodeData?.input?.required ?? {}),
...(nodeData?.input?.optional ?? {})
};
let combinedInputDataOrder = [];
if (nodeData.input_order) {
if (nodeData.input_order.required) combinedInputDataOrder.push(...nodeData.input_order.required);
if (nodeData.input_order.optional) combinedInputDataOrder.push(...nodeData.input_order.optional);
} else if (nodeData.input) {
if (nodeData.input.required) combinedInputDataOrder.push(...Object.keys(nodeData.input.required));
if (nodeData.input.optional) combinedInputDataOrder.push(...Object.keys(nodeData.input.optional));
}
const dynamicInputs = [];
const dynamicInputGroups = [];
for (const name of combinedInputDataOrder) {
if (!combinedInputData[name]) continue;
const dynamicSetting = combinedInputData[name][1]?._dynamic;
const dynamicGroup = combinedInputData[name][1]?._dynamicGroup ?? 0;
if (dynamicSetting) { // Only process for inputs marked with _dynamic
const inputOptions = combinedInputData[name][1] || {};
const dynamicComfyType = combinedInputData[name][0];
let isActuallyWidget = false;
let effectiveWidgetGuiType;
if (inputOptions.widget?.type) {
isActuallyWidget = true;
effectiveWidgetGuiType = inputOptions.widget.type;
} else if (inputOptions.widget && typeof inputOptions.widget === 'string') {
isActuallyWidget = true;
effectiveWidgetGuiType = inputOptions.widget;
}
else if (inputOptions._widgetType) {
isActuallyWidget = true;
effectiveWidgetGuiType = inputOptions._widgetType;
} else if (inputOptions.widgetType) {
isActuallyWidget = true;
effectiveWidgetGuiType = inputOptions.widgetType;
}
else {
const comfyTypeUpper = String(dynamicComfyType).toUpperCase();
const implicitWidgetGuiTypesMap = {
"STRING": "text", "INT": "number", "FLOAT": "number",
"NUMBER": "number", "BOOLEAN": "toggle"
};
if (implicitWidgetGuiTypesMap[comfyTypeUpper] && inputOptions.forceInput !== true && dynamicComfyType !== '*') {
isActuallyWidget = true;
effectiveWidgetGuiType = implicitWidgetGuiTypesMap[comfyTypeUpper];
} else if (comfyTypeUpper === "COMBO" && inputOptions.values) {
isActuallyWidget = true;
effectiveWidgetGuiType = "combo";
} else {
isActuallyWidget = false;
effectiveWidgetGuiType = dynamicComfyType;
}
}
let determinedBaseName;
let suffixExtractionMatcher;
if (dynamicSetting === 'number') {
const r = /^(.*?)(\d+)$/;
const m = name.match(r);
determinedBaseName = (m && m[1] !== undefined) ? m[1] : name;
suffixExtractionMatcher = new RegExp(`^(${escapeRegExp(determinedBaseName)})(\\d*)$`);
} else if (dynamicSetting === 'letter') {
const r = /^(.*?)([a-zA-Z])$/;
const m = name.match(r);
determinedBaseName = (m && m[1] !== undefined) ? m[1] : name;
suffixExtractionMatcher = new RegExp(`^(${escapeRegExp(determinedBaseName)})([a-zA-Z])$`);
} else {
console.warn(`[Basic Data Handling] Unknown dynamic type: ${dynamicSetting} for input ${name} on node ${nodeType.comfyClass}`);
continue;
}
if (dynamicInputGroups[dynamicGroup] === undefined) {
dynamicInputGroups[dynamicGroup] = [];
}
dynamicInputGroups[dynamicGroup].push(dynamicInputs.length);
dynamicInputs.push({
name,
baseName: determinedBaseName,
matcher: suffixExtractionMatcher,
dynamic: dynamicSetting,
dynamicComfyType,
dynamicGroup,
isWidget: isActuallyWidget,
actualWidgetGuiType: effectiveWidgetGuiType,
originalOptions: inputOptions
});
}
}
if (dynamicInputs.length === 0) {
return;
}
const getDynamicInputDefinition = (inputName) => {
for (const di of dynamicInputs) {
const m = inputName.match(di.matcher);
if (m && m[1] === di.baseName) {
return { definition: di, suffix: m[2] || "" };
}
}
return null;
};
const isDynamicInput = (inputName) => !!getDynamicInputDefinition(inputName);
const updateSlotIndices = (node) => {
node.inputs.forEach((input, index) => {
input.slot_index = index;
if (input.link !== null && input.link !== undefined) {
const linkInfo = node.graph.links[input.link];
if (linkInfo) {
linkInfo.target_slot = index;
}
}
});
};
const getWidgetDefaultValue = (widgetDef) => { // widgetDef = { type: actualWidgetGuiType, options: originalOptions }
const type = widgetDef.type;
const opts = widgetDef.options || {};
switch (String(type).toLowerCase()) {
case 'number':
return opts.default ?? 0;
case 'combo':
return opts.default ?? (opts.values && opts.values.length > 0 ? opts.values[0] : '');
case 'text':
case 'string':
return opts.default ?? '';
case 'toggle':
case 'boolean':
return opts.default ?? false;
default:
return opts.default ?? '';
}
};
const isDynamicInputEmpty = (node, inputIndex) => {
const input = node.inputs[inputIndex];
if (input.link !== null && input.link !== undefined) return false;
const diData = getDynamicInputDefinition(input.name);
if (diData && diData.definition.isWidget) {
if (input.widget && input.widget.name) {
const widget = node.widgets.find(w => w.name === input.widget.name);
if (widget) {
return widget.value === getWidgetDefaultValue({
type: diData.definition.actualWidgetGuiType,
options: diData.definition.originalOptions
});
} else {
return true;
}
} else {
return true;
}
}
return true;
};
const removeWidgetForInput = (node, inputIdx) => {
const widgetName = node.inputs[inputIdx]?.widget?.name;
if (!widgetName) return;
const widget = node.widgets?.find((w) => w.name === widgetName);
if (!widget) return;
// Use the node API so ComfyUI's widget value store is cleaned up as well.
// A plain array splice leaves a stale store entry behind which blocks the
// rename in renumberDynamicInputs, desyncing widget names from input names.
if (typeof node.removeWidget === "function") {
node.removeWidget(widget);
} else {
const widgetIdx = node.widgets.indexOf(widget);
if (widgetIdx !== -1) {
node.widgets.splice(widgetIdx, 1);
}
}
};
nodeType.prototype.getDynamicGroup = function(inputName) {
const diData = getDynamicInputDefinition(inputName);
return diData ? diData.definition.dynamicGroup : undefined;
};
const addStandardWidget = function(name, widgetGuiType, defaultValue, fullConfigOptions = {}) {
let currentVal = defaultValue;
const widgetSpecificOpts = { ...fullConfigOptions };
if (String(widgetGuiType).toLowerCase() === "combo") {
if (!widgetSpecificOpts.values && Array.isArray(fullConfigOptions.default)) {
widgetSpecificOpts.values = fullConfigOptions.default;
} else if (!widgetSpecificOpts.values && fullConfigOptions.options?.values) {
widgetSpecificOpts.values = fullConfigOptions.options.values;
}
if (!widgetSpecificOpts.values) {
widgetSpecificOpts.values = [currentVal];
}
if (widgetSpecificOpts.values && !widgetSpecificOpts.values.includes(currentVal) && widgetSpecificOpts.values.length > 0) {
currentVal = widgetSpecificOpts.values[0];
}
}
return this.addWidget(widgetGuiType, name, currentVal, () => {}, widgetSpecificOpts);
};
nodeType.prototype.addInputAtPosition = function (name, comfyInputType, widgetGuiType, position, isWidgetFlag, shape, widgetDefaultVal, widgetConfOpts) {
if (isWidgetFlag) {
addStandardWidget.call(this, name, widgetGuiType, widgetDefaultVal, widgetConfOpts);
this.addInput(name, comfyInputType, { shape, widget: { name } });
} else {
this.addInput(name, comfyInputType, { shape });
}
const newInputIndex = this.inputs.length - 1;
if (position < newInputIndex && position < this.inputs.length -1 ) {
const newInput = this.inputs.pop();
this.inputs.splice(position, 0, newInput);
}
updateSlotIndices(this);
return this.inputs[position];
};
let isProcessingConnection = false;
const generateDynamicInputName = (dynamicBehavior, baseName, count) => {
if (dynamicBehavior === 'letter') {
return `${baseName}${String.fromCharCode(97 + count)}`;
} else { // 'number'
return `${baseName}${count}`;
}
};
nodeType.prototype.renumberDynamicInputs = function(baseNameToRenumber, dynamicBehavior) {
const inputsToRenumber = [];
for (let i = 0; i < this.inputs.length; i++) {
const input = this.inputs[i];
const diData = getDynamicInputDefinition(input.name);
if (diData && diData.definition.baseName === baseNameToRenumber && diData.definition.dynamic === dynamicBehavior) {
inputsToRenumber.push({
inputRef: input,
widgetName: input.widget ? input.widget.name : null
});
}
}
for (let i = 0; i < inputsToRenumber.length; i++) {
const { inputRef, widgetName } = inputsToRenumber[i];
const newName = generateDynamicInputName(dynamicBehavior, baseNameToRenumber, i);
if (inputRef.name === newName) continue;
if (widgetName) {
const widget = this.widgets.find(w => w.name === widgetName);
if (widget) {
widget.name = newName;
}
if (inputRef.widget) inputRef.widget.name = newName;
}
inputRef.name = newName;
}
updateSlotIndices(this);
};
const handleEmptyDynamicInput = function() { // (Content largely unchanged from previous good version, ensure it uses new DI props if needed)
let overallChangesMade = false;
let scanAgainPass = true;
while (scanAgainPass) {
scanAgainPass = false;
const dynamicGroupInfo = new Map();
for (let i = 0; i < this.inputs.length; i++) {
const currentInput = this.inputs[i];
const diData = getDynamicInputDefinition(currentInput.name);
if (!diData) continue;
const { definition: di, suffix } = diData;
const group = di.dynamicGroup;
if (!dynamicGroupInfo.has(group)) {
dynamicGroupInfo.set(group, { items: new Map() });
}
const groupData = dynamicGroupInfo.get(group);
if (!groupData.items.has(suffix)) {
groupData.items.set(suffix, { inputsInfo: [], allEmpty: true });
}
const itemData = groupData.items.get(suffix);
itemData.inputsInfo.push({ diDefinition: di, originalInput: currentInput, originalIndex: i });
if (!isDynamicInputEmpty(this, i)) { // isDynamicInputEmpty uses new DI props
itemData.allEmpty = false;
}
}
for (const [groupId, groupData] of dynamicGroupInfo) {
const { items } = groupData;
if (items.size === 0) continue;
const sortedItemSuffixes = Array.from(items.keys()).sort((a, b) => {
const numA = parseInt(a, 10);
const numB = parseInt(b, 10);
if (!isNaN(numA) && !isNaN(numB)) return numA - numB;
if (a.length !== b.length) return a.length - b.length;
return String(a).localeCompare(String(b));
});
let activeItemCount = 0;
const emptyItemSuffixes = [];
for (const suffix of sortedItemSuffixes) {
if (items.get(suffix).allEmpty) {
emptyItemSuffixes.push(suffix);
} else {
activeItemCount++;
}
}
if (emptyItemSuffixes.length === 0) continue;
let placeholderSuffix = null;
if (activeItemCount === 0 && sortedItemSuffixes.length > 0) {
placeholderSuffix = sortedItemSuffixes[0];
} else if (activeItemCount > 0 && emptyItemSuffixes.length > 0) {
placeholderSuffix = emptyItemSuffixes[emptyItemSuffixes.length - 1];
}
const inputsToRemoveDetails = [];
for (const suffix of emptyItemSuffixes) {
if (suffix !== placeholderSuffix) {
const itemData = items.get(suffix);
for (const inputDetail of itemData.inputsInfo) {
inputsToRemoveDetails.push(inputDetail);
}
}
}
if (inputsToRemoveDetails.length > 0) {
inputsToRemoveDetails.sort((a, b) => b.originalIndex - a.originalIndex);
for (const { originalInput, originalIndex } of inputsToRemoveDetails) {
if (this.inputs[originalIndex] === originalInput) {
removeWidgetForInput(this, originalIndex);
this.removeInput(originalIndex);
scanAgainPass = true;
overallChangesMade = true;
} else {
scanAgainPass = true;
overallChangesMade = true;
}
}
if (scanAgainPass) break;
}
}
if (scanAgainPass) continue;
}
if (overallChangesMade) {
const renumberedBaseNames = new Set();
for (const groupIdx_str in dynamicInputGroups) {
const groupIdx = parseInt(groupIdx_str, 10);
if (dynamicInputGroups[groupIdx]) {
for (const memberIdx of dynamicInputGroups[groupIdx]) {
const { baseName, dynamic } = dynamicInputs[memberIdx]; // 'dynamic' here is behavior ('number'/'letter')
if (!renumberedBaseNames.has(baseName + dynamic)) { // Ensure unique combination
this.renumberDynamicInputs(baseName, dynamic);
renumberedBaseNames.add(baseName + dynamic);
}
}
}
}
this.setDirtyCanvas(true, true);
}
};
const handleDynamicInputActivation = function(dynamicGroup) {
const { slots: dynamicSlotsFromGetter } = this.getDynamicSlots();
for (const slot of dynamicSlotsFromGetter) {
if (slot.dynamicGroup === dynamicGroup && this.widgets &&
!this.widgets.some((w) => w.name === slot.name)) {
const diData = getDynamicInputDefinition(slot.name);
if (diData && diData.definition.isWidget) { // Use our enhanced definition
const originalInputDef = diData.definition;
addStandardWidget.call(this, slot.name,
originalInputDef.actualWidgetGuiType,
getWidgetDefaultValue({ type: originalInputDef.actualWidgetGuiType, options: originalInputDef.originalOptions }),
originalInputDef.originalOptions
);
}
}
}
let allExistingItemsActive = true;
const itemSuffixesInGroup = new Set();
const groupSlots = dynamicSlotsFromGetter.filter(s => s.dynamicGroup === dynamicGroup);
groupSlots.forEach(s => {
const diData = getDynamicInputDefinition(s.name);
if (diData) itemSuffixesInGroup.add(diData.suffix);
});
if (itemSuffixesInGroup.size === 0 && dynamicInputGroups[dynamicGroup]?.length > 0) {
allExistingItemsActive = true;
} else {
let hasCompletelyEmptyItem = false;
for (const suffix of itemSuffixesInGroup) {
const inputsOfThisItem = groupSlots.filter(s => {
const diData = getDynamicInputDefinition(s.name);
return diData && diData.suffix === suffix;
});
if (inputsOfThisItem.length > 0 && inputsOfThisItem.every(s => !s.connected)) { // s.connected from getDynamicSlots
hasCompletelyEmptyItem = true;
break;
}
}
allExistingItemsActive = !hasCompletelyEmptyItem;
}
if (allExistingItemsActive) {
let lastDynamicIdx = -1;
if (groupSlots.length > 0) {
lastDynamicIdx = Math.max(...groupSlots.map(slot => slot.index));
} else {
let maxIdx = -1;
for(const di of dynamicInputs){ // di is a full definition object
if(di.dynamicGroup < dynamicGroup) {
for(let i=this.inputs.length-1; i>=0; --i){
if(this.inputs[i].name.startsWith(di.baseName)){
maxIdx = Math.max(maxIdx, i);
break;
}
}
}
}
lastDynamicIdx = (maxIdx === -1) ? (this.inputs.length -1) : maxIdx;
}
this.addNewDynamicInputForGroup(dynamicGroup, lastDynamicIdx);
}
this.setDirtyCanvas(true, true);
};
// --- Event Handlers (onConnectionsChange, onConnectInput, onRemoved, onWidgetChanged) ---
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
nodeType.prototype.onConnectionsChange = function (type, slotIndex, isConnected, link, ioSlot) {
const originalReturn = onConnectionsChange?.apply(this, arguments);
if (type === TypeSlot.Input && slotIndex < this.inputs.length && this.inputs[slotIndex] && isDynamicInput(this.inputs[slotIndex].name)) {
if (isProcessingConnection) return originalReturn;
isProcessingConnection = true;
const dynamicGroup = this.getDynamicGroup(this.inputs[slotIndex].name);
if (dynamicGroup !== undefined) {
if (isConnected === TypeSlotEvent.Connect) {
handleDynamicInputActivation.call(this, dynamicGroup);
} else if (isConnected === TypeSlotEvent.Disconnect) {
handleEmptyDynamicInput.call(this);
}
}
isProcessingConnection = false;
}
return originalReturn;
};
const onConnectInput = nodeType.prototype.onConnectInput;
nodeType.prototype.onConnectInput = function(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
const result = onConnectInput?.apply(this, arguments) ?? true;
if (this.inputs[inputIndex].link !== null && this.inputs[inputIndex].link !== undefined ) {
const pre_isProcessingConnection = isProcessingConnection;
isProcessingConnection = true;
this.disconnectInput(inputIndex, true);
isProcessingConnection = pre_isProcessingConnection;
}
return result;
}
const onRemoved = nodeType.prototype.onRemoved;
nodeType.prototype.onRemoved = function () {
if(!isProcessingConnection){
isProcessingConnection = true;
for (let i = this.inputs.length - 1; i >= 0; i--) {
if (this.inputs[i].link !== null && this.inputs[i].link !== undefined) {
this.disconnectInput(i, true);
}
}
isProcessingConnection = false;
}
return onRemoved?.apply(this, arguments);
}
const onWidgetChanged = nodeType.prototype.onWidgetChanged;
nodeType.prototype.onWidgetChanged = function (widgetName, newValue, oldValue, widgetObject) {
const originalReturn = onWidgetChanged?.apply(this, arguments);
if (isProcessingConnection || !isDynamicInput(widgetName)) {
return originalReturn;
}
const dynamicGroup = this.getDynamicGroup(widgetName);
if (dynamicGroup === undefined) return originalReturn;
const diData = getDynamicInputDefinition(widgetName);
const defaultValue = diData ? getWidgetDefaultValue({type: diData.definition.actualWidgetGuiType, options: diData.definition.originalOptions}) : getWidgetDefaultValue(widgetObject) /*fallback*/;
const wasEffectivelyEmpty = oldValue === defaultValue;
const isNowEffectivelyEmpty = newValue === defaultValue;
if (wasEffectivelyEmpty === isNowEffectivelyEmpty) return originalReturn;
isProcessingConnection = true;
if (wasEffectivelyEmpty && !isNowEffectivelyEmpty) {
handleDynamicInputActivation.call(this, dynamicGroup);
} else if (!wasEffectivelyEmpty && isNowEffectivelyEmpty) {
handleEmptyDynamicInput.call(this);
}
isProcessingConnection = false;
return originalReturn;
};
nodeType.prototype.getDynamicSlots = function(filterDynamicGroup = null) { // (Content largely unchanged, relies on isDynamicInputEmpty which is updated)
const dynamicSlotsResult = [];
const dynamicGroupCount = {};
const dynamicGroupConnected = {};
const itemsState = new Map();
for (const [index, input] of this.inputs.entries()) {
const diData = getDynamicInputDefinition(input.name);
if (!diData) continue;
const { definition: di, suffix } = diData;
const currentDynamicGroup = di.dynamicGroup;
if (filterDynamicGroup !== null && currentDynamicGroup !== filterDynamicGroup) continue;
if (!itemsState.has(currentDynamicGroup)) itemsState.set(currentDynamicGroup, new Map());
const groupItems = itemsState.get(currentDynamicGroup);
if (!groupItems.has(suffix)) groupItems.set(suffix, { isActive: false, inputCount: 0, activeInputCount: 0 });
const itemState = groupItems.get(suffix);
itemState.inputCount++;
const isInputActive = !isDynamicInputEmpty(this, index); // Uses updated isDynamicInputEmpty
if (isInputActive) itemState.activeInputCount++;
dynamicSlotsResult.push({
index, name: input.name, isWidget: input.widget !== undefined, shape: input.shape,
connected: isInputActive,
isDynamic: true, dynamicGroup: currentDynamicGroup,
});
}
for(const [groupId, groupItems] of itemsState) {
dynamicGroupCount[groupId] = groupItems.size;
dynamicGroupConnected[groupId] = [];
const sortedSuffixes = Array.from(groupItems.keys()).sort((a,b) => {
const numA = parseInt(a,10); const numB = parseInt(b,10);
if(!isNaN(numA) && !isNaN(numB)) return numA-numB;
if(a.length !== b.length) return a.length - b.length;
return String(a).localeCompare(String(b));
});
for(const suffix of sortedSuffixes){
const item = groupItems.get(suffix);
dynamicGroupConnected[groupId].push(item.activeInputCount > 0);
}
}
return { slots: dynamicSlotsResult, groupCount: dynamicGroupCount, groupConnected: dynamicGroupConnected };
};
nodeType.prototype.addNewDynamicInputForGroup = function(dynamicGroup, lastKnownInputIndexInGroup) {
let insertPosition = lastKnownInputIndexInGroup + 1;
let inputInRange = true;
const groupMemberDefinitions = dynamicInputGroups[dynamicGroup].map(idx => dynamicInputs[idx]);
const { slots: currentGroupSlots } = this.getDynamicSlots(dynamicGroup);
const existingSuffixes = new Set();
currentGroupSlots.forEach(s => {
const diData = getDynamicInputDefinition(s.name);
if(diData) existingSuffixes.add(diData.suffix);
});
let newItemNumericSuffix = 0;
// Find the smallest non-negative integer not in existingSuffixes (if they are numbers)
// or just use existingSuffixes.size if complex/letter based for simplicity (renumbering will fix)
// For simplicity here, using size, assuming renumbering will handle actual ordering.
// A more robust suffix generation might be needed if strict ordering before renumbering is critical.
const isNumericSuffix = groupMemberDefinitions.length > 0 && groupMemberDefinitions[0].dynamic === 'number';
if (isNumericSuffix) {
while (existingSuffixes.has(String(newItemNumericSuffix))) {
newItemNumericSuffix++;
}
} else { // letter or complex
newItemNumericSuffix = existingSuffixes.size; // This is the 'count' for letter generation
}
for (const diDefinition of groupMemberDefinitions) {
// Use the new properties from diDefinition
const { baseName, dynamic, dynamicComfyType, isWidget, actualWidgetGuiType, originalOptions } = diDefinition;
if (dynamic === 'letter' && newItemNumericSuffix >= 26) {
inputInRange = false; continue;
}
const newName = generateDynamicInputName(dynamic, baseName, newItemNumericSuffix);
const refSlot = currentGroupSlots.find(s => s.name.startsWith(baseName)) || currentGroupSlots[0];
const widgetDefault = getWidgetDefaultValue({ type: actualWidgetGuiType, options: originalOptions });
this.addInputAtPosition(
newName,
dynamicComfyType, // ComfyUI input type (e.g., "STRING", "*")
actualWidgetGuiType, // GUI widget type (e.g., "text", "number", "combo")
insertPosition++,
isWidget, // The crucial boolean
refSlot?.shape,
widgetDefault,
originalOptions // Full original options for widget config
);
}
return inputInRange;
};
}
});