Files
alFrame-ComfyUI-AF-Pack-Pro…/web/AF_Model_Switch.js
T
Alex Furer 9f5abfb8b5 v1.1.0
- Added the "Model Switch" node
- Updated README
- Cleaned up the CHANGELOG to meet standard
2025-12-15 13:32:22 +01:00

348 lines
15 KiB
JavaScript

import { app } from "/scripts/app.js";
app.registerExtension({
name: "AF.ModelSwitch",
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name === "AF_Model_Switch") {
// Hook into onExecuted to update status after workflow runs
const originalOnExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function(message) {
const r = originalOnExecuted?.apply(this, arguments);
if (message?.status && Array.isArray(message.status) && message.status.length > 0) {
const statusText = message.status[0];
const statusWidget = this.widgets?.find(w => w.name === "status");
if (statusWidget) {
statusWidget.value = statusText;
if (statusWidget.inputEl) {
statusWidget.inputEl.value = statusText;
if (statusText.includes("auto")) {
statusWidget.inputEl.style.backgroundColor = "#2a2a1a";
statusWidget.inputEl.style.color = "#ffaa44";
statusWidget.inputEl.style.borderColor = "#cc8833";
} else if (statusText.includes("none")) {
statusWidget.inputEl.style.backgroundColor = "#2a1a1a";
statusWidget.inputEl.style.color = "#ff6666";
statusWidget.inputEl.style.borderColor = "#cc4444";
} else {
statusWidget.inputEl.style.backgroundColor = "#1a2a3a";
statusWidget.inputEl.style.color = "#88ccff";
statusWidget.inputEl.style.borderColor = "#4488cc";
}
}
this.setDirtyCanvas(true, false);
}
}
return r;
};
// Store original onNodeCreated
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function() {
const result = originalOnNodeCreated?.apply(this, arguments);
// Rails 1 and 2 are PERMANENT (defined in Python INPUT_TYPES)
// So we start counting from rail 2 as the baseline
// _currentRailCount represents the TOTAL number of rails visible
// Detect existing rail count from loaded inputs
// Check for the highest rail number present (rails 3-10 are dynamic)
let detectedRailCount = 2; // Always have at least rails 1 and 2
if (this.inputs) {
for (let i = 10; i >= 3; i--) {
if (this.inputs.find(inp => inp.name === `model_${i}`)) {
detectedRailCount = i;
break;
}
}
}
// Initialize tracking
this._currentRailCount = detectedRailCount;
this._userResized = false;
this._isInitializing = true; // Flag to prevent duplicate initialization
// Setup callbacks with delay to ensure widgets exist
setTimeout(() => {
const railCountWidget = this.widgets?.find(w => w.name === "number_of_rails");
const selectWidget = this.widgets?.find(w => w.name === "selected_rail");
// Rail count callback
if (railCountWidget) {
railCountWidget.callback = (value) => {
const count = Math.max(2, Math.min(10, parseInt(value) || 2));
this.updateRailInputs(count);
};
}
// Selected rail callback
if (selectWidget) {
selectWidget.callback = (value) => {
this.updateStatusDisplay(parseInt(value));
};
}
this.updateStatusDisplay(0);
this._isInitializing = false;
}, 100);
return result;
};
// Update status display (UI only, no execution)
nodeType.prototype.updateStatusDisplay = function(selectedRail) {
const statusWidget = this.widgets?.find(w => w.name === "status");
if (!statusWidget) return;
let statusText, bgColor, textColor, borderColor;
// 0 = AUTO, 1-10 = manual rail selection
if (selectedRail === 0 || isNaN(selectedRail)) {
statusText = "💡 Active Rail: auto";
bgColor = "#2a2a1a";
textColor = "#ffaa44";
borderColor = "#cc8833";
} else {
// Display 1-based rail number
statusText = `🎯 Active Rail: ${selectedRail}`;
bgColor = "#1a2a3a";
textColor = "#88ccff";
borderColor = "#4488cc";
}
statusWidget.value = statusText;
if (statusWidget.inputEl) {
statusWidget.inputEl.value = statusText;
statusWidget.inputEl.style.backgroundColor = bgColor;
statusWidget.inputEl.style.color = textColor;
statusWidget.inputEl.style.borderColor = borderColor;
}
this.setDirtyCanvas(true, false);
};
// Update rail inputs dynamically
nodeType.prototype.updateRailInputs = function(railCount) {
railCount = Math.max(2, Math.min(10, railCount));
if (this._currentRailCount === railCount) return;
const oldCount = this._currentRailCount;
const newCount = railCount;
console.log(`AF Model Switch - Updating rails from ${oldCount} to ${newCount}`);
if (newCount > oldCount) {
// Adding rails: model_3, model_4, etc. (1-based!)
// Rails 1 and 2 are permanent, so we only add 3+
for (let i = oldCount + 1; i <= newCount; i++) {
// Check if this input already exists before adding
const modelExists = this.inputs.find(inp => inp.name === `model_${i}`);
const clipExists = this.inputs.find(inp => inp.name === `clip_${i}`);
const vaeExists = this.inputs.find(inp => inp.name === `vae_${i}`);
if (!modelExists) {
console.log(`AF Model Switch - Adding model_${i}`);
this.addInput(`model_${i}`, "MODEL");
}
if (!clipExists) {
console.log(`AF Model Switch - Adding clip_${i}`);
this.addInput(`clip_${i}`, "CLIP");
}
if (!vaeExists) {
console.log(`AF Model Switch - Adding vae_${i}`);
this.addInput(`vae_${i}`, "VAE");
}
}
} else {
// Removing rails from the end
// NEVER remove rails 1 or 2 (they're permanent)
for (let i = oldCount; i > newCount && i > 2; i--) {
console.log(`AF Model Switch - Removing rail ${i}`);
for (let j = this.inputs.length - 1; j >= 0; j--) {
const input = this.inputs[j];
if (input.name === `vae_${i}` ||
input.name === `clip_${i}` ||
input.name === `model_${i}`) {
this.removeInput(j);
}
}
}
}
this._currentRailCount = newCount;
// Update select widget: 0=AUTO, 1 to newCount for rails
const selectWidget = this.widgets?.find(w => w.name === "selected_rail");
if (selectWidget) {
const maxRail = newCount;
selectWidget.options = selectWidget.options || {};
selectWidget.options.min = 0;
selectWidget.options.max = maxRail;
// Clamp current selection to valid range
const currentValue = parseInt(selectWidget.value);
if (currentValue > maxRail) {
selectWidget.value = maxRail;
this.updateStatusDisplay(maxRail);
}
// Update the input element's attributes
if (selectWidget.inputEl) {
selectWidget.inputEl.max = maxRail;
selectWidget.inputEl.min = 0;
}
}
// Only adjust height, preserve width
const widgetHeight = 100;
const inputHeight = 20;
const totalInputs = this._currentRailCount * 3;
const newHeight = widgetHeight + (totalInputs * inputHeight);
// Preserve current width, only update height
if (this.size) {
this.size[1] = newHeight;
} else {
this.size = [160, newHeight];
}
this.setDirtyCanvas(true, true);
};
// Override computeSize - minimal intervention, let LiteGraph work
const originalComputeSize = nodeType.prototype.computeSize;
nodeType.prototype.computeSize = function(out) {
// If user hasn't resized, calculate proper height
if (!this._userResized) {
let height = 200;
if (this._currentRailCount) {
const widgetHeight = 100;
const inputHeight = 20;
const totalInputs = this._currentRailCount * 3;
height = widgetHeight + (totalInputs * inputHeight);
}
const size = [160, height];
if (out) {
out[0] = size[0];
out[1] = size[1];
return out;
}
return size;
}
// User has resized - let LiteGraph handle it normally
return originalComputeSize?.apply(this, arguments);
};
// Track when user manually resizes
const originalOnResize = nodeType.prototype.onResize;
nodeType.prototype.onResize = function(size) {
this._userResized = true;
return originalOnResize?.apply(this, arguments);
};
}
},
async nodeCreated(node) {
if (node.comfyClass === "AF_Model_Switch") {
setTimeout(() => {
// Style rail count widget
const railCountWidget = node.widgets?.find(w => w.name === "number_of_rails");
if (railCountWidget?.inputEl) {
railCountWidget.inputEl.style.border = "1px solid #4488cc";
railCountWidget.inputEl.style.backgroundColor = "#1a2a3a";
railCountWidget.inputEl.style.padding = "4px";
railCountWidget.inputEl.style.borderRadius = "3px";
}
// Style selected rail widget
const selectWidget = node.widgets?.find(w => w.name === "selected_rail");
if (selectWidget?.inputEl) {
selectWidget.inputEl.style.border = "1px solid #88cc44";
selectWidget.inputEl.style.backgroundColor = "#1a3a2a";
selectWidget.inputEl.style.padding = "4px";
selectWidget.inputEl.style.borderRadius = "3px";
selectWidget.inputEl.style.fontWeight = "bold";
}
// Style status widget
const statusWidget = node.widgets?.find(w => w.name === "status");
if (statusWidget?.inputEl) {
statusWidget.inputEl.readOnly = true;
statusWidget.inputEl.style.fontFamily = "monospace";
statusWidget.inputEl.style.fontSize = "13px";
statusWidget.inputEl.style.fontWeight = "bold";
statusWidget.inputEl.style.cursor = "default";
statusWidget.inputEl.style.padding = "6px 8px";
statusWidget.inputEl.style.textAlign = "center";
statusWidget.inputEl.style.userSelect = "none";
statusWidget.inputEl.style.resize = "none";
statusWidget.inputEl.style.minHeight = "32px";
statusWidget.inputEl.style.maxHeight = "32px";
statusWidget.inputEl.style.height = "32px";
statusWidget.inputEl.style.overflow = "hidden";
statusWidget.inputEl.style.width = "100%";
statusWidget.inputEl.style.boxSizing = "border-box";
// Set initial AUTO color (orange)
statusWidget.inputEl.style.backgroundColor = "#2a2a1a";
statusWidget.inputEl.style.color = "#ffaa44";
statusWidget.inputEl.style.border = "1px solid #cc8833";
}
}, 200);
}
},
async loadedGraphNode(node) {
if (node.comfyClass === "AF_Model_Switch") {
setTimeout(() => {
// Mark as user-resized if node has custom size stored
if (node.size && node.size[0] !== 160) {
node._userResized = true;
}
// Detect current rail count from actual inputs
let actualRailCount = 2; // Minimum (rails 1 and 2 are permanent)
if (node.inputs) {
for (let i = 10; i >= 3; i--) {
if (node.inputs.find(inp => inp.name === `model_${i}`)) {
actualRailCount = i;
break;
}
}
}
// Update _currentRailCount to match actual state
node._currentRailCount = actualRailCount;
const railCountWidget = node.widgets?.find(w => w.name === "number_of_rails");
if (railCountWidget) {
const widgetValue = parseInt(railCountWidget.value) || 2;
console.log(`AF Model Switch - Loaded: widget=${widgetValue}, actual inputs=${actualRailCount}`);
// Sync: if widget and actual don't match, update to match widget
if (widgetValue !== actualRailCount && node.updateRailInputs) {
console.log(`AF Model Switch - Syncing rails to ${widgetValue}`);
node.updateRailInputs(widgetValue);
}
}
}, 100);
}
}
});