Add files via upload

This commit is contained in:
Vlad Gohn
2026-03-27 13:16:15 +02:00
committed by GitHub
parent caa4ee7601
commit cf9196b4e4
7 changed files with 452 additions and 2 deletions
-2
View File
@@ -1,2 +0,0 @@
# ComfyUI-FetchMe
ComfyUI-FetchMe
+20
View File
@@ -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",
]
+48
View File
@@ -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
+71
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
+156
View File
@@ -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);
}
},
});
+156
View File
@@ -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);
}
},
});