167 lines
7.3 KiB
JavaScript
167 lines
7.3 KiB
JavaScript
// Copyright 2025 FeiMao-326
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { app } from "../../scripts/app.js";
|
|
|
|
app.registerExtension({
|
|
name: "FeiMao-326.DynamicInputs",
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
|
|
// --- 1. General API Node (Dynamic Image Inputs) ---
|
|
if (nodeData.name === "FeiMao_326_GeneralAPINode") {
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
|
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info, slot) {
|
|
if (onConnectionsChange) {
|
|
onConnectionsChange.apply(this, arguments);
|
|
}
|
|
|
|
if (type !== 1) return;
|
|
|
|
const imageInputRegex = /^image_(\d+)$/;
|
|
const imageInputs = this.inputs.filter(inp => imageInputRegex.test(inp.name));
|
|
|
|
let maxConnectedIndex = 0;
|
|
for (const inp of imageInputs) {
|
|
if (inp.link !== null) {
|
|
const idx = parseInt(inp.name.match(imageInputRegex)[1]);
|
|
if (idx > maxConnectedIndex) maxConnectedIndex = idx;
|
|
}
|
|
}
|
|
|
|
const targetCount = Math.max(3, maxConnectedIndex + 1);
|
|
const existingIndices = imageInputs.map(inp => parseInt(inp.name.match(imageInputRegex)[1])).sort((a, b) => a - b);
|
|
const maxExistingIndex = existingIndices.length > 0 ? existingIndices[existingIndices.length - 1] : 0;
|
|
|
|
if (maxExistingIndex < targetCount) {
|
|
for (let i = maxExistingIndex + 1; i <= targetCount; i++) {
|
|
const newName = `image_${i}`;
|
|
if (!this.inputs.find(inp => inp.name === newName)) {
|
|
this.addInput(newName, "IMAGE");
|
|
}
|
|
}
|
|
this.onResize?.(this.size);
|
|
app.canvas.setDirty?.(true);
|
|
}
|
|
|
|
if (maxExistingIndex > targetCount) {
|
|
for (let i = maxExistingIndex; i > targetCount; i--) {
|
|
const nameToRemove = `image_${i}`;
|
|
const inputIndex = this.inputs.findIndex(inp => inp.name === nameToRemove);
|
|
if (inputIndex !== -1) {
|
|
this.removeInput(inputIndex);
|
|
}
|
|
}
|
|
this.onResize?.(this.size);
|
|
app.canvas.setDirty?.(true);
|
|
}
|
|
};
|
|
}
|
|
|
|
// --- 2. JSON Parser (Dynamic Outputs) ---
|
|
if (nodeData.name === "FeiMao_326_JsonParser") {
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
|
nodeType.prototype.onConnectionsChange = function (type, index, connected, link_info, slot) {
|
|
if (onConnectionsChange) {
|
|
onConnectionsChange.apply(this, arguments);
|
|
}
|
|
|
|
if (type !== 2) return;
|
|
|
|
const outputRegex = /^output_(\d+)$/;
|
|
const outputs = this.outputs.filter(out => outputRegex.test(out.name));
|
|
|
|
let maxConnectedIndex = 0;
|
|
for (const out of outputs) {
|
|
if (out.links && out.links.length > 0) {
|
|
const idx = parseInt(out.name.match(outputRegex)[1]);
|
|
if (idx > maxConnectedIndex) maxConnectedIndex = idx;
|
|
}
|
|
}
|
|
|
|
const targetCount = Math.max(2, maxConnectedIndex + 1);
|
|
const MAX_ALLOWED = 50;
|
|
const finalTargetCount = Math.min(targetCount, MAX_ALLOWED);
|
|
|
|
const existingIndices = outputs.map(out => parseInt(out.name.match(outputRegex)[1])).sort((a, b) => a - b);
|
|
const maxExistingIndex = existingIndices.length > 0 ? existingIndices[existingIndices.length - 1] : 0;
|
|
|
|
if (maxExistingIndex < finalTargetCount) {
|
|
for (let i = maxExistingIndex + 1; i <= finalTargetCount; i++) {
|
|
const newName = `output_${i}`;
|
|
if (!this.outputs.find(out => out.name === newName)) {
|
|
this.addOutput(newName, "STRING");
|
|
}
|
|
}
|
|
this.onResize?.(this.size);
|
|
app.canvas.setDirty?.(true);
|
|
}
|
|
|
|
if (maxExistingIndex > finalTargetCount) {
|
|
for (let i = maxExistingIndex; i > finalTargetCount; i--) {
|
|
const nameToRemove = `output_${i}`;
|
|
const outputIndex = this.outputs.findIndex(out => out.name === nameToRemove);
|
|
if (outputIndex !== -1) {
|
|
this.removeOutput(outputIndex);
|
|
}
|
|
}
|
|
this.onResize?.(this.size);
|
|
}
|
|
};
|
|
}
|
|
|
|
// --- 3. Text Template (Variable-based Dynamic Inputs) ---
|
|
if (nodeData.name === "FeiMao_326_TextTemplate") {
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
|
const templateWidget = this.widgets.find(w => w.name === "template");
|
|
|
|
const updateInputs = () => {
|
|
const text = templateWidget.value || "";
|
|
// Match all {var_name} (Support Unicode/Chinese)
|
|
const matches = text.match(/\{([^{}\s]+)\}/g) || [];
|
|
const vars = [...new Set(matches.map(m => m.slice(1, -1)))];
|
|
|
|
// Add new inputs
|
|
vars.forEach(v => {
|
|
if (!this.inputs || !this.inputs.find(i => i.name === v)) {
|
|
this.addInput(v, "*"); // Universal type
|
|
}
|
|
});
|
|
|
|
// Remove outdated inputs
|
|
if (this.inputs) {
|
|
for (let i = this.inputs.length - 1; i >= 0; i--) {
|
|
const inputName = this.inputs[i].name;
|
|
if (!vars.includes(inputName)) {
|
|
this.removeInput(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.onResize?.(this.size);
|
|
app.canvas.setDirty?.(true);
|
|
};
|
|
|
|
// Watch for widget changes
|
|
templateWidget.callback = updateInputs;
|
|
|
|
// Initial sync
|
|
setTimeout(updateInputs, 100);
|
|
return r;
|
|
};
|
|
}
|
|
},
|
|
});
|