Add files via upload
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
WEB_DIRECTORY = "js"
|
||||
|
||||
from .fetch_filter import FetchMeFilter
|
||||
from .fetch_codex import FetchMeCodex
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FetchMeFilter": FetchMeFilter,
|
||||
"FetchMeCodex": FetchMeCodex,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"FetchMeFilter": "🍖Fetch Filter",
|
||||
"FetchMeCodex": "🍖Fetch Codex",
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
"WEB_DIRECTORY",
|
||||
"NODE_CLASS_MAPPINGS",
|
||||
"NODE_DISPLAY_NAME_MAPPINGS",
|
||||
]
|
||||
@@ -0,0 +1,48 @@
|
||||
from __future__ import annotations
|
||||
from typing import Dict, Tuple
|
||||
|
||||
|
||||
class FetchMeCodex:
|
||||
MAX_KEYS = 64
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
required = {
|
||||
"value": ("STRING", {"default": "", "multiline": False}),
|
||||
"key_1": ("STRING", {"default": "", "multiline": False}),
|
||||
"key_2": ("STRING", {"default": "", "multiline": False}),
|
||||
}
|
||||
optional = {
|
||||
f"key_{idx}": ("STRING", {"default": "", "multiline": False})
|
||||
for idx in range(3, cls.MAX_KEYS + 1)
|
||||
}
|
||||
return {"required": required, "optional": optional}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
RETURN_NAMES = ("codex",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "FetchMe/Text"
|
||||
|
||||
def _put(self, out: Dict[str, str], key: str, value: str) -> None:
|
||||
if key is None:
|
||||
return
|
||||
k = str(key).strip()
|
||||
if k == "":
|
||||
return
|
||||
out[k] = value
|
||||
|
||||
def run(self, value: str, key_1: str, key_2: str, **kwargs) -> Tuple[Dict[str, str]]:
|
||||
replacement = "" if value is None else str(value)
|
||||
out: Dict[str, str] = {}
|
||||
|
||||
self._put(out, key_1, replacement)
|
||||
self._put(out, key_2, replacement)
|
||||
|
||||
for idx in range(3, self.MAX_KEYS + 1):
|
||||
self._put(out, kwargs.get(f"key_{idx}", ""), replacement)
|
||||
|
||||
return (out,)
|
||||
|
||||
|
||||
# Backward-compatible alias.
|
||||
fetch_codex = FetchMeCodex
|
||||
@@ -0,0 +1,71 @@
|
||||
from __future__ import annotations
|
||||
import re
|
||||
from typing import Dict, Tuple
|
||||
|
||||
|
||||
class FetchMeFilter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
},
|
||||
"optional": {
|
||||
"codex_1": ("DICT",),
|
||||
"codex_2": ("DICT",),
|
||||
"codex_3": ("DICT",),
|
||||
"codex_4": ("DICT",),
|
||||
"codex_5": ("DICT",),
|
||||
"codex_6": ("DICT",),
|
||||
"codex_7": ("DICT",),
|
||||
"codex_8": ("DICT",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "FetchMe/Text"
|
||||
|
||||
def _merge_codex(self, out: Dict[str, str], value: Dict | None) -> None:
|
||||
if value is None or not isinstance(value, dict):
|
||||
return
|
||||
out.update(value)
|
||||
|
||||
def run(
|
||||
self,
|
||||
text: str,
|
||||
codex_1: Dict | None = None,
|
||||
codex_2: Dict | None = None,
|
||||
codex_3: Dict | None = None,
|
||||
codex_4: Dict | None = None,
|
||||
codex_5: Dict | None = None,
|
||||
codex_6: Dict | None = None,
|
||||
codex_7: Dict | None = None,
|
||||
codex_8: Dict | None = None,
|
||||
) -> Tuple[str]:
|
||||
out = "" if text is None else str(text)
|
||||
|
||||
merged: Dict[str, str] = {}
|
||||
self._merge_codex(merged, codex_1)
|
||||
self._merge_codex(merged, codex_2)
|
||||
self._merge_codex(merged, codex_3)
|
||||
self._merge_codex(merged, codex_4)
|
||||
self._merge_codex(merged, codex_5)
|
||||
self._merge_codex(merged, codex_6)
|
||||
self._merge_codex(merged, codex_7)
|
||||
self._merge_codex(merged, codex_8)
|
||||
|
||||
for key, value in merged.items():
|
||||
token = str(key)
|
||||
if token == "":
|
||||
continue
|
||||
replacement = "" if value is None else str(value)
|
||||
pattern = re.compile(re.escape(token), re.IGNORECASE)
|
||||
out = pattern.sub(replacement, out)
|
||||
|
||||
return (out,)
|
||||
|
||||
|
||||
# Backward-compatible alias.
|
||||
fetch_filter = FetchMeFilter
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const BRAND = "#5e2db3";
|
||||
const FETCHME_CLASSES = new Set(["FetchMeFilter", "FetchMeCodex"]);
|
||||
const DYNAMIC_KEY_CLASSES = new Set(["FetchMeCodex"]);
|
||||
|
||||
const FILTER_INPUT_LABELS = {
|
||||
codex_1: "codex_I",
|
||||
codex_2: "codex_II",
|
||||
codex_3: "codex_III",
|
||||
codex_4: "codex_IV",
|
||||
codex_5: "codex_V",
|
||||
codex_6: "codex_VI",
|
||||
codex_7: "codex_VII",
|
||||
codex_8: "codex_VIII",
|
||||
};
|
||||
|
||||
function applyBrandColor(node) {
|
||||
node.color = BRAND;
|
||||
node.bgcolor = BRAND;
|
||||
}
|
||||
|
||||
function relabelInputs(node, labels) {
|
||||
if (!Array.isArray(node.inputs)) {
|
||||
return;
|
||||
}
|
||||
for (const input of node.inputs) {
|
||||
if (!input || typeof input.name !== "string") {
|
||||
continue;
|
||||
}
|
||||
const label = labels[input.name];
|
||||
if (label) {
|
||||
input.label = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyIndex(name) {
|
||||
if (typeof name !== "string") {
|
||||
return null;
|
||||
}
|
||||
const match = /^key_(\d+)$/.exec(name);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return Number(match[1]);
|
||||
}
|
||||
|
||||
function isFilled(widget) {
|
||||
const v = widget?.value;
|
||||
return typeof v === "string" ? v.trim() !== "" : !!v;
|
||||
}
|
||||
|
||||
function installDynamicKeyFields(node) {
|
||||
if (!Array.isArray(node.widgets) || node.widgets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allWidgets = [...node.widgets];
|
||||
const keyEntries = allWidgets
|
||||
.map((widget, pos) => ({ widget, idx: keyIndex(widget?.name), pos }))
|
||||
.filter((entry) => Number.isFinite(entry.idx))
|
||||
.sort((a, b) => a.idx - b.idx);
|
||||
|
||||
if (keyEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstKeyPos = Math.min(...keyEntries.map((e) => e.pos));
|
||||
const lastKeyPos = Math.max(...keyEntries.map((e) => e.pos));
|
||||
|
||||
const prefix = allWidgets.slice(0, firstKeyPos);
|
||||
const suffix = allWidgets.slice(lastKeyPos + 1);
|
||||
const keyWidgets = keyEntries.map((e) => e.widget);
|
||||
const fullWidgets = [...prefix, ...keyWidgets, ...suffix];
|
||||
|
||||
const applyValuesFromConfig = (cfg) => {
|
||||
const values = cfg?.widgets_values;
|
||||
if (!Array.isArray(values)) {
|
||||
return;
|
||||
}
|
||||
const n = Math.min(values.length, fullWidgets.length);
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
fullWidgets[i].value = values[i];
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
let lastFilled = 0;
|
||||
for (let i = 0; i < keyWidgets.length; i += 1) {
|
||||
if (isFilled(keyWidgets[i])) {
|
||||
lastFilled = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const minVisible = 2;
|
||||
const desiredCount = Math.max(minVisible, Math.min(keyWidgets.length, lastFilled + 1));
|
||||
node.widgets = [...prefix, ...keyWidgets.slice(0, desiredCount), ...suffix];
|
||||
|
||||
if (typeof node.computeSize === "function" && typeof node.setSize === "function") {
|
||||
node.setSize(node.computeSize());
|
||||
}
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
for (const widget of keyWidgets) {
|
||||
if (widget._fetchDynamicBound) {
|
||||
continue;
|
||||
}
|
||||
const originalCallback = widget.callback;
|
||||
widget.callback = (...args) => {
|
||||
if (typeof originalCallback === "function") {
|
||||
originalCallback(...args);
|
||||
}
|
||||
refresh();
|
||||
};
|
||||
widget._fetchDynamicBound = true;
|
||||
}
|
||||
|
||||
const originalSerialize = node.onSerialize;
|
||||
node.onSerialize = function (o) {
|
||||
if (typeof originalSerialize === "function") {
|
||||
originalSerialize.call(this, o);
|
||||
}
|
||||
o.widgets_values = fullWidgets.map((w) => w.value);
|
||||
};
|
||||
|
||||
const originalConfigure = node.onConfigure;
|
||||
node.onConfigure = function (...args) {
|
||||
const result = originalConfigure ? originalConfigure.apply(this, args) : undefined;
|
||||
applyValuesFromConfig(args[0]);
|
||||
refresh();
|
||||
return result;
|
||||
};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "FetchMe.ThemeAndLabels",
|
||||
nodeCreated(node) {
|
||||
if (!node || !FETCHME_CLASSES.has(node.comfyClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyBrandColor(node);
|
||||
|
||||
if (node.comfyClass === "FetchMeFilter") {
|
||||
relabelInputs(node, FILTER_INPUT_LABELS);
|
||||
}
|
||||
|
||||
if (DYNAMIC_KEY_CLASSES.has(node.comfyClass)) {
|
||||
installDynamicKeyFields(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
|
||||
const BRAND = "#5e2db3";
|
||||
const FETCHME_CLASSES = new Set(["FetchMeFilter", "FetchMeCodex"]);
|
||||
const DYNAMIC_KEY_CLASSES = new Set(["FetchMeCodex"]);
|
||||
|
||||
const FILTER_INPUT_LABELS = {
|
||||
codex_1: "codex_I",
|
||||
codex_2: "codex_II",
|
||||
codex_3: "codex_III",
|
||||
codex_4: "codex_IV",
|
||||
codex_5: "codex_V",
|
||||
codex_6: "codex_VI",
|
||||
codex_7: "codex_VII",
|
||||
codex_8: "codex_VIII",
|
||||
};
|
||||
|
||||
function applyBrandColor(node) {
|
||||
node.color = BRAND;
|
||||
node.bgcolor = BRAND;
|
||||
}
|
||||
|
||||
function relabelInputs(node, labels) {
|
||||
if (!Array.isArray(node.inputs)) {
|
||||
return;
|
||||
}
|
||||
for (const input of node.inputs) {
|
||||
if (!input || typeof input.name !== "string") {
|
||||
continue;
|
||||
}
|
||||
const label = labels[input.name];
|
||||
if (label) {
|
||||
input.label = label;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyIndex(name) {
|
||||
if (typeof name !== "string") {
|
||||
return null;
|
||||
}
|
||||
const match = /^key_(\d+)$/.exec(name);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return Number(match[1]);
|
||||
}
|
||||
|
||||
function isFilled(widget) {
|
||||
const v = widget?.value;
|
||||
return typeof v === "string" ? v.trim() !== "" : !!v;
|
||||
}
|
||||
|
||||
function installDynamicKeyFields(node) {
|
||||
if (!Array.isArray(node.widgets) || node.widgets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allWidgets = [...node.widgets];
|
||||
const keyEntries = allWidgets
|
||||
.map((widget, pos) => ({ widget, idx: keyIndex(widget?.name), pos }))
|
||||
.filter((entry) => Number.isFinite(entry.idx))
|
||||
.sort((a, b) => a.idx - b.idx);
|
||||
|
||||
if (keyEntries.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstKeyPos = Math.min(...keyEntries.map((e) => e.pos));
|
||||
const lastKeyPos = Math.max(...keyEntries.map((e) => e.pos));
|
||||
|
||||
const prefix = allWidgets.slice(0, firstKeyPos);
|
||||
const suffix = allWidgets.slice(lastKeyPos + 1);
|
||||
const keyWidgets = keyEntries.map((e) => e.widget);
|
||||
const fullWidgets = [...prefix, ...keyWidgets, ...suffix];
|
||||
|
||||
const applyValuesFromConfig = (cfg) => {
|
||||
const values = cfg?.widgets_values;
|
||||
if (!Array.isArray(values)) {
|
||||
return;
|
||||
}
|
||||
const n = Math.min(values.length, fullWidgets.length);
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
fullWidgets[i].value = values[i];
|
||||
}
|
||||
};
|
||||
|
||||
const refresh = () => {
|
||||
let lastFilled = 0;
|
||||
for (let i = 0; i < keyWidgets.length; i += 1) {
|
||||
if (isFilled(keyWidgets[i])) {
|
||||
lastFilled = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const minVisible = 2;
|
||||
const desiredCount = Math.max(minVisible, Math.min(keyWidgets.length, lastFilled + 1));
|
||||
node.widgets = [...prefix, ...keyWidgets.slice(0, desiredCount), ...suffix];
|
||||
|
||||
if (typeof node.computeSize === "function" && typeof node.setSize === "function") {
|
||||
node.setSize(node.computeSize());
|
||||
}
|
||||
node.setDirtyCanvas(true, true);
|
||||
};
|
||||
|
||||
for (const widget of keyWidgets) {
|
||||
if (widget._fetchDynamicBound) {
|
||||
continue;
|
||||
}
|
||||
const originalCallback = widget.callback;
|
||||
widget.callback = (...args) => {
|
||||
if (typeof originalCallback === "function") {
|
||||
originalCallback(...args);
|
||||
}
|
||||
refresh();
|
||||
};
|
||||
widget._fetchDynamicBound = true;
|
||||
}
|
||||
|
||||
const originalSerialize = node.onSerialize;
|
||||
node.onSerialize = function (o) {
|
||||
if (typeof originalSerialize === "function") {
|
||||
originalSerialize.call(this, o);
|
||||
}
|
||||
o.widgets_values = fullWidgets.map((w) => w.value);
|
||||
};
|
||||
|
||||
const originalConfigure = node.onConfigure;
|
||||
node.onConfigure = function (...args) {
|
||||
const result = originalConfigure ? originalConfigure.apply(this, args) : undefined;
|
||||
applyValuesFromConfig(args[0]);
|
||||
refresh();
|
||||
return result;
|
||||
};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "FetchMe.ThemeAndLabels",
|
||||
nodeCreated(node) {
|
||||
if (!node || !FETCHME_CLASSES.has(node.comfyClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyBrandColor(node);
|
||||
|
||||
if (node.comfyClass === "FetchMeFilter") {
|
||||
relabelInputs(node, FILTER_INPUT_LABELS);
|
||||
}
|
||||
|
||||
if (DYNAMIC_KEY_CLASSES.has(node.comfyClass)) {
|
||||
installDynamicKeyFields(node);
|
||||
}
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user