diff --git a/model_management.py b/model_management.py index 3d99d07f..6e35e2e3 100644 --- a/model_management.py +++ b/model_management.py @@ -24,7 +24,10 @@ LTX_IMAGE_MODELS = {"ltxv", "ltxav"} @PromptServer.instance.routes.get("/funpack/loras") async def funpack_loras(_): - return web.json_response(["None"] + folder_paths.get_filename_list("loras")) + return web.json_response( + ["None"] + folder_paths.get_filename_list("loras"), + headers={"Cache-Control": "no-store, max-age=0"}, + ) class AnyType(str): diff --git a/web/funpack_lora_weights.js b/web/funpack_lora_weights.js index 78c75e8a..f62c55d7 100644 --- a/web/funpack_lora_weights.js +++ b/web/funpack_lora_weights.js @@ -6,26 +6,39 @@ const LORA_TYPES = ["general", "concept", "style", "quality", "character"]; const ADD_BUTTON_NAME = "+ Add LoRA"; let cachedLoraValues = null; +let latestNodeData = null; +const trackedNodes = new Set(); +let pendingRefresh = null; function getLoraValues(nodeData) { return cachedLoraValues || nodeData?.input?.optional?.lora_0?.[0] || ["None"]; } +function rememberLoraValues(nodeData, values) { + cachedLoraValues = values; + const loraInput = nodeData?.input?.optional?.lora_0; + if (Array.isArray(loraInput)) { + loraInput[0] = values; + } +} + async function fetchLoraValues(nodeData) { try { - const response = await api.fetchApi("/funpack/loras"); + const response = await api.fetchApi(`/funpack/loras?cache_bust=${Date.now()}`, { + cache: "no-store", + }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const values = await response.json(); if (Array.isArray(values) && values.length) { - cachedLoraValues = values; + rememberLoraValues(nodeData, values); return values; } } catch (error) { console.warn("FunPack: failed to refresh LoRA list", error); } - cachedLoraValues = getLoraValues(nodeData); + rememberLoraValues(nodeData, getLoraValues(nodeData)); return cachedLoraValues; } @@ -55,6 +68,37 @@ async function refreshLoraWidgets(node, nodeData) { updateLoraWidgets(node, await fetchLoraValues(nodeData)); } +function trackNode(node) { + trackedNodes.add(node); +} + +async function refreshTrackedLoraWidgets(nodeData = latestNodeData) { + if (!nodeData || pendingRefresh) { + return pendingRefresh; + } + + pendingRefresh = (async () => { + const values = await fetchLoraValues(nodeData); + for (const node of [...trackedNodes]) { + if (!node?.graph) { + trackedNodes.delete(node); + continue; + } + updateLoraWidgets(node, values); + } + })().finally(() => { + pendingRefresh = null; + }); + + return pendingRefresh; +} + +function scheduleTrackedRefresh(nodeData = latestNodeData) { + window.setTimeout(() => { + void refreshTrackedLoraWidgets(nodeData); + }, 100); +} + function getNextLoraIndex(node) { let next = 0; for (const widget of node.widgets || []) { @@ -174,16 +218,64 @@ function restoreExtraRows(node, nodeData, info) { } } +function wrapRefreshFunction(functionName) { + const original = app[functionName]; + if (typeof original !== "function" || original.__funpackLoraWrapped) { + return; + } + + const wrapped = function () { + const result = original.apply(this, arguments); + Promise.resolve(result).finally(() => scheduleTrackedRefresh()); + return result; + }; + wrapped.__funpackLoraWrapped = true; + app[functionName] = wrapped; +} + +function hookComfyRefreshControls() { + for (const functionName of ["refreshComboInNodes", "refreshComboInNode", "refreshNodeDefs"]) { + wrapRefreshFunction(functionName); + } +} + +function isRefreshControl(element) { + for (let current = element; current && current !== document.body; current = current.parentElement) { + const text = [ + current.title, + current.ariaLabel, + current.textContent, + ].filter(Boolean).join(" ").toLowerCase(); + if (text.includes("refresh")) { + return true; + } + } + return false; +} + app.registerExtension({ name: "funpack.loraWeights", + setup() { + hookComfyRefreshControls(); + window.setTimeout(hookComfyRefreshControls, 0); + window.setTimeout(hookComfyRefreshControls, 1000); + document.addEventListener("click", (event) => { + if (isRefreshControl(event.target)) { + scheduleTrackedRefresh(); + } + }, true); + }, beforeRegisterNodeDef(nodeType, nodeData) { if (nodeData.name !== NODE_NAME) { return; } + latestNodeData = nodeData; + hookComfyRefreshControls(); const originalOnNodeCreated = nodeType.prototype.onNodeCreated; nodeType.prototype.onNodeCreated = function () { originalOnNodeCreated?.apply(this, arguments); + trackNode(this); ensureAddButton(this, nodeData); void refreshLoraWidgets(this, nodeData); }; @@ -191,6 +283,7 @@ app.registerExtension({ const originalConfigure = nodeType.prototype.configure; nodeType.prototype.configure = function (info) { originalConfigure?.apply(this, arguments); + trackNode(this); ensureAddButton(this, nodeData); restoreExtraRows(this, nodeData, info); void refreshLoraWidgets(this, nodeData);