This commit is contained in:
Danchi93
2026-07-05 11:30:57 +08:00
parent 821ff0ef7c
commit e0f97b9908
6 changed files with 82 additions and 66 deletions
+1 -36
View File
@@ -1,42 +1,7 @@
import os, json
import comfy.utils, comfy.sd, folder_paths
from .multi_lora_loader import MultiLoraLoader
from .prompt_segments import PromptSegments
from .resolution_switcher import ResolutionSwitcher
class MultiLoraLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"lora_stack": ("STRING", {"default": "[]", "multiline": False}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "apply"
CATEGORY = "loaders"
def apply(self, model, lora_stack="[]"):
try:
stack = json.loads(lora_stack)
except Exception:
return (model,)
for entry in stack:
if not entry.get("enabled", False):
continue
lora_name = entry.get("lora", "None")
strength = float(entry.get("strength", 1.0))
if lora_name == "None" or strength == 0:
continue
path = folder_paths.get_full_path("loras", lora_name)
if path and os.path.exists(path):
lora = comfy.utils.load_torch_file(path)
model, _ = comfy.sd.load_lora_for_models(model, None, lora, strength, 0)
return (model,)
NODE_CLASS_MAPPINGS = {
"MultiLoraLoader": MultiLoraLoader,
"PromptSegments": PromptSegments,
+1 -1
View File
@@ -1,7 +1,7 @@
import { app } from "../../scripts/app.js";
// 语言配置:将下面一行的 "en" 改成 "zh" 即可切换中文
const LANG = "en";
const LANG = "zh";
const I18N = {
en: { on_off: "On/Off", lora: "LoRA", strength: "Strength", note: "Note", del: "Del",
+36
View File
@@ -0,0 +1,36 @@
import os, json
import comfy.utils, comfy.sd, folder_paths
class MultiLoraLoader:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"lora_stack": ("STRING", {"default": "[]", "multiline": False}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "apply"
CATEGORY = "loaders"
def apply(self, model, lora_stack="[]"):
try:
stack = json.loads(lora_stack)
except Exception:
return (model,)
for entry in stack:
if not entry.get("enabled", False):
continue
lora_name = entry.get("lora", "None")
strength = float(entry.get("strength", 1.0))
if lora_name == "None" or strength == 0:
continue
path = folder_paths.get_full_path("loras", lora_name)
if path and os.path.exists(path):
lora = comfy.utils.load_torch_file(path)
model, _ = comfy.sd.load_lora_for_models(model, None, lora, strength, 0)
return (model,)
+31 -13
View File
@@ -1,7 +1,7 @@
import { app } from "../../scripts/app.js";
// 语言配置:将下面一行的 "en" 改成 "zh" 即可切换中文
const LANG = "en";
const LANG = "zh";
const I18N = {
en: { label_ph: "Label...", add: "+ Add Segment", default_label: "Quality Tags",
@@ -34,20 +34,20 @@ app.registerExtension({
// When the node is off-screen or zoomed too small, ComfyUI hides
// the DOM widget (display:none), which makes scrollHeight return 0.
if (!domH || domH <= 0) {
// No reliable layout info. If we already have a measured size,
// keep it (don't collapse). Otherwise fall back to a content
// based estimate so a brand-new hidden node still gets a size.
if (!this._minH) {
const h = this._calcHeight();
this._minH = h;
this.size[1] = h;
app.graph.setDirtyCanvas(true, true);
}
// No reliable layout info. Use the content-based estimate so
// the node still gets a reasonable height — and so deleting
// segments while hidden actually shrinks the node (otherwise
// _minH would be stuck at the old, larger value and the
// deleted segments leave behind empty, non-interactive space).
const h = this._calcHeight();
this._minH = h;
this.setSize([this.size[0], h]);
app.graph.setDirtyCanvas(true, true);
return;
}
const h = domH + 50;
this._minH = h;
this.size[1] = h;
this.setSize([this.size[0], h]);
app.graph.setDirtyCanvas(true, true);
});
};
@@ -127,7 +127,16 @@ app.registerExtension({
del.style.cssText = "background:transparent;color:#666;border:none;border-radius:3px;padding:2px 5px;cursor:pointer;font-size:11px;flex-shrink:0;line-height:1;transition:color 0.15s,background 0.15s;";
del.onmouseenter = () => { del.style.color = "#fff"; del.style.background = "#c0392b"; };
del.onmouseleave = () => { del.style.color = "#666"; del.style.background = "transparent"; };
del.onclick = () => { this._segments.splice(i, 1); this._sync(); this._render(); };
del.onclick = () => {
this._segments.splice(i, 1);
this._sync();
// Clear the cached min height so _applySize can actually shrink
// the node. Without this, _minH stays at the old (larger) value
// and the deleted segment's area becomes dead, non-interactive
// space — most visible after copy/paste.
this._minH = 0;
this._render();
};
top.append(toggle, labelNum, label, del);
@@ -219,7 +228,10 @@ app.registerExtension({
const onSerialize = nodeType.prototype.onSerialize;
nodeType.prototype.onSerialize = function (o) {
onSerialize?.apply(this, arguments);
o.prompt_segments = this._segments;
// Deep copy so a pasted/duplicated node doesn't share the same array
// reference with the original (which would cause edits in one to
// leak into the other).
o.prompt_segments = JSON.parse(JSON.stringify(this._segments));
};
const onRemoved = nodeType.prototype.onRemoved;
@@ -233,6 +245,12 @@ app.registerExtension({
onConfigure?.apply(this, arguments);
if (o.prompt_segments) {
this._segments = o.prompt_segments;
// On paste/duplicate, ComfyUI may carry over a stale cached min
// height and a stale DOM widget element height from the original.
// Reset both so the new node measures fresh and shrinks correctly
// when segments are deleted.
this._minH = 0;
if (this._domWidget?.element) this._domWidget.element.style.height = "auto";
requestAnimationFrame(() => this._render());
}
};
+12 -6
View File
@@ -1,7 +1,7 @@
import { app } from "../../scripts/app.js";
// 语言配置:将下面一行的 "en" 改成 "zh" 即可切换中文
const LANG = "en";
const LANG = "zh";
const I18N = {
en: { w: "W", h: "H", batch: "Batch", add: "+ Add Resolution",
@@ -62,14 +62,14 @@ app.registerExtension({
if (!this._minH) {
const h = this._calcHeight();
this._minH = h;
this.size[1] = h;
this.setSize([this.size[0], h]);
app.graph.setDirtyCanvas(true, true);
}
return;
}
const h = domH + 50;
this._minH = h;
this.size[1] = h;
this.setSize([this.size[0], h]);
app.graph.setDirtyCanvas(true, true);
});
};
@@ -113,8 +113,12 @@ app.registerExtension({
capsule.style.cssText = `width:30px;height:16px;border-radius:8px;background:${active ? "#43a047" : "#555"};position:relative;transition:background 0.15s;border:1px solid ${active ? "#2e7d32" : "#424242"};`;
capsule.innerHTML = `<div style="position:absolute;top:1px;left:${active ? "15px" : "2px"};width:12px;height:12px;border-radius:50%;background:#fff;transition:left 0.15s;"></div>`;
toggle.onclick = () => {
// Mutually exclusive: turning one on turns all others off.
this._presets.forEach((q, j) => { q.active = (j === i) ? !q.active : false; });
// Radio behavior: clicking the active one is a no-op (must
// always keep exactly one active — this node switches
// resolutions, it can't disable resolution). Clicking any
// other moves the active flag to it.
if (active) return;
this._presets.forEach((q, j) => { q.active = (j === i); });
this._sync();
this._render();
};
@@ -240,7 +244,9 @@ app.registerExtension({
const onSerialize = nodeType.prototype.onSerialize;
nodeType.prototype.onSerialize = function (o) {
onSerialize?.apply(this, arguments);
o.resolution_presets = this._presets;
// Deep copy so a pasted/duplicated node doesn't share the same array
// reference with the original.
o.resolution_presets = JSON.parse(JSON.stringify(this._presets));
};
const onConfigure = nodeType.prototype.onConfigure;
+1 -10
View File
@@ -1,3 +1,4 @@
import json
import torch
class ResolutionSwitcher:
@@ -21,8 +22,6 @@ class ResolutionSwitcher:
CATEGORY = "latent"
def build(self, presets="[]"):
import json
# Default: 1024x1024, batch 1
width, height, batch = 1024, 1024, 1
@@ -49,11 +48,3 @@ class ResolutionSwitcher:
latent = torch.zeros([batch, 4, height // 8, width // 8])
return ({"samples": latent},)
NODE_CLASS_MAPPINGS = {
"ResolutionSwitcher": ResolutionSwitcher,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ResolutionSwitcher": "Resolution Switcher",
}