From 90684e9e570d8944d95957ea6fb8cd5ed70ccbb9 Mon Sep 17 00:00:00 2001 From: StableLlama Date: Mon, 2 Jun 2025 22:57:05 +0200 Subject: [PATCH] Try to make it work as intended --- web/js/dynamicnode.js | 614 ++++++++++++++++++++++++++++++------------ 1 file changed, 440 insertions(+), 174 deletions(-) diff --git a/web/js/dynamicnode.js b/web/js/dynamicnode.js index 7db19b2..fa8d893 100644 --- a/web/js/dynamicnode.js +++ b/web/js/dynamicnode.js @@ -55,6 +55,7 @@ app.registerExtension({ /** Array of groups of (generic) dynamic inputs. */ const dynamicInputGroups = []; for (const name of combinedInputDataOrder) { + const widgetType = combinedInputData[name][1]?.widgetType; const dynamic = combinedInputData[name][1]?._dynamic; const dynamicGroup = combinedInputData[name][1]?._dynamicGroup ?? 0; @@ -81,7 +82,7 @@ app.registerExtension({ dynamicInputGroups[dynamicGroup].push( dynamicInputs.length ) - dynamicInputs.push({name, baseName, matcher, dynamic, dynamicType, dynamicGroup}); + dynamicInputs.push({name, baseName, matcher, dynamic, dynamicType, dynamicGroup, widgetType}); } } if (dynamicInputs.length === 0) { @@ -89,16 +90,13 @@ app.registerExtension({ } /** - * Utility: Check if an input is dynamic. - * @param {string} inputName - Name of the input to check. + * Utility functions for dynamic input operations */ + // Check if an input is dynamic const isDynamicInput = (inputName) => - dynamicInputs.some((di) => di.matcher.test(inputName)); + dynamicInputs.some((di) => di.matcher.test(inputName)); - /** - * Utility: Update inputs' slot indices after reordering. - * @param {ComfyNode} node - The node to update. - */ + // Update inputs' slot indices after reordering const updateSlotIndices = (node) => { node.inputs.forEach((input, index) => { input.slot_index = index; @@ -113,21 +111,88 @@ app.registerExtension({ }); }; + // Get default value for widget based on its type + const getWidgetDefaultValue = (widget) => { + switch (widget.type) { + case 'number': + return 0; + case 'combo': + return widget.options?.[0] || ''; + case 'text': + case 'string': + default: + return ''; + } + }; + + // Check if a dynamic input is empty (not connected and widget has default value) + const isDynamicInputEmpty = (node, inputIndex) => { + const input = node.inputs[inputIndex]; + if (input.isConnected) return false; + + if (input.widget) { + const widget = node.widgets.find(w => w.name === input.widget.name); + return widget?.value === getWidgetDefaultValue(widget); + } + return true; + }; + + // Find if input is the last of its base name group + const isLastDynamicInput = (node, idx, baseName) => { + let isLast = true; + for (let i = idx + 1; i < node.inputs.length; i++) { + isLast &&= !node.inputs[i].name.startsWith(baseName); + } + return isLast; + }; + + // Remove widget associated with an input + const removeWidgetForInput = (node, inputIdx) => { + if (node.inputs[inputIdx].widget !== undefined) { + const widgetIdx = node.widgets.findIndex((w) => w.name === node.inputs[inputIdx].widget.name); + node.widgets.splice(widgetIdx, 1); + node.widgets_values?.splice(widgetIdx, 1); + } + }; + + // Add helper method to get dynamic group for an input name + nodeType.prototype.getDynamicGroup = function(inputName) { + // Find the dynamicGroup by matching the baseName with input name + for (const di of dynamicInputs) { + if (inputName.startsWith(di.baseName)) { + return di.dynamicGroup; + } + } + return undefined; + }; + + /** + * Add a widget with standard configuration. + * @param {string} name - The name of the widget. + * @param {string} widget_type - The type of widget. + * @returns {object} The created widget. + */ + const addStandardWidget = function(name, widget_type) { + return this.addWidget(widget_type, name, '', () => {}, {}); + }; // Add helper method to insert input at a specific position - nodeType.prototype.addInputAtPosition = function (name, type, position, isWidget, shape) { + nodeType.prototype.addInputAtPosition = function (name, input_type, widget_type, position, isWidget, shape) { + // Add widget if needed if (isWidget) { - this.addWidget(type, name, '', ()=>{}, {}); + addStandardWidget.call(this, name, widget_type); const GET_CONFIG = Symbol(); - const input = this.addInput(name, type, { + this.addInput(name, input_type, { shape, widget: {name, [GET_CONFIG]: () =>{}} - }) + }); } else { - this.addInput(name, type, {shape}); // Add new input + this.addInput(name, input_type, {shape}); // Add new input without widget } - const newInput = this.inputs.pop(); // Fetch the newly added input (last item) + + // Position the input at the desired location + const newInput = this.inputs.pop(); // Get the newly added input (last item) this.inputs.splice(position, 0, newInput); // Place it at the desired position updateSlotIndices(this); // Update indices return newInput; @@ -137,155 +202,141 @@ app.registerExtension({ // running in parallel. let isProcessingConnection = false; + /** + * Utility: Handle when a dynamic input becomes empty (disconnected or empty widget value). + * @param {ComfyNode} node - The node with the empty input. + */ + const handleEmptyDynamicInput = function() { + // Process each input to check for empty dynamic inputs + for (let idx = 0; idx < this.inputs.length; idx++) { + const input = this.inputs[idx]; + + // Skip if not a dynamic input + if (!isDynamicInput(input.name)) { + continue; + } + + // Check if this input is empty + if (!isDynamicInputEmpty(this, idx)) { + continue; + } + + // Get information about this dynamic input group + const dynamicGroup = this.getDynamicGroup(input.name); + const baseName = dynamicInputs[dynamicInputGroups[dynamicGroup][0]].baseName; + + // Don't remove if it's the last dynamic input of its type + if (isLastDynamicInput(this, idx, baseName)) { + continue; + } + + // Move the empty input to the end + for (let i = idx + 1; i < this.inputs.length; i++) { + this.swapInputs(i - 1, i); + } + + // Remove the input that's now at the end + const lastIdx = this.inputs.length - 1; + removeWidgetForInput(this, lastIdx); + this.removeInput(lastIdx); + + // Adjust idx to check the current position again (which now has a new input) + idx--; + } + + // Renumber all dynamic inputs to ensure proper ordering + for (const groupIdx in dynamicInputGroups) { + for (const memberIdx of dynamicInputGroups[groupIdx]) { + const baseName = dynamicInputs[memberIdx].baseName; + const dynamic = dynamicInputs[memberIdx].dynamic; + this.renumberDynamicInputs(baseName, dynamicInputs, dynamic); + } + } + }; + + /** + * Utility: Handle when a dynamic input becomes active (connected or non-empty widget value). + * @param {ComfyNode} node - The node with the activated input. + * @param {number} dynamicGroup - The dynamic group to handle. + */ + const handleDynamicInputActivation = function(dynamicGroup) { + // Get information about dynamic inputs + const { + slots: dynamicSlots, + groupConnected: dynamicGroupConnected + } = this.getDynamicSlots(); + + // Ensure all widget-based inputs have actual widgets + // This is important when loading workflows + for (const slot of dynamicSlots) { + if (slot.isWidget && this.widgets && + !this.widgets.some((w) => w.name === slot.name)) { + this.addWidget( + this.inputs[slot.index].type, + slot.name, + '', + ()=>{}, + {} + ); + } + } + + // If all inputs in this group are active, we need to add a new empty one + const hasEmptyInput = dynamicGroupConnected[dynamicGroup]?.some(isActive => !isActive); + + if (!hasEmptyInput) { + // Find position for the new input (after the last one in this group) + const groupSlots = dynamicSlots.filter(slot => slot.dynamicGroup === dynamicGroup); + const lastDynamicIdx = groupSlots.length > 0 + ? Math.max(...groupSlots.map(slot => slot.index)) + : -1; + + // Add a new empty input for this group + this.addNewDynamicInputForGroup(dynamicGroup, lastDynamicIdx); + } + + // Ensure the canvas is updated + + this.setDirtyCanvas(true, true); + }; + // Override onConnectionsChange: Handle connections for dynamic inputs const onConnectionsChange = nodeType.prototype.onConnectionsChange; nodeType.prototype.onConnectionsChange = function (type, slotIndex, isConnected, link, ioSlot) { + // Call the original method first const result = onConnectionsChange?.apply(this, arguments); - if (type !== TypeSlot.Input || isProcessingConnection || !isDynamicInput(this.inputs[slotIndex].name)) { + // Only process input connections for dynamic inputs + const isInput = type === TypeSlot.Input; + const isDynamic = isInput && isDynamicInput(this.inputs[slotIndex].name); + + // Skip if not an input, already processing, or not a dynamic input + if (!isInput || isProcessingConnection || !isDynamic) { return result; } - function getDynamicGroup(inputName) { - // Find the dynamicGroup by matching the baseName with input name - for (const di of dynamicInputs) { - if (inputName.startsWith(di.baseName)) { - return di.dynamicGroup; - } - } - return undefined; - } - + // Prevent recursive processing isProcessingConnection = true; - try { - // Get dynamic input slots - const dynamicSlots = []; - const dynamicGroupCount = []; - const dynamicGroupConnected = []; - for (const [index, input] of this.inputs.entries()) { - const isDynamic = isDynamicInput(input.name); - if (isDynamic) { - const connected = input.isConnected; - const dynamicGroup = getDynamicGroup(input.name); - if (dynamicGroup in dynamicGroupCount) { - if (input.name.startsWith(dynamicInputs[dynamicInputGroups[dynamicGroup][0]].baseName)) { - dynamicGroupConnected[dynamicGroup][dynamicGroupCount[dynamicGroup]] ||= connected; - dynamicGroupCount[dynamicGroup]++; - } - } else { - dynamicGroupCount[dynamicGroup] = 1; - dynamicGroupConnected[dynamicGroup] = [connected]; - } - dynamicSlots.push({ - index, - name: input.name, - isWidget: input.widget !== undefined, - shape: input.shape, - connected, - isDynamic, - dynamicGroup, - dynamicGroupCount: dynamicGroupCount[dynamicGroup] - }); + // Get the dynamic group for this input + const dynamicGroup = this.getDynamicGroup(this.inputs[slotIndex].name); - // sanity check to make sure every widget is in reality a widget. When loading a workflow this - // isn't the case so we must fix it ourselves. - if (this.widgets && !this.widgets.some((w) => w.name === input.name)) { - this.addWidget(input.type, input.name, '', ()=>{}, {}); - } - } - } - - // Handle connection event - if (isConnected === TypeSlotEvent.Connect) { - const hasEmptyDynamic = dynamicGroupConnected[0].some(dgc => !dgc); - - if (!hasEmptyDynamic) { - // No empty slot - add a new one after the last dynamic input - const lastDynamicIdx = Math.max(...dynamicSlots.map((slot) => slot.index), -1); - let insertPosition = lastDynamicIdx + 1; - let inputInRange = true; - - for (const groupMember of dynamicInputGroups[dynamicInputs[0].dynamicGroup]) { - const baseName = dynamicInputs[groupMember].baseName; - const dynamicType = dynamicInputs[groupMember].dynamicType; - let newName; - if (dynamicInputs[0].dynamic === 'letter') { - if (dynamicSlots.length >= 26) { - inputInRange = false; - } - // For letter type, use the next letter in sequence - newName = String.fromCharCode(97 + dynamicSlots.length); // 97 is ASCII for 'a' - } else { - // For number type, use baseName + index as before - newName = `${baseName}${dynamicSlots.length}`; - } - - if (inputInRange) { - // Insert the new empty input at the correct position - this.addInputAtPosition(newName, dynamicType, insertPosition++, dynamicSlots[groupMember].isWidget, dynamicSlots[groupMember].shape); - // Renumber inputs after addition - this.renumberDynamicInputs(baseName, dynamicInputs, dynamicInputs[0].dynamic); - } - } - } - } else if (isConnected === TypeSlotEvent.Disconnect) { - let foundEmptyIndex = -1; - - for (let idx = 0; idx < this.inputs.length; idx++) { - const input = this.inputs[idx]; - - if (!isDynamicInput(input.name)) { - continue; - } - - if (!input.isConnected) { // Check if the input is empty - // remove empty input - but only when it's not the last one - const dynamicGroup = getDynamicGroup(input.name); - let isLast = true; - for (let i = idx + 1; i < this.inputs.length; i++) { - isLast &&= !this.inputs[i].name.startsWith(dynamicInputs[dynamicInputGroups[dynamicGroup][0]].baseName); - } - if (isLast) { - continue; - } - - for (let i = idx + 1; i < this.inputs.length; i++) { - this.swapInputs(i - 1, i); - } - const lastIdx = this.inputs.length - 1; - if (this.inputs[lastIdx].widget !== undefined) { - const widgetIdx = this.widgets.findIndex((w) => w.name === this.inputs[lastIdx].widget.name) - this.widgets.splice(widgetIdx, 1); - this.widgets_values?.splice(widgetIdx, 1); - } - this.removeInput(lastIdx); - } - } - - // Renumber dynamic inputs to ensure proper ordering - for (const groupMember of dynamicInputGroups[dynamicInputs[0].dynamicGroup]) { - const baseName = dynamicInputs[groupMember].baseName; - this.renumberDynamicInputs(baseName, dynamicInputs, dynamicInputs[0].dynamic); - } - - } - - this.setDirtyCanvas(true, true); - } catch (e) { - console.error(e); - debugger; - alert(e); - } finally { - isProcessingConnection = false; + // Handle connection or disconnection event + if (isConnected === TypeSlotEvent.Connect) { + // Input was connected + handleDynamicInputActivation.call(this, dynamicGroup); + } else if (isConnected === TypeSlotEvent.Disconnect) { + // Input was disconnected + handleEmptyDynamicInput.call(this); } + isProcessingConnection = false; return result; }; const onConnectInput = nodeType.prototype.onConnectInput; nodeType.prototype.onConnectInput = function(inputIndex, outputType, outputSlot, outputNode, outputIndex) { - const result = onRemoved?.apply(this, arguments) ?? true; + const result = onConnectInput?.apply(this, arguments) ?? true; if (this.inputs[inputIndex].isConnected) { const pre_isProcessingConnection = isProcessingConnection; @@ -312,6 +363,67 @@ app.registerExtension({ return result; } + /** + * Utility: Find input index for a widget by name. + * @param {ComfyNode} node - The node containing the widget. + * @param {string} widgetName - Name of the widget to find. + * @returns {number} Index of the input associated with the widget, or -1 if not found. + */ + const findInputIndexForWidget = (node, widgetName) => { + for (let i = 0; i < node.inputs.length; i++) { + if (node.inputs[i].widget && node.inputs[i].widget.name === widgetName) { + return i; + } + } + return -1; + }; + + const onWidgetChanged = nodeType.prototype.onWidgetChanged; + nodeType.prototype.onWidgetChanged = function () { + const result = onWidgetChanged?.apply(this, arguments); + + // Extract arguments + const widget_name = arguments[0]; + const new_val = arguments[1]; + const old_val = arguments[2]; + const widget = arguments[3]; + + // Skip if not a dynamic input widget or already processing connections + if (!isDynamicInput(widget_name) || isProcessingConnection) { + return result; + } + + // Find the dynamic group for this widget + const dynamicGroup = this.getDynamicGroup(widget_name); + if (dynamicGroup === undefined) { + return result; + } + + // Check if widget value changed between default and non-default + const default_val = getWidgetDefaultValue(widget); + const wasEmpty = old_val === default_val; + const isNowEmpty = new_val === default_val; + + // Only process if the empty state changed + if (wasEmpty === isNowEmpty) { + return result; + } + + // Prevent recursive processing + isProcessingConnection = true; + + if (wasEmpty && !isNowEmpty) { + // Widget changed from empty to non-empty (like connecting an input) + handleDynamicInputActivation.call(this, dynamicGroup); + } else if (!wasEmpty && isNowEmpty) { + // Widget changed to empty (like disconnecting an input) + handleEmptyDynamicInput.call(this); + } + + isProcessingConnection = false; + return result; + } + // Method to swap two inputs in the "this.inputs" array by their indices nodeType.prototype.swapInputs = function(indexA, indexB) { // Validate indices @@ -325,64 +437,218 @@ app.registerExtension({ return; } - // reflect the swap with the widgets - if (this.inputs[indexA].widget !== undefined) { - if (this.inputs[indexB].widget === undefined) { - console.error("Bad swap: input A is a widget but input B is not", indexA, indexB); - } - const widgetIdxA = this.widgets.findIndex((w) => w.name === this.inputs[indexA].widget.name); - const widgetIdxB = this.widgets.findIndex((w) => w.name === this.inputs[indexB].widget.name); - [this.widgets[widgetIdxA].y, this.widgets[widgetIdxB].y] = [this.widgets[widgetIdxB].y, this.widgets[widgetIdxA].y]; - [this.widgets[widgetIdxA].last_y, this.widgets[widgetIdxB].last_y] = [this.widgets[widgetIdxB].last_y, this.widgets[widgetIdxA].last_y]; - [this.widgets[widgetIdxA], this.widgets[widgetIdxB]] = [this.widgets[widgetIdxB], this.widgets[widgetIdxA]]; + // Handle widgets if both inputs have them + const hasWidgetA = this.inputs[indexA].widget !== undefined; + const hasWidgetB = this.inputs[indexB].widget !== undefined; + + if (hasWidgetA && hasWidgetB) { + // Find widget indices + const widgetIdxA = this.widgets.findIndex( + (w) => w.name === this.inputs[indexA].widget.name + ); + const widgetIdxB = this.widgets.findIndex( + (w) => w.name === this.inputs[indexB].widget.name + ); + + // Swap widget positions + [this.widgets[widgetIdxA].y, this.widgets[widgetIdxB].y] = + [this.widgets[widgetIdxB].y, this.widgets[widgetIdxA].y]; + [this.widgets[widgetIdxA].last_y, this.widgets[widgetIdxB].last_y] = + [this.widgets[widgetIdxB].last_y, this.widgets[widgetIdxA].last_y]; + + // Swap the widgets themselves + [this.widgets[widgetIdxA], this.widgets[widgetIdxB]] = + [this.widgets[widgetIdxB], this.widgets[widgetIdxA]]; + + // Swap widget values if they exist if (this.widgets_values) { - [this.widgets_values[widgetIdxA], this.widgets_values[widgetIdxB]] = [this.widgets_values[widgetIdxB], this.widgets_values[widgetIdxA]]; + [this.widgets_values[widgetIdxA], this.widgets_values[widgetIdxB]] = + [this.widgets_values[widgetIdxB], this.widgets_values[widgetIdxA]]; } + } else if (hasWidgetA || hasWidgetB) { + console.error("Bad swap: one input has a widget but the other doesn't", indexA, indexB); } - // Swap the inputs in the array - [this.inputs[indexA].boundingRect, this.inputs[indexB].boundingRect] = [this.inputs[indexB].boundingRect, this.inputs[indexA].boundingRect]; - [this.inputs[indexA].pos, this.inputs[indexB].pos] = [this.inputs[indexB].pos, this.inputs[indexA].pos]; - [this.inputs[indexA], this.inputs[indexB]] = [this.inputs[indexB], this.inputs[indexA]]; - updateSlotIndices(this); // Refresh indices + // Swap input properties + [this.inputs[indexA].boundingRect, this.inputs[indexB].boundingRect] = + [this.inputs[indexB].boundingRect, this.inputs[indexA].boundingRect]; + [this.inputs[indexA].pos, this.inputs[indexB].pos] = + [this.inputs[indexB].pos, this.inputs[indexA].pos]; - // Redraw the node to ensure the graph updates properly - // -> not needed as the calling method must do it! - this.setDirtyCanvas(true, true); + // Swap the inputs themselves + [this.inputs[indexA], this.inputs[indexB]] = + [this.inputs[indexB], this.inputs[indexA]]; + + // Update indices to maintain connections + updateSlotIndices(this); + + // The calling method is responsible for redrawing the canvas if needed + }; + + // Add helper method to get dynamic slots info + nodeType.prototype.getDynamicSlots = function(dynamicGroup = null) { + const dynamicSlots = []; + const dynamicGroupCount = {}; + const dynamicGroupConnected = {}; + + // Process each input to gather information about dynamic inputs + for (const [index, input] of this.inputs.entries()) { + // Skip non-dynamic inputs + if (!isDynamicInput(input.name)) { + continue; + } + + // Get the dynamic group for this input + const currentDynamicGroup = this.getDynamicGroup(input.name); + + // Skip if filtering by group and this doesn't match + if (dynamicGroup !== null && currentDynamicGroup !== dynamicGroup) { + continue; + } + + // Determine if this input is active (connected or has non-default widget value) + const isActive = !isDynamicInputEmpty(this, index); + + // Initialize group tracking if this is the first input for this group + if (!(currentDynamicGroup in dynamicGroupCount)) { + dynamicGroupCount[currentDynamicGroup] = 0; + dynamicGroupConnected[currentDynamicGroup] = []; + } + + // Get the base name for this dynamic input + const baseNameInfo = dynamicInputs[dynamicInputGroups[currentDynamicGroup][0]]; + + // Track connection status for this input in its group + if (input.name.startsWith(baseNameInfo.baseName)) { + const groupIndex = dynamicGroupCount[currentDynamicGroup]; + // Use OR assignment to preserve 'true' values + dynamicGroupConnected[currentDynamicGroup][groupIndex] = + dynamicGroupConnected[currentDynamicGroup][groupIndex] || isActive; + dynamicGroupCount[currentDynamicGroup]++; + } + + // Store detailed information about this dynamic input + dynamicSlots.push({ + index, + name: input.name, + isWidget: input.widget !== undefined, + shape: input.shape, + connected: isActive, + isDynamic: true, + dynamicGroup: currentDynamicGroup, + dynamicGroupCount: dynamicGroupCount[currentDynamicGroup] + }); + } + + return { + slots: dynamicSlots, + groupCount: dynamicGroupCount, + groupConnected: dynamicGroupConnected + }; + }; + + /** + * Generate a new dynamic input name based on type and count. + * @param {string} dynamic - The dynamic type ('number' or 'letter'). + * @param {string} baseName - The base name for the input. + * @param {number} count - The count/position for the new input. + * @returns {string} The generated input name. + */ + const generateDynamicInputName = (dynamic, baseName, count) => { + if (dynamic === 'letter') { + // For letter type, use the next letter in sequence + return String.fromCharCode(97 + count); // 97 is ASCII for 'a' + } else { + // For number type, use baseName + index + return `${baseName}${count}`; + } + }; + + // Add helper method to add new dynamic input for a group + nodeType.prototype.addNewDynamicInputForGroup = function(dynamicGroup, lastDynamicIdx) { + let insertPosition = lastDynamicIdx + 1; + let inputInRange = true; + + // Add new inputs for each member of the dynamic group + for (const groupMember of dynamicInputGroups[dynamicGroup]) { + const dynamicInput = dynamicInputs[groupMember]; + const baseName = dynamicInput.baseName; + const dynamicType = dynamicInput.dynamicType; + const widgetType = dynamicInput.widgetType ?? dynamicType; + const dynamic = dynamicInput.dynamic; + + // Get current slots for this group + const { slots } = this.getDynamicSlots(dynamicGroup); + const groupSlots = slots.filter(s => s.name.startsWith(baseName)); + + // Check if we've reached the limit for letter inputs (a-z) + if (dynamic === 'letter' && groupSlots.length >= 26) { + inputInRange = false; + continue; + } + + // Generate the new input name based on current count + const newName = generateDynamicInputName(dynamic, baseName, groupSlots.length); + + // Find a reference slot to copy properties from + const referenceSlot = groupSlots[0] || slots.find(s => + s.name.startsWith(dynamicInput.baseName) + ); + + // Create the new input at the correct position + this.addInputAtPosition( + newName, + dynamicType, + widgetType, + insertPosition++, + referenceSlot?.isWidget ?? false, + referenceSlot?.shape + ); + + // Ensure inputs are numbered correctly + this.renumberDynamicInputs(baseName, dynamicInputs, dynamic); + } + + return inputInRange; }; // Add method to safely renumber dynamic inputs without breaking connections nodeType.prototype.renumberDynamicInputs = function(baseName, dynamicInputs, dynamic) { - // Get current dynamic inputs info + // Collect information about dynamic inputs with this base name const dynamicInputInfo = []; + // Find all inputs that match this base name for (let i = 0; i < this.inputs.length; i++) { const input = this.inputs[i]; - const isDynamic = isDynamicInput(input.name); - if (isDynamic && input.name.startsWith(baseName)) { + if (isDynamicInput(input.name) && input.name.startsWith(baseName)) { + // Store info about this input dynamicInputInfo.push({ index: i, - widgetIdx: input.widget !== undefined ? this.widgets.findIndex((w) => w.name === input.widget.name) : undefined, + widgetIdx: input.widget !== undefined + ? this.widgets.findIndex((w) => w.name === input.widget.name) + : undefined, name: input.name, connected: input.isConnected }); } } - // Just rename the inputs in place - don't remove/add to keep connections intact + // Rename inputs in place to maintain connections for (let i = 0; i < dynamicInputInfo.length; i++) { const info = dynamicInputInfo[i]; const input = this.inputs[info.index]; - const newName = dynamic === "number" ? `${baseName}${i}` : String.fromCharCode(97 + i); // 97 is ASCII for 'a' + const newName = generateDynamicInputName(dynamic, baseName, i); - if (input.widget !== undefined) { - const widgetIdx = info.widgetIdx; + // Update widget name if this input has a widget + if (input.widget !== undefined && info.widgetIdx !== undefined) { + const widget = this.widgets[info.widgetIdx]; + widget.name = newName; + widget.label = newName; input.widget.name = newName; - this.widgets[widgetIdx].name = newName; - this.widgets[widgetIdx].label = newName; } + // Update the input name if it's different if (input.name !== newName) { input.name = newName; input.localized_name = newName;