Save cached character profiles without rerun

This commit is contained in:
2026-06-24 20:52:20 +02:00
parent 4172797b43
commit fba1825496
4 changed files with 176 additions and 8 deletions
+116 -2
View File
@@ -1,6 +1,8 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
const EXTENSION = "ethanfel.prompt_builder.profile_buttons";
const profileCache = new Map();
function widget(node, name) {
return node.widgets?.find((w) => w.name === name);
@@ -20,6 +22,96 @@ function resizeNode(node) {
app.graph?.setDirtyCanvas(true, true);
}
function nodeKey(nodeOrId) {
return String(typeof nodeOrId === "object" ? nodeOrId?.id : nodeOrId);
}
function isProfileSaveNode(node) {
return node?.comfyClass === "SxCPCharacterProfileSave" || node?.type === "SxCPCharacterProfileSave";
}
function isProfileLoadNode(node) {
return node?.comfyClass === "SxCPCharacterProfileLoad" || node?.type === "SxCPCharacterProfileLoad";
}
function firstOutput(output, key) {
const value = output?.[key];
return Array.isArray(value) ? value[0] : value || "";
}
function cacheStatus(cache) {
if (!cache?.profile_json) return "no cached profile";
const name = cache.profile_name || "unnamed";
const descriptor = cache.descriptor ? ` - ${cache.descriptor}` : "";
const text = `cached: ${name}${descriptor}`;
return text.length > 120 ? `${text.slice(0, 117)}...` : text;
}
function updateCacheWidget(node) {
const cache = profileCache.get(nodeKey(node)) || node._sxcpProfileCache;
if (node._sxcpCachedProfileWidget) node._sxcpCachedProfileWidget.value = cacheStatus(cache);
node.setDirtyCanvas?.(true, true);
}
function setProfileCache(node, cache) {
if (!node || !cache?.profile_json) return;
node._sxcpProfileCache = cache;
profileCache.set(nodeKey(node), cache);
updateCacheWidget(node);
}
function getNodeById(id) {
return app.graph?.getNodeById?.(Number(id)) || app.graph?._nodes_by_id?.[id] || app.graph?._nodes_by_id?.[Number(id)];
}
function addProfileChoice(profileName) {
if (!profileName) return;
for (const node of app.graph?._nodes || []) {
if (!isProfileLoadNode(node)) continue;
const profileWidget = widget(node, "profile_name");
const values = profileWidget?.options?.values;
if (Array.isArray(values) && !values.includes(profileName)) values.push(profileName);
}
}
async function saveCachedProfile(node) {
const cache = profileCache.get(nodeKey(node)) || node._sxcpProfileCache;
if (!cache?.profile_json) {
alert("No cached profile yet. Run the workflow once with this save node connected, then press Save Profile Now.");
return;
}
const profileName = (widget(node, "profile_name")?.value || cache.profile_name || "").trim();
if (!profileName) {
alert("Fill profile_name before saving.");
return;
}
try {
const response = await api.fetchApi("/sxcp/profile/save_cached", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
profile_name: profileName,
profile_json: cache.profile_json,
}),
});
const data = await response.json();
if (!response.ok) throw new Error(data?.error || response.statusText);
setProfileCache(node, {
profile_json: data.profile_json || cache.profile_json,
descriptor: data.descriptor || cache.descriptor || "",
profile_name: data.profile_name || profileName,
saved_path: data.saved_path || "",
status: data.status || "saved",
});
addProfileChoice(data.profile_name || profileName);
alert(`Saved profile "${data.profile_name || profileName}".`);
} catch (err) {
console.error(`[${EXTENSION}] save cached profile failed`, err);
alert(`Save failed: ${err}`);
}
}
async function queueOnce(node, triggerName) {
const trigger = widget(node, triggerName);
if (!trigger) {
@@ -46,9 +138,14 @@ async function queueOnce(node, triggerName) {
function setupSaveNode(node) {
hideWidget(widget(node, "save_now"));
if (!node._sxcpSaveButton) {
node._sxcpSaveButton = node.addWidget("button", "Save Profile Now", null, () => queueOnce(node, "save_now"));
if (!node._sxcpCachedProfileWidget) {
node._sxcpCachedProfileWidget = node.addWidget("text", "cached_profile", "no cached profile", () => {});
node._sxcpCachedProfileWidget.serialize = false;
}
if (!node._sxcpSaveButton) {
node._sxcpSaveButton = node.addWidget("button", "Save Profile Now", null, () => saveCachedProfile(node));
}
updateCacheWidget(node);
resizeNode(node);
}
@@ -88,6 +185,23 @@ function setupLoadNode(node) {
app.registerExtension({
name: EXTENSION,
async setup() {
api.addEventListener("executed", ({detail}) => {
const node = getNodeById(detail?.node);
if (!isProfileSaveNode(node)) return;
const output = detail?.output || {};
const profileJson = firstOutput(output, "profile_json");
if (!profileJson) return;
setProfileCache(node, {
profile_json: profileJson,
descriptor: firstOutput(output, "descriptor"),
profile_name: firstOutput(output, "profile_name"),
saved_path: firstOutput(output, "saved_path"),
status: firstOutput(output, "status"),
});
});
},
async beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData.name !== "SxCPCharacterProfileSave" && nodeData.name !== "SxCPCharacterProfileLoad") return;