fixed the issue of LoRA data loss in the RS Models Loader node
This commit is contained in:
@@ -41,12 +41,17 @@ class RaykoModelsLoader:
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, lora_data="[]", **kwargs):
|
||||
import hashlib
|
||||
if lora_data is None:
|
||||
lora_data = "[]"
|
||||
return hashlib.md5(lora_data.encode()).hexdigest()
|
||||
|
||||
def load_models(self, unet_name, weight_dtype, clip_name, clip_type, clip_device, vae_name, lora_data="[]"):
|
||||
print(f"\n[Rayko] === Start load_models ===")
|
||||
print(f"[Rayko] lora_data RAW: {lora_data}")
|
||||
|
||||
if not lora_data:
|
||||
lora_data = "[]"
|
||||
|
||||
wd = None if weight_dtype == "default" else weight_dtype
|
||||
model = UNETLoader().load_unet(unet_name=unet_name, weight_dtype=wd)[0]
|
||||
print(f"[Rayko] UNET: {unet_name}")
|
||||
@@ -60,6 +65,8 @@ class RaykoModelsLoader:
|
||||
|
||||
try:
|
||||
loras = json.loads(lora_data) if lora_data else []
|
||||
if not isinstance(loras, list):
|
||||
loras = []
|
||||
print(f"[Rayko] LoRA count: {len(loras)}")
|
||||
except Exception as e:
|
||||
loras = []
|
||||
@@ -68,6 +75,9 @@ class RaykoModelsLoader:
|
||||
lora_loader = LoraLoader()
|
||||
|
||||
for i, lora in enumerate(loras):
|
||||
if not isinstance(lora, dict):
|
||||
continue
|
||||
|
||||
name = lora.get("name", "")
|
||||
strength_model = float(lora.get("strength_model", 1.0))
|
||||
strength_clip = float(lora.get("strength_clip", strength_model))
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
__version__ = "0.15.8"
|
||||
__version__ = "0.15.9"
|
||||
import importlib
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ComfyUI_RaykoStudio",
|
||||
"version": "0.15.8",
|
||||
"version": "0.15.9",
|
||||
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
|
||||
"author": "Raykosan",
|
||||
"license": "MIT",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ComfyUI_RaykoStudio"
|
||||
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
|
||||
version = "0.15.8"
|
||||
version = "0.15.9"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["torch>=1.7.0", "numpy>=1.19.0", "Pillow>=8.0.0", "freetype-py>=2.5.1"]
|
||||
|
||||
|
||||
+242
-52
@@ -5,12 +5,16 @@ app.registerExtension({
|
||||
name: "RaykoLoraWidget",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "RaykoModelsLoader") {
|
||||
console.log("[Rayko] JS loaded");
|
||||
console.log("[Rayko] JS Extension Loaded");
|
||||
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
// Сохраняем оригинальные методы ПЕРЕД переопределением
|
||||
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
const originalOnConfigure = nodeType.prototype.onConfigure;
|
||||
const originalOnSerialize = nodeType.prototype.onSerialize;
|
||||
const originalOnRemoved = nodeType.prototype.onRemoved;
|
||||
|
||||
nodeType.prototype.onNodeCreated = function() {
|
||||
const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
const result = originalOnNodeCreated ? originalOnNodeCreated.apply(this, arguments) : undefined;
|
||||
|
||||
this.loraRows = [];
|
||||
this.loraOptions = [];
|
||||
@@ -18,13 +22,28 @@ app.registerExtension({
|
||||
this.targetWidth = 450;
|
||||
this.rowHeight = 30;
|
||||
this.clickZones = [];
|
||||
this.storageKey = null;
|
||||
this.isRestoring = false;
|
||||
this.isInitialized = false;
|
||||
|
||||
// Находим виджет lora_data
|
||||
this.hiddenWidget = this.widgets.find(w => w.name === "lora_data");
|
||||
|
||||
// ВАЖНО: Сохраняем ссылку на ноду для использования в замыкании
|
||||
const nodeRef = this;
|
||||
|
||||
if (this.hiddenWidget) {
|
||||
this.hiddenWidget.hidden = true;
|
||||
this.hiddenWidget.serializeValue = () => {
|
||||
this.syncData();
|
||||
return this.hiddenWidget.value;
|
||||
|
||||
// ИСПРАВЛЕНИЕ: Используем nodeRef вместо this внутри замыкания
|
||||
const originalSerializeValue = this.hiddenWidget.serializeValue;
|
||||
this.hiddenWidget.serializeValue = function() {
|
||||
// Синхронизируем данные перед сериализацией
|
||||
nodeRef.syncData();
|
||||
|
||||
// Возвращаем значение виджета, гарантируем что оно не undefined
|
||||
const val = nodeRef.hiddenWidget ? nodeRef.hiddenWidget.value : "[]";
|
||||
return (val !== undefined && val !== null) ? val : "[]";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,33 +51,157 @@ app.registerExtension({
|
||||
|
||||
this.addWidget("button", "✔️ Update LoRA list", "", async () => {
|
||||
await this.loadLoraList();
|
||||
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
console.log("[Rayko] LoRA list updated (rows saved)");
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
console.log("[Rayko] LoRA list updated");
|
||||
});
|
||||
|
||||
this.addWidget("button", "➕ Add LoRA", "", () => {
|
||||
this.showLoraTreeSelector();
|
||||
});
|
||||
|
||||
this.loadLoraList().then(() => {
|
||||
if (this.hiddenWidget && this.hiddenWidget.value) {
|
||||
try {
|
||||
const saved = JSON.parse(this.hiddenWidget.value);
|
||||
if (Array.isArray(saved) && saved.length > 0) {
|
||||
this.loraRows = saved;
|
||||
console.log("[Rayko] LoRA restored:", this.loraRows.length);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Rayko] Recovery error:", e);
|
||||
}
|
||||
// Инициализация после получения ID
|
||||
const self = this;
|
||||
setTimeout(() => {
|
||||
if (this.id) {
|
||||
this.storageKey = `rayko_lora_${this.id}`;
|
||||
console.log("[Rayko] Storage key:", this.storageKey);
|
||||
|
||||
this.loadLoraList().then(() => {
|
||||
this.restoreData();
|
||||
this.isInitialized = true;
|
||||
self.updateUI();
|
||||
});
|
||||
}
|
||||
this.updateUI();
|
||||
});
|
||||
}, 100);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Восстановление данных при загрузке графа
|
||||
nodeType.prototype.onConfigure = function(info) {
|
||||
console.log("[Rayko] onConfigure called for node", this.id);
|
||||
this.isRestoring = true;
|
||||
|
||||
// Восстанавливаем из properties ноды
|
||||
if (info.properties && info.properties["lora_rows"]) {
|
||||
try {
|
||||
const saved = JSON.parse(info.properties["lora_rows"]);
|
||||
if (Array.isArray(saved)) {
|
||||
this.loraRows = saved;
|
||||
console.log("[Rayko] Restored from properties:", this.loraRows.length);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Rayko] Properties restore error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем widgets_values
|
||||
if (info.widgets_values && Array.isArray(info.widgets_values)) {
|
||||
for (let i = 0; i < info.widgets_values.length; i++) {
|
||||
const val = info.widgets_values[i];
|
||||
if (val && typeof val === "string" && val.startsWith("[")) {
|
||||
try {
|
||||
const saved = JSON.parse(val);
|
||||
if (Array.isArray(saved) && saved.length > 0) {
|
||||
this.loraRows = saved;
|
||||
console.log("[Rayko] Restored from widgets_values:", this.loraRows.length);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Не ошибка, просто не JSON
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isRestoring = false;
|
||||
|
||||
// Загружаем список лор и обновляем UI
|
||||
const self = this;
|
||||
this.loadLoraList().then(() => {
|
||||
self.updateUI();
|
||||
});
|
||||
|
||||
return originalOnConfigure ? originalOnConfigure.apply(this, arguments) : undefined;
|
||||
};
|
||||
|
||||
// Сохранение в localStorage
|
||||
nodeType.prototype.saveToStorage = function() {
|
||||
if (!this.storageKey) return;
|
||||
try {
|
||||
const data = {
|
||||
loraRows: this.loraRows,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
||||
} catch (e) {
|
||||
console.error("[Rayko] Storage save error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Восстановление из localStorage
|
||||
nodeType.prototype.restoreFromStorage = function() {
|
||||
if (!this.storageKey) return null;
|
||||
try {
|
||||
const stored = localStorage.getItem(this.storageKey);
|
||||
if (stored) {
|
||||
const data = JSON.parse(stored);
|
||||
const isFresh = Date.now() - data.timestamp < 86400000;
|
||||
if (isFresh && Array.isArray(data.loraRows)) {
|
||||
return data.loraRows;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Rayko] Storage restore error:", e);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Функция восстановления данных
|
||||
nodeType.prototype.restoreData = function() {
|
||||
if (this.isRestoring) return;
|
||||
|
||||
let savedData = null;
|
||||
|
||||
// Приоритет 1: properties ноды
|
||||
if (this.properties && this.properties["lora_rows"]) {
|
||||
try {
|
||||
savedData = JSON.parse(this.properties["lora_rows"]);
|
||||
if (Array.isArray(savedData) && savedData.length > 0) {
|
||||
console.log("[Rayko] Restored from properties:", savedData.length);
|
||||
this.loraRows = savedData;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Rayko] Properties parse error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Приоритет 2: hiddenWidget
|
||||
if (!savedData && this.hiddenWidget && this.hiddenWidget.value) {
|
||||
try {
|
||||
const widgetVal = JSON.parse(this.hiddenWidget.value);
|
||||
if (Array.isArray(widgetVal) && widgetVal.length > 0) {
|
||||
savedData = widgetVal;
|
||||
console.log("[Rayko] Restored from widget:", savedData.length);
|
||||
this.loraRows = savedData;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Rayko] Widget parse error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Приоритет 3: localStorage
|
||||
if (!savedData) {
|
||||
savedData = this.restoreFromStorage();
|
||||
if (savedData) {
|
||||
console.log("[Rayko] Restored from localStorage:", savedData.length);
|
||||
this.loraRows = savedData;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
nodeType.prototype.loadLoraList = async function() {
|
||||
try {
|
||||
const response = await api.fetchApi("/rayko/get_loras");
|
||||
@@ -67,7 +210,7 @@ app.registerExtension({
|
||||
this.loraTree = this.buildLoraTree(this.loraOptions);
|
||||
console.log("[Rayko] LoRA found:", this.loraOptions.length);
|
||||
} catch (e) {
|
||||
console.error("[Rayko] ERROR:", e);
|
||||
console.error("[Rayko] ERROR loading list:", e);
|
||||
this.loraOptions = [];
|
||||
this.loraTree = {};
|
||||
}
|
||||
@@ -98,8 +241,10 @@ app.registerExtension({
|
||||
if (this.loraRows.length === 0) return;
|
||||
|
||||
this.clickZones = [];
|
||||
const lastWidget = this.widgets[this.widgets.length - 1];
|
||||
const startY = lastWidget.y + lastWidget.height + 15;
|
||||
const visibleWidgets = this.widgets.filter(w => !w.hidden);
|
||||
const lastWidget = visibleWidgets.length > 0 ? visibleWidgets[visibleWidgets.length - 1] : null;
|
||||
|
||||
const startY = lastWidget ? (lastWidget.y + lastWidget.height + 15) : 40;
|
||||
const padding = 10;
|
||||
|
||||
const rightPanelWidth = 180;
|
||||
@@ -180,17 +325,6 @@ app.registerExtension({
|
||||
if (this.size[1] < totalH) this.setSize([this.targetWidth, totalH]);
|
||||
};
|
||||
|
||||
nodeType.prototype.formatLoraName = function(name) {
|
||||
if (!name || name === "None") return "None";
|
||||
const parts = name.replace(/\\/g, "/").split("/");
|
||||
if (parts.length > 1) {
|
||||
const folder = parts[parts.length - 2];
|
||||
const file = parts[parts.length - 1];
|
||||
return folder + "/" + file;
|
||||
}
|
||||
return name;
|
||||
};
|
||||
|
||||
nodeType.prototype.onMouseDown = function(e, pos, canvas) {
|
||||
if (!this.clickZones || this.clickZones.length === 0) return false;
|
||||
|
||||
@@ -202,18 +336,18 @@ app.registerExtension({
|
||||
if (zone.type === "toggle") {
|
||||
this.loraRows[zone.index].enabled = !this.loraRows[zone.index].enabled;
|
||||
this.syncData();
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
return true;
|
||||
}
|
||||
else if (zone.type === "strength_input") {
|
||||
const currentValue = this.loraRows[zone.index].strength_model;
|
||||
const newValue = prompt("Enter strenght LoRA:", currentValue.toFixed(2));
|
||||
const newValue = prompt("Enter strength LoRA:", currentValue.toFixed(2));
|
||||
if (newValue !== null) {
|
||||
const parsed = parseFloat(newValue);
|
||||
if (!isNaN(parsed) && parsed >= -10 && parsed <= 10) {
|
||||
this.loraRows[zone.index].strength_model = parsed;
|
||||
this.syncData();
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
} else {
|
||||
alert("Enter a number from -10 to 10");
|
||||
}
|
||||
@@ -224,18 +358,19 @@ app.registerExtension({
|
||||
this.loraRows[zone.index].strength_model = Math.max(-10,
|
||||
Math.round((this.loraRows[zone.index].strength_model - 0.05) * 20) / 20);
|
||||
this.syncData();
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
return true;
|
||||
}
|
||||
else if (zone.type === "right") {
|
||||
this.loraRows[zone.index].strength_model = Math.min(10,
|
||||
Math.round((this.loraRows[zone.index].strength_model + 0.05) * 20) / 20);
|
||||
this.syncData();
|
||||
this.graph.setDirtyCanvas(true, true);
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
return true;
|
||||
}
|
||||
else if (zone.type === "delete") {
|
||||
this.loraRows.splice(zone.index, 1);
|
||||
this.syncData();
|
||||
this.updateUI();
|
||||
return true;
|
||||
}
|
||||
@@ -265,7 +400,7 @@ app.registerExtension({
|
||||
|
||||
if (Object.keys(this.loraTree).length === 0) {
|
||||
const emptyMsg = document.createElement("div");
|
||||
emptyMsg.textContent = "⚠️ List is empty";
|
||||
emptyMsg.textContent = "⚠️ List is empty (Click Update LoRA list)";
|
||||
emptyMsg.style.cssText = `padding: 20px; color: #f44336; text-align: center;`;
|
||||
menu.appendChild(emptyMsg);
|
||||
} else {
|
||||
@@ -298,6 +433,7 @@ app.registerExtension({
|
||||
|
||||
nodeType.prototype.addLoraRow = function(loraName) {
|
||||
this.loraRows.push({ name: loraName, strength_model: 1.0, strength_clip: 1.0, enabled: true });
|
||||
this.syncData();
|
||||
this.updateUI();
|
||||
};
|
||||
|
||||
@@ -306,28 +442,82 @@ app.registerExtension({
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
// ГЛАВНАЯ ФУНКЦИЯ СОХРАНЕНИЯ
|
||||
nodeType.prototype.syncData = function() {
|
||||
if (this.isRestoring) return;
|
||||
|
||||
const jsonData = JSON.stringify(this.loraRows);
|
||||
|
||||
// 1. Обновляем скрытый виджет - гарантируем что значение всегда установлено
|
||||
if (this.hiddenWidget) {
|
||||
this.hiddenWidget.value = JSON.stringify(this.loraRows);
|
||||
console.log("[Rayko] syncData:", this.hiddenWidget.value);
|
||||
this.hiddenWidget.value = jsonData;
|
||||
}
|
||||
|
||||
// 2. Обновляем properties ноды
|
||||
if (!this.properties) this.properties = {};
|
||||
this.properties["lora_rows"] = jsonData;
|
||||
|
||||
// 3. Сохраняем в localStorage
|
||||
this.saveToStorage();
|
||||
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, false);
|
||||
};
|
||||
|
||||
const onExecute = this.onExecute;
|
||||
nodeType.prototype.onExecute = function() {
|
||||
this.syncData();
|
||||
return onExecute ? onExecute.apply(this, arguments) : undefined;
|
||||
};
|
||||
|
||||
const onSerialize = this.onSerialize;
|
||||
// Сериализация - данные должны попасть в JSON воркфлоу
|
||||
nodeType.prototype.onSerialize = function(o) {
|
||||
this.syncData();
|
||||
return onSerialize ? onSerialize.apply(this, arguments) : undefined;
|
||||
|
||||
// Гарантируем что properties будут сохранены
|
||||
if (!o.properties) o.properties = {};
|
||||
o.properties["lora_rows"] = this.properties["lora_rows"];
|
||||
|
||||
console.log("[Rayko] onSerialize:", o.properties["lora_rows"]);
|
||||
|
||||
return originalOnSerialize ? originalOnSerialize.apply(this, arguments) : undefined;
|
||||
};
|
||||
|
||||
// При удалении ноды очищаем localStorage
|
||||
nodeType.prototype.onRemoved = function() {
|
||||
if (this.storageKey) {
|
||||
localStorage.removeItem(this.storageKey);
|
||||
console.log("[Rayko] Cleaned localStorage for node:", this.storageKey);
|
||||
}
|
||||
return originalOnRemoved ? originalOnRemoved.apply(this, arguments) : undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Глобальный обработчик visibilitychange
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (!document.hidden && app && app.graph) {
|
||||
console.log("[Rayko] Page visible, restoring all nodes");
|
||||
setTimeout(() => {
|
||||
app.graph._nodes.forEach(node => {
|
||||
if (node.type === "RaykoModelsLoader" && node.restoreData) {
|
||||
node.restoreData();
|
||||
node.updateUI();
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
// Глобальный обработчик focus
|
||||
window.addEventListener("focus", () => {
|
||||
console.log("[Rayko] Window focused");
|
||||
if (app && app.graph) {
|
||||
setTimeout(() => {
|
||||
app.graph._nodes.forEach(node => {
|
||||
if (node.type === "RaykoModelsLoader" && node.restoreData) {
|
||||
node.restoreData();
|
||||
node.updateUI();
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
function createTreeItems(path, tree, level, menu, expandedFolders, self, header, noneItem) {
|
||||
const sortedKeys = Object.keys(tree).sort((a, b) => {
|
||||
const aIsFolder = tree[a] !== null;
|
||||
|
||||
Reference in New Issue
Block a user