diff --git a/js/active_model.js b/js/active_model.js deleted file mode 100644 index 05d1ebf..0000000 --- a/js/active_model.js +++ /dev/null @@ -1,624 +0,0 @@ -// --- 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 = {}; - // Спробуємо знайти та вивести вузли одразу при створенні - this.logTargetNodeNames(); - } - - // Статичні властивості для визначення типу вузла - static title = "Active Model"; - static type = "ActiveModel"; // Унікальний тип для цього вузла - - // Функція для пошуку та логування імен цільових вузлів - logTargetNodeNames() { - const nodes = app.graph._nodes.filter(node => node.type === TARGET_NODE_TYPE); - return nodes; // Повертаємо для можливого використання в onAction - } - - // Метод для встановлення режиму для всіх знайдених цільових вузлів - setHubsMode(mode) { - const nodes = this.logTargetNodeNames(); - if (nodes.length === 0) { - return; // Виходимо, якщо немає цільових вузлів - } - const modeStr = mode === MODE_ALWAYS ? "ALWAYS" : (mode === MODE_BYPASS ? "BYPASS" : mode); - - let changed = false; - for (const node of nodes) { - if (node.mode !== mode) { - node.mode = mode; - changed = true; - } - } - - if (changed) { - app.graph.setDirtyCanvas(true, true); - } - } - - // Спрощена обробка дій для тестування - onAction(action) { - // Просто логуємо знайдені вузли ще раз при виклику дії - this.logTargetNodeNames(); - } - - // Цей вузол не виконує обчислень, тому 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"; // Змінюємо категорію на основну - - try { - // Реєструємо сам клас - LiteGraph.registerNodeType(clazz.type, clazz); - } 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"; - - // Якщо ми вмикаємо вузол (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) { - otherActiveNodes.forEach(node => { - node.mode = MODE_BYPASS; - }); - } - } - - // Змінюємо режим цільового вузла, незалежно від того, чи є інші активні вузли - if (currentMode !== mode) { - targetNode.mode = mode; - app.graph.setDirtyCanvas(true, true); - } -} - -// Нова функція для оновлення віджетів у всіх байпасерах для конкретного вузла -function updateAllBypasserWidgets(targetNode) { - if (!targetNode) return; - - 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) { - widget.value = newValue; - changed = true; - } - - if (widget.label !== 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)) { - this.bypasserNodes.push(node); - this.scheduleUpdate(`registerBypasser_${node.id}`); - } - }, - - unregisterBypasserNode(node) { - const index = this.bypasserNodes.findIndex(n => n.id === node.id); - if (index !== -1) { - 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; - 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; - 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; - this.scheduleUpdate(`notifyHubRenamed_${node.id}`); - }, - // --- Кінець нових методів --- // - - scheduleUpdate(reason = "unknown") { - if (this.updateTimeout) { - clearTimeout(this.updateTimeout); - } - this.updateTimeout = setTimeout(() => { - this.updateTimeout = null; - this.bypasserNodes.forEach(node => { - if (node.refreshWidgets) { - node.refreshWidgets(); - } else { - console.warn(`[HubNodesService] Bypasser node ID: ${node.id} missing refreshWidgets method.`); - } - }); - }, this.updateDelay); - } -}; - -app.registerExtension({ - name: "MultiModel.ActiveModel.ServiceDriven", // Оновлено назву розширення - - setup(appInstance) { - // Реєструємо сервіс, щоб він був доступний іншим розширенням - 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) { - // Викликаємо обробку ноди для додавання віджетів - const extension = appInstance.extensions.find(ext => ext.name === "MultiModel.ActiveModel.ServiceDriven"); - if (extension) { - // Конфігуруємо ноду - extension.loadedGraphNode(node, appInstance); - - // Оновлюємо віджети з невеликою затримкою - setTimeout(() => { - if (node.refreshWidgets) { - node.refreshWidgets(); - } - }, 100); - } - } - }; - - appInstance.graph._activemodel_onNodeAdded_attached = true; - } - - // Початкове сканування потрібне, щоб знайти вузли, які вже існують при завантаженні - setTimeout(() => { - // Знаходимо існуючі 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); - } - }); - - // Додаємо окремий виклик для оновлення віджетів для всіх bypassers з більшою затримкою - if (initialBypassersFound > 0) { - setTimeout(() => { - HubNodesService.bypasserNodes.forEach(node => { - if (node && node.refreshWidgets) { - node.refreshWidgets(); - } - }); - }, 500); - } - }, 500); - }, - - // loadedGraphNode тепер відповідає ТІЛЬКИ за конфігурацію САМОГО ActiveModel - loadedGraphNode(node, appInstance) { - if (node.type === NODE_TYPE) { - node.app = appInstance; - node.IS_BYPASSER_NODE = true; - delete node.onAction; // Видаляємо, бо додамо getActions - - // Визначаємо refreshWidgets - node.refreshWidgets = function() { - const targetNodes = findTargetNodes(); // Отримуємо з сервісу - - // Додаємо детальну діагностику - 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) { - 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); - - // Якщо більше одного активного вузла, залишаємо тільки перший - // Але не активуємо автоматично вузли, якщо всі вимкнені - if (activeNodes.length > 1) { - // Залишаємо перший вузол активним, інші вимикаємо - 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); - }; - - // Визначаємо дії для контекстного меню - 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() { // Додаємо метод до екземпляра - const nodes = HubNodesService.getHubNodes(); - }; - - // Оновлений метод для встановлення режиму всіх хабів - node.setAllHubsMode = function(mode) { - const hubNodes = HubNodesService.getHubNodes(); - - if (hubNodes.length === 0) { - return; - } - - if (mode === MODE_ALWAYS) { - // Активуємо тільки перший вузол, інші вимикаємо - 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, вимикаємо всі вузли - hubNodes.forEach(node => { - if (node.mode !== MODE_BYPASS) { - node.mode = MODE_BYPASS; - updateAllBypasserWidgets(node); - } - }); - - app.graph.setDirtyCanvas(true, true); - } - }; - - // Оновлений метод для перемикання вузлів - node.toggleAllHubsMode = function() { - const hubNodes = HubNodesService.getHubNodes(); - - if (hubNodes.length === 0) { - return; - } - - // Перевіряємо, чи є хоча б один активний вузол - const activeNodes = hubNodes.filter(n => n.mode === MODE_ALWAYS); - - if (activeNodes.length === 0) { - // Якщо немає активних вузлів, активуємо перший - 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]; - - // Активуємо наступний вузол - nextNode.mode = MODE_ALWAYS; - updateAllBypasserWidgets(nextNode); - - app.graph.setDirtyCanvas(true, true); - } - }; - - // Реєстрація/видалення самого bypasser'а в сервісі - node.onAdded = function() { - if (appInstance.HubNodesService) { // Перевіряємо наявність сервісу - appInstance.HubNodesService.registerBypasserNode(this); - } else { - console.error(`[${NODE_TITLE} ID: ${this.id}] Cannot register bypasser, HubNodesService not found!`); - } - }; - node.onRemoved = function() { - if (appInstance.HubNodesService) { - appInstance.HubNodesService.unregisterBypasserNode(this); - } else { - console.error(`[${NODE_TITLE} ID: ${this.id}] Cannot unregister bypasser, HubNodesService not found!`); - } - }; - - // Початкове оновлення віджетів для цього конкретного вузла - 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; - - // Явно налаштуємо ноду після створення з меню - if (app.HubNodesService) { - // Отримуємо розширення - const extension = app.extensions.find(ext => ext.name === "MultiModel.ActiveModel.ServiceDriven"); - if (extension) { - // Конфігуруємо ноду - extension.loadedGraphNode(node, app); - - // Оновлюємо віджети з затримкою - setTimeout(() => { - if (node.refreshWidgets) { - node.refreshWidgets(); - } - }, 100); - } - } - } - }); - }, - - nodeRegistered(type, clazz) { - if (type === NODE_TYPE) { - try { - ActiveModel.setUp(clazz); - } catch (e) { - console.error(`[${NODE_TITLE}] Error in additional node setup:`, e); - } - } - }, - - // Додаємо обробники створення ноди для максимального покриття - nodeCreated(node) { - if (node && node.type === NODE_TYPE) { - this.loadedGraphNode(node, app); - - // Запускаємо оновлення віджетів з затримкою - setTimeout(() => { - if (node.refreshWidgets) { - node.refreshWidgets(); - } - }, 100); - } - }, - - onNodeAdded(node) { - if (node && node.type === NODE_TYPE) { - this.loadedGraphNode(node, app); - - // Запускаємо оновлення віджетів з затримкою - setTimeout(() => { - if (node.refreshWidgets) { - node.refreshWidgets(); - } - }, 100); - } - } -}); - -console.log(`[${NODE_TITLE}] JS file loaded`); \ No newline at end of file