diff --git a/README.md b/README.md index 7dd1b11..1042417 100644 --- a/README.md +++ b/README.md @@ -719,14 +719,13 @@ ComfyUI-SDMatte 传 `aux_input="trimap"`,把模型推到了没训练过的输 连接 [ZenMux](https://zenmux.ai) 聚合平台(OpenAI 兼容协议),一个节点即可调用其收录的**所有文本类模型**(Anthropic、OpenAI、Google、DeepSeek、Qwen 等 20 家厂商、130+ 模型)。支持文本生成与多模态图像理解(最多 6 张图)。 **特色功能**: -- **厂商 → 模型二级选择**: 先在 `vendor` 下拉选厂商,`model` 下拉会自动收窄为该厂商的模型(由前端脚本 `web/zenmux_cascade.js` 联动) - **价格直接标在选项上**: 每个模型后缀形如 `[入$0.2/M 出$1.25/M]`,即输入/输出每百万 token 的美元价格,选型时一目了然 +- **快速筛选**: 模型列表按「厂商/模型名」排序聚类,同厂商模型天然相邻;在下拉的搜索框输入厂商前缀(如 `qwen/`、`anthropic/`)即可只看该厂商的模型 - **离线可用的模型清单**: 模型与价格来自随包分发的 `zenmux/models_snapshot.json`;价格有变动时运行 `python zenmux/build_snapshot.py` 即可重新拉取更新 - **旧工作流兼容**: 价格快照更新后,旧工作流里保存的带旧价格标签仍能正确解析出模型 id,不会失效 **输入参数**: - `api_key` (STRING): ZenMux 平台的 API Key(在 zenmux.ai 控制台获取) -- `vendor` (选择): 厂商筛选,默认 `openai` - `model` (选择): 模型(带价格标注),默认 `openai/gpt-5.4-nano` - `system_prompt` (STRING): 系统提示词 - `user_prompt` (STRING): 用户提示词 diff --git a/__init__.py b/__init__.py index 2edef9c..cad857e 100644 --- a/__init__.py +++ b/__init__.py @@ -93,7 +93,4 @@ NODE_DISPLAY_NAME_MAPPINGS.update(IMAGESPLITTER_NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(SDMATTE_NODE_DISPLAY_NAME_MAPPINGS) NODE_DISPLAY_NAME_MAPPINGS.update(ZENMUX_NODE_DISPLAY_NAME_MAPPINGS) -# 前端扩展目录(ZenMux 节点的「厂商→模型」级联脚本等) -WEB_DIRECTORY = "./web" - -__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY'] +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/web/zenmux_cascade.js b/web/zenmux_cascade.js deleted file mode 100644 index 955af41..0000000 --- a/web/zenmux_cascade.js +++ /dev/null @@ -1,60 +0,0 @@ -// ZenMux 节点「厂商 → 模型」二级级联 -// ================================== -// 后端把全量模型标签(含价格)都放进 model 下拉以通过校验; -// 本扩展在前端按 vendor 收窄 model 的候选列表。 -// 约定:模型标签第一个空格前是 "vendor/model" 形式的 id, -// 故 label.split("/")[0] 即厂商名,与 vendor 下拉的取值同源。 -import { app } from "../../scripts/app.js"; - -app.registerExtension({ - name: "Ruinode.ZenMuxCascade", - async beforeRegisterNodeDef(nodeType, nodeData, _app) { - if (nodeData.name !== "ZenMuxAPINode") return; - - const onNodeCreated = nodeType.prototype.onNodeCreated; - nodeType.prototype.onNodeCreated = function () { - const r = onNodeCreated?.apply(this, arguments); - - const vendorW = this.widgets?.find((w) => w.name === "vendor"); - const modelW = this.widgets?.find((w) => w.name === "model"); - if (!vendorW || !modelW) return r; - - // 全量标签快照(此时 options.values 是后端给的完整列表) - const allLabels = (modelW.options.values || []).slice(); - const vendorOf = (label) => String(label).split("/")[0]; - - // resetIfMissing:当前值不在收窄后的列表时是否重置为列表首项。 - // 手动切厂商 → 重置;加载旧工作流 → 保留(旧价格标签后端能解析)。 - const applyFilter = (vendor, resetIfMissing) => { - const filtered = allLabels.filter((l) => vendorOf(l) === vendor); - modelW.options.values = filtered.length ? filtered : allLabels.slice(); - if (resetIfMissing && !modelW.options.values.includes(modelW.value)) { - modelW.value = modelW.options.values[0]; - modelW.callback?.(modelW.value); - } - this.setDirtyCanvas?.(true, true); - }; - - const origCallback = vendorW.callback; - vendorW.callback = function (value) { - const r2 = origCallback?.apply(this, arguments); - applyFilter(value, true); - return r2; - }; - - // 新建节点:按默认厂商先收窄一次 - applyFilter(vendorW.value, false); - - // 加载已保存的工作流:configure 恢复 widget 值后再按真实厂商收窄, - // 但不动用户保存的 model 选择 - const onConfigure = this.onConfigure; - this.onConfigure = function () { - const r3 = onConfigure?.apply(this, arguments); - applyFilter(vendorW.value, false); - return r3; - }; - - return r; - }; - }, -}); diff --git a/zenmux/model_registry.py b/zenmux/model_registry.py index 02a4126..2bf8440 100644 --- a/zenmux/model_registry.py +++ b/zenmux/model_registry.py @@ -4,14 +4,13 @@ ZenMux 模型注册表 ================= 从随包分发的 models_snapshot.json 加载模型清单,为节点提供: -- all_vendors() 厂商列表(模型 id 的前缀,如 "openai") -- all_model_labels() 全量「带价格」下拉标签,按厂商 → 模型排序 +- all_model_labels() 全量「带价格」下拉标签,按厂商 → 模型排序聚类 - default_model_label() 默认模型 openai/gpt-5.4-nano 对应的标签 - label_to_model_id() 从下拉标签解析真实 model id(对旧标签/纯 id 也兼容) 标签格式(价格单位:USD / 百万 token): openai/gpt-5.4-nano [入$0.04/M 出$0.32/M] -标签第一个空格前恒为 model id,前端级联 JS 与后端解析都依赖这一点。 +标签第一个空格前恒为 model id,后端解析依赖这一点。 快照可用 build_snapshot.py 随时重新拉取更新。 """ @@ -64,12 +63,6 @@ _MODELS = _load_models() _LABELS = [_make_label(m) for m in _MODELS] _LABEL_TO_ID = {lb: m["id"] for lb, m in zip(_LABELS, _MODELS)} _KNOWN_IDS = {m["id"] for m in _MODELS} -_VENDORS = sorted({m.get("vendor") or m["id"].split("/")[0] for m in _MODELS}, - key=str.lower) - - -def all_vendors(): - return list(_VENDORS) def all_model_labels(): diff --git a/zenmux/zenmux_node.py b/zenmux/zenmux_node.py index 68affd1..f92f92f 100644 --- a/zenmux/zenmux_node.py +++ b/zenmux/zenmux_node.py @@ -5,8 +5,8 @@ ZenMux API 连接节点 通过 ZenMux 聚合平台(https://zenmux.ai)调用其收录的所有文本类模型。 特性: -- 模型选择做成「厂商 → 模型」两级级联:先在 vendor 下拉里选厂商, - 再在 model 下拉里选该厂商的模型(联动由配套前端 zenmux_cascade.js 完成)。 +- 模型下拉覆盖 ZenMux 全部文本模型,按「厂商/模型名」排序聚类; + ComfyUI 下拉自带搜索,输入厂商前缀(如 "anthropic/")即可快速过滤。 - 每个模型选项后面直接标注输入/输出价格(USD / 百万 token)。 - 具备常规 API 节点的完整参数:api_key、system/user prompt、seed、 temperature、top_p、max_tokens、以及可选的多模态图像输入与代理。 @@ -31,7 +31,6 @@ from .model_registry import ( DEFAULT_MODEL_ID, default_model_label, all_model_labels, - all_vendors, label_to_model_id, ) @@ -83,7 +82,6 @@ class ZenMuxNode: # ────────── 输入定义 ────────── @classmethod def INPUT_TYPES(cls): - vendors = all_vendors() model_labels = all_model_labels() default_label = default_model_label() return { @@ -92,13 +90,8 @@ class ZenMuxNode: "default": "", "multiline": False, }), - # vendor 只用于前端级联筛选;后端不依赖它(真实模型从 model 解析)。 - # 默认选中 openai。 - "vendor": (vendors, { - "default": "openai" if "openai" in vendors else (vendors[0] if vendors else "openai"), - }), - # model 的候选是「全量」模型标签(含价格),保证 ComfyUI 后端校验通过; - # 前端 JS 会按 vendor 把可见项收窄到该厂商。 + # 全量模型标签(含价格),已按厂商排序聚类; + # 下拉搜索框输入厂商前缀(如 "qwen/")即可过滤。 "model": (model_labels, { "default": default_label, }), @@ -169,12 +162,12 @@ class ZenMuxNode: # ────────── 宽松校验 ────────── @classmethod - def VALIDATE_INPUTS(cls, vendor, model): + def VALIDATE_INPUTS(cls, model): """ - 接管 vendor / model 两个下拉的校验,替代 ComfyUI 内置的 - 「值必须在候选列表里」检查:价格快照更新后,旧工作流里保存的 - 标签(带旧价格)不再逐字匹配新列表,但只要能解析出 model id - 就应放行,避免整个工作流被判为无效。 + 接管 model 下拉的校验,替代 ComfyUI 内置的「值必须在候选列表里」 + 检查:价格快照更新后,旧工作流里保存的标签(带旧价格)不再逐字 + 匹配新列表,但只要能解析出 model id 就应放行,避免整个工作流 + 被判为无效。 """ if label_to_model_id(model) is None: return f"无法从 '{model}' 解析出 ZenMux 模型 id" @@ -198,7 +191,6 @@ class ZenMuxNode: def generate( self, api_key, - vendor, model, system_prompt, user_prompt,