feat: 新增 ZenMux API 连接节点(厂商→模型级联选择,选项含价格标注)
- 一个节点调用 ZenMux 聚合平台全部文本类模型(20 厂商 / 138 模型) - vendor→model 二级下拉:前端 web/zenmux_cascade.js 按厂商实时收窄模型列表 - 模型选项后缀标注输入/输出价格(USD/百万token),如 [入$0.2/M 出$1.25/M] - 默认模型 openai/gpt-5.4-nano;具备 api_key/system/user prompt/seed/ temperature/top_p/max_tokens/多模态图像×6/detail/base_url/proxy 等完整参数 - 模型清单随包分发(models_snapshot.json),离线可列出; build_snapshot.py 可随时重拉最新价格 - VALIDATE_INPUTS 宽松校验:价格快照更新后旧工作流的模型标签仍可解析执行 - base_url 默认值沿用仓库约定不含 "://",规避 ComfyUI 前端吞协议片段的老问题 - 导出 WEB_DIRECTORY 以装载前端级联脚本 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a826b42576
commit
ca61c48dbd
@@ -0,0 +1,60 @@
|
||||
// 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;
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user