Adding preset functions to the RS Models Loader node
This commit is contained in:
+74
-5
@@ -22,6 +22,12 @@ from nodes import UNETLoader, CLIPLoader, VAELoader, LoraLoader, DualCLIPLoader
|
||||
from server import PromptServer
|
||||
import aiohttp
|
||||
|
||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PRESETS_DIR = os.path.join(CURRENT_DIR, "preset_models")
|
||||
|
||||
if not os.path.exists(PRESETS_DIR):
|
||||
os.makedirs(PRESETS_DIR)
|
||||
|
||||
class RaykoModelsLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -29,7 +35,6 @@ class RaykoModelsLoader:
|
||||
clip_files = folder_paths.get_filename_list("clip")
|
||||
vae_files = folder_paths.get_filename_list("vae")
|
||||
|
||||
# Добавляем "pixel_space" в список VAE (как в нативной ноде ComfyUI)
|
||||
vae_files_with_pixel = vae_files + ["pixel_space"]
|
||||
|
||||
weight_dtype_opts = ["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"]
|
||||
@@ -57,7 +62,7 @@ class RaykoModelsLoader:
|
||||
RETURN_NAMES = ("MODEL", "CLIP", "VAE")
|
||||
FUNCTION = "load_models"
|
||||
CATEGORY = "🦊 RaykoStudio"
|
||||
DESCRIPTION = "The node combines the loaders of the model, clip, vae and lore."
|
||||
DESCRIPTION = "The node combines the loaders of the model, clip, vae and lore. Presets allow quick switching between model configurations (UNET/CLIP/VAE) without affecting LoRA settings."
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, lora_data="[]", **kwargs):
|
||||
@@ -80,7 +85,6 @@ class RaykoModelsLoader:
|
||||
|
||||
dev = None if clip_device == "default" else clip_device
|
||||
|
||||
# CLIP loading logic with dual CLIP support
|
||||
if use_clip2 and clip_name2 and clip_name2 != "None":
|
||||
print(f"[Rayko] Using DualCLIPLoader mode")
|
||||
print(f"[Rayko] CLIP1: {clip_name}")
|
||||
@@ -103,7 +107,6 @@ class RaykoModelsLoader:
|
||||
)[0]
|
||||
print(f"[Rayko] CLIP loaded successfully")
|
||||
|
||||
# VAE loading logic with pixel_space support
|
||||
if vae_name == "pixel_space":
|
||||
print(f"[Rayko] Using pixel_space VAE (fake VAE for direct pixel manipulation)")
|
||||
sd = {}
|
||||
@@ -164,4 +167,70 @@ NODE_DISPLAY_NAME_MAPPINGS = {"RaykoModelsLoader": "🦊 RS Models Loader"}
|
||||
@PromptServer.instance.routes.get("/rayko/get_loras")
|
||||
async def get_loras(request):
|
||||
loras = folder_paths.get_filename_list("loras")
|
||||
return aiohttp.web.json_response(sorted(loras, key=lambda x: x.lower()))
|
||||
return aiohttp.web.json_response(sorted(loras, key=lambda x: x.lower()))
|
||||
|
||||
@PromptServer.instance.routes.post("/rayko_models/save_preset")
|
||||
async def rayko_models_save_preset(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
name = data.get("name", "").strip()
|
||||
if not name:
|
||||
return aiohttp.web.Response(status=400, text="Name required")
|
||||
name = "".join(c for c in name if c.isalnum() or c in " _-").strip()
|
||||
if not name:
|
||||
return aiohttp.web.Response(status=400, text="Invalid name")
|
||||
filepath = os.path.join(PRESETS_DIR, f"{name}.json")
|
||||
preset_data = {
|
||||
"unet_name": data.get("unet_name", ""),
|
||||
"weight_dtype": data.get("weight_dtype", "default"),
|
||||
"use_clip2": data.get("use_clip2", False),
|
||||
"clip_name": data.get("clip_name", ""),
|
||||
"clip_name2": data.get("clip_name2", ""),
|
||||
"clip_type": data.get("clip_type", "stable_diffusion"),
|
||||
"clip_device": data.get("clip_device", "default"),
|
||||
"vae_name": data.get("vae_name", "")
|
||||
}
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
json.dump(preset_data, f, indent=2)
|
||||
return aiohttp.web.Response(status=200, text="OK")
|
||||
except Exception as e:
|
||||
return aiohttp.web.Response(status=500, text=str(e))
|
||||
|
||||
@PromptServer.instance.routes.post("/rayko_models/list_presets")
|
||||
async def rayko_models_list_presets(request):
|
||||
try:
|
||||
presets = []
|
||||
if os.path.exists(PRESETS_DIR):
|
||||
presets = [f[:-5] for f in os.listdir(PRESETS_DIR) if f.endswith('.json')]
|
||||
return aiohttp.web.json_response(sorted(presets, key=lambda x: x.lower()))
|
||||
except Exception as e:
|
||||
return aiohttp.web.Response(status=500, text=str(e))
|
||||
|
||||
@PromptServer.instance.routes.post("/rayko_models/load_preset")
|
||||
async def rayko_models_load_preset(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
name = data.get("name")
|
||||
filepath = os.path.join(PRESETS_DIR, f"{name}.json")
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
result = json.load(f)
|
||||
return aiohttp.web.json_response(result)
|
||||
return aiohttp.web.Response(status=404, text="Preset not found")
|
||||
except Exception as e:
|
||||
return aiohttp.web.Response(status=500, text=str(e))
|
||||
|
||||
@PromptServer.instance.routes.post("/rayko_models/delete_preset")
|
||||
async def rayko_models_delete_preset(request):
|
||||
try:
|
||||
data = await request.json()
|
||||
name = data.get("name")
|
||||
if not name:
|
||||
return aiohttp.web.Response(status=400, text="Name required")
|
||||
filepath = os.path.join(PRESETS_DIR, f"{name}.json")
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
return aiohttp.web.Response(status=200, text="OK")
|
||||
return aiohttp.web.Response(status=404, text="Preset not found")
|
||||
except Exception as e:
|
||||
return aiohttp.web.Response(status=500, text=str(e))
|
||||
@@ -1,3 +1,18 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2025-2026 Raykosan (RaykoStudio)
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import torch
|
||||
import math
|
||||
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
|
||||
REQUIRED_MODULES: Set[str] = set()
|
||||
|
||||
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
|
||||
__version__ = "0.27.0"
|
||||
__version__ = "0.28.0"
|
||||
|
||||
def load_modules():
|
||||
print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ComfyUI_RaykoStudio",
|
||||
"version": "0.27.0",
|
||||
"version": "0.28.0",
|
||||
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
|
||||
"author": "Raykosan",
|
||||
"license": "Apache License 2.0",
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "ComfyUI_RaykoStudio"
|
||||
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
|
||||
version = "0.27.0"
|
||||
version = "0.28.0"
|
||||
license = { file = "LICENSE" }
|
||||
dependencies = ["torch", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"]
|
||||
|
||||
|
||||
+266
-20
@@ -35,15 +35,12 @@ app.registerExtension({
|
||||
};
|
||||
}
|
||||
|
||||
// Find widgets for CLIP2 disabled logic
|
||||
const useClip2Widget = this.widgets.find(w => w.name === "use_clip2");
|
||||
const clipName2Widget = this.widgets.find(w => w.name === "clip_name2");
|
||||
|
||||
if (useClip2Widget && clipName2Widget) {
|
||||
// Set initial disabled state
|
||||
clipName2Widget.disabled = !useClip2Widget.value;
|
||||
|
||||
// Apply CSS class for visual styling
|
||||
if (clipName2Widget.element) {
|
||||
if (clipName2Widget.disabled) {
|
||||
clipName2Widget.element.classList.add("comfy-disabled");
|
||||
@@ -52,7 +49,6 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
|
||||
// Callback when use_clip2 changes
|
||||
const originalCallback = useClip2Widget.callback;
|
||||
useClip2Widget.callback = function(value) {
|
||||
if (originalCallback) originalCallback(value);
|
||||
@@ -70,6 +66,258 @@ app.registerExtension({
|
||||
|
||||
this.setSize([this.targetWidth, this.size[1]]);
|
||||
|
||||
const collectPresetData = () => {
|
||||
return {
|
||||
unet_name: this.widgets.find(w => w.name === "unet_name")?.value || "",
|
||||
weight_dtype: this.widgets.find(w => w.name === "weight_dtype")?.value || "default",
|
||||
use_clip2: this.widgets.find(w => w.name === "use_clip2")?.value || false,
|
||||
clip_name: this.widgets.find(w => w.name === "clip_name")?.value || "",
|
||||
clip_name2: this.widgets.find(w => w.name === "clip_name2")?.value || "",
|
||||
clip_type: this.widgets.find(w => w.name === "clip_type")?.value || "stable_diffusion",
|
||||
clip_device: this.widgets.find(w => w.name === "clip_device")?.value || "default",
|
||||
vae_name: this.widgets.find(w => w.name === "vae_name")?.value || ""
|
||||
};
|
||||
};
|
||||
|
||||
const applyPresetData = (data) => {
|
||||
const setWidgetValue = (name, value) => {
|
||||
const widget = this.widgets.find(w => w.name === name);
|
||||
if (widget) {
|
||||
widget.value = value;
|
||||
if (widget.callback) widget.callback(value);
|
||||
}
|
||||
};
|
||||
|
||||
setWidgetValue("unet_name", data.unet_name);
|
||||
setWidgetValue("weight_dtype", data.weight_dtype);
|
||||
setWidgetValue("use_clip2", data.use_clip2);
|
||||
setWidgetValue("clip_name", data.clip_name);
|
||||
setWidgetValue("clip_name2", data.clip_name2);
|
||||
setWidgetValue("clip_type", data.clip_type);
|
||||
setWidgetValue("clip_device", data.clip_device);
|
||||
setWidgetValue("vae_name", data.vae_name);
|
||||
|
||||
this.loraRows = [];
|
||||
this.syncData();
|
||||
|
||||
this.updateUI();
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
const presetListOverlay = document.createElement("div");
|
||||
presetListOverlay.style.cssText = "position:fixed;display:none;flex-direction:column;max-height:300px;overflow-y:auto;background:#2a2a2a;border:1px solid #5090cc;border-radius:6px;z-index:10000;box-shadow:0 4px 12px rgba(0,0,0,0.8);min-width:250px;padding:5px;";
|
||||
document.body.appendChild(presetListOverlay);
|
||||
|
||||
const presetNameInput = document.createElement("div");
|
||||
presetNameInput.style.cssText = "position:fixed;display:none;background:#2a2a2a;padding:10px;border:1px solid #5090cc;border-radius:6px;z-index:10000;box-shadow:0 4px 15px rgba(0,0,0,0.7);width:220px;text-align:center;";
|
||||
|
||||
const inputLabel = document.createElement("div");
|
||||
inputLabel.style.cssText = "color:#999;font-size:11px;margin-bottom:4px;text-align:left;";
|
||||
inputLabel.textContent = "Preset name:";
|
||||
|
||||
const inputField = document.createElement("input");
|
||||
inputField.style.cssText = "width:100%;padding:5px;background:#111;color:#fff;border:1px solid #444;border-radius:3px;margin-bottom:5px;font-size:12px;box-sizing:border-box;";
|
||||
|
||||
const inputBtns = document.createElement("div");
|
||||
inputBtns.style.cssText = "display:flex;gap:5px;";
|
||||
|
||||
const inputOk = document.createElement("button");
|
||||
inputOk.style.cssText = "flex:1;padding:4px;background:#1a3a5a;color:#aadaff;border:1px solid #5090cc;border-radius:3px;cursor:pointer;font-size:11px;";
|
||||
inputOk.textContent = "OK";
|
||||
|
||||
const inputCancel = document.createElement("button");
|
||||
inputCancel.style.cssText = "flex:1;padding:4px;background:#2a2a2a;color:#ccc;border:1px solid #444;border-radius:3px;cursor:pointer;font-size:11px;";
|
||||
inputCancel.textContent = "Cancel";
|
||||
|
||||
inputBtns.append(inputOk, inputCancel);
|
||||
presetNameInput.append(inputLabel, inputField, inputBtns);
|
||||
document.body.appendChild(presetNameInput);
|
||||
|
||||
const deleteConfirmOverlay = document.createElement("div");
|
||||
deleteConfirmOverlay.style.cssText = "position:fixed;display:none;background:#2a2a2a;padding:10px;border:1px solid #5090cc;border-radius:6px;z-index:10000;box-shadow:0 4px 15px rgba(0,0,0,0.7);width:220px;text-align:center;";
|
||||
|
||||
const deleteText = document.createElement("div");
|
||||
deleteText.style.cssText = "color:#ccc;font-size:12px;margin-bottom:10px;word-break:break-word;";
|
||||
|
||||
const deleteBtns = document.createElement("div");
|
||||
deleteBtns.style.cssText = "display:flex;gap:5px;";
|
||||
|
||||
const deleteOk = document.createElement("button");
|
||||
deleteOk.style.cssText = "flex:1;padding:4px;background:#1a3a5a;color:#aadaff;border:1px solid #5090cc;border-radius:3px;cursor:pointer;font-size:11px;";
|
||||
deleteOk.textContent = "OK";
|
||||
|
||||
const deleteCancel = document.createElement("button");
|
||||
deleteCancel.style.cssText = "flex:1;padding:4px;background:#2a2a2a;color:#ccc;border:1px solid #444;border-radius:3px;cursor:pointer;font-size:11px;";
|
||||
deleteCancel.textContent = "Cancel";
|
||||
|
||||
deleteBtns.append(deleteOk, deleteCancel);
|
||||
deleteConfirmOverlay.append(deleteText, deleteBtns);
|
||||
document.body.appendChild(deleteConfirmOverlay);
|
||||
|
||||
let pendingDeleteName = null;
|
||||
|
||||
const presetsRoot = document.createElement("div");
|
||||
presetsRoot.style.cssText = "display:flex;gap:4px;width:100%;margin:0;padding:0;box-sizing:border-box;height:40px;align-items:center;";
|
||||
|
||||
const savePresetBtn = document.createElement("button");
|
||||
savePresetBtn.textContent = " Save preset";
|
||||
savePresetBtn.style.cssText = "flex:1;padding:4px 2px;font-size:11px;border:1px solid #99c0ee;border-radius:5px;background:#1a3a5a;color:#aadaff;cursor:pointer;height:26px;margin:0;";
|
||||
|
||||
const selectPresetBtn = document.createElement("button");
|
||||
selectPresetBtn.textContent = "📂 Select preset";
|
||||
selectPresetBtn.style.cssText = "flex:1;padding:4px 2px;font-size:11px;border:1px solid #99c0ee;border-radius:5px;background:#1a3a5a;color:#aadaff;cursor:pointer;height:26px;margin:0;";
|
||||
|
||||
presetsRoot.append(savePresetBtn, selectPresetBtn);
|
||||
|
||||
savePresetBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
presetListOverlay.style.display = "none";
|
||||
deleteConfirmOverlay.style.display = "none";
|
||||
|
||||
const saveBtnRect = savePresetBtn.getBoundingClientRect();
|
||||
presetNameInput.style.left = saveBtnRect.left + "px";
|
||||
presetNameInput.style.top = (saveBtnRect.bottom + 5) + "px";
|
||||
presetNameInput.style.display = "block";
|
||||
inputField.value = "";
|
||||
setTimeout(() => inputField.focus(), 50);
|
||||
});
|
||||
|
||||
const performSave = () => {
|
||||
const name = inputField.value.trim();
|
||||
if (!name) return;
|
||||
presetNameInput.style.display = "none";
|
||||
const presetData = collectPresetData();
|
||||
fetch("/rayko_models/save_preset", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name, ...presetData })
|
||||
});
|
||||
};
|
||||
|
||||
inputOk.addEventListener("click", performSave);
|
||||
inputCancel.addEventListener("click", () => {
|
||||
presetNameInput.style.display = "none";
|
||||
});
|
||||
inputField.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter") performSave();
|
||||
if (e.key === "Escape") presetNameInput.style.display = "none";
|
||||
});
|
||||
|
||||
selectPresetBtn.addEventListener("click", async (e) => {
|
||||
e.stopPropagation();
|
||||
presetNameInput.style.display = "none";
|
||||
deleteConfirmOverlay.style.display = "none";
|
||||
if (presetListOverlay.style.display === "flex") {
|
||||
presetListOverlay.style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
const selectBtnRect = selectPresetBtn.getBoundingClientRect();
|
||||
presetListOverlay.style.left = selectBtnRect.left + "px";
|
||||
presetListOverlay.style.top = (selectBtnRect.bottom + 5) + "px";
|
||||
|
||||
presetListOverlay.innerHTML = "<div style='padding:8px;color:#999;text-align:center;'>Loading...</div>";
|
||||
presetListOverlay.style.display = "flex";
|
||||
try {
|
||||
const res = await fetch("/rayko_models/list_presets", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
const list = await res.json();
|
||||
presetListOverlay.innerHTML = "";
|
||||
if (!list.length) {
|
||||
presetListOverlay.textContent = "No presets found";
|
||||
return;
|
||||
}
|
||||
list.forEach(name => {
|
||||
const row = document.createElement("div");
|
||||
row.style.cssText = "display:flex;align-items:center;justify-content:space-between;padding:6px 10px;border-bottom:1px solid #333;";
|
||||
|
||||
const nameSpan = document.createElement("span");
|
||||
nameSpan.textContent = name;
|
||||
nameSpan.style.cssText = "flex:1;cursor:pointer;color:#ccc;font-size:12px;";
|
||||
nameSpan.onmouseenter = () => nameSpan.style.background = "#3a3a3a";
|
||||
nameSpan.onmouseleave = () => nameSpan.style.background = "transparent";
|
||||
nameSpan.onclick = async (e) => {
|
||||
e.stopPropagation();
|
||||
presetListOverlay.style.display = "none";
|
||||
const res2 = await fetch("/rayko_models/load_preset", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name })
|
||||
});
|
||||
if (res2.ok) {
|
||||
const data = await res2.json();
|
||||
applyPresetData(data);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteBtn = document.createElement("span");
|
||||
deleteBtn.textContent = "❌";
|
||||
deleteBtn.style.cssText = "cursor:pointer;margin-left:8px;font-size:14px;opacity:0.7;";
|
||||
deleteBtn.onmouseenter = () => {
|
||||
deleteBtn.style.opacity = "1";
|
||||
deleteBtn.style.transform = "scale(1.2)";
|
||||
};
|
||||
deleteBtn.onmouseleave = () => {
|
||||
deleteBtn.style.opacity = "0.7";
|
||||
deleteBtn.style.transform = "scale(1)";
|
||||
};
|
||||
deleteBtn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
pendingDeleteName = name;
|
||||
deleteText.textContent = `Delete "${name}"?`;
|
||||
|
||||
const selectBtnRect = selectPresetBtn.getBoundingClientRect();
|
||||
deleteConfirmOverlay.style.left = selectBtnRect.left + "px";
|
||||
deleteConfirmOverlay.style.top = (selectBtnRect.bottom + 5) + "px";
|
||||
deleteConfirmOverlay.style.display = "block";
|
||||
};
|
||||
|
||||
row.appendChild(nameSpan);
|
||||
row.appendChild(deleteBtn);
|
||||
presetListOverlay.appendChild(row);
|
||||
});
|
||||
} catch (e) {
|
||||
presetListOverlay.textContent = "Error loading";
|
||||
}
|
||||
});
|
||||
|
||||
deleteOk.addEventListener("click", async () => {
|
||||
if (pendingDeleteName) {
|
||||
await fetch("/rayko_models/delete_preset", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: pendingDeleteName })
|
||||
});
|
||||
deleteConfirmOverlay.style.display = "none";
|
||||
presetListOverlay.style.display = "none";
|
||||
pendingDeleteName = null;
|
||||
}
|
||||
});
|
||||
|
||||
deleteCancel.addEventListener("click", () => {
|
||||
deleteConfirmOverlay.style.display = "none";
|
||||
pendingDeleteName = null;
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!presetListOverlay?.contains(e.target)) {
|
||||
presetListOverlay.style.display = "none";
|
||||
}
|
||||
if (!presetNameInput?.contains(e.target)) {
|
||||
presetNameInput.style.display = "none";
|
||||
}
|
||||
if (!deleteConfirmOverlay?.contains(e.target)) {
|
||||
deleteConfirmOverlay.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
const presetsWidget = this.addDOMWidget("presets_ui", "custom", presetsRoot);
|
||||
presetsWidget.computeSize = function() {
|
||||
return [this.width || 130, 50];
|
||||
};
|
||||
|
||||
this.addWidget("button", "✔️ Update LoRA list", "", async () => {
|
||||
await this.loadLoraList();
|
||||
if (this.graph) this.graph.setDirtyCanvas(true, true);
|
||||
@@ -120,7 +368,6 @@ app.registerExtension({
|
||||
const self = this;
|
||||
this.loadLoraList().then(() => self.updateUI());
|
||||
|
||||
// Restore disabled state of clip_name2 after configuration load
|
||||
const useClip2Widget = this.widgets.find(w => w.name === "use_clip2");
|
||||
const clipName2Widget = this.widgets.find(w => w.name === "clip_name2");
|
||||
if (useClip2Widget && clipName2Widget) {
|
||||
@@ -220,9 +467,9 @@ app.registerExtension({
|
||||
nodeType.prototype.onDrawForeground = function(ctx, visibleRect) {
|
||||
if (this.loraRows.length === 0) return;
|
||||
this.clickZones = [];
|
||||
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 addButton = this.widgets.find(w => w.name === " Add LoRA");
|
||||
const startY = addButton ? (addButton.y + addButton.height + 15) : 40;
|
||||
const padding = 10;
|
||||
const rightPanelWidth = 180;
|
||||
|
||||
@@ -340,11 +587,11 @@ app.registerExtension({
|
||||
|
||||
nodeType.prototype.showLoraTreeSelector = function() {
|
||||
const self = this;
|
||||
const expandedFolders = {};
|
||||
const expandedFolders = {};
|
||||
let buttonElement = null;
|
||||
const widgets = this.widgets;
|
||||
for(let w of widgets) {
|
||||
if(w.name === "➕ Add LoRA" && w.element) {
|
||||
for (let w of widgets) {
|
||||
if (w.name === "➕ Add LoRA" && w.element) {
|
||||
buttonElement = w.element;
|
||||
break;
|
||||
}
|
||||
@@ -368,8 +615,8 @@ app.registerExtension({
|
||||
|
||||
if (buttonElement) {
|
||||
const rect = buttonElement.getBoundingClientRect();
|
||||
menuLeft = rect.right + 10;
|
||||
menuTop = rect.top;
|
||||
menuLeft = rect.right + 10;
|
||||
menuTop = rect.top;
|
||||
} else {
|
||||
menuLeft = (window.innerWidth / 2) - (menuWidth / 2);
|
||||
menuTop = (window.innerHeight / 2) - (menuHeight / 2);
|
||||
@@ -460,7 +707,7 @@ app.registerExtension({
|
||||
});
|
||||
}
|
||||
} else {
|
||||
listContainer.innerHTML = "";
|
||||
listContainer.innerHTML = "";
|
||||
createTreeItems("", self.loraTree, 0, listContainer, expandedFolders, self, null, null);
|
||||
}
|
||||
};
|
||||
@@ -491,9 +738,8 @@ app.registerExtension({
|
||||
this.loraRows.push({ name: loraName, strength_model: 1.0, strength_clip: 1.0, enabled: true });
|
||||
this.syncData();
|
||||
if (this.graph) {
|
||||
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 addButton = this.widgets.find(w => w.name === "➕ Add LoRA");
|
||||
const startY = addButton ? (addButton.y + addButton.height + 15) : 40;
|
||||
const newHeight = startY + (this.loraRows.length * this.rowHeight) + 10;
|
||||
if (this.size[1] < newHeight) {
|
||||
this.setSize([this.targetWidth, newHeight]);
|
||||
@@ -583,7 +829,7 @@ function createTreeItems(path, tree, level, container, expandedFolders, self, he
|
||||
folderHeader.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
expandedFolders[itemPath] = !expandedFolders[itemPath];
|
||||
container.innerHTML = "";
|
||||
container.innerHTML = "";
|
||||
createTreeItems("", self.loraTree, 0, container, expandedFolders, self, header, noneItem);
|
||||
};
|
||||
folderContainer.appendChild(folderHeader);
|
||||
@@ -600,8 +846,8 @@ function createTreeItems(path, tree, level, container, expandedFolders, self, he
|
||||
e.stopPropagation();
|
||||
self.addLoraRow(itemPath);
|
||||
let root = fileItem;
|
||||
while(root.parentNode && root.parentNode !== document.body) root = root.parentNode;
|
||||
if(root && root.parentNode === document.body) root.remove();
|
||||
while (root.parentNode && root.parentNode !== document.body) root = root.parentNode;
|
||||
if (root && root.parentNode === document.body) root.remove();
|
||||
};
|
||||
container.appendChild(fileItem);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user