mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-06-17 07:59:24 -03:00
136 lines
4.3 KiB
JavaScript
136 lines
4.3 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
||
import { chainCallback, getAllGraphNodes, getWidgetByName } from "./utils.js";
|
||
|
||
/**
|
||
* Format a date string using the given pattern (e.g. "yyyy-MM-dd").
|
||
* Supports: yyyy, yy, MM, M, dd, d, hh, h, mm, m, ss, s
|
||
*/
|
||
function formatDate(text, date) {
|
||
const pad = (n, len) => n.toString().padStart(len, "0");
|
||
// Order matters: longer patterns first to avoid partial substring matches.
|
||
// The original ComfyUI frontend uses the same ordered-alternation approach.
|
||
return text
|
||
.replace(/yyyy/g, () => date.getFullYear().toString())
|
||
.replace(/yy/g, () => pad(date.getFullYear() % 100, 2))
|
||
.replace(/MM/g, () => pad(date.getMonth() + 1, 2))
|
||
.replace(/M/g, () => (date.getMonth() + 1).toString())
|
||
.replace(/dd/g, () => pad(date.getDate(), 2))
|
||
.replace(/d/g, () => date.getDate().toString())
|
||
.replace(/hh/g, () => pad(date.getHours(), 2))
|
||
.replace(/h/g, () => date.getHours().toString())
|
||
.replace(/mm/g, () => pad(date.getMinutes(), 2))
|
||
.replace(/m/g, () => date.getMinutes().toString())
|
||
.replace(/ss/g, () => pad(date.getSeconds(), 2))
|
||
.replace(/s/g, () => date.getSeconds().toString());
|
||
}
|
||
|
||
/**
|
||
* Resolve %NodeTitle.WidgetName% placeholders in a string using the current graph.
|
||
*
|
||
* Patterns supported:
|
||
* %NodeTitle.WidgetName% – widget value from a node (by title or "Node name for S&R")
|
||
* %date:format% – current date/time formatted (e.g. %date:yyyy-MM-dd%)
|
||
* %width%, %height% – left as-is, handled by the backend
|
||
*
|
||
* All other %text% patterns are passed through unchanged (they may be handled by
|
||
* the backend's format_filename, e.g. %seed%, %model%, %pprompt%).
|
||
*/
|
||
function applyTextReplacements(value) {
|
||
if (!value || typeof value !== "string" || !value.includes("%")) {
|
||
return value;
|
||
}
|
||
|
||
// Collect all nodes from the entire graph hierarchy (including subgraphs)
|
||
const allNodes = getAllGraphNodes(app.graph);
|
||
|
||
return value.replace(/%([^%]+)%/g, function (match, text) {
|
||
const split = text.split(".");
|
||
if (split.length !== 2) {
|
||
// Handle %date:format% patterns
|
||
if (split[0].startsWith("date:")) {
|
||
return formatDate(split[0].substring(5), new Date());
|
||
}
|
||
|
||
// %width% and %height% are left for the backend to handle
|
||
if (text !== "width" && text !== "height") {
|
||
console.warn(
|
||
"[Save Image (LoraManager)] Unknown placeholder: %" + text + "%"
|
||
);
|
||
}
|
||
return match;
|
||
}
|
||
|
||
// Try finding the node by its "Node name for S&R" property first
|
||
let nodes = allNodes
|
||
.filter((n) => n.node.properties?.["Node name for S&R"] === split[0])
|
||
.map((n) => n.node);
|
||
|
||
// Fall back to matching by node title
|
||
if (!nodes.length) {
|
||
nodes = allNodes
|
||
.filter((n) => n.node.title === split[0])
|
||
.map((n) => n.node);
|
||
}
|
||
|
||
if (!nodes.length) {
|
||
console.warn(
|
||
"[Save Image (LoraManager)] Node not found: " + split[0]
|
||
);
|
||
return match;
|
||
}
|
||
|
||
if (nodes.length > 1) {
|
||
console.warn(
|
||
"[Save Image (LoraManager)] Multiple nodes matched '" +
|
||
split[0] +
|
||
"', using first match"
|
||
);
|
||
}
|
||
|
||
const node = nodes[0];
|
||
const widget = node.widgets?.find((w) => w.name === split[1]);
|
||
if (!widget) {
|
||
console.warn(
|
||
"[Save Image (LoraManager)] Widget '" +
|
||
split[1] +
|
||
"' not found on node " +
|
||
split[0]
|
||
);
|
||
return match;
|
||
}
|
||
|
||
// Sanitize the value: replace characters invalid for filenames
|
||
// eslint-disable-next-line no-control-regex
|
||
return ((widget.value ?? "") + "").replaceAll(
|
||
/[/?<>\\:*|"\x00-\x1F\x7F]/g,
|
||
"_"
|
||
);
|
||
});
|
||
}
|
||
|
||
app.registerExtension({
|
||
name: "LoraManager.SaveImageExtraOutput",
|
||
|
||
async beforeRegisterNodeDef(nodeType, nodeData) {
|
||
if (nodeData.name !== "Save Image (LoraManager)") {
|
||
return;
|
||
}
|
||
|
||
chainCallback(nodeType.prototype, "onNodeCreated", function () {
|
||
// Find the filename_prefix widget
|
||
const widget = getWidgetByName(this, "filename_prefix");
|
||
if (!widget) {
|
||
console.warn(
|
||
"[Save Image (LoraManager)] filename_prefix widget not found"
|
||
);
|
||
return;
|
||
}
|
||
|
||
// Override serialization to resolve %NodeTitle.WidgetName% placeholders
|
||
widget.serializeValue = () => {
|
||
return applyTextReplacements(widget.value);
|
||
};
|
||
});
|
||
},
|
||
});
|