refactor: ZenMux 节点移除 vendor 参数,模型下拉直接全量选择
- 模型列表本身按厂商排序聚类,配合 ComfyUI 下拉搜索(输入 "qwen/" 等 前缀过滤)已能满足筛选需求,vendor 下拉冗余 - 一并移除只为级联服务的前端脚本 web/zenmux_cascade.js 与 WEB_DIRECTORY Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c8c546a873
commit
cc5852dc18
@@ -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): 用户提示词
|
||||
|
||||
+1
-4
@@ -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']
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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():
|
||||
|
||||
+9
-17
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user