738 lines
38 KiB
JavaScript
738 lines
38 KiB
JavaScript
console.log("%c[ActiveModel] JS файл завантажився! (v2-dynamic-inputs)", "color: white; background: #007acc; font-weight: bold; padding: 2px 8px; border-radius: 3px;");
|
||
// --- Loading js/active_model.js --- v2-dynamic-inputs
|
||
import { app } from "/scripts/app.js"; // Виправлено шлях на абсолютний
|
||
|
||
// Цільовий тип вузла, який ми шукаємо
|
||
const TARGET_NODE_TYPE = "ModelParamsPipe";
|
||
// Режими роботи вузлів ComfyUI/LiteGraph
|
||
const MODE_ALWAYS = 0; // Завжди активний
|
||
const MODE_BYPASS = 4; // Режим обходу (Bypass)
|
||
const NODE_TITLE = "Active Model"; // Змінено заголовок
|
||
const NODE_TYPE = "ActiveModel"; // І тип
|
||
|
||
class ActiveModel {
|
||
|
||
// Налаштування для LiteGraph
|
||
constructor() {
|
||
// Цей вузол є переважно контейнером для дій
|
||
this.properties = {};
|
||
console.log(`[${ActiveModel.title}] CONSTRUCTOR CALLED.`);
|
||
// Спробуємо знайти та вивести вузли одразу при створенні
|
||
this.logTargetNodeNames();
|
||
}
|
||
|
||
// Статичні властивості для визначення типу вузла
|
||
static title = "Active Model";
|
||
static type = "ActiveModel"; // Унікальний тип для цього вузла
|
||
|
||
// Функція для пошуку та логування імен цільових вузлів
|
||
logTargetNodeNames() {
|
||
console.log(`[${ActiveModel.title}] Searching for nodes of type '${TARGET_NODE_TYPE}'...`);
|
||
const nodes = app.graph._nodes.filter(node => node.type === TARGET_NODE_TYPE);
|
||
console.log(`[${ActiveModel.title}] Found ${nodes.length} nodes:`);
|
||
if (nodes.length > 0) {
|
||
nodes.forEach(n => console.log(` - Node ID: ${n.id}, Title: ${n.title}, Current mode: ${n.mode}`));
|
||
} else {
|
||
console.log(` (No nodes found)`);
|
||
}
|
||
return nodes; // Повертаємо для можливого використання в onAction
|
||
}
|
||
|
||
// Метод для встановлення режиму для всіх знайдених цільових вузлів
|
||
setHubsMode(mode) {
|
||
const nodes = this.logTargetNodeNames();
|
||
if (nodes.length === 0) {
|
||
console.log(`[${ActiveModel.title}] No target nodes found to set mode.`);
|
||
return; // Виходимо, якщо немає цільових вузлів
|
||
}
|
||
const modeStr = mode === MODE_ALWAYS ? "ALWAYS" : (mode === MODE_BYPASS ? "BYPASS" : mode);
|
||
console.log(`[${ActiveModel.title}] Attempting to set mode to ${modeStr} for ${nodes.length} nodes.`);
|
||
|
||
let changed = false;
|
||
for (const node of nodes) {
|
||
console.log(` - Processing Node ID: ${node.id}, Title: ${node.title}, Current mode: ${node.mode}`);
|
||
if (node.mode !== mode) {
|
||
node.mode = mode;
|
||
console.log(` - Mode set to: ${node.mode}`);
|
||
changed = true;
|
||
} else {
|
||
console.log(` - Mode already set to ${node.mode}. Skipping.`);
|
||
}
|
||
}
|
||
|
||
if (changed) {
|
||
console.log(`[${ActiveModel.title}] Triggering canvas redraw.`);
|
||
app.graph.setDirtyCanvas(true, true);
|
||
} else {
|
||
console.log(`[${ActiveModel.title}] No modes were changed. Skipping redraw.`);
|
||
}
|
||
}
|
||
|
||
// Спрощена обробка дій для тестування
|
||
onAction(action) {
|
||
console.log(`[${ActiveModel.title}] === onAction ENTERED === Action: ${action}`);
|
||
// Просто логуємо знайдені вузли ще раз при виклику дії
|
||
this.logTargetNodeNames();
|
||
// Логіку зміни режимів поки що прибрали
|
||
// if (action === "Bypass All Hubs") { ... }
|
||
}
|
||
|
||
// Цей вузол не виконує обчислень, тому onExecute може бути порожнім
|
||
onExecute() {
|
||
// console.log(`[${ActiveModel.title}] Executed (likely no-op).`);
|
||
}
|
||
|
||
// Статичний метод для налаштування та реєстрації вузла
|
||
static setUp(clazz) {
|
||
// Явно встановлюємо статичні властивості, які потрібні LiteGraph
|
||
clazz.title = ActiveModel.title;
|
||
clazz.type = ActiveModel.type;
|
||
clazz.title_mode = LiteGraph.NORMAL_TITLE;
|
||
clazz.collapsable = true;
|
||
clazz.size = [240, 50];
|
||
clazz.category = "MultiModel"; // Змінюємо категорію на основну
|
||
|
||
console.log(`[${ActiveModel.title}] Attempting to register node type via setUp: ${clazz.type}`);
|
||
try {
|
||
// Реєструємо сам клас
|
||
LiteGraph.registerNodeType(clazz.type, clazz);
|
||
console.log(`[${ActiveModel.title}] Node type registration via setUp SUCCESSFUL.`);
|
||
} catch (e) {
|
||
console.error(`[${ActiveModel.title}] Node type registration via setUp FAILED:`, e);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Визначаємо дії, доступні в контекстному меню
|
||
ActiveModel.exposedActions = ["Log Hub Names", "Enable All Hubs", "Bypass All Hubs", "Toggle All Hubs"];
|
||
// Визначаємо статичні властивості title та type тут, щоб вони були доступні для setUp
|
||
ActiveModel.title = "Active Model";
|
||
ActiveModel.type = "ActiveModel";
|
||
|
||
// --- Допоміжні функції --- //
|
||
|
||
// Функція пошуку цільових вузлів (тепер отримує їх з сервісу)
|
||
function findTargetNodes() {
|
||
return HubNodesService.getHubNodes();
|
||
}
|
||
|
||
// Функція зміни режиму ОДНОГО вузла
|
||
function setHubMode(targetNode, mode) {
|
||
if (!targetNode) return;
|
||
const currentMode = targetNode.mode;
|
||
const modeStr = mode === MODE_ALWAYS ? "ON" : "OFF";
|
||
console.log(`[${NODE_TITLE}] Setting Node ID ${targetNode.id} ('${targetNode.title}') to mode ${modeStr}`);
|
||
|
||
// Якщо ми вмикаємо вузол (MODE_ALWAYS), і при цьому поточний режим НЕ MODE_ALWAYS
|
||
// (тобто вузол був вимкнений, а тепер ми його вмикаємо)
|
||
if (mode === MODE_ALWAYS && currentMode !== MODE_ALWAYS) {
|
||
// Потрібно перевірити, чи є інші активні вузли, і вимкнути їх
|
||
const allHubNodes = HubNodesService.getHubNodes();
|
||
const otherActiveNodes = allHubNodes.filter(n => n.id !== targetNode.id && n.mode === MODE_ALWAYS);
|
||
|
||
if (otherActiveNodes.length > 0) {
|
||
console.log(`[${NODE_TITLE}] Found ${otherActiveNodes.length} other active nodes, disabling them...`);
|
||
otherActiveNodes.forEach(node => {
|
||
node.mode = MODE_BYPASS;
|
||
console.log(`[${NODE_TITLE}] Disabled node ID ${node.id} ('${node.title}')`);
|
||
|
||
// Оновлюємо віджети всіх байпасерів для цього вузла
|
||
updateAllBypasserWidgets(node);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Змінюємо режим цільового вузла, незалежно від того, чи є інші активні вузли
|
||
if (currentMode !== mode) {
|
||
targetNode.mode = mode;
|
||
app.graph.setDirtyCanvas(true, true);
|
||
|
||
// Також оновлюємо віджети для цього вузла
|
||
updateAllBypasserWidgets(targetNode);
|
||
}
|
||
}
|
||
|
||
// Нова функція для оновлення віджетів у всіх байпасерах для конкретного вузла
|
||
function updateAllBypasserWidgets(targetNode) {
|
||
if (!targetNode) return;
|
||
|
||
console.log(`[${NODE_TITLE}] Updating widgets for node ID: ${targetNode.id} in all bypassers`);
|
||
const bypassers = HubNodesService.bypasserNodes;
|
||
|
||
bypassers.forEach(bypasser => {
|
||
if (!bypasser || !bypasser.widgets) return;
|
||
|
||
const widgetName = `hub_${targetNode.id}`;
|
||
const widget = bypasser.widgets.find(w => w.name === widgetName);
|
||
|
||
if (widget) {
|
||
// Оновлюємо значення віджета
|
||
const isBypassed = targetNode.mode === MODE_BYPASS;
|
||
const newValue = !isBypassed;
|
||
|
||
// Оновлюємо також заголовок, щоб він відповідав новому формату
|
||
const newLabel = `${targetNode.title || '(Untitled)'}`;
|
||
|
||
let changed = false;
|
||
|
||
if (widget.value !== newValue) {
|
||
console.log(`[${NODE_TITLE}] Updating widget value in bypasser ID: ${bypasser.id} for node ID: ${targetNode.id} to: ${newValue}`);
|
||
widget.value = newValue;
|
||
changed = true;
|
||
}
|
||
|
||
if (widget.label !== newLabel) {
|
||
console.log(`[${NODE_TITLE}] Updating widget label in bypasser ID: ${bypasser.id} for node ID: ${targetNode.id} to: "${newLabel}"`);
|
||
widget.label = newLabel;
|
||
changed = true;
|
||
}
|
||
|
||
if (changed) {
|
||
bypasser.graph.setDirtyCanvas(true, false);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// --- Сервіс для керування вузлами --- //
|
||
const HubNodesService = {
|
||
hubNodes: [],
|
||
bypasserNodes: [],
|
||
updateTimeout: null,
|
||
updateDelay: 100,
|
||
app: null, // Додано для доступу до app
|
||
|
||
registerBypasserNode(node) {
|
||
if (!this.bypasserNodes.find(n => n.id === node.id)) {
|
||
console.log(`[HubNodesService] Registering bypasser node ID: ${node.id}`);
|
||
this.bypasserNodes.push(node);
|
||
this.scheduleUpdate(`registerBypasser_${node.id}`);
|
||
}
|
||
},
|
||
|
||
unregisterBypasserNode(node) {
|
||
const index = this.bypasserNodes.findIndex(n => n.id === node.id);
|
||
if (index !== -1) {
|
||
console.log(`[HubNodesService] Unregistering bypasser node ID: ${node.id}`);
|
||
this.bypasserNodes.splice(index, 1);
|
||
// Оновлення не потрібне при видаленні самого байпасера
|
||
} else {
|
||
console.log(`[HubNodesService] Node ID ${node.id} not found for unregistration.`);
|
||
}
|
||
},
|
||
|
||
getHubNodes() {
|
||
return [...this.hubNodes];
|
||
},
|
||
|
||
// --- Нові методи для сповіщень від Hub --- //
|
||
notifyHubAdded(node) {
|
||
if (!node) return;
|
||
console.log(`[HubNodesService] Hub node added notification received: ID ${node.id}`);
|
||
if (!this.hubNodes.find(n => n.id === node.id)) {
|
||
this.hubNodes.push(node);
|
||
this.hubNodes.sort((a, b) => a.id - b.id); // Підтримуємо сортування
|
||
this.scheduleUpdate(`notifyHubAdded_${node.id}`);
|
||
} else {
|
||
console.log(`[HubNodesService] Hub node ID ${node.id} already exists.`);
|
||
}
|
||
},
|
||
|
||
notifyHubRemoved(node) {
|
||
if (!node) return;
|
||
console.log(`[HubNodesService] Hub node removed notification received: ID ${node.id}`);
|
||
const index = this.hubNodes.findIndex(n => n.id === node.id);
|
||
if (index !== -1) {
|
||
this.hubNodes.splice(index, 1);
|
||
this.scheduleUpdate(`notifyHubRemoved_${node.id}`);
|
||
} else {
|
||
console.log(`[HubNodesService] Hub node ID ${node.id} not found for removal notification.`);
|
||
}
|
||
},
|
||
|
||
notifyHubRenamed(node) {
|
||
if (!node) return;
|
||
console.log(`[HubNodesService] Hub node rename notification received: ID ${node.id}`);
|
||
// Сам список hubNodes містить посилання на об'єкти вузлів,
|
||
// тому оновлювати список не потрібно, зміна title вже відбулася.
|
||
// Просто плануємо оновлення віджетів, щоб відобразити нову назву.
|
||
this.scheduleUpdate(`notifyHubRenamed_${node.id}`);
|
||
},
|
||
// --- Кінець нових методів --- //
|
||
|
||
scheduleUpdate(reason = "unknown") {
|
||
console.log(`[HubNodesService] Scheduling update. Reason: ${reason}`);
|
||
if (this.updateTimeout) {
|
||
clearTimeout(this.updateTimeout);
|
||
}
|
||
this.updateTimeout = setTimeout(() => {
|
||
console.log("[HubNodesService] Timeout reached. Running update...");
|
||
this.updateTimeout = null;
|
||
// Оновлення списку hubNodes більше не потрібне тут
|
||
// this.refreshHubNodes();
|
||
this.bypasserNodes.forEach(node => {
|
||
if (node.refreshWidgets) {
|
||
console.log(`[HubNodesService] Refreshing widgets for bypasser node ID: ${node.id}`);
|
||
node.refreshWidgets();
|
||
} else {
|
||
console.warn(`[HubNodesService] Bypasser node ID: ${node.id} missing refreshWidgets method.`);
|
||
}
|
||
});
|
||
console.log("[HubNodesService] Update finished.");
|
||
}, this.updateDelay);
|
||
}
|
||
};
|
||
|
||
app.registerExtension({
|
||
name: "MultiModel.ActiveModel.ServiceDriven", // Оновлено назву розширення
|
||
|
||
setup(appInstance) {
|
||
console.log(`[${NODE_TITLE}] === Extension setup CALLED ===`);
|
||
// Реєструємо сервіс, щоб він був доступний іншим розширенням
|
||
appInstance.HubNodesService = HubNodesService;
|
||
HubNodesService.app = appInstance;
|
||
|
||
// Додаємо перехоплення onNodeAdded для відстеження додавання нашої ноди
|
||
if (appInstance.graph && !appInstance.graph._activemodel_onNodeAdded_attached) {
|
||
const origNodeAdded = appInstance.graph.onNodeAdded;
|
||
|
||
appInstance.graph.onNodeAdded = function(node) {
|
||
// Спочатку викликаємо оригінальний обробник
|
||
if (origNodeAdded) {
|
||
origNodeAdded.call(this, node);
|
||
}
|
||
|
||
// Перевіряємо, чи це наша нода ActiveModel
|
||
if (node.type === NODE_TYPE) {
|
||
console.log(`[${NODE_TITLE}] Our node type was added to graph: ID ${node.id}`);
|
||
|
||
// Викликаємо обробку ноди для додавання віджетів
|
||
const extension = appInstance.extensions.find(ext => ext.name === "MultiModel.ActiveModel.ServiceDriven");
|
||
if (extension) {
|
||
// Конфігуруємо ноду
|
||
extension.loadedGraphNode(node, appInstance);
|
||
|
||
// Оновлюємо віджети з невеликою затримкою
|
||
setTimeout(() => {
|
||
if (node.refreshWidgets) {
|
||
console.log(`[${NODE_TITLE}] Running refreshWidgets for newly added node ID: ${node.id}`);
|
||
node.refreshWidgets();
|
||
}
|
||
}, 100);
|
||
}
|
||
}
|
||
};
|
||
|
||
appInstance.graph._activemodel_onNodeAdded_attached = true;
|
||
console.log(`[${NODE_TITLE}] Attached our onNodeAdded handler to graph`);
|
||
}
|
||
|
||
// Початкове сканування потрібне, щоб знайти вузли, які вже існують при завантаженні
|
||
setTimeout(() => {
|
||
console.log("[HubNodesService] Initial node scan after setup.");
|
||
// Знаходимо існуючі Hub та Bypasser ноди і реєструємо їх
|
||
let initialHubsFound = 0;
|
||
let initialBypassersFound = 0;
|
||
appInstance.graph._nodes.forEach(node => {
|
||
if (node.type === TARGET_NODE_TYPE) {
|
||
HubNodesService.notifyHubAdded(node);
|
||
initialHubsFound++;
|
||
} else if (node.type === NODE_TYPE) {
|
||
// Реєстрація bypasser'а відбувається також у loadedGraphNode,
|
||
// але продублюємо тут на випадок проблем з порядком
|
||
HubNodesService.registerBypasserNode(node);
|
||
initialBypassersFound++;
|
||
|
||
// Додаємо додаткову ініціалізацію для вже існуючих Bypasser нод
|
||
this.loadedGraphNode(node, appInstance);
|
||
}
|
||
});
|
||
console.log(`[HubNodesService] Initial scan found ${initialHubsFound} hubs and ${initialBypassersFound} bypassers.`);
|
||
|
||
// Додаємо окремий виклик для оновлення віджетів для всіх bypassers з більшою затримкою
|
||
if (initialBypassersFound > 0) {
|
||
setTimeout(() => {
|
||
console.log(`[${NODE_TITLE}] Refreshing widgets for ${initialBypassersFound} bypassers after initial scan`);
|
||
HubNodesService.bypasserNodes.forEach(node => {
|
||
if (node && node.refreshWidgets) {
|
||
node.refreshWidgets();
|
||
}
|
||
});
|
||
}, 500);
|
||
}
|
||
|
||
// Оновлення віджетів запланується всередині notifyHubAdded/registerBypasserNode
|
||
}, 500);
|
||
},
|
||
|
||
// loadedGraphNode тепер відповідає ТІЛЬКИ за конфігурацію САМОГО ActiveModel
|
||
loadedGraphNode(node, appInstance) {
|
||
if (node.type === NODE_TYPE) {
|
||
console.log(`[${NODE_TITLE}] Configuring node instance ID: ${node.id}`);
|
||
node.app = appInstance;
|
||
node.IS_BYPASSER_NODE = true;
|
||
delete node.onAction; // Видаляємо, бо додамо getActions
|
||
|
||
// Визначаємо refreshWidgets
|
||
node.refreshWidgets = function() {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Running refreshWidgets...`);
|
||
const targetNodes = findTargetNodes(); // Отримуємо з сервісу
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Got ${targetNodes.length} target nodes from service.`);
|
||
|
||
// Додаємо детальну діагностику
|
||
if (targetNodes.length === 0) {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] No target nodes found. HubNodesService state:`,
|
||
{hubNodesCount: HubNodesService.hubNodes.length,
|
||
bypassersCount: HubNodesService.bypasserNodes.length});
|
||
} else {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Target nodes details:`,
|
||
targetNodes.map(n => ({id: n.id, title: n.title, mode: n.mode})));
|
||
}
|
||
|
||
// --- Подальша логіка залишається ТАКОЮ Ж САМОЮ, як і раніше ---
|
||
// (Порівняння віджетів, додавання, видалення, оновлення)
|
||
const currentWidgets = this.widgets ? [...this.widgets] : [];
|
||
let widgetsChanged = false;
|
||
const activeWidgetNames = new Set();
|
||
|
||
// Add/Update widgets
|
||
targetNodes.forEach(targetNode => {
|
||
if (!targetNode) {
|
||
console.warn(`[${NODE_TITLE} ID: ${this.id}] Found a null/undefined targetNode in the list from service. Skipping.`);
|
||
return; // Пропускаємо невалідні ноди
|
||
}
|
||
const widgetName = `hub_${targetNode.id}`;
|
||
// Прибираємо ID ноди з заголовка
|
||
const widgetLabel = `${targetNode.title || '(Untitled)'}`;
|
||
const isBypassed = targetNode.mode === MODE_BYPASS;
|
||
const widgetValue = !isBypassed;
|
||
|
||
activeWidgetNames.add(widgetName);
|
||
|
||
let existingWidget = this.widgets?.find(w => w.name === widgetName);
|
||
|
||
if (existingWidget) {
|
||
let labelChanged = false;
|
||
if (existingWidget.label !== widgetLabel) {
|
||
existingWidget.label = widgetLabel;
|
||
labelChanged = true;
|
||
}
|
||
if (existingWidget.value !== widgetValue) {
|
||
existingWidget.value = widgetValue;
|
||
widgetsChanged = true;
|
||
}
|
||
if (labelChanged) widgetsChanged = true;
|
||
|
||
if (!existingWidget.callback) {
|
||
existingWidget.callback = (value) => {
|
||
// Нова логіка: якщо перемикач вмикається, вимикаємо всі інші
|
||
// якщо вимикається - просто вимикаємо
|
||
const nodeToChange = app.graph.getNodeById(targetNode.id);
|
||
if (nodeToChange) setHubMode(nodeToChange, value ? MODE_ALWAYS : MODE_BYPASS);
|
||
};
|
||
widgetsChanged = true;
|
||
}
|
||
} else {
|
||
this.addWidget(
|
||
"toggle", widgetName, widgetValue,
|
||
(value) => {
|
||
// Нова логіка: дозволяємо будь-які переключення
|
||
const nodeToChange = app.graph.getNodeById(targetNode.id);
|
||
if (nodeToChange) setHubMode(nodeToChange, value ? MODE_ALWAYS : MODE_BYPASS);
|
||
else this.refreshWidgets();
|
||
},
|
||
{ on: "ON", off: "OFF" }
|
||
);
|
||
this.widgets[this.widgets.length - 1].label = widgetLabel;
|
||
widgetsChanged = true;
|
||
}
|
||
});
|
||
|
||
// Remove outdated widgets
|
||
let removedWidgets = false;
|
||
if (this.widgets) {
|
||
for (let i = this.widgets.length - 1; i >= 0; i--) {
|
||
const widget = this.widgets[i];
|
||
if (widget.name?.startsWith("hub_") && !activeWidgetNames.has(widget.name)) {
|
||
this.widgets.splice(i, 1);
|
||
widgetsChanged = true;
|
||
removedWidgets = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Resize node only if widgets were added/removed
|
||
if (widgetsChanged) {
|
||
// ... (логіка зміни розміру залишається такою ж) ...
|
||
const baseHeight = LiteGraph.NODE_TITLE_HEIGHT + 5;
|
||
const widgetHeight = LiteGraph.NODE_WIDGET_HEIGHT;
|
||
const spacing = 4;
|
||
const hubWidgets = this.widgets?.filter(w => w.name?.startsWith("hub_")) || [];
|
||
const numWidgets = hubWidgets.length;
|
||
const requiredHeight = baseHeight + numWidgets * (widgetHeight + spacing) + (numWidgets > 0 ? spacing : 0);
|
||
const computedSize = this.computeSize();
|
||
const newHeight = Math.max(computedSize[1], requiredHeight);
|
||
if (Math.abs(this.size[1] - newHeight) > 2 || removedWidgets) {
|
||
this.size[1] = newHeight;
|
||
this.graph.setDirtyCanvas(true, true);
|
||
} else if (widgetsChanged) {
|
||
this.graph.setDirtyCanvas(true, false);
|
||
}
|
||
}
|
||
|
||
// Перевірка на кількість активних вузлів після оновлення віджетів
|
||
setTimeout(() => {
|
||
const activeNodes = targetNodes.filter(node => node.mode === MODE_ALWAYS);
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] After refresh: ${activeNodes.length} active nodes`);
|
||
|
||
// Якщо більше одного активного вузла, залишаємо тільки перший
|
||
// Але не активуємо автоматично вузли, якщо всі вимкнені
|
||
if (activeNodes.length > 1) {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Multiple active nodes (${activeNodes.length}), keeping only the first one...`);
|
||
// Залишаємо перший вузол активним, інші вимикаємо
|
||
for (let i = 1; i < activeNodes.length; i++) {
|
||
setHubMode(activeNodes[i], MODE_BYPASS);
|
||
|
||
// Оновлюємо відповідний віджет
|
||
const widgetName = `hub_${activeNodes[i].id}`;
|
||
const widget = this.widgets?.find(w => w.name === widgetName);
|
||
if (widget) {
|
||
widget.value = false;
|
||
}
|
||
}
|
||
this.graph.setDirtyCanvas(true, false);
|
||
}
|
||
}, 100);
|
||
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] refreshWidgets finished.`);
|
||
};
|
||
|
||
// Визначаємо дії для контекстного меню
|
||
node.getActions = function() {
|
||
const actions = [];
|
||
if (this.widgets?.some(w => w.name.startsWith("hub_"))) {
|
||
actions.push({ name: "Activate First Hub", callback: () => this.setAllHubsMode(MODE_ALWAYS) });
|
||
actions.push({ name: "Switch To Next Hub", callback: () => this.toggleAllHubsMode() });
|
||
}
|
||
actions.push({ name: "Log Hub Names (Console)", callback: () => this.logTargetNodeNames() });
|
||
return actions;
|
||
};
|
||
node.logTargetNodeNames = function() { // Додаємо метод до екземпляра
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Logging target node names...`);
|
||
const nodes = HubNodesService.getHubNodes();
|
||
console.log(` Found ${nodes.length} nodes via service:`);
|
||
if (nodes.length > 0) {
|
||
nodes.forEach(n => console.log(` - Node ID: ${n.id}, Title: ${n.title}, Current mode: ${n.mode}`));
|
||
} else {
|
||
console.log(` (No nodes found)`);
|
||
}
|
||
};
|
||
|
||
// Оновлений метод для встановлення режиму всіх хабів
|
||
node.setAllHubsMode = function(mode) {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] setAllHubsMode called with mode: ${mode}`);
|
||
const hubNodes = HubNodesService.getHubNodes();
|
||
|
||
if (hubNodes.length === 0) {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] No hub nodes found to set mode.`);
|
||
return;
|
||
}
|
||
|
||
if (mode === MODE_ALWAYS) {
|
||
// Активуємо тільки перший вузол, інші вимикаємо
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Enabling only the first hub node, disabling others.`);
|
||
|
||
hubNodes.forEach((node, index) => {
|
||
const newMode = index === 0 ? MODE_ALWAYS : MODE_BYPASS;
|
||
|
||
if (node.mode !== newMode) {
|
||
node.mode = newMode;
|
||
updateAllBypasserWidgets(node);
|
||
}
|
||
});
|
||
|
||
app.graph.setDirtyCanvas(true, true);
|
||
} else if (mode === MODE_BYPASS) {
|
||
// Якщо режим BYPASS, вимикаємо всі вузли
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Disabling all hub nodes.`);
|
||
|
||
let changed = false;
|
||
hubNodes.forEach(node => {
|
||
if (node.mode !== MODE_BYPASS) {
|
||
node.mode = MODE_BYPASS;
|
||
updateAllBypasserWidgets(node);
|
||
changed = true;
|
||
}
|
||
});
|
||
|
||
if (changed) {
|
||
app.graph.setDirtyCanvas(true, true);
|
||
}
|
||
}
|
||
};
|
||
|
||
// Оновлений метод для перемикання вузлів
|
||
node.toggleAllHubsMode = function() {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] toggleAllHubsMode called`);
|
||
const hubNodes = HubNodesService.getHubNodes();
|
||
|
||
if (hubNodes.length === 0) {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] No hub nodes found to toggle.`);
|
||
return;
|
||
}
|
||
|
||
// Перевіряємо, чи є хоча б один активний вузол
|
||
const activeNodes = hubNodes.filter(n => n.mode === MODE_ALWAYS);
|
||
|
||
if (activeNodes.length === 0) {
|
||
// Якщо немає активних вузлів, активуємо перший
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] No active nodes, activating the first one.`);
|
||
if (hubNodes.length > 0) {
|
||
const node = hubNodes[0];
|
||
node.mode = MODE_ALWAYS;
|
||
updateAllBypasserWidgets(node);
|
||
app.graph.setDirtyCanvas(true, true);
|
||
}
|
||
} else {
|
||
// Якщо є активний вузол, знаходимо його індекс і активуємо наступний
|
||
const activeNode = activeNodes[0];
|
||
const currentIndex = hubNodes.findIndex(n => n.id === activeNode.id);
|
||
|
||
// Вимикаємо поточний активний вузол
|
||
activeNode.mode = MODE_BYPASS;
|
||
updateAllBypasserWidgets(activeNode);
|
||
|
||
// Знаходимо наступний вузол (або переходимо до першого, якщо це останній)
|
||
const nextIndex = (currentIndex + 1) % hubNodes.length;
|
||
const nextNode = hubNodes[nextIndex];
|
||
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Toggling from node ID ${activeNode.id} to node ID ${nextNode.id}`);
|
||
|
||
// Активуємо наступний вузол
|
||
nextNode.mode = MODE_ALWAYS;
|
||
updateAllBypasserWidgets(nextNode);
|
||
|
||
app.graph.setDirtyCanvas(true, true);
|
||
}
|
||
};
|
||
|
||
// Реєстрація/видалення самого bypasser'а в сервісі
|
||
node.onAdded = function() {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Bypasser node added to graph. Registering with service.`);
|
||
if (appInstance.HubNodesService) { // Перевіряємо наявність сервісу
|
||
appInstance.HubNodesService.registerBypasserNode(this);
|
||
} else {
|
||
console.error(`[${NODE_TITLE} ID: ${this.id}] Cannot register bypasser, HubNodesService not found!`);
|
||
}
|
||
};
|
||
node.onRemoved = function() {
|
||
console.log(`[${NODE_TITLE} ID: ${this.id}] Bypasser node removed from graph. Unregistering from service.`);
|
||
if (appInstance.HubNodesService) {
|
||
appInstance.HubNodesService.unregisterBypasserNode(this);
|
||
} else {
|
||
console.error(`[${NODE_TITLE} ID: ${this.id}] Cannot unregister bypasser, HubNodesService not found!`);
|
||
}
|
||
};
|
||
|
||
// Початкова реєстрація, якщо вузол вже існує при завантаженні
|
||
if (appInstance.HubNodesService) {
|
||
appInstance.HubNodesService.registerBypasserNode(node);
|
||
} else {
|
||
// Якщо сервіс ще не створено (малоймовірно, але можливо),
|
||
// реєстрація відбудеться пізніше через onAdded
|
||
console.warn(`[${NODE_TITLE} ID: ${node.id}] Initial registration deferred, HubNodesService not yet available.`);
|
||
}
|
||
|
||
// Початкове оновлення віджетів для цього конкретного вузла
|
||
setTimeout(() => { if (node.refreshWidgets) node.refreshWidgets(); }, 50);
|
||
}
|
||
},
|
||
|
||
getExtraMenuOptions(_, options) {
|
||
options.push({
|
||
content: "Add Active Model",
|
||
callback: () => {
|
||
const node = LiteGraph.createNode(NODE_TYPE);
|
||
// Записуємо текст для коментаря, щоб показати функціональність
|
||
node.properties.desc = "Controls the flow of Model Parameters Pipe Hubs";
|
||
app.graph.add(node);
|
||
|
||
// Розміщення вузла в центрі видимої області
|
||
const canvas = app.canvas;
|
||
const visible_rect = canvas.visible_area;
|
||
node.pos[0] = visible_rect[0] + visible_rect[2] * 0.5 - node.size[0] * 0.5;
|
||
node.pos[1] = visible_rect[1] + visible_rect[3] * 0.5 - node.size[1] * 0.5;
|
||
|
||
console.log(`[${NODE_TITLE}] Node created from menu with ID: ${node.id}`);
|
||
|
||
// Явно налаштуємо ноду після створення з меню
|
||
if (app.HubNodesService) {
|
||
// Отримуємо розширення
|
||
const extension = app.extensions.find(ext => ext.name === "MultiModel.ActiveModel.ServiceDriven");
|
||
if (extension) {
|
||
// Конфігуруємо ноду
|
||
console.log(`[${NODE_TITLE}] Manually configuring node created from menu`);
|
||
extension.loadedGraphNode(node, app);
|
||
|
||
// Оновлюємо віджети з затримкою
|
||
setTimeout(() => {
|
||
if (node.refreshWidgets) {
|
||
console.log(`[${NODE_TITLE}] Refreshing widgets for node created from menu`);
|
||
node.refreshWidgets();
|
||
}
|
||
}, 100);
|
||
}
|
||
}
|
||
}
|
||
});
|
||
},
|
||
|
||
nodeRegistered(type, clazz) {
|
||
console.log(`[${NODE_TITLE}] nodeRegistered called for type: ${type}`);
|
||
if (type === NODE_TYPE) {
|
||
try {
|
||
console.log(`[${NODE_TITLE}] Running additional setup for our node type`);
|
||
ActiveModel.setUp(clazz);
|
||
} catch (e) {
|
||
console.error(`[${NODE_TITLE}] Error in additional node setup:`, e);
|
||
}
|
||
}
|
||
},
|
||
|
||
// Додаємо обробники створення ноди для максимального покриття
|
||
nodeCreated(node) {
|
||
console.log(`[${NODE_TITLE}] nodeCreated called for node type: ${node?.type || 'undefined'}, ID: ${node?.id || 'undefined'}`);
|
||
|
||
if (node && node.type === NODE_TYPE) {
|
||
console.log(`[${NODE_TITLE}] Our node created, configuring it...`);
|
||
this.loadedGraphNode(node, app);
|
||
|
||
// Запускаємо оновлення віджетів з затримкою
|
||
setTimeout(() => {
|
||
if (node.refreshWidgets) {
|
||
console.log(`[${NODE_TITLE}] Refreshing widgets after node creation`);
|
||
node.refreshWidgets();
|
||
}
|
||
}, 100);
|
||
}
|
||
},
|
||
|
||
onNodeAdded(node) {
|
||
console.log(`[${NODE_TITLE}] onNodeAdded callback called for node type: ${node?.type || 'undefined'}, ID: ${node?.id || 'undefined'}`);
|
||
|
||
if (node && node.type === NODE_TYPE) {
|
||
console.log(`[${NODE_TITLE}] Our node added via onNodeAdded, configuring it...`);
|
||
this.loadedGraphNode(node, app);
|
||
|
||
// Запускаємо оновлення віджетів з затримкою
|
||
setTimeout(() => {
|
||
if (node.refreshWidgets) {
|
||
console.log(`[${NODE_TITLE}] Refreshing widgets after node was added`);
|
||
node.refreshWidgets();
|
||
}
|
||
}, 100);
|
||
}
|
||
}
|
||
});
|
||
|
||
console.log(`[${NODE_TITLE}] JS file loaded`);
|