From f9fb6f895ceec9d4e3aa05911a49f04234591f7c Mon Sep 17 00:00:00 2001 From: "DESKTOP-TVBJISQ\\Primere" Date: Thu, 26 Mar 2026 10:48:33 +0100 Subject: [PATCH] V 2.0.0 - Rasterix - read/save by concept FE --- front_end/frontend_helper.js | 3 +- front_end/primere_rasterix_settings.js | 178 +++++++++++++++++++++++++ 2 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 front_end/primere_rasterix_settings.js diff --git a/front_end/frontend_helper.js b/front_end/frontend_helper.js index 74501df..c114a88 100644 --- a/front_end/frontend_helper.js +++ b/front_end/frontend_helper.js @@ -1,5 +1,4 @@ -const TOAST_DURATION_MS = 10000; - +const TOAST_DURATION_MS = 6000; const BTN_HEIGHT = 32; const BTN_COLOR = "#771a1a"; const BTN_COLOR_ACTIVE = "#932424"; diff --git a/front_end/primere_rasterix_settings.js b/front_end/primere_rasterix_settings.js new file mode 100644 index 0000000..50278f5 --- /dev/null +++ b/front_end/primere_rasterix_settings.js @@ -0,0 +1,178 @@ +import { app } from "/scripts/app.js"; +import { applyPrimereButtonStyle, showToast } from "./frontend_helper.js"; + +const TARGET_NODE_NAME = "PrimereRasterix"; +const SETTINGS_JSON_URL = new URL("/extensions/ComfyUI_Primere_Nodes/rasterix_settings.json", import.meta.url).href; + +const JSON_EXCLUDE_KEYS = new Set([ + "image", + "model_concept", + "model_name", + "show_histogram", + "histogram_source", + "histogram_channel", + "histogram_style", +]); + +function modelNameToKey(modelPath) { + const base = modelPath.split(/[\\/]/).pop(); + return base.replace(/\.[^/.]+$/, ""); +} + +function collectNodeData(node) { + const SKIP_KEYS = new Set(["concepts", "models", ...JSON_EXCLUDE_KEYS]); + const widgets = node.widgets || []; + + const suppressedPrefixes = []; + for (const w of widgets) { + if (w.type === "toggle" && w.value === false) { + suppressedPrefixes.push(w.name + "_"); + } + } + + const data = {}; + for (const w of widgets) { + if (!w.name || SKIP_KEYS.has(w.name)) continue; + if (w.value === null || w.value === undefined) continue; + if (suppressedPrefixes.some((p) => w.name.startsWith(p))) continue; + data[w.name] = w.value; + } + + return data; +} + +async function loadSettingsValues(node, key, silent = false) { + let data; + try { + const response = await fetch(SETTINGS_JSON_URL + "?t=" + Date.now()); + if (!response.ok) { + if (!silent) showToast("error", `No saved settings found. Save settings for "${key}" first.`); + return; + } + data = await response.json(); + } catch (_) { + return; + } + + if (!data[key]) { + if (!silent) showToast("error", `No saved settings for "${key}".`); + return; + } + + const saved = data[key]; + for (const w of node.widgets || []) { + if (!w.name || !Object.prototype.hasOwnProperty.call(saved, w.name)) continue; + const newValue = saved[w.name]; + if (w.options?.values) { + const match = w.options.values.find((v) => String(v) === String(newValue)); + if (match !== undefined) w.value = match; + } else { + w.value = newValue; + } + w.callback?.(w.value); + } + + node.setDirtyCanvas?.(true, true); +} + +function initializeRasterixSettings(node) { + if (node.__primereRasterixSettingsHooked) return; + node.__primereRasterixSettingsHooked = true; + + const saveBtn = node.addWidget("button", "💾 Save node setting", null, async () => { + const modelsWidget = node.widgets?.find((w) => w.name === "models"); + const conceptsWidget = node.widgets?.find((w) => w.name === "concepts"); + + let saveKey = null; + const modelVal = modelsWidget?.value; + const conceptVal = conceptsWidget?.value; + + if (modelVal && modelVal !== "Auto") { + saveKey = modelNameToKey(modelVal); + } else if (conceptVal && conceptVal !== "Auto") { + saveKey = conceptVal; + } + + if (!saveKey) { + showToast("error", "Cannot save: select a specific model or model type first."); + return; + } + + const data = collectNodeData(node); + + try { + const response = await fetch("/primere_rasterix_setting_save", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ concept: saveKey, data }), + }); + const result = response.ok ? await response.json() : null; + if (result?.success) { + showToast("success", `Settings saved for "${saveKey}".`); + } else { + showToast("error", `Failed to save settings for "${saveKey}".`); + } + } catch (error) { + showToast("error", `Save error: ${error.message}`); + } + }); + + saveBtn.serialize = false; + saveBtn.options = saveBtn.options || {}; + saveBtn.options.serialize = false; + applyPrimereButtonStyle(saveBtn); + + const originalOnWidgetChanged = node.onWidgetChanged; + node.onWidgetChanged = function (name, value, oldValue, widget) { + originalOnWidgetChanged?.call(this, name, value, oldValue, widget); + if (name === "concepts" && value !== "Auto") { + loadSettingsValues(this, value); + } + if (name === "models" && value !== "Auto") { + const conceptsWidget = this.widgets?.find((w) => w.name === "concepts"); + if (conceptsWidget?.value === "Auto") { + loadSettingsValues(this, modelNameToKey(value), true); + } + } + }; + + const originalOnExecuted = node.onExecuted; + node.onExecuted = function (message) { + originalOnExecuted?.call(this, message); + const displayKey = message?.active_concept?.[0]; + if (!displayKey) return; + const modelsWidget = this.widgets?.find((w) => w.name === "models"); + const conceptsWidget = this.widgets?.find((w) => w.name === "concepts"); + if (conceptsWidget?.value !== "Auto" || modelsWidget?.value !== "Auto") return; + loadSettingsValues(this, displayKey, true); + }; +} + +app.registerExtension({ + name: "Primere.RasterixSettings", + + setup() { + app.api.addEventListener("primere.rasterix_setting", (event) => { + const detail = event.detail; + if (detail?.status === "missing") { + showToast("error", `No saved settings for model type "${detail.concept}". Current node values will be used.`); + } + }); + }, + + async beforeRegisterNodeDef(nodeType, nodeData, _app) { + if (nodeData.name !== TARGET_NODE_NAME) return; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, []); + initializeRasterixSettings(this); + }; + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function (config) { + onConfigure?.apply(this, [config]); + initializeRasterixSettings(this); + }; + }, +});