573 lines
19 KiB
JavaScript
573 lines
19 KiB
JavaScript
// This JS class is used to handle the specific visibility issues for the ComfyUI-SmartHelperNodes extension.
|
|
|
|
import { app } from '../../scripts/app.js'
|
|
import { api } from '../../scripts/api.js'
|
|
|
|
const SMART_HV_LORA_STACK_NAME = "Smart HV LoRA Stack";
|
|
const SMART_HV_LORA_STACK_COUNT_WIDGET = "lora_count";
|
|
|
|
const nodeWidgetHandlers = {
|
|
"SmartHVLoraStack": {
|
|
[SMART_HV_LORA_STACK_COUNT_WIDGET]: handleSmartHVLoraStackCount
|
|
}
|
|
};
|
|
|
|
const SMART_MODEL_LOADER_VISIBILITY = {
|
|
SmartModelLoader: {
|
|
safetensors: ["unet_name", "weight_dtype"],
|
|
gguf: ["gguf_name"],
|
|
},
|
|
SmartDualModelLoader: {
|
|
safetensors: ["unet_name_1", "unet_name_2", "weight_dtype"],
|
|
gguf: ["gguf_name_1", "gguf_name_2"],
|
|
},
|
|
};
|
|
|
|
const SMART_BUS_DEFAULT_LABELS = [
|
|
"any 1",
|
|
"any 2",
|
|
"any 3",
|
|
"any 4",
|
|
"any 5",
|
|
"any 6",
|
|
"any 7",
|
|
"any 8",
|
|
"any 9",
|
|
"any 10",
|
|
];
|
|
|
|
const SMART_BUS_NODE_CONFIG = {
|
|
SmartBusIn: {
|
|
inputOffset: 1,
|
|
outputOffset: null,
|
|
editable: true,
|
|
slotCount: 10,
|
|
},
|
|
SmartBusOut: {
|
|
inputOffset: null,
|
|
outputOffset: 1,
|
|
editable: false,
|
|
slotCount: 10,
|
|
},
|
|
SmartBusIn5: {
|
|
inputOffset: 1,
|
|
outputOffset: null,
|
|
editable: true,
|
|
slotCount: 5,
|
|
},
|
|
SmartBusOut5: {
|
|
inputOffset: null,
|
|
outputOffset: 1,
|
|
editable: false,
|
|
slotCount: 5,
|
|
},
|
|
};
|
|
|
|
function isSmartBusInNodeType(nodeType) {
|
|
return nodeType === "SmartBusIn" || nodeType === "SmartBusIn5";
|
|
}
|
|
|
|
function isSmartBusOutNodeType(nodeType) {
|
|
return nodeType === "SmartBusOut" || nodeType === "SmartBusOut5";
|
|
}
|
|
|
|
function getSmartBusPropertyNames(config) {
|
|
return SMART_BUS_DEFAULT_LABELS.slice(0, config.slotCount);
|
|
}
|
|
|
|
function ensureSmartBusProperties(node, config) {
|
|
if (!node.properties) {
|
|
node.properties = {};
|
|
}
|
|
if (config.editable && !node.properties_info) {
|
|
node.properties_info = [];
|
|
}
|
|
|
|
const propertyNames = getSmartBusPropertyNames(config);
|
|
|
|
if (config.editable && node.properties_info) {
|
|
node.properties_info = node.properties_info.filter(
|
|
(info) => !SMART_BUS_DEFAULT_LABELS.includes(info.name) || propertyNames.includes(info.name)
|
|
);
|
|
}
|
|
|
|
propertyNames.forEach((defaultLabel) => {
|
|
const propertyName = defaultLabel;
|
|
const existingValue = node.properties[propertyName];
|
|
if (config.editable && !node.properties_info.some((info) => info.name === propertyName)) {
|
|
node.addProperty(propertyName, existingValue ?? defaultLabel, "string");
|
|
}
|
|
if (existingValue === undefined || existingValue === null || existingValue === "") {
|
|
node.properties[propertyName] = defaultLabel;
|
|
} else {
|
|
node.properties[propertyName] = existingValue;
|
|
}
|
|
});
|
|
}
|
|
|
|
function getSmartBusLabel(node, index) {
|
|
const fallback = SMART_BUS_DEFAULT_LABELS[index];
|
|
const value = node.properties?.[fallback];
|
|
if (typeof value !== "string") {
|
|
return fallback;
|
|
}
|
|
|
|
const trimmed = value.trim();
|
|
return trimmed || fallback;
|
|
}
|
|
|
|
function copySmartBusLabels(sourceNode, targetNode) {
|
|
const sourceConfig = SMART_BUS_NODE_CONFIG[sourceNode.type];
|
|
const targetConfig = SMART_BUS_NODE_CONFIG[targetNode.type];
|
|
if (!sourceConfig || !targetConfig) {
|
|
return;
|
|
}
|
|
|
|
ensureSmartBusProperties(sourceNode, sourceConfig);
|
|
ensureSmartBusProperties(targetNode, targetConfig);
|
|
|
|
const sharedSlotCount = Math.min(sourceConfig.slotCount, targetConfig.slotCount);
|
|
SMART_BUS_DEFAULT_LABELS.slice(0, sharedSlotCount).forEach((defaultLabel, index) => {
|
|
targetNode.properties[defaultLabel] = getSmartBusLabel(sourceNode, index);
|
|
});
|
|
}
|
|
|
|
function getSmartBusSourceNode(node) {
|
|
const linkId = node.inputs?.[0]?.link;
|
|
if (!linkId || !node.graph?.links) {
|
|
return null;
|
|
}
|
|
|
|
const link = node.graph.links[linkId];
|
|
if (!link) {
|
|
return null;
|
|
}
|
|
|
|
return node.graph.getNodeById?.(link.origin_id) ?? null;
|
|
}
|
|
|
|
/** Walk bus input chain (BusOut passthrough …) until a SmartBusIn that owns labels. */
|
|
function getSmartBusLabelSourceNode(node) {
|
|
let current = getSmartBusSourceNode(node);
|
|
const visited = new Set();
|
|
while (current) {
|
|
if (visited.has(current.id)) {
|
|
return null;
|
|
}
|
|
visited.add(current.id);
|
|
if (isSmartBusInNodeType(current.type)) {
|
|
return current;
|
|
}
|
|
if (isSmartBusOutNodeType(current.type)) {
|
|
current = getSmartBusSourceNode(current);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function syncSmartBusOutFromSource(node) {
|
|
const labelSource = getSmartBusLabelSourceNode(node);
|
|
if (labelSource) {
|
|
copySmartBusLabels(labelSource, node);
|
|
} else {
|
|
const immediate = getSmartBusSourceNode(node);
|
|
if (immediate && isSmartBusOutNodeType(immediate.type)) {
|
|
copySmartBusLabels(immediate, node);
|
|
}
|
|
}
|
|
|
|
const outLinkIds = node.outputs?.[0]?.links ?? [];
|
|
if (!node.graph || !outLinkIds.length) {
|
|
return;
|
|
}
|
|
for (const linkId of outLinkIds) {
|
|
const link = node.graph.links?.[linkId];
|
|
if (!link) {
|
|
continue;
|
|
}
|
|
if (link.target_slot != null && link.target_slot !== 0) {
|
|
continue;
|
|
}
|
|
const targetNode = node.graph.getNodeById?.(link.target_id);
|
|
if (!targetNode || !isSmartBusOutNodeType(targetNode.type)) {
|
|
continue;
|
|
}
|
|
const targetCfg = SMART_BUS_NODE_CONFIG[targetNode.type];
|
|
syncSmartBusOutFromSource(targetNode);
|
|
applySmartBusLabels(targetNode, targetCfg);
|
|
}
|
|
}
|
|
|
|
function syncConnectedSmartBusOutputs(node) {
|
|
const linkIds = node.outputs?.[0]?.links ?? [];
|
|
if (!node.graph || !linkIds.length) {
|
|
return;
|
|
}
|
|
|
|
for (const linkId of linkIds) {
|
|
const link = node.graph.links?.[linkId];
|
|
if (!link) {
|
|
continue;
|
|
}
|
|
|
|
const targetNode = node.graph.getNodeById?.(link.target_id);
|
|
if (!targetNode || !isSmartBusOutNodeType(targetNode.type)) {
|
|
continue;
|
|
}
|
|
|
|
const targetCfg = SMART_BUS_NODE_CONFIG[targetNode.type];
|
|
syncSmartBusOutFromSource(targetNode);
|
|
applySmartBusLabels(targetNode, targetCfg);
|
|
}
|
|
}
|
|
|
|
function applySmartBusLabels(node, config) {
|
|
ensureSmartBusProperties(node, config);
|
|
|
|
if (node.inputs?.[0]) {
|
|
node.inputs[0].label = "bus";
|
|
}
|
|
if (node.outputs?.[0]) {
|
|
node.outputs[0].label = "bus";
|
|
}
|
|
|
|
SMART_BUS_DEFAULT_LABELS.slice(0, config.slotCount).forEach((_, index) => {
|
|
const label = getSmartBusLabel(node, index);
|
|
|
|
if (config.inputOffset !== null) {
|
|
const input = node.inputs?.[config.inputOffset + index];
|
|
if (input) {
|
|
input.label = label;
|
|
}
|
|
}
|
|
|
|
if (config.outputOffset !== null) {
|
|
const output = node.outputs?.[config.outputOffset + index];
|
|
if (output) {
|
|
output.label = label;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (node.computeSize) {
|
|
node.size = node.computeSize();
|
|
}
|
|
node.setDirtyCanvas?.(true, true);
|
|
}
|
|
|
|
function applySmartModelLoaderGgufVisibility(node, config) {
|
|
const ggufW = findWidgetByName(node, "gguf");
|
|
if (!ggufW || !config) return;
|
|
const isGguf = ggufW.value === true;
|
|
for (const name of config.safetensors) {
|
|
toggleWidget(node, findWidgetByName(node, name), !isGguf);
|
|
}
|
|
for (const name of config.gguf) {
|
|
toggleWidget(node, findWidgetByName(node, name), isGguf);
|
|
}
|
|
}
|
|
|
|
function handleSmartHVLoraStackCount(node, widget) {
|
|
handleVisibility(node, widget.value, SMART_HV_LORA_STACK_NAME);
|
|
}
|
|
|
|
function handleVisibility(node, count, type) {
|
|
if (type === SMART_HV_LORA_STACK_NAME) {
|
|
//console.log("Smart: handleVisibility called for", type, "with count", count);
|
|
for (let i = 1; i <= 50; i++) {
|
|
const show = i <= count;
|
|
const enabledWidget = findWidgetByName(node, `lora_${i}_enabled`);
|
|
const nameWidget = findWidgetByName(node, `lora_${i}_name`);
|
|
const strengthWidget = findWidgetByName(node, `lora_${i}_strength`);
|
|
|
|
toggleWidget(node, enabledWidget, show);
|
|
toggleWidget(node, nameWidget, show);
|
|
toggleWidget(node, strengthWidget, show);
|
|
|
|
// Set disabled state based on enabled checkbox
|
|
if (nameWidget && strengthWidget && enabledWidget) {
|
|
nameWidget.disabled = !enabledWidget.value;
|
|
strengthWidget.disabled = !enabledWidget.value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function findWidgetByName(node, name) {
|
|
return node.widgets?.find(w => w.name === name);
|
|
}
|
|
|
|
function toggleWidget(node, widget, show = false) {
|
|
if (widget) {
|
|
widget.hidden = !show;
|
|
|
|
// Trigger height recalculation
|
|
if (node.computeSize) {
|
|
const size = node.computeSize();
|
|
node.size[1] = size[1];
|
|
}
|
|
|
|
// Mark canvas as dirty to trigger redraw
|
|
node.setDirtyCanvas(true, true);
|
|
}
|
|
}
|
|
|
|
function setupVisibilityHandler(node, countWidgetName, blockType) {
|
|
const countWidget = node.widgets.find(w => w.name === countWidgetName);
|
|
if (countWidget) {
|
|
handleVisibility(node, countWidget.value, blockType);
|
|
}
|
|
return countWidget;
|
|
}
|
|
|
|
function isBypassSwitcherNodeType(nodeType) {
|
|
return nodeType === "SmartBypassSwitcher";
|
|
}
|
|
|
|
function getBypassSwitcherPairCount(node) {
|
|
return Math.max(1, Math.floor((node.inputs?.length || 0) / 2));
|
|
}
|
|
|
|
function renameBypassSwitcherInputs(node) {
|
|
const pairCount = getBypassSwitcherPairCount(node);
|
|
for (let i = 0; i < pairCount; i++) {
|
|
const inputSlot = node.inputs?.[i * 2];
|
|
const controlSlot = node.inputs?.[i * 2 + 1];
|
|
if (inputSlot) {
|
|
inputSlot.name = `input${i + 1}`;
|
|
}
|
|
if (controlSlot) {
|
|
controlSlot.name = `control${i + 1}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
function hasBypassSwitcherPairConnection(node, pairIndex) {
|
|
const inputSlot = node.inputs?.[pairIndex * 2];
|
|
const controlSlot = node.inputs?.[pairIndex * 2 + 1];
|
|
return Boolean(inputSlot?.link != null || controlSlot?.link != null);
|
|
}
|
|
|
|
function syncBypassSwitcherInputs(node) {
|
|
if (!node?.inputs?.length) {
|
|
return;
|
|
}
|
|
|
|
renameBypassSwitcherInputs(node);
|
|
|
|
let lastConnectedPair = -1;
|
|
const pairCount = getBypassSwitcherPairCount(node);
|
|
for (let i = 0; i < pairCount; i++) {
|
|
if (hasBypassSwitcherPairConnection(node, i)) {
|
|
lastConnectedPair = i;
|
|
}
|
|
}
|
|
|
|
const desiredPairCount = Math.max(1, lastConnectedPair + 2);
|
|
|
|
while (getBypassSwitcherPairCount(node) < desiredPairCount) {
|
|
const nextIndex = getBypassSwitcherPairCount(node) + 1;
|
|
node.addInput(`input${nextIndex}`, "*");
|
|
node.addInput(`control${nextIndex}`, "*");
|
|
}
|
|
|
|
while (getBypassSwitcherPairCount(node) > desiredPairCount) {
|
|
const lastPairIndex = getBypassSwitcherPairCount(node) - 1;
|
|
if (hasBypassSwitcherPairConnection(node, lastPairIndex)) {
|
|
break;
|
|
}
|
|
node.removeInput(node.inputs.length - 1);
|
|
node.removeInput(node.inputs.length - 1);
|
|
}
|
|
|
|
renameBypassSwitcherInputs(node);
|
|
node.setDirtyCanvas?.(true, true);
|
|
node.graph?.setDirtyCanvas?.(true, true);
|
|
}
|
|
|
|
// Update the registerExtension implementation
|
|
app.registerExtension({
|
|
name: "SmartHelperNodes",
|
|
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
|
if (nodeData.name === "SmartHVLoraStack") {
|
|
// Store original computeSize
|
|
const origComputeSize = nodeType.prototype.computeSize;
|
|
|
|
// Override computeSize
|
|
nodeType.prototype.computeSize = function() {
|
|
const size = origComputeSize ? origComputeSize.call(this) : [200, 100];
|
|
|
|
// Calculate height based on visible widgets
|
|
let height = 50; // Base height
|
|
|
|
for (const w of this.widgets || []) {
|
|
if (w.hidden) continue;
|
|
height += w.computeSize ? w.computeSize()[1] + 4 : 24;
|
|
}
|
|
|
|
return [size[0], height];
|
|
};
|
|
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function() {
|
|
const result = onNodeCreated?.apply(this, arguments);
|
|
|
|
// Set up widget callbacks
|
|
const loraCountWidget = setupVisibilityHandler(this, SMART_HV_LORA_STACK_COUNT_WIDGET, SMART_HV_LORA_STACK_NAME);
|
|
|
|
if (loraCountWidget) {
|
|
loraCountWidget.callback = () => {
|
|
handleVisibility(this, loraCountWidget.value, SMART_HV_LORA_STACK_NAME);
|
|
};
|
|
}
|
|
|
|
// Set up callbacks for enabled checkboxes
|
|
for (let i = 1; i <= 50; i++) {
|
|
const enabledWidget = this.widgets.find(w => w.name === `lora_${i}_enabled`);
|
|
if (enabledWidget) {
|
|
enabledWidget.callback = () => {
|
|
const nameWidget = findWidgetByName(this, `lora_${i}_name`);
|
|
const strengthWidget = findWidgetByName(this, `lora_${i}_strength`);
|
|
if (nameWidget && strengthWidget) {
|
|
nameWidget.disabled = !enabledWidget.value;
|
|
strengthWidget.disabled = !enabledWidget.value;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// Initial setup - hide widgets based on current count
|
|
if (loraCountWidget) {
|
|
handleVisibility(this, loraCountWidget.value, SMART_HV_LORA_STACK_NAME);
|
|
}
|
|
|
|
// Handle workflow switching and initial load
|
|
const onConnectionsChange = this.onConnectionsChange;
|
|
this.onConnectionsChange = function() {
|
|
const result = onConnectionsChange?.apply(this, arguments);
|
|
setupVisibilityHandler(this, SMART_HV_LORA_STACK_COUNT_WIDGET, SMART_HV_LORA_STACK_NAME);
|
|
return result;
|
|
};
|
|
|
|
const onNodeGraphConfigure = this.onConfigure;
|
|
this.onConfigure = function() {
|
|
const result = onNodeGraphConfigure?.apply(this, arguments);
|
|
setupVisibilityHandler(this, SMART_HV_LORA_STACK_COUNT_WIDGET, SMART_HV_LORA_STACK_NAME);
|
|
return result;
|
|
};
|
|
|
|
return result;
|
|
};
|
|
}
|
|
|
|
const smartLoaderCfg = SMART_MODEL_LOADER_VISIBILITY[nodeData.name];
|
|
if (smartLoaderCfg) {
|
|
const origComputeSize = nodeType.prototype.computeSize;
|
|
nodeType.prototype.computeSize = function () {
|
|
const size = origComputeSize ? origComputeSize.call(this) : [200, 100];
|
|
let height = 50;
|
|
for (const w of this.widgets || []) {
|
|
if (w.hidden) continue;
|
|
height += w.computeSize ? w.computeSize()[1] + 4 : 24;
|
|
}
|
|
return [size[0], height];
|
|
};
|
|
|
|
const onNodeCreatedLoader = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const result = onNodeCreatedLoader?.apply(this, arguments);
|
|
const ggufW = findWidgetByName(this, "gguf");
|
|
const sync = () => applySmartModelLoaderGgufVisibility(this, smartLoaderCfg);
|
|
if (ggufW) {
|
|
const prev = ggufW.callback;
|
|
ggufW.callback = () => {
|
|
prev?.apply(ggufW, arguments);
|
|
sync();
|
|
};
|
|
}
|
|
sync();
|
|
const onConfigure = this.onConfigure;
|
|
this.onConfigure = function () {
|
|
const r = onConfigure?.apply(this, arguments);
|
|
sync();
|
|
return r;
|
|
};
|
|
return result;
|
|
};
|
|
}
|
|
|
|
const smartBusCfg = SMART_BUS_NODE_CONFIG[nodeData.name];
|
|
if (smartBusCfg) {
|
|
const onNodeCreatedBus = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const result = onNodeCreatedBus?.apply(this, arguments);
|
|
if (isSmartBusOutNodeType(nodeData.name)) {
|
|
syncSmartBusOutFromSource(this);
|
|
}
|
|
applySmartBusLabels(this, smartBusCfg);
|
|
return result;
|
|
};
|
|
|
|
const onConfigureBus = nodeType.prototype.onConfigure;
|
|
nodeType.prototype.onConfigure = function () {
|
|
const result = onConfigureBus?.apply(this, arguments);
|
|
if (isSmartBusOutNodeType(nodeData.name)) {
|
|
syncSmartBusOutFromSource(this);
|
|
}
|
|
applySmartBusLabels(this, smartBusCfg);
|
|
return result;
|
|
};
|
|
|
|
const onPropertyChangedBus = nodeType.prototype.onPropertyChanged;
|
|
nodeType.prototype.onPropertyChanged = function (property, value, prevValue) {
|
|
const result = onPropertyChangedBus?.apply(this, arguments);
|
|
const propertyNames = getSmartBusPropertyNames(smartBusCfg);
|
|
if (isSmartBusInNodeType(nodeData.name) && propertyNames.includes(property)) {
|
|
this.properties[property] = (typeof value === "string" && value.trim()) ? value.trim() : property;
|
|
applySmartBusLabels(this, smartBusCfg);
|
|
syncConnectedSmartBusOutputs(this);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const onConnectionsChangeBus = nodeType.prototype.onConnectionsChange;
|
|
nodeType.prototype.onConnectionsChange = function () {
|
|
const result = onConnectionsChangeBus?.apply(this, arguments);
|
|
if (isSmartBusOutNodeType(nodeData.name)) {
|
|
syncSmartBusOutFromSource(this);
|
|
}
|
|
applySmartBusLabels(this, smartBusCfg);
|
|
if (isSmartBusInNodeType(nodeData.name)) {
|
|
syncConnectedSmartBusOutputs(this);
|
|
}
|
|
return result;
|
|
};
|
|
}
|
|
|
|
if (isBypassSwitcherNodeType(nodeData.name)) {
|
|
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
const result = onNodeCreated?.apply(this, arguments);
|
|
syncBypassSwitcherInputs(this);
|
|
return result;
|
|
};
|
|
|
|
const onConfigure = nodeType.prototype.onConfigure;
|
|
nodeType.prototype.onConfigure = function () {
|
|
const result = onConfigure?.apply(this, arguments);
|
|
syncBypassSwitcherInputs(this);
|
|
return result;
|
|
};
|
|
|
|
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
|
nodeType.prototype.onConnectionsChange = function () {
|
|
const result = onConnectionsChange?.apply(this, arguments);
|
|
syncBypassSwitcherInputs(this);
|
|
return result;
|
|
};
|
|
}
|
|
}
|
|
});
|