Publish current ComfyUI O1Key code baseline
Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { app } from "../../../scripts/app.js";
|
||||
|
||||
// ── Seedance 多模态节点参数联动 ───────────────────────────────────────────────
|
||||
//
|
||||
// fast、mini 不支持 1080p / 4k;2.5 支持全部分辨率和最长 30 秒。
|
||||
// 当用户切换到受限模型,或选择不支持的分辨率时,
|
||||
// 弹 toast 报错并把分辨率回退到 720p,避免提交后才在后端报错浪费一次调用。
|
||||
//
|
||||
// 注:「模型」下拉存的是展示名(seedance 2.0 fast 等),非真实模型 ID。
|
||||
|
||||
const LIMITED_MODELS = new Set([
|
||||
"seedance 2.0 fast",
|
||||
"seedance 2.0 mini",
|
||||
]);
|
||||
const isLimitedModel = (v) => LIMITED_MODELS.has(v);
|
||||
const MODEL_25 = "seedance 2.5";
|
||||
const ALL_RESOLUTIONS = ["720p", "1080p", "4k", "480p"];
|
||||
const LIMITED_RESOLUTIONS = ["720p", "480p"];
|
||||
const RESOLUTION_FALLBACK = "720p";
|
||||
const durationOptions = (maximum) => [
|
||||
"自动",
|
||||
...Array.from({ length: maximum - 3 }, (_, index) => `${index + 4}秒`),
|
||||
];
|
||||
const DURATIONS_20 = durationOptions(15);
|
||||
const DURATIONS_25 = durationOptions(30);
|
||||
|
||||
const SEEDANCE_NODES = new Set(["SeedanceMultiModal"]);
|
||||
|
||||
function notify(detail) {
|
||||
app.extensionManager?.toast?.add({
|
||||
severity: "warn",
|
||||
summary: "Seedance 多模态参数已调整",
|
||||
detail,
|
||||
life: 4000,
|
||||
});
|
||||
}
|
||||
|
||||
function findWidget(node, name) {
|
||||
return node.widgets?.find((w) => w.name === name);
|
||||
}
|
||||
|
||||
function setWidgetValue(widget, value) {
|
||||
if (!widget || widget.value === value) return;
|
||||
widget.value = value;
|
||||
widget.callback?.(value);
|
||||
}
|
||||
|
||||
function updateDynamicParameters(node, silent = false) {
|
||||
const modelWidget = findWidget(node, "主模型");
|
||||
const resWidget = findWidget(node, "分辨率");
|
||||
const durationWidget = findWidget(node, "时长");
|
||||
if (!modelWidget || !resWidget || !durationWidget) return;
|
||||
|
||||
const limited = isLimitedModel(modelWidget.value);
|
||||
const maximumDuration = modelWidget.value === MODEL_25 ? 30 : 15;
|
||||
resWidget.options ??= {};
|
||||
resWidget.options.values = limited ? LIMITED_RESOLUTIONS : ALL_RESOLUTIONS;
|
||||
durationWidget.options ??= {};
|
||||
durationWidget.options.values = maximumDuration === 30 ? DURATIONS_25 : DURATIONS_20;
|
||||
|
||||
if (limited && !LIMITED_RESOLUTIONS.includes(resWidget.value)) {
|
||||
setWidgetValue(resWidget, RESOLUTION_FALLBACK);
|
||||
if (!silent) {
|
||||
notify(`${modelWidget.value} 仅支持 480p/720p,已自动切换为 ${RESOLUTION_FALLBACK}。`);
|
||||
}
|
||||
}
|
||||
|
||||
const duration = Number.parseInt(durationWidget.value, 10);
|
||||
if (Number.isFinite(duration) && duration > maximumDuration) {
|
||||
setWidgetValue(durationWidget, `${maximumDuration}秒`);
|
||||
if (!silent) {
|
||||
notify(`${modelWidget.value} 的最大时长为 ${maximumDuration} 秒,已自动调整。`);
|
||||
}
|
||||
}
|
||||
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
}
|
||||
|
||||
function guardNode(node) {
|
||||
if (node.__o1keySeedanceParameterGuard) return;
|
||||
node.__o1keySeedanceParameterGuard = true;
|
||||
|
||||
const modelWidget = findWidget(node, "主模型");
|
||||
const resWidget = findWidget(node, "分辨率");
|
||||
const durationWidget = findWidget(node, "时长");
|
||||
if (!modelWidget || !resWidget || !durationWidget) return;
|
||||
|
||||
const origModelCb = modelWidget.callback;
|
||||
modelWidget.callback = function (value) {
|
||||
const ret = origModelCb?.apply(this, arguments);
|
||||
updateDynamicParameters(node);
|
||||
return ret;
|
||||
};
|
||||
|
||||
const origResCb = resWidget.callback;
|
||||
resWidget.callback = function (value) {
|
||||
if (isLimitedModel(modelWidget.value) && !LIMITED_RESOLUTIONS.includes(value)) {
|
||||
resWidget.value = RESOLUTION_FALLBACK;
|
||||
notify(`${modelWidget.value} 仅支持 480p/720p。`);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
return origResCb?.call(this, RESOLUTION_FALLBACK);
|
||||
}
|
||||
return origResCb?.apply(this, arguments);
|
||||
};
|
||||
|
||||
const origDurationCb = durationWidget.callback;
|
||||
durationWidget.callback = function (value) {
|
||||
const maximumDuration = modelWidget.value === MODEL_25 ? 30 : 15;
|
||||
const duration = Number.parseInt(value, 10);
|
||||
if (Number.isFinite(duration) && duration > maximumDuration) {
|
||||
const fallback = `${maximumDuration}秒`;
|
||||
durationWidget.value = fallback;
|
||||
notify(`${modelWidget.value} 的最大时长为 ${maximumDuration} 秒。`);
|
||||
node.setDirtyCanvas?.(true, true);
|
||||
return origDurationCb?.call(this, fallback);
|
||||
}
|
||||
return origDurationCb?.apply(this, arguments);
|
||||
};
|
||||
|
||||
updateDynamicParameters(node, true);
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "o1key.seedanceResolutionGuard",
|
||||
|
||||
nodeCreated(node) {
|
||||
if (SEEDANCE_NODES.has(node.comfyClass)) {
|
||||
guardNode(node);
|
||||
}
|
||||
},
|
||||
|
||||
loadedGraphNode(node) {
|
||||
if (!SEEDANCE_NODES.has(node.comfyClass)) return;
|
||||
guardNode(node);
|
||||
updateDynamicParameters(node, true);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user