Serialize compact LoRA rows as a list

This commit is contained in:
digital-garbage
2026-04-24 15:28:28 +03:00
parent 97d6195338
commit 0eaeabbbc4
2 changed files with 97 additions and 16 deletions
+53 -15
View File
@@ -159,6 +159,7 @@ class FunPackApplyLoraWeights:
optional = FlexibleOptionalInputType(
any_type,
{
"lora_list": ("STRING", {"default": "[]", "multiline": False}),
"lora_0": (loras, {"default": "None"}),
"lora_0_type": (LORA_TYPES, {"default": "general"}),
"lora_0_base_weight": (
@@ -194,6 +195,10 @@ class FunPackApplyLoraWeights:
def IS_CHANGED(cls, **kwargs):
return float("nan")
@classmethod
def VALIDATE_INPUTS(cls, lora_list=None, lora_0=None):
return True
def _load_suggestions(self, refinement_key, mode, prompt_key):
path = refiner_state_path(refinement_key, mode)
if not os.path.exists(path):
@@ -215,7 +220,52 @@ class FunPackApplyLoraWeights:
return suggestions, "refiner suggestions applied"
def _entry_from_row(self, index, row):
if not isinstance(row, dict):
return None
if not row.get("on", True):
return None
name = row.get("lora", row.get("name", "None"))
if not name or name == "None":
return None
lora_type = row.get("type", row.get("lora_type", "general"))
if lora_type not in LORA_TYPES:
lora_type = "general"
return {
"slot": index,
"name": name,
"type": lora_type,
"id": lora_state_id(name, lora_type),
"base_model_weight": safe_float(row.get("strength", row.get("base_weight", 1.0))),
}
def _iter_lora_list(self, value):
if value in (None, "", "[]"):
return None
if isinstance(value, str):
try:
value = json.loads(value)
except (json.JSONDecodeError, TypeError, ValueError):
return None
if not isinstance(value, list):
return None
entries = []
for index, row in enumerate(value):
entry = self._entry_from_row(index, row)
if entry:
entries.append(entry)
return entries
def _iter_slots(self, kwargs):
listed_entries = self._iter_lora_list(kwargs.get("lora_list"))
if listed_entries is not None:
yield from listed_entries
return
indexed_slots = set()
for key in kwargs:
match = re.fullmatch(r"lora_(\d+)", key)
@@ -225,21 +275,9 @@ class FunPackApplyLoraWeights:
for index in sorted(indexed_slots):
lora_name = kwargs.get(f"lora_{index}", "None")
if isinstance(lora_name, dict):
if not lora_name.get("on", True):
continue
name = lora_name.get("lora", "None")
if not name or name == "None":
continue
lora_type = lora_name.get("type", lora_name.get("lora_type", "general"))
if lora_type not in LORA_TYPES:
lora_type = "general"
yield {
"slot": index,
"name": name,
"type": lora_type,
"id": lora_state_id(name, lora_type),
"base_model_weight": safe_float(lora_name.get("strength", lora_name.get("base_weight", 1.0))),
}
entry = self._entry_from_row(index, lora_name)
if entry:
yield entry
continue
shifted_base_weight = None
+44 -1
View File
@@ -5,6 +5,7 @@ const NODE_NAME = "FunPackApplyLoraWeights";
const LORA_TYPES = ["general", "concept", "style", "quality", "character"];
const ADD_BUTTON_NAME = "+ Add LoRA";
const HEADER_WIDGET_NAME = "funpack_lora_header";
const LORA_LIST_WIDGET_NAME = "lora_list";
const BASE_WIDGET_NAMES = new Set(["positive_prompt", "refinement_key", "mode", "per_block"]);
let cachedLoraValues = null;
@@ -108,7 +109,11 @@ function inside(pos, bounds) {
}
function getLoraValues(nodeData) {
return cachedLoraValues || nodeData?.input?.optional?.lora_0?.[0] || ["None"];
const inputType = nodeData?.input?.optional?.lora_0?.[0];
const embeddedValues = Array.isArray(inputType)
? inputType
: nodeData?.input?.optional?.lora_0?.[1]?.available_loras;
return cachedLoraValues || (Array.isArray(embeddedValues) ? embeddedValues : ["None"]);
}
function rememberLoraValues(nodeData, values) {
@@ -286,9 +291,28 @@ class FunPackLoraHeaderWidget extends FunPackBaseWidget {
}
}
class FunPackLoraListWidget extends FunPackBaseWidget {
constructor(node) {
super(LORA_LIST_WIDGET_NAME);
this.node = node;
this.value = "[]";
}
computeSize(width) {
return [width, 0];
}
draw() {}
serializeValue() {
return JSON.stringify(getLoraRows(this.node).map((row) => row.serializeValue()));
}
}
class FunPackLoraRowWidget extends FunPackBaseWidget {
constructor(name, value, nodeData) {
super(name);
this.options.serialize = false;
this.value = normalizeRowValue(value);
this.nodeData = nodeData;
this.draggedWeight = false;
@@ -494,6 +518,7 @@ function isDefaultLoraWidget(widget) {
function isManagedWidget(widget) {
return (
isDefaultLoraWidget(widget) ||
widget.name === LORA_LIST_WIDGET_NAME ||
widget.name === HEADER_WIDGET_NAME ||
widget.name === ADD_BUTTON_NAME ||
widget.__funpackLoraRow === true
@@ -611,6 +636,23 @@ function parseSerializedRows(values, baseCount) {
}
const rowValues = values.slice(baseCount);
for (const value of rowValues) {
if (typeof value !== "string") {
continue;
}
try {
const parsed = JSON.parse(value);
if (Array.isArray(parsed)) {
const rows = parsed.map(normalizeRowValue);
if (rows.length) {
return rows;
}
}
} catch (_error) {
// Older workflows stored rows as individual widget values.
}
}
const objectRows = rowValues
.filter((value) => value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, "lora"))
.map(normalizeRowValue);
@@ -637,6 +679,7 @@ function ensureCompactLoraUi(node, nodeData, info = null) {
const rows = serializedRows.length ? serializedRows : defaultRows;
removeManagedWidgets(node);
node.addCustomWidget(new FunPackLoraListWidget(node));
node.addCustomWidget(new FunPackLoraHeaderWidget());
if (rows.length) {
for (const row of rows) {