Add files via upload
This commit is contained in:
+2
-196
@@ -1,197 +1,3 @@
|
||||
// js/ModelParamsNodeExt.js
|
||||
// Розширення для ModelParamsPipeNode, щоб він повідомляв про свої зміни
|
||||
|
||||
import { app } from "/scripts/app.js";
|
||||
|
||||
const TARGET_NODE_TYPE = "ModelParamsPipe"; // Цільовий тип вузла, який ми розширюємо
|
||||
|
||||
// --- Loading js/ModelParamsNodeExt.js --- v2-dynamic-inputs
|
||||
|
||||
app.registerExtension({
|
||||
name: "MultiModel.ModelParamsPipeNode.Notifier",
|
||||
|
||||
// Додатковий трекінг для діагностики
|
||||
newNodesDetected: false,
|
||||
|
||||
// Додаємо базову ініціалізацію та прямий пошук вузлів
|
||||
async setup() {
|
||||
// Перевіряємо наявність сервісу одразу
|
||||
if (app.HubNodesService) {
|
||||
} else {
|
||||
console.error(`[${TARGET_NODE_TYPE} Notifier] HubNodesService NOT found during setup!`);
|
||||
}
|
||||
|
||||
// Зачекаємо, доки граф буде завантажено, і потім проскануємо його
|
||||
const checkForGraph = () => {
|
||||
if (app.graph) {
|
||||
// Додаємо обробник зміни графу
|
||||
if (app.graph.onNodeAdded && !app.graph._notifier_attached) {
|
||||
const origNodeAdded = app.graph.onNodeAdded;
|
||||
|
||||
app.graph.onNodeAdded = function(node) {
|
||||
if (origNodeAdded) {
|
||||
origNodeAdded.call(this, node);
|
||||
}
|
||||
|
||||
// Перевіряємо, чи це наш цільовий тип
|
||||
if (node.type === TARGET_NODE_TYPE) {
|
||||
// Викликаємо наш обробник
|
||||
const extension = app.extensions.find(ext => ext.name === "MultiModel.ModelParamsPipeNode.Notifier");
|
||||
if (extension) {
|
||||
extension.setupNodeMonitoring(node);
|
||||
|
||||
if (app.HubNodesService) {
|
||||
app.HubNodesService.notifyHubAdded(node);
|
||||
|
||||
// Пробуємо різні методи оновлення інтерфейсу
|
||||
if (typeof app.HubNodesService.forceUpdates === 'function') {
|
||||
app.HubNodesService.forceUpdates();
|
||||
} else if (typeof app.HubNodesService.updateWidgets === 'function') {
|
||||
app.HubNodesService.updateWidgets();
|
||||
} else if (app.HubNodesService.refreshUI && typeof app.HubNodesService.refreshUI === 'function') {
|
||||
app.HubNodesService.refreshUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
app.graph._notifier_attached = true;
|
||||
}
|
||||
|
||||
// Зробимо сканування графу з невеликою затримкою для певності
|
||||
setTimeout(() => {
|
||||
if (!app.graph || !app.graph._nodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetNodes = app.graph._nodes.filter(node => node.type === TARGET_NODE_TYPE);
|
||||
|
||||
const extension = app.extensions.find(ext => ext.name === "MultiModel.ModelParamsPipeNode.Notifier");
|
||||
if (!extension) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Обробляємо знайдені вузли
|
||||
for (const node of targetNodes) {
|
||||
extension.setupNodeMonitoring(node);
|
||||
|
||||
// Якщо знайшли хоча б один вузол, спробуємо оновити інтерфейс
|
||||
if (app.HubNodesService) {
|
||||
}
|
||||
}
|
||||
|
||||
// Якщо було знайдено вузли, форсуємо оновлення
|
||||
if (targetNodes.length > 0 && app.HubNodesService) {
|
||||
if (typeof app.HubNodesService.forceUpdates === 'function') {
|
||||
app.HubNodesService.forceUpdates();
|
||||
} else if (typeof app.HubNodesService.updateWidgets === 'function') {
|
||||
app.HubNodesService.updateWidgets();
|
||||
} else if (app.HubNodesService.refreshUI && typeof app.HubNodesService.refreshUI === 'function') {
|
||||
app.HubNodesService.refreshUI();
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
setTimeout(checkForGraph, 500);
|
||||
}
|
||||
};
|
||||
|
||||
// Запускаємо перевірку графу
|
||||
checkForGraph();
|
||||
},
|
||||
|
||||
// Встановлення моніторингу title/видалення для вузла
|
||||
setupNodeMonitoring(node, appInstance = app) {
|
||||
if (!node) return false;
|
||||
|
||||
// --- Встановлюємо моніторинг title ---
|
||||
if (!node.__title_monitored) {
|
||||
try {
|
||||
const originalTitle = node.title;
|
||||
|
||||
// Зберігаємо оригінальне значення
|
||||
Object.defineProperty(node, '_monitored_title', {
|
||||
value: originalTitle,
|
||||
writable: true
|
||||
});
|
||||
|
||||
// Перевизначаємо геттер/сеттер для title
|
||||
Object.defineProperty(node, 'title', {
|
||||
get: function() {
|
||||
return this._monitored_title;
|
||||
},
|
||||
set: function(newTitle) {
|
||||
// Якщо значення не змінилось, не виконуємо додаткові дії
|
||||
if (this._monitored_title === newTitle) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Зберігаємо нове значення
|
||||
this._monitored_title = newTitle;
|
||||
|
||||
// Повідомляємо сервіс про перейменування з невеликою затримкою
|
||||
setTimeout(() => {
|
||||
if (appInstance.HubNodesService) {
|
||||
appInstance.HubNodesService.notifyHubRenamed(this);
|
||||
} else {
|
||||
console.error(` HubNodesService not found! Cannot notify rename.`);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
node.__title_monitored = true;
|
||||
} catch (error) {
|
||||
console.error(`[${TARGET_NODE_TYPE} Notifier] Error setting up title monitoring:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Встановлюємо перехоплення видалення ---
|
||||
if (!node.onRemoved || !node.onRemoved.__isNotifierOverridden) {
|
||||
try {
|
||||
const originalOnRemoved = node.onRemoved;
|
||||
|
||||
node.onRemoved = function() {
|
||||
// Повідомляємо сервіс про видалення
|
||||
if (appInstance.HubNodesService) {
|
||||
appInstance.HubNodesService.notifyHubRemoved(this);
|
||||
} else {
|
||||
console.error(` HubNodesService not found! Cannot notify removal.`);
|
||||
}
|
||||
|
||||
// Викликаємо оригінальний метод, якщо він існує
|
||||
if (originalOnRemoved) {
|
||||
originalOnRemoved.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
node.onRemoved.__isNotifierOverridden = true;
|
||||
} catch (error) {
|
||||
console.error(`[${TARGET_NODE_TYPE} Notifier] Error overriding onRemoved:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Для логування проблемних випадків
|
||||
async nodeCreated(node) {
|
||||
},
|
||||
|
||||
// Для логування проблемних випадків
|
||||
async onNodeAdded(node) {
|
||||
},
|
||||
|
||||
// Хук для обробки вузлів, що вже існують при завантаженні
|
||||
async loadedGraphNode(node, appInstance) {
|
||||
if (!node) return;
|
||||
|
||||
if (node.type === TARGET_NODE_TYPE) {
|
||||
// Встановлюємо моніторинг для існуючого вузла
|
||||
this.setupNodeMonitoring(node, appInstance);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Заглушка. Вся логіка перенесена в active_model.js.
|
||||
// Можна безпечно видалити цей файл або залишити як заглушку.
|
||||
+152
-195
@@ -20,215 +20,172 @@ app.registerExtension({
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
// Compare with the class name
|
||||
if (nodeData.name === nodeClassName) {
|
||||
console.log(`[MultiModel] Patching ${nodeClassName}`);
|
||||
console.log(`[ModelSwitch] Patching ${nodeClassName}`);
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
console.log(`[MultiModel] ${nodeClassName} created`);
|
||||
// --- ФУНКЦІЇ ---
|
||||
function handleModeSwitch(modeWidget, indexWidget, app, value) {
|
||||
const updateVisibility = (modeValue) => {
|
||||
if (indexWidget) {
|
||||
const shouldBeHidden = (modeValue !== "index");
|
||||
indexWidget.hidden = shouldBeHidden;
|
||||
if (app.graph) app.graph.setDirtyCanvas(true, false);
|
||||
}
|
||||
};
|
||||
updateVisibility(value ?? modeWidget.value);
|
||||
// Перевизначаємо callback
|
||||
const originalModeCallback = modeWidget.callback;
|
||||
modeWidget.callback = (val) => {
|
||||
originalModeCallback?.apply(modeWidget, [val]);
|
||||
updateVisibility(val);
|
||||
};
|
||||
}
|
||||
|
||||
function updateDynamicInputIndexes(node) {
|
||||
console.log('[ModelSwitch] Оновлення індексів динамічних входів');
|
||||
const prefix = node._input_prefix || "input_";
|
||||
let dynamicInputs = node.inputs ? node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index") : [];
|
||||
// Перейменовуємо динамічні входи по порядку
|
||||
for (let i = 0; i < dynamicInputs.length; i++) {
|
||||
const expectedName = `${prefix}${i + 1}`;
|
||||
if (dynamicInputs[i].name !== expectedName) {
|
||||
console.log(`[ModelSwitch] Перейменування входу ${dynamicInputs[i].name} -> ${expectedName}`);
|
||||
dynamicInputs[i].name = expectedName;
|
||||
}
|
||||
}
|
||||
let maxIdx = dynamicInputs.length > 0 ? dynamicInputs.length : 1;
|
||||
node.last_connected = -1;
|
||||
for (let i = 0; i < dynamicInputs.length; i++) {
|
||||
const inp = dynamicInputs[i];
|
||||
if (inp.link != null) node.last_connected = node.inputs.indexOf(inp);
|
||||
}
|
||||
node._last_input_index = maxIdx;
|
||||
// Останній порожній динамічний вхід
|
||||
let lastEmptyIdx = -1;
|
||||
for (let i = node.inputs.length - 1; i >= 0; i--) {
|
||||
if (node.inputs[i].name !== "mode" && node.inputs[i].name !== "index" && node.inputs[i].name.startsWith(prefix) && node.inputs[i].link == null) {
|
||||
lastEmptyIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
node.last_empty = lastEmptyIdx;
|
||||
}
|
||||
|
||||
function updateDynamicInputsOnConnect(node, type, index, connected) {
|
||||
if (node._updatingInputs) return;
|
||||
node._updatingInputs = true;
|
||||
const changedInput = node.inputs[index];
|
||||
const inputName = changedInput?.name;
|
||||
const isDynamic = inputName && inputName !== "mode" && inputName !== "index";
|
||||
if (!isDynamic) {
|
||||
node._updatingInputs = false;
|
||||
return;
|
||||
}
|
||||
if (connected) {
|
||||
console.log(`[ModelSwitch] Вхід "${inputName}" (${index}) підключено`);
|
||||
if (index === node.last_empty) {
|
||||
const prefix = node._input_prefix || "input_";
|
||||
let dynamicInputs = node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index");
|
||||
node.addInput(`${prefix}${dynamicInputs.length + 1}`, "*");
|
||||
console.log('[ModelSwitch] Додаємо ще один порожній динамічний вхід');
|
||||
updateDynamicInputIndexes(node);
|
||||
} else {
|
||||
console.log('[ModelSwitch] Підключено не до останнього вільного входу, новий не додаємо');
|
||||
}
|
||||
} else {
|
||||
console.log(`[ModelSwitch] Вхід "${inputName}" (${index}) відключено`);
|
||||
// Якщо відключено від останнього підключеного, прибираємо цей вхід, але лише якщо порожніх динамічних входів більше одного
|
||||
let dynamicInputs = node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index");
|
||||
let emptyInputs = dynamicInputs.filter(inp => inp.link == null);
|
||||
if (index === node.last_connected && emptyInputs.length > 1) {
|
||||
node.removeInput(index);
|
||||
console.log('[ModelSwitch] Відключено від останнього підключеного — видаляємо цей вхід');
|
||||
updateDynamicInputIndexes(node);
|
||||
} else if (index === node.last_connected) {
|
||||
console.log('[ModelSwitch] Це останній порожній вхід — не видаляємо');
|
||||
} else {
|
||||
console.log('[ModelSwitch] Відключено не від останнього підключеного — нічого не робимо');
|
||||
}
|
||||
}
|
||||
node._updatingInputs = false;
|
||||
}
|
||||
|
||||
function handleNodeCreation(node, app) {
|
||||
// --- Віджети ---
|
||||
const modeWidget = this.widgets.find(w => w.name === "mode");
|
||||
const indexWidget = this.widgets.find(w => w.name === "index");
|
||||
const modeWidget = node.widgets.find(w => w.name === "mode");
|
||||
const indexWidget = node.widgets.find(w => w.name === "index");
|
||||
if (!modeWidget) {
|
||||
console.error(`[MultiModel] ${nodeClassName}: 'mode' widget not found! Cannot apply dynamic visibility.`);
|
||||
console.error(`[ModelSwitch] ${nodeClassName}: 'mode' widget not found! Cannot apply dynamic visibility.`);
|
||||
return;
|
||||
}
|
||||
if (!indexWidget) {
|
||||
console.error(`[MultiModel] ${nodeClassName}: 'index' widget not found! Cannot apply dynamic visibility.`);
|
||||
console.error(`[ModelSwitch] ${nodeClassName}: 'index' widget not found! Cannot apply dynamic visibility.`);
|
||||
return;
|
||||
}
|
||||
const updateVisibility = (modeValue) => {
|
||||
if (indexWidget) {
|
||||
const shouldBeHidden = (modeValue !== "index");
|
||||
indexWidget.hidden = shouldBeHidden;
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
}
|
||||
};
|
||||
const originalModeCallback = modeWidget.callback;
|
||||
modeWidget.callback = (value) => {
|
||||
originalModeCallback?.apply(modeWidget, [value]);
|
||||
updateVisibility(value);
|
||||
};
|
||||
updateVisibility(modeWidget.value);
|
||||
handleModeSwitch(modeWidget, indexWidget, app);
|
||||
|
||||
// --- Динамічні входи (last_empty, last_connected) ---
|
||||
this._input_prefix = "input_";
|
||||
const prefix = this._input_prefix;
|
||||
this._last_input_index = 1;
|
||||
this.last_empty = 0;
|
||||
this.last_connected = null;
|
||||
// Якщо входів немає або немає input_1 — додаємо перший вхід
|
||||
let hasInput1 = false;
|
||||
if (this.inputs && this.inputs.length > 0) {
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
if (this.inputs[i].name === `${prefix}1`) {
|
||||
hasInput1 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
node._input_prefix = "input_";
|
||||
const prefix = node._input_prefix;
|
||||
let dynamicInputs = node.inputs ? node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index") : [];
|
||||
console.log('[ModelSwitch] dynamicInputs:', dynamicInputs.map(inp => ({name: inp.name, link: inp.link})));
|
||||
// --- Якщо входів немає, додаємо перший ---
|
||||
if (dynamicInputs.length === 0) {
|
||||
console.log('[ModelSwitch] Входів немає, додаємо перший');
|
||||
node.addInput(`${prefix}1`, "*");
|
||||
dynamicInputs = node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index");
|
||||
}
|
||||
if (!hasInput1) {
|
||||
this.addInput(`${prefix}1`, "*");
|
||||
}
|
||||
// Оновлюємо індекси для існуючих входів
|
||||
if (this.inputs && this.inputs.length > 0) {
|
||||
let maxIdx = 1;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
const inp = this.inputs[i];
|
||||
if (inp.name && inp.name.startsWith(prefix)) {
|
||||
const idx = parseInt(inp.name.replace(prefix, ""), 10);
|
||||
if (!isNaN(idx) && idx > maxIdx) maxIdx = idx;
|
||||
if (inp.link != null) this.last_connected = i;
|
||||
}
|
||||
}
|
||||
this._last_input_index = maxIdx;
|
||||
this.last_empty = this.inputs.length - 1;
|
||||
}
|
||||
// --- Зберігаємо попередній стан links ---
|
||||
this._prevLinks = [];
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
this._prevLinks[i] = this.inputs[i].link;
|
||||
}
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
// this.updateInputs(); // закоментовано, бо ця функція більше не використовується
|
||||
|
||||
// --- Динамічна логіка підключень (last_empty, last_connected) ---
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info, slot) {
|
||||
onConnectionsChange?.apply(this, arguments);
|
||||
if (this._updatingInputs) return;
|
||||
if (type !== 1) return;
|
||||
const prefix = this._input_prefix || "input_";
|
||||
const wasLinked = this._prevLinks && this._prevLinks[index];
|
||||
const isLinked = this.inputs[index] && this.inputs[index].link;
|
||||
// Додаємо новий вхід тільки якщо було null, а стало не null
|
||||
if (
|
||||
connected &&
|
||||
index === this.last_empty &&
|
||||
wasLinked == null &&
|
||||
isLinked != null
|
||||
) {
|
||||
this._updatingInputs = true;
|
||||
// --- Коректна нумерація ---
|
||||
let maxIdx = 0;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
const inp = this.inputs[i];
|
||||
if (inp.name && inp.name.startsWith(prefix)) {
|
||||
const idx = parseInt(inp.name.replace(prefix, ""), 10);
|
||||
if (!isNaN(idx) && idx > maxIdx) maxIdx = idx;
|
||||
let connectedInputs = dynamicInputs.filter(inp => inp.link != null);
|
||||
let emptyInputs = dynamicInputs.filter(inp => inp.link == null);
|
||||
console.log('[ModelSwitch] connectedInputs:', connectedInputs);
|
||||
console.log('[ModelSwitch] emptyInputs:', emptyInputs);
|
||||
if (emptyInputs.length === 0) {
|
||||
console.log('[ModelSwitch] Додаємо новий порожній вхід');
|
||||
node.addInput(`${prefix}${connectedInputs.length + 1}`, "*");
|
||||
dynamicInputs = node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index");
|
||||
} else if (emptyInputs.length > 1) {
|
||||
console.log('[ModelSwitch] Видаляємо зайві порожні входи');
|
||||
for (let i = node.inputs.length - 1; i >= 0; i--) {
|
||||
if (node.inputs[i].name !== "mode" && node.inputs[i].name !== "index" && node.inputs[i].link == null && emptyInputs.length > 1) {
|
||||
node.removeInput(i);
|
||||
emptyInputs.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
this._last_input_index = maxIdx + 1;
|
||||
this.addInput(`${prefix}${this._last_input_index}`, "*");
|
||||
this.last_connected = this.last_empty;
|
||||
this.last_empty = this.inputs.length - 1;
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
this._updatingInputs = false;
|
||||
}
|
||||
// Якщо відключили будь-який вхід
|
||||
if (!connected) {
|
||||
// Знайти новий last_connected
|
||||
let new_last_connected = null;
|
||||
for (let i = this.inputs.length - 1; i >= 0; i--) {
|
||||
if (this.inputs[i].name.startsWith(prefix) && this.inputs[i].link != null) {
|
||||
new_last_connected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Видалити всі порожні входи після last_connected, але залишити один (last_empty)
|
||||
let removed = false;
|
||||
for (let i = this.inputs.length - 1; i > new_last_connected + 1; i--) {
|
||||
if (this.inputs[i].name.startsWith(prefix) && this.inputs[i].link == null) {
|
||||
this.removeInput(i);
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
this.last_connected = new_last_connected;
|
||||
this.last_empty = this.inputs.length - 1;
|
||||
// Якщо останній вхід зайнятий, додаємо ще один порожній
|
||||
if (
|
||||
this.inputs.length === 0 ||
|
||||
(this.inputs[this.inputs.length - 1].name.startsWith(prefix) && this.inputs[this.inputs.length - 1].link != null)
|
||||
) {
|
||||
// --- Коректна нумерація ---
|
||||
let maxIdx = 0;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
const inp = this.inputs[i];
|
||||
if (inp.name && inp.name.startsWith(prefix)) {
|
||||
const idx = parseInt(inp.name.replace(prefix, ""), 10);
|
||||
if (!isNaN(idx) && idx > maxIdx) maxIdx = idx;
|
||||
}
|
||||
}
|
||||
this._last_input_index = maxIdx + 1;
|
||||
this.addInput(`${prefix}${this._last_input_index}`, "*");
|
||||
this.last_empty = this.inputs.length - 1;
|
||||
removed = true;
|
||||
}
|
||||
if (removed) {
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
// --- Оновлюємо _prevLinks після всіх змін ---
|
||||
this._prevLinks = [];
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
this._prevLinks[i] = this.inputs[i].link;
|
||||
}
|
||||
};
|
||||
// --- Закоментовано стару логіку ---
|
||||
/*
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info, slot) {
|
||||
onConnectionsChange?.apply(this, arguments);
|
||||
this.scheduleUpdateInputs();
|
||||
};
|
||||
nodeType.prototype.scheduleUpdateInputs = debounce(function() {
|
||||
this.updateInputs();
|
||||
}, 100);
|
||||
nodeType.prototype.updateInputs = function() {
|
||||
const prefix = "input_";
|
||||
const minInputs = 1;
|
||||
// --- Гарантуємо перший слот ---
|
||||
const dynamicInputs = this.inputs.filter(inp => inp.name && inp.name.startsWith(prefix));
|
||||
if (dynamicInputs.length === 0) {
|
||||
const name = `${prefix}1`;
|
||||
this.addInput(name, "*");
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
return;
|
||||
}
|
||||
let lastConnectedIndex = -1;
|
||||
for (let i = 0; i < this.inputs.length; i++) {
|
||||
if (this.inputs[i].name.startsWith(prefix) && this.inputs[i].link != null) {
|
||||
lastConnectedIndex = i;
|
||||
}
|
||||
}
|
||||
let removalIndex = this.inputs.length - 1;
|
||||
while (removalIndex > lastConnectedIndex && this.inputs.length > minInputs && removalIndex >= 0) {
|
||||
if (this.inputs[removalIndex].name.startsWith(prefix) && this.inputs[removalIndex].link == null) {
|
||||
if(this.inputs.length > minInputs) {
|
||||
this.removeInput(removalIndex);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
removalIndex--;
|
||||
}
|
||||
// Add a new input if the last one is connected OR if there are no inputs yet
|
||||
if (dynamicInputs.length > 0 && this.inputs[this.inputs.length - 1].name.startsWith(prefix) && this.inputs[this.inputs.length - 1].link != null) {
|
||||
// Знаходимо найбільший індекс серед динамічних входів
|
||||
const dynamicIndexes = dynamicInputs
|
||||
.map(inp => parseInt(inp.name.replace(prefix, ""), 10))
|
||||
.filter(num => !isNaN(num));
|
||||
const maxIndex = dynamicIndexes.length > 0 ? Math.max(...dynamicIndexes) : 0;
|
||||
const nextIndex = maxIndex + 1;
|
||||
const name = `${prefix}${nextIndex}`;
|
||||
this.addInput(name, "*");
|
||||
}
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
*/
|
||||
dynamicInputs = node.inputs.filter(inp => inp.name !== "mode" && inp.name !== "index");
|
||||
}
|
||||
console.log('[ModelSwitch] dynamicInputs після додавання/видалення:', dynamicInputs.map(inp => ({name: inp.name, link: inp.link})));
|
||||
updateDynamicInputIndexes(node);
|
||||
// --- Зберігаємо попередній стан links ---
|
||||
node._prevLinks = [];
|
||||
for (let i = 0; i < node.inputs.length; i++) {
|
||||
node._prevLinks[i] = node.inputs[i].link;
|
||||
}
|
||||
if (app.graph) app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
function handleConnectionsChange(node, type, index, connected, link_info, slot, app) {
|
||||
updateDynamicInputsOnConnect(node, type, index, connected);
|
||||
node._prevLinks = [];
|
||||
for (let i = 0; i < node.inputs.length; i++) {
|
||||
node._prevLinks[i] = node.inputs[i].link;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Патчимо onNodeCreated ---
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
onNodeCreated?.apply(this, arguments);
|
||||
console.log(`[ModelSwitch] ${nodeClassName} created`);
|
||||
setTimeout(() => {
|
||||
handleNodeCreation(this, app);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
// --- Патчимо onConnectionsChange ---
|
||||
const onConnectionsChange = nodeType.prototype.onConnectionsChange;
|
||||
nodeType.prototype.onConnectionsChange = function(type, index, connected, link_info, slot) {
|
||||
onConnectionsChange?.apply(this, arguments);
|
||||
handleConnectionsChange(this, type, index, connected, link_info, slot, app);
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
+165
-14
@@ -245,6 +245,7 @@ app.registerExtension({
|
||||
name: "MultiModel.ActiveModel.ServiceDriven", // Оновлено назву розширення
|
||||
|
||||
setup(appInstance) {
|
||||
console.log("[MultiModel] ✅ Ініціалізовано HubNodesService у app.HubNodesService");
|
||||
// Реєструємо сервіс, щоб він був доступний іншим розширенням
|
||||
appInstance.HubNodesService = HubNodesService;
|
||||
HubNodesService.app = appInstance;
|
||||
@@ -367,24 +368,39 @@ app.registerExtension({
|
||||
}
|
||||
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;
|
||||
}
|
||||
// --- Виправлений callback: взаємовиключна логіка ---
|
||||
existingWidget.callback = (value) => {
|
||||
const hubNodes = HubNodesService.getHubNodes();
|
||||
const nodeToActivate = app.graph.getNodeById(targetNode.id);
|
||||
if (value) {
|
||||
// Вимикаємо всі інші, вмикаємо лише обраний
|
||||
hubNodes.forEach(n => {
|
||||
const nObj = app.graph.getNodeById(n.id);
|
||||
if (nObj) nObj.mode = (n.id === targetNode.id) ? MODE_ALWAYS : MODE_BYPASS;
|
||||
});
|
||||
} else {
|
||||
// Якщо вимикаємо, просто переводимо цей вузол у BYPASS
|
||||
if (nodeToActivate) nodeToActivate.mode = MODE_BYPASS;
|
||||
}
|
||||
// Оновлюємо всі віджети
|
||||
if (this.refreshWidgets) this.refreshWidgets();
|
||||
};
|
||||
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);
|
||||
const hubNodes = HubNodesService.getHubNodes();
|
||||
const nodeToActivate = app.graph.getNodeById(targetNode.id);
|
||||
if (value) {
|
||||
hubNodes.forEach(n => {
|
||||
const nObj = app.graph.getNodeById(n.id);
|
||||
if (nObj) nObj.mode = (n.id === targetNode.id) ? MODE_ALWAYS : MODE_BYPASS;
|
||||
});
|
||||
} else {
|
||||
if (nodeToActivate) nodeToActivate.mode = MODE_BYPASS;
|
||||
}
|
||||
else this.refreshWidgets();
|
||||
if (this.refreshWidgets) this.refreshWidgets();
|
||||
},
|
||||
{ on: "ON", off: "OFF" }
|
||||
);
|
||||
@@ -629,4 +645,139 @@ app.registerExtension({
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`[${NODE_TITLE}] JS file loaded`);
|
||||
// --- ModelParamsPipe Notifier (інтегровано з ModelParamsNodeExt.js) ---
|
||||
(function(){
|
||||
const TARGET_NODE_TYPE = "ModelParamsPipe";
|
||||
app.registerExtension({
|
||||
name: "MultiModel.ModelParamsPipeNode.Notifier",
|
||||
newNodesDetected: false,
|
||||
async setup() {
|
||||
// Чекаємо на HubNodesService
|
||||
const waitForHubNodesService = async (timeout = 5000) => {
|
||||
const start = Date.now();
|
||||
while (!app.HubNodesService) {
|
||||
if (Date.now() - start > timeout) {
|
||||
throw new Error("HubNodesService not found after waiting");
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
};
|
||||
await waitForHubNodesService();
|
||||
// Додаємо базову ініціалізацію та прямий пошук вузлів
|
||||
const checkForGraph = () => {
|
||||
if (app.graph) {
|
||||
if (app.graph.onNodeAdded && !app.graph._notifier_attached) {
|
||||
const origNodeAdded = app.graph.onNodeAdded;
|
||||
app.graph.onNodeAdded = function(node) {
|
||||
if (origNodeAdded) origNodeAdded.call(this, node);
|
||||
if (node.type === TARGET_NODE_TYPE) {
|
||||
const extension = app.extensions.find(ext => ext.name === "MultiModel.ModelParamsPipeNode.Notifier");
|
||||
if (extension) {
|
||||
extension.setupNodeMonitoring(node);
|
||||
if (app.HubNodesService) {
|
||||
app.HubNodesService.notifyHubAdded(node);
|
||||
if (typeof app.HubNodesService.forceUpdates === 'function') {
|
||||
app.HubNodesService.forceUpdates();
|
||||
} else if (typeof app.HubNodesService.updateWidgets === 'function') {
|
||||
app.HubNodesService.updateWidgets();
|
||||
} else if (app.HubNodesService.refreshUI && typeof app.HubNodesService.refreshUI === 'function') {
|
||||
app.HubNodesService.refreshUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
app.graph._notifier_attached = true;
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (!app.graph || !app.graph._nodes) return;
|
||||
const targetNodes = app.graph._nodes.filter(node => node.type === TARGET_NODE_TYPE);
|
||||
const extension = app.extensions.find(ext => ext.name === "MultiModel.ModelParamsPipeNode.Notifier");
|
||||
if (!extension) return;
|
||||
for (const node of targetNodes) {
|
||||
extension.setupNodeMonitoring(node);
|
||||
}
|
||||
if (targetNodes.length > 0 && app.HubNodesService) {
|
||||
if (typeof app.HubNodesService.forceUpdates === 'function') {
|
||||
app.HubNodesService.forceUpdates();
|
||||
} else if (typeof app.HubNodesService.updateWidgets === 'function') {
|
||||
app.HubNodesService.updateWidgets();
|
||||
} else if (app.HubNodesService.refreshUI && typeof app.HubNodesService.refreshUI === 'function') {
|
||||
app.HubNodesService.refreshUI();
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
} else {
|
||||
setTimeout(checkForGraph, 500);
|
||||
}
|
||||
};
|
||||
checkForGraph();
|
||||
},
|
||||
setupNodeMonitoring(node, appInstance = app) {
|
||||
if (!node) return false;
|
||||
if (!node.__title_monitored) {
|
||||
try {
|
||||
const originalTitle = node.title;
|
||||
if (!Object.getOwnPropertyDescriptor(node, '_monitored_title')) {
|
||||
Object.defineProperty(node, '_monitored_title', {
|
||||
value: originalTitle,
|
||||
writable: true
|
||||
});
|
||||
}
|
||||
const titleDescriptor = Object.getOwnPropertyDescriptor(node, 'title');
|
||||
if (!titleDescriptor || titleDescriptor.configurable) {
|
||||
Object.defineProperty(node, 'title', {
|
||||
get: function() { return this._monitored_title; },
|
||||
set: function(newTitle) {
|
||||
if (this._monitored_title === newTitle) return;
|
||||
this._monitored_title = newTitle;
|
||||
setTimeout(() => {
|
||||
if (appInstance.HubNodesService) {
|
||||
appInstance.HubNodesService.notifyHubRenamed(this);
|
||||
} else {
|
||||
console.error(` HubNodesService not found! Cannot notify rename.`);
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
node.__title_monitored = true;
|
||||
} catch (error) {
|
||||
console.error(`[${TARGET_NODE_TYPE} Notifier] Error setting up title monitoring:`, error);
|
||||
}
|
||||
}
|
||||
if (!node.onRemoved || !node.onRemoved.__isNotifierOverridden) {
|
||||
try {
|
||||
const originalOnRemoved = node.onRemoved;
|
||||
node.onRemoved = function() {
|
||||
if (appInstance.HubNodesService) {
|
||||
appInstance.HubNodesService.notifyHubRemoved(this);
|
||||
} else {
|
||||
console.error(` HubNodesService not found! Cannot notify removal.`);
|
||||
}
|
||||
if (originalOnRemoved) {
|
||||
originalOnRemoved.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
node.onRemoved.__isNotifierOverridden = true;
|
||||
} catch (error) {
|
||||
console.error(`[${TARGET_NODE_TYPE} Notifier] Error overriding onRemoved:`, error);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
async nodeCreated(node) {},
|
||||
async onNodeAdded(node) {},
|
||||
async loadedGraphNode(node, appInstance) {
|
||||
if (!node) return;
|
||||
if (node.type === TARGET_NODE_TYPE) {
|
||||
this.setupNodeMonitoring(node, appInstance);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
console.log(`[${NODE_TITLE}] JS file loaded`);
|
||||
console.log("[MultiModel] ✅ Завантажено active_model.js, HubNodesService буде доступний після ініціалізації.");
|
||||
Reference in New Issue
Block a user