Replace the prior release tree with the current plugin, frontend, tests, and documentation. Document retired node IDs and the public Gitea update source.
1456 lines
59 KiB
JavaScript
1456 lines
59 KiB
JavaScript
import { app } from "../../../scripts/app.js";
|
||
|
||
const LEGACY_NETWORK_ROUTES = new Set(["全球加速", "CF加速", "美国直连"]);
|
||
const GPT_IMAGE_ROUTE_ALIASES = new Map([
|
||
["gpt-image-2-特价", "畅速"],
|
||
["gpt-image-2-官方", "专线"],
|
||
["gpt-image-2-次卡", "畅速"],
|
||
["gpt-image-2-按量", "专线"],
|
||
["gpt-image-2-c", "畅速"],
|
||
["gpt-image-2-c-sp", "畅速"],
|
||
["gpt-image-2-c-sd", "直连"],
|
||
["gpt-image-2", "专线"],
|
||
]);
|
||
const GPT_IMAGE_NODE_TYPES = new Set(["O1keyGPTImage", "O1keyGPTImageBatch"]);
|
||
const GPT_IMAGE_MODELS = new Set([
|
||
"gpt-image-2",
|
||
"gpt-image-2.5-sunburst",
|
||
"gpt-image-2.5-flare",
|
||
]);
|
||
const GPT_IMAGE_ROUTES = new Set(["畅速", "直连", "专线"]);
|
||
const GPT_IMAGE_RESIZE_MODES = new Set(["不缩放", "智能缩放"]);
|
||
const GPT_IMAGE_COLOR_CORRECTION_MODES = new Set(["不纠正", "智能纠正"]);
|
||
const UNIFIED_COLOR_CORRECTION_MODES = new Set(["不纠正", "智能纠正"]);
|
||
const GPT_IMAGE_BACKGROUNDS = new Set(["auto", "transparent", "opaque"]);
|
||
const GPT_IMAGE_MODERATION_MODES = new Set(["自动", "低"]);
|
||
const GPT_IMAGE_BATCH_OUTPUT_FORMATS = new Set(["原始", "JPEG", "PNG", "WebP"]);
|
||
const GPT_IMAGE_BATCH_NAMING_RULES = new Set(["和原始图片名保持一致", "自然数字"]);
|
||
|
||
function findLastIndex(values, predicate) {
|
||
for (let index = values.length - 1; index >= 0; index--) {
|
||
if (predicate(values[index], index, values)) return index;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
function batchGPTImageRandomSelectionIndex(wv) {
|
||
const match = /^([1-5])个路径$/.exec(String(wv[6] ?? ""));
|
||
if (!match) return -1;
|
||
const pathCount = Number(match[1]);
|
||
const pairingWidgetCount = pathCount >= 2 ? 1 : 0;
|
||
return 7 + pathCount + pairingWidgetCount;
|
||
}
|
||
|
||
function batchGPTImageConcurrencyIndex(wv) {
|
||
const randomSelectionIndex = batchGPTImageRandomSelectionIndex(wv);
|
||
return randomSelectionIndex < 0 ? -1 : randomSelectionIndex + 1;
|
||
}
|
||
const SEEDANCE_AUTO_PASS_MODELS = new Set([
|
||
"seedance 2.0",
|
||
"seedance 2.0 fast",
|
||
"seedance 2.0 mini",
|
||
"seedance 2.5",
|
||
]);
|
||
const SEEDANCE_AUTO_PASS_ROUTES = new Set(["国内", "海外", "海外HC", "海外破限高并发", "海外破限", "海外破限标准", "海外标准"]);
|
||
const SEEDANCE_AUTO_PASS_NODE_TYPES = new Set(["SeedanceAutoPass", "SeedanceAutoPassBatch"]);
|
||
const SEEDANCE_ASSET_CREATE_TOGGLES = new Set(["关闭", "打开"]);
|
||
const SEEDANCE_ASSET_CREATE_MODES = new Set(["标准", "高并发", "HC"]);
|
||
const SEEDANCE_AUTO_PASS_DEFAULT_MODE = "多模态";
|
||
const SEEDANCE_AUTO_PASS_MODE_ALIASES = new Map([
|
||
["多模态参考生视频", "多模态"],
|
||
["文生视频", "多模态"],
|
||
["图生视频-首帧", "首尾帧"],
|
||
["图生视频-首尾帧", "首尾帧"],
|
||
]);
|
||
const LEGACY_BATCH_MODELS = new Map([
|
||
["seedance 2.0 海外版(高并发)", ["seedance 2.0", "海外"]],
|
||
["seedance 2.0 fast 海外版(高并发)", ["seedance 2.0 fast", "海外"]],
|
||
["seedance 2.0 mini 海外版(高并发)", ["seedance 2.0 mini", "海外"]],
|
||
["seedance 2.0 海外版", ["seedance 2.0", "海外"]],
|
||
["seedance 2.0 海外版(破限)", ["seedance 2.0", "海外"]],
|
||
["seedance 2.0 fast 海外版", ["seedance 2.0 fast", "海外"]],
|
||
["seedance 2.0 fast 海外版(破限)", ["seedance 2.0 fast", "海外"]],
|
||
["seedance 2.0 mini 海外版", ["seedance 2.0 mini", "海外"]],
|
||
["seedance 2.0 mini 海外版(破限)", ["seedance 2.0 mini", "海外"]],
|
||
]);
|
||
const GLOBAL_ROUTE_NODE_TYPES = new Set([
|
||
"NanoBanana",
|
||
"BatchNanoBananaPro",
|
||
"MiniMaxH3Video",
|
||
"K3Video",
|
||
"K3MotionControl",
|
||
"Google31Video",
|
||
"O1keyGPTImage",
|
||
"O1keyGPTImageBatch",
|
||
"O1keyGrokImage",
|
||
"O1keyGrokVideo",
|
||
"O1keyGrokVideoEdit",
|
||
"SeedanceMultiModal",
|
||
"SeedanceElementCreate",
|
||
"SeedanceAutoPass",
|
||
"SeedanceAutoPassBatch",
|
||
"UniversalLLMChat",
|
||
]);
|
||
const MINIMAX_H3_MODELS = new Set(["MiniMax-H3", "MiniMax-H3-MAX"]);
|
||
const OMNI_FLASH_MODELS = new Set(["omni_flash_8s", "omni_flash_10s"]);
|
||
const GROK_VIDEO_MODES = new Set(["文生视频", "图生视频", "参考生视频"]);
|
||
const GROK_VIDEO_EDIT_MODES = new Set(["编辑视频", "续写视频"]);
|
||
const LEGACY_GROK_VIDEO_MODELS = new Map([
|
||
["grok-imagine-video-1.5-preview", "grok-imagine-video-1.5"],
|
||
["grok-imagine-1.0-video", "grok-imagine-video"],
|
||
]);
|
||
|
||
function findLegacySeedanceAssetWidgets(wv) {
|
||
const modelIndex = wv.findIndex(value => SEEDANCE_AUTO_PASS_MODELS.has(value));
|
||
if (modelIndex < 0) return -1;
|
||
const autoCreateIndex = modelIndex + 6;
|
||
return SEEDANCE_ASSET_CREATE_TOGGLES.has(wv[autoCreateIndex])
|
||
&& SEEDANCE_ASSET_CREATE_MODES.has(wv[autoCreateIndex + 1])
|
||
? autoCreateIndex
|
||
: -1;
|
||
}
|
||
const NANO_BANANA_MODELS = new Set([
|
||
"Nano Banana Pro", "Nano Banana 2 Lite", "Nano Banana 2", "Nano Banana",
|
||
]);
|
||
const NANO_BANANA_ROUTE_ALIASES = new Map([
|
||
["特价", "畅速"],
|
||
["官方", "专线"],
|
||
]);
|
||
const NANO_BANANA_ROUTES = new Set(["畅速", "直连", "专线"]);
|
||
const NANO_BANANA_RESOLUTIONS = new Set(["512", "1K", "2K", "4K"]);
|
||
const NANO_BANANA_NODE_TYPES = new Set(["NanoBanana", "BatchNanoBananaPro"]);
|
||
const REMOVED_NANO_BANANA_RESOLUTIONS = new Set(["512", "512px"]);
|
||
const NANO_BANANA_ASPECT_RATIOS = new Set([
|
||
"智能", "1:1", "1:4", "1:8", "2:3", "3:2", "3:4", "4:1",
|
||
"4:3", "4:5", "5:4", "8:1", "9:16", "16:9", "21:9",
|
||
]);
|
||
const BATCH_NANO_PAIRING_ALIASES = new Map([
|
||
["按相同图片命名", "相同文件名"],
|
||
["1*N", "全匹配"],
|
||
]);
|
||
const BATCH_NANO_IMAGE_FORMATS = new Set(["原始", "JPEG", "PNG", "WebP"]);
|
||
const BATCH_NANO_NAMING_RULES = new Set(["和原始图片名保持一致", "自然数字"]);
|
||
const NANO_BANANA_RESIZE_MODES = new Set(["不缩放", "智能缩放"]);
|
||
const NANO_BANANA_COLOR_CORRECTION_MODES = new Set(["不纠正", "智能纠正"]);
|
||
|
||
function isCurrentGPTImageBatchWidgetLayout(wv) {
|
||
const namingIndex = findLastIndex(wv, value => GPT_IMAGE_BATCH_NAMING_RULES.has(value));
|
||
if (namingIndex < 1) return false;
|
||
const formatIndex = namingIndex - 2;
|
||
const backgroundIndex = namingIndex - 1;
|
||
const savePathIndex = namingIndex + 1;
|
||
const resizeIndex = namingIndex + 2;
|
||
const seedIndex = namingIndex + 3;
|
||
const randomSelectionIndex = batchGPTImageRandomSelectionIndex(wv);
|
||
return GPT_IMAGE_BATCH_OUTPUT_FORMATS.has(wv[formatIndex])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[backgroundIndex])
|
||
&& typeof wv[savePathIndex] === "string"
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[resizeIndex])
|
||
&& Number.isInteger(wv[seedIndex])
|
||
&& formatIndex === randomSelectionIndex
|
||
&& seedIndex === wv.length - 1;
|
||
}
|
||
|
||
function isGPTImageBatchWidgetLayoutWithRandomSelection(wv) {
|
||
const namingIndex = findLastIndex(wv, value => GPT_IMAGE_BATCH_NAMING_RULES.has(value));
|
||
if (namingIndex < 1) return false;
|
||
const formatIndex = namingIndex - 2;
|
||
const backgroundIndex = namingIndex - 1;
|
||
const savePathIndex = namingIndex + 1;
|
||
const resizeIndex = namingIndex + 2;
|
||
const seedIndex = namingIndex + 3;
|
||
const randomSelectionIndex = batchGPTImageRandomSelectionIndex(wv);
|
||
const concurrencyIndex = batchGPTImageConcurrencyIndex(wv);
|
||
const hasLegacyConcurrency = Number.isInteger(wv[concurrencyIndex])
|
||
&& formatIndex === concurrencyIndex + 1;
|
||
const hasRandomSelection = wv[randomSelectionIndex] === "关闭"
|
||
|| (typeof wv[randomSelectionIndex] === "string"
|
||
&& /^[1-5](?:[,,、 ]+[1-5])*$/.test(wv[randomSelectionIndex]));
|
||
return GPT_IMAGE_BATCH_OUTPUT_FORMATS.has(wv[formatIndex])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[backgroundIndex])
|
||
&& typeof wv[savePathIndex] === "string"
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[resizeIndex])
|
||
&& Number.isInteger(wv[seedIndex])
|
||
&& hasRandomSelection
|
||
&& (formatIndex === concurrencyIndex || hasLegacyConcurrency)
|
||
&& seedIndex === wv.length - 1;
|
||
}
|
||
|
||
function isLegacyGPTImageBatchWidgetLayout(wv) {
|
||
const namingIndex = findLastIndex(wv, value => GPT_IMAGE_BATCH_NAMING_RULES.has(value));
|
||
if (namingIndex < 2) return false;
|
||
const formatIndex = namingIndex - 1;
|
||
const savePathIndex = namingIndex + 1;
|
||
const resizeIndex = namingIndex + 2;
|
||
const seedIndex = formatIndex - 1;
|
||
const concurrencyIndex = batchGPTImageConcurrencyIndex(wv);
|
||
if (!GPT_IMAGE_BATCH_OUTPUT_FORMATS.has(wv[formatIndex])
|
||
|| !Number.isInteger(wv[seedIndex])
|
||
|| !Number.isInteger(wv[concurrencyIndex])
|
||
|| typeof wv[savePathIndex] !== "string"
|
||
|| !GPT_IMAGE_RESIZE_MODES.has(wv[resizeIndex])
|
||
|| formatIndex !== concurrencyIndex + 2) {
|
||
return false;
|
||
}
|
||
|
||
let backgroundIndex = resizeIndex + 1;
|
||
if (GPT_IMAGE_COLOR_CORRECTION_MODES.has(wv[backgroundIndex])) backgroundIndex++;
|
||
if (!GPT_IMAGE_BACKGROUNDS.has(wv[backgroundIndex])) return false;
|
||
let endIndex = backgroundIndex + 1;
|
||
if (GPT_IMAGE_MODERATION_MODES.has(wv[endIndex])) endIndex++;
|
||
return endIndex === wv.length;
|
||
}
|
||
|
||
function removeLegacyNetworkRoute(wv) {
|
||
const index = wv.findIndex(value => LEGACY_NETWORK_ROUTES.has(value));
|
||
if (index < 0) return false;
|
||
wv.splice(index, 1);
|
||
return true;
|
||
}
|
||
|
||
function batchNanoPairingWidgetIndex(wv) {
|
||
const match = /^([1-5])个(?:路径|文件夹)$/.exec(String(wv[4]));
|
||
if (!match) return -1;
|
||
const pathCount = Number(match[1]);
|
||
return pathCount >= 2 ? 5 + pathCount : -1;
|
||
}
|
||
|
||
function batchNanoRandomWidgetIndex(wv) {
|
||
const match = /^([1-5])个路径$/.exec(String(wv[6] ?? ""));
|
||
if (!match) return -1;
|
||
const count = Number(match[1]);
|
||
return 7 + count + (count >= 2 ? 1 : 0);
|
||
}
|
||
|
||
// ComfyUI 把每个节点的控件值按「位置」存进 widgets_values 数组,而不是按名字。
|
||
// 一旦某个节点的 schema 删掉中间的控件,旧工作流里该位置之后的所有值都会整体错位一格。
|
||
// 下面的规则在 ComfyUI 把数组映射到控件之前,把废弃的值从原始数组里剔除,使后续值重新对齐。
|
||
//
|
||
// 新增规则时:type 填节点的注册名(与保存文件里的 "type" 一致),
|
||
// when 判断旧结构特征(务必保证幂等,避免对已迁移的数据重复处理),apply 做数组修正。
|
||
const MIGRATIONS = [
|
||
{
|
||
type: "O1keyAutoRedCast",
|
||
note: "为旧版自动红偏校正补齐末尾 seed 参数",
|
||
when: (wv) => wv.length === 6
|
||
&& typeof wv[0] === "number"
|
||
&& typeof wv[1] === "number"
|
||
&& typeof wv[2] === "number"
|
||
&& typeof wv[3] === "string"
|
||
&& typeof wv[4] === "number"
|
||
&& typeof wv[5] === "number",
|
||
apply: (wv) => wv.push(0),
|
||
},
|
||
{
|
||
type: "O1keyAutoRedCast",
|
||
note: "将自动红偏校正的 seed 移到末尾",
|
||
when: (wv) => wv.length === 7
|
||
&& Number.isInteger(wv[4])
|
||
&& wv[4] >= 0
|
||
&& typeof wv[5] === "number" && wv[5] >= 20 && wv[5] <= 95
|
||
&& typeof wv[6] === "number" && wv[6] >= 3 && wv[6] <= 40
|
||
&& (wv[4] < 20 || wv[4] > 95 || wv[5] > 40),
|
||
apply: (wv) => wv.push(wv.splice(4, 1)[0]),
|
||
},
|
||
{
|
||
type: "O1keyOmniFlashVideo",
|
||
note: "移除 Omni Flash 节点的模型控件",
|
||
when: (wv) => OMNI_FLASH_MODELS.has(wv[2]),
|
||
apply: (wv) => wv.splice(2, 1),
|
||
},
|
||
{
|
||
type: "O1keyPromptMultiFunction",
|
||
note: "为提示词(多功能)节点补齐多选参数",
|
||
when: (wv) => wv.length >= 2 && wv.length < 4,
|
||
apply: (wv) => {
|
||
const appendedDefaults = [3, "1,2,3"];
|
||
while (wv.length < 4) wv.push(appendedDefaults[wv.length - 2]);
|
||
},
|
||
},
|
||
{
|
||
type: "O1keyPromptMultiFunction",
|
||
note: "合并提示词节点的旧随机抽取模式",
|
||
when: (wv) => wv[1] === "随机抽取1套" || wv[1] === "随机抽取多套",
|
||
apply: (wv) => {
|
||
const wasRandomOne = wv[1] === "随机抽取1套";
|
||
wv[1] = "随机抽取n套";
|
||
if (wasRandomOne) wv[2] = 1;
|
||
},
|
||
},
|
||
{
|
||
type: "MiniMaxH3Video",
|
||
note: "为 MiniMax H3 节点补齐模型参数",
|
||
when: (wv) => wv.length >= 4
|
||
&& !MINIMAX_H3_MODELS.has(wv[wv.length - 1])
|
||
&& !MINIMAX_H3_MODELS.has(wv[wv.length - 2]),
|
||
apply: (wv) => wv.push("MiniMax-H3"),
|
||
},
|
||
{
|
||
type: "MiniMaxH3Video",
|
||
note: "为 MiniMax H3 节点补齐原生 seed 参数",
|
||
when: (wv) => wv.length >= 5 && MINIMAX_H3_MODELS.has(wv[wv.length - 1]),
|
||
apply: (wv) => wv.push(0),
|
||
},
|
||
{
|
||
type: "O1keyImageGenerator",
|
||
note: "移除统一图片生成节点已停用的色彩纠正参数",
|
||
when: (wv) => UNIFIED_COLOR_CORRECTION_MODES.has(wv[13]),
|
||
apply: (wv) => wv.splice(13, 1),
|
||
},
|
||
{
|
||
type: "O1keyImageGenerator",
|
||
note: "为统一图片生成节点补齐 GPT 输出、缩放、背景及内容审查强度字段",
|
||
when: (wv) => wv.length >= 9 && wv.length < 15,
|
||
apply: (wv) => {
|
||
const appendedDefaults = ["自动", "png", "{}", "不缩放", "auto", "自动"];
|
||
while (wv.length < 15) wv.push(appendedDefaults[wv.length - 9]);
|
||
},
|
||
},
|
||
{
|
||
type: "O1keyImageGenerator",
|
||
note: "为统一图片生成节点补齐可选批量出图字段",
|
||
when: (wv) => wv.length >= 15 && wv.length < 18,
|
||
apply: (wv) => {
|
||
const appendedDefaults = [false, "一组搭配+多模特", "[]"];
|
||
while (wv.length < 18) wv.push(appendedDefaults[wv.length - 15]);
|
||
},
|
||
},
|
||
{
|
||
type: "NanoBanana",
|
||
note: "移除 Nano Banana 2 的「思考深度」(idx 4)",
|
||
when: (wv) => wv[1] === "Nano Banana 2" && (wv[4] === "高" || wv[4] === "低"),
|
||
apply: (wv) => wv.splice(4, 1),
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "移除 Nano Banana 2 的「思考深度」(idx 5)",
|
||
when: (wv) => wv[1] === "Nano Banana 2" && (wv[5] === "高" || wv[5] === "低"),
|
||
apply: (wv) => wv.splice(5, 1),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "移除 Seedance 自动过审的「固定镜头」(idx 6)",
|
||
when: (wv) => wv.length >= 13
|
||
&& (wv[6] === "关闭" || wv[6] === "打开")
|
||
&& (wv[7] === "关闭" || wv[7] === "打开")
|
||
&& (wv[8] === "关闭" || wv[8] === "打开")
|
||
&& (wv[9] === "关闭" || wv[9] === "打开")
|
||
&& SEEDANCE_ASSET_CREATE_MODES.has(wv[10]),
|
||
apply: (wv) => wv.splice(6, 1),
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "移除已迁移到全局设置的节点网络线路",
|
||
when: (wv, node) => GLOBAL_ROUTE_NODE_TYPES.has(node.type)
|
||
&& wv.some(value => LEGACY_NETWORK_ROUTES.has(value)),
|
||
apply: (wv) => removeLegacyNetworkRoute(wv),
|
||
},
|
||
{
|
||
type: "O1keyGrokVideo",
|
||
note: "迁移旧版 Grok Video 模型、模式、时长与分辨率参数",
|
||
when: (wv) => wv.length === 5 && LEGACY_GROK_VIDEO_MODELS.has(wv[1]),
|
||
apply: (wv, node) => {
|
||
const [prompt, legacyModel, ratio, rawDuration] = wv;
|
||
const connectedReference = Array.isArray(node?.inputs)
|
||
&& node.inputs.some(input => /^图片[1-3]$/.test(input?.name) && input?.link != null);
|
||
const parsedDuration = Number(rawDuration);
|
||
const duration = Number.isFinite(parsedDuration)
|
||
? Math.max(1, Math.min(15, Math.trunc(parsedDuration)))
|
||
: 8;
|
||
wv.splice(
|
||
0,
|
||
wv.length,
|
||
connectedReference ? "参考生视频" : "文生视频",
|
||
prompt,
|
||
LEGACY_GROK_VIDEO_MODELS.get(legacyModel),
|
||
duration,
|
||
ratio,
|
||
"720p",
|
||
"",
|
||
);
|
||
},
|
||
},
|
||
{
|
||
type: "O1keyGrokVideo",
|
||
note: "为新版 Grok Video 节点补齐参考音色 ID 参数",
|
||
when: (wv) => wv.length === 6 && GROK_VIDEO_MODES.has(wv[0]),
|
||
apply: (wv) => wv.push(""),
|
||
},
|
||
{
|
||
type: "O1keyGrokVideoEdit",
|
||
note: "为 Grok Video 编辑/续写节点补齐模型参数",
|
||
when: (wv) => wv.length === 3 && GROK_VIDEO_EDIT_MODES.has(wv[0]),
|
||
apply: (wv) => wv.push("grok-imagine-video-1.5"),
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "迁移早期批量 Nano Banana 的动态模型布局",
|
||
when: (wv) => wv.length === 15
|
||
&& NANO_BANANA_MODELS.has(wv[1])
|
||
&& NANO_BANANA_ASPECT_RATIOS.has(wv[2])
|
||
&& (NANO_BANANA_RESOLUTIONS.has(wv[3]) || wv[3] === "512px")
|
||
&& (wv[4] === "关闭" || wv[4] === "打开")
|
||
&& (wv[5] === "原始" || wv[5] === "JPEG" || wv[5] === "PNG" || wv[5] === "WebP")
|
||
&& (wv[6] === "特价" || wv[6] === "官方"),
|
||
apply: (wv) => {
|
||
const [prompt, model, ratio, rawResolution, , outputFormat, billing, seed,
|
||
path1, path2, path3, path4, path5, savePath, pairing] = wv;
|
||
const paths = [path1, path2, path3, path4, path5];
|
||
const highestFilledPath = paths.reduce(
|
||
(highest, value, index) => typeof value === "string" && value.trim()
|
||
? index + 1
|
||
: highest,
|
||
0,
|
||
);
|
||
const folderCount = Math.min(
|
||
5,
|
||
Math.max(1, highestFilledPath, pairing !== "不配对" ? 2 : 1),
|
||
);
|
||
const migrated = [
|
||
prompt,
|
||
model,
|
||
rawResolution === "512px" ? "512" : rawResolution,
|
||
ratio,
|
||
`${folderCount}个路径`,
|
||
...paths.slice(0, folderCount),
|
||
];
|
||
if (folderCount >= 2) migrated.push(pairing);
|
||
migrated.push(
|
||
"关闭",
|
||
billing,
|
||
seed,
|
||
outputFormat,
|
||
"和原始图片名保持一致",
|
||
savePath,
|
||
);
|
||
wv.splice(0, wv.length, ...migrated);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "将批量 Nano Banana 迁移为动态图片路径和高级参数布局",
|
||
when: (wv) => wv.length === 16
|
||
&& NANO_BANANA_MODELS.has(wv[1])
|
||
&& NANO_BANANA_ASPECT_RATIOS.has(wv[2])
|
||
&& NANO_BANANA_RESOLUTIONS.has(wv[3])
|
||
&& (wv[4] === "特价" || wv[4] === "官方"),
|
||
apply: (wv) => {
|
||
const [prompt, model, ratio, resolution, billing, seed,
|
||
path1, path2, path3, path4, path5, pairing, randomPath,
|
||
outputFormat, namingRule, savePath] = wv;
|
||
const paths = [path1, path2, path3, path4, path5];
|
||
const highestFilledPath = paths.reduce(
|
||
(highest, value, index) => typeof value === "string" && value.trim()
|
||
? index + 1
|
||
: highest,
|
||
0,
|
||
);
|
||
const randomIndex = /^\d+$/.test(String(randomPath))
|
||
? Number(randomPath)
|
||
: 0;
|
||
const folderCount = Math.min(
|
||
5,
|
||
Math.max(1, highestFilledPath, randomIndex, pairing !== "不配对" ? 2 : 1),
|
||
);
|
||
const migrated = [
|
||
prompt,
|
||
model,
|
||
resolution,
|
||
ratio,
|
||
`${folderCount}个路径`,
|
||
...paths.slice(0, folderCount),
|
||
];
|
||
if (folderCount >= 2) migrated.push(pairing);
|
||
migrated.push(
|
||
randomPath,
|
||
billing,
|
||
seed,
|
||
outputFormat,
|
||
namingRule,
|
||
savePath,
|
||
);
|
||
wv.splice(0, wv.length, ...migrated);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "将图片文件夹数量重命名为图片路径数量",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& /^([1-5])个文件夹$/.test(String(wv[4])),
|
||
apply: (wv) => {
|
||
wv[4] = String(wv[4]).replace("个文件夹", "个路径");
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "更新批量 Nano Banana 图片配对模式名称",
|
||
when: (wv) => {
|
||
const index = batchNanoPairingWidgetIndex(wv);
|
||
return index >= 0 && BATCH_NANO_PAIRING_ALIASES.has(wv[index]);
|
||
},
|
||
apply: (wv) => {
|
||
const index = batchNanoPairingWidgetIndex(wv);
|
||
wv[index] = BATCH_NANO_PAIRING_ALIASES.get(wv[index]);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "为批量节点新增「思考等级」(idx 2)",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1]) && NANO_BANANA_RESOLUTIONS.has(wv[2]),
|
||
apply: (wv) => wv.splice(2, 0, "高"),
|
||
},
|
||
{
|
||
type: "NanoBanana",
|
||
note: "将 Nano Banana 的计费参数迁移为模型线路",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& NANO_BANANA_ROUTE_ALIASES.has(wv[6]),
|
||
apply: (wv) => { wv[6] = NANO_BANANA_ROUTE_ALIASES.get(wv[6]); },
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "将批量 Nano Banana 的计费参数迁移为模型线路",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& wv.some(value => NANO_BANANA_ROUTE_ALIASES.has(value)),
|
||
apply: (wv) => {
|
||
const index = wv.findIndex(value => NANO_BANANA_ROUTE_ALIASES.has(value));
|
||
if (index >= 0) wv[index] = NANO_BANANA_ROUTE_ALIASES.get(wv[index]);
|
||
},
|
||
},
|
||
{
|
||
type: "NanoBanana",
|
||
note: "将 Nano Banana 模型线路移动到模型下方",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& !NANO_BANANA_ROUTES.has(wv[2])
|
||
&& NANO_BANANA_ROUTES.has(wv[6]),
|
||
apply: (wv) => {
|
||
const [route] = wv.splice(6, 1);
|
||
wv.splice(2, 0, route);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "将批量 Nano Banana 模型线路移动到模型下方",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& !NANO_BANANA_ROUTES.has(wv[2])
|
||
&& wv.some((value, index) => index > 2 && NANO_BANANA_ROUTES.has(value)),
|
||
apply: (wv) => {
|
||
const index = wv.findIndex((value, valueIndex) => (
|
||
valueIndex > 2 && NANO_BANANA_ROUTES.has(value)
|
||
));
|
||
if (index < 0) return;
|
||
const [route] = wv.splice(index, 1);
|
||
wv.splice(2, 0, route);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "为批量 Nano Banana 的有损图片输出新增「图片质量」",
|
||
when: (wv) => {
|
||
const formatIndex = wv.findIndex((value, index) => (
|
||
index > 0
|
||
&& BATCH_NANO_IMAGE_FORMATS.has(value)
|
||
&& BATCH_NANO_NAMING_RULES.has(wv[index + 1])
|
||
));
|
||
return formatIndex >= 0;
|
||
},
|
||
apply: (wv) => {
|
||
const formatIndex = wv.findIndex((value, index) => (
|
||
index > 0
|
||
&& BATCH_NANO_IMAGE_FORMATS.has(value)
|
||
&& BATCH_NANO_NAMING_RULES.has(wv[index + 1])
|
||
));
|
||
if (formatIndex >= 0) wv.splice(formatIndex + 1, 0, 95);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "将批量 Nano Banana 的字符串图片质量恢复为整数",
|
||
when: (wv) => {
|
||
const formatIndex = wv.findIndex((value, index) => (
|
||
index > 0 && BATCH_NANO_IMAGE_FORMATS.has(value)
|
||
));
|
||
if (formatIndex < 0 || typeof wv[formatIndex + 1] !== "string") {
|
||
return false;
|
||
}
|
||
const quality = Number(wv[formatIndex + 1]);
|
||
return Number.isInteger(quality)
|
||
&& quality >= 1
|
||
&& quality <= 100;
|
||
},
|
||
apply: (wv) => {
|
||
const formatIndex = wv.findIndex((value, index) => (
|
||
index > 0 && BATCH_NANO_IMAGE_FORMATS.has(value)
|
||
));
|
||
if (formatIndex >= 0) wv[formatIndex + 1] = Number(wv[formatIndex + 1]);
|
||
},
|
||
},
|
||
{
|
||
type: "O1keyGPTImageBatch",
|
||
note: "将旧 GPT Image 批量节点迁移为动态路径和并发布局",
|
||
when: (wv) => wv.length >= 14
|
||
&& GPT_IMAGE_ROUTE_ALIASES.has(wv[1])
|
||
&& typeof wv[2] === "string"
|
||
&& Number.isInteger(wv[3])
|
||
&& (wv[4] === "高" || wv[4] === "中" || wv[4] === "低" || wv[4] === "自动")
|
||
&& (wv[6] === "原始" || wv[6] === "JPEG" || wv[6] === "PNG" || wv[6] === "WebP"),
|
||
apply: (wv) => {
|
||
const [prompt, model, resolution, imageCount, quality, seed, imageFormat,
|
||
path1, path2, path3, path4, path5, savePath, rawPairing] = wv;
|
||
const paths = [path1, path2, path3, path4, path5];
|
||
const pairing = BATCH_NANO_PAIRING_ALIASES.get(rawPairing) || rawPairing || "不配对";
|
||
const highestFilled = paths.reduce(
|
||
(highest, value, index) => typeof value === "string" && value.trim() ? index + 1 : highest,
|
||
0,
|
||
);
|
||
const pathCount = Math.min(5, Math.max(1, highestFilled, pairing !== "不配对" ? 2 : 1));
|
||
const migrated = [
|
||
prompt, model, resolution, imageCount, quality,
|
||
`${pathCount}个路径`, ...paths.slice(0, pathCount),
|
||
];
|
||
if (pathCount >= 2) migrated.push(pairing);
|
||
migrated.push("关闭", 3, seed, imageFormat, "和原始图片名保持一致", savePath);
|
||
wv.splice(0, wv.length, ...migrated);
|
||
},
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "修复被旧迁移规则错位的 GPT Image 参数(wv[2] 为误插入的线路)",
|
||
when: (wv, node) => GPT_IMAGE_NODE_TYPES.has(node.type)
|
||
&& wv[1] === "gpt-image-2"
|
||
&& GPT_IMAGE_ROUTES.has(wv[2])
|
||
&& GPT_IMAGE_ROUTES.has(wv[3]),
|
||
apply: (wv) => wv.splice(2, 1),
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "将 GPT Image 模型名称迁移为模型线路",
|
||
// 新格式的 wv[1] 恰好也是 "gpt-image-2"(主模型),须以 wv[2] 是否已是线路来区分,
|
||
// 否则会把新格式误判为旧格式并导致后续参数整体错位。
|
||
when: (wv, node) => GPT_IMAGE_NODE_TYPES.has(node.type)
|
||
&& GPT_IMAGE_ROUTE_ALIASES.has(wv[1])
|
||
&& !GPT_IMAGE_ROUTES.has(wv[2]),
|
||
apply: (wv) => { wv[1] = GPT_IMAGE_ROUTE_ALIASES.get(wv[1]); },
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "为 GPT Image 节点补充主模型参数",
|
||
when: (wv, node) => GPT_IMAGE_NODE_TYPES.has(node.type)
|
||
&& GPT_IMAGE_ROUTES.has(wv[1])
|
||
&& !GPT_IMAGE_ROUTES.has(wv[2]),
|
||
apply: (wv) => wv.splice(1, 0, "gpt-image-2"),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "移除 Seedance 自动过审的「图片链接」(idx 11)",
|
||
when: (wv) => wv.length >= 12
|
||
&& (wv[6] === "关闭" || wv[6] === "打开")
|
||
&& (wv[7] === "关闭" || wv[7] === "打开")
|
||
&& (wv[8] === "关闭" || wv[8] === "打开")
|
||
&& SEEDANCE_ASSET_CREATE_MODES.has(wv[9])
|
||
&& typeof wv[11] === "string",
|
||
apply: (wv) => wv.splice(11, 1),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "新增 Seedance 自动过审的「生成模式」(idx 1)",
|
||
when: (wv) => wv.length === 11
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[1])
|
||
&& SEEDANCE_AUTO_PASS_ROUTES.has(wv[2]),
|
||
apply: (wv) => wv.splice(1, 0, SEEDANCE_AUTO_PASS_DEFAULT_MODE),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "将 Seedance 自动过审的「生成模式」移动到提示词之后",
|
||
when: (wv) => wv.length >= 12
|
||
&& _isSeedanceAutoPassMode(wv[0])
|
||
&& typeof wv[1] === "string"
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[2]),
|
||
apply: (wv) => wv.splice(0, 2, wv[1], wv[0]),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "将 Seedance 自动过审的「联网搜索」迁入生成模式",
|
||
when: (wv) => wv.length >= 12
|
||
&& wv.length !== 62
|
||
&& _isSeedanceAutoPassMode(wv[1])
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[2])
|
||
&& (wv[8] === "关闭" || wv[8] === "打开")
|
||
&& (wv[9] === "关闭" || wv[9] === "打开")
|
||
&& SEEDANCE_ASSET_CREATE_MODES.has(wv[10]),
|
||
apply: (wv) => {
|
||
const [webSearch] = wv.splice(8, 1);
|
||
wv.splice(2, 0, webSearch);
|
||
},
|
||
},
|
||
{
|
||
type: "SeedanceAutoPassBatch",
|
||
note: "将旧批量节点迁移为 Seedance 全能生成视频(批量)",
|
||
when: (wv) => wv.length === 12
|
||
&& LEGACY_BATCH_MODELS.has(wv[3])
|
||
&& typeof wv[1] === "string"
|
||
&& typeof wv[2] === "string",
|
||
apply: (wv) => {
|
||
const [baseModel, modelRoute] = LEGACY_BATCH_MODELS.get(wv[3]);
|
||
const [prompt, imageFolder, videoFolder, , resolution, ratio, duration,
|
||
generateAudio, webSearch, concurrency, outputSubdir, seed] = wv;
|
||
wv.splice(0, wv.length,
|
||
prompt,
|
||
SEEDANCE_AUTO_PASS_DEFAULT_MODE,
|
||
webSearch,
|
||
imageFolder,
|
||
videoFolder,
|
||
"",
|
||
baseModel,
|
||
modelRoute,
|
||
resolution,
|
||
ratio,
|
||
duration,
|
||
generateAudio,
|
||
"打开",
|
||
"HC",
|
||
seed,
|
||
concurrency,
|
||
outputSubdir,
|
||
);
|
||
},
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "移除 Seedance 全能生成视频的素材创建参数(改为按模型线路自动匹配)",
|
||
when: (wv, node) => SEEDANCE_AUTO_PASS_NODE_TYPES.has(node.type)
|
||
&& !(node.type === "SeedanceAutoPass" && wv.length === 62)
|
||
&& findLegacySeedanceAssetWidgets(wv) >= 0,
|
||
apply: (wv) => wv.splice(findLegacySeedanceAssetWidgets(wv), 2),
|
||
},
|
||
{
|
||
type: "SeedanceElementCreate",
|
||
note: "移除 Seedance 真人素材的图片链接并迁移到 HC 请求模式",
|
||
when: (wv) => wv.length >= 4
|
||
&& SEEDANCE_ASSET_CREATE_MODES.has(wv[1]),
|
||
apply: (wv) => {
|
||
wv.splice(2, 1);
|
||
wv[1] = "HC";
|
||
},
|
||
},
|
||
{
|
||
type: "SeedanceElementCreate",
|
||
note: "将 Seedance 真人素材请求模式迁移为 HC",
|
||
when: (wv) => wv[1] === "标准" || wv[1] === "高并发",
|
||
apply: (wv) => { wv[1] = "HC"; },
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "为 Seedance 全能生成视频补齐返回末帧参数",
|
||
when: (wv) => wv.length === 10
|
||
&& _isSeedanceAutoPassMode(wv[1])
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[3])
|
||
&& SEEDANCE_AUTO_PASS_ROUTES.has(wv[4])
|
||
&& typeof wv[9] === "number",
|
||
apply: (wv) => wv.push("关闭"),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "将 Seedance 全能生成视频简化为两种模式并下移原高级参数",
|
||
when: (wv) => wv.length === 11
|
||
&& SEEDANCE_AUTO_PASS_MODE_ALIASES.has(wv[1])
|
||
&& (wv[2] === "关闭" || wv[2] === "打开")
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[3])
|
||
&& SEEDANCE_AUTO_PASS_ROUTES.has(wv[4]),
|
||
apply: (wv) => {
|
||
const [prompt, legacyMode, webSearch, model, route, resolution, ratio,
|
||
duration, generateAudio, seed, returnLastFrame] = wv;
|
||
wv.splice(
|
||
0,
|
||
wv.length,
|
||
prompt,
|
||
SEEDANCE_AUTO_PASS_MODE_ALIASES.get(legacyMode),
|
||
model,
|
||
route === "国内" ? "国内" : "海外",
|
||
resolution,
|
||
ratio,
|
||
duration,
|
||
generateAudio,
|
||
"自动创建",
|
||
webSearch,
|
||
seed,
|
||
returnLastFrame,
|
||
);
|
||
},
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "将 Seedance 全能生成视频的海外HC线路重命名为海外",
|
||
when: (wv) => wv.length >= 12
|
||
&& (wv[1] === "多模态" || wv[1] === "首尾帧")
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[2])
|
||
&& wv[3] === "海外HC",
|
||
apply: (wv) => { wv[3] = "海外"; },
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "为稳定版素材创建下拉补齐三个手动素材 ID 参数",
|
||
when: (wv) => wv.length === 12
|
||
&& (wv[1] === "多模态" || wv[1] === "首尾帧")
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[2])
|
||
&& SEEDANCE_AUTO_PASS_ROUTES.has(wv[3])
|
||
&& (wv[8] === "自动创建" || wv[8] === "手动")
|
||
&& (wv[9] === "关闭" || wv[9] === "打开")
|
||
&& typeof wv[10] === "number",
|
||
apply: (wv) => wv.splice(9, 0, "", "", ""),
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "将三类聚合素材 ID 迁移为渐进显示的编号单行输入",
|
||
when: (wv) => wv.length === 15
|
||
&& (wv[1] === "多模态" || wv[1] === "首尾帧")
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[2])
|
||
&& (wv[8] === "自动创建" || wv[8] === "手动")
|
||
&& typeof wv[13] === "number",
|
||
apply: (wv) => {
|
||
const rows = [];
|
||
for (const [index, maximum] of [[9, 30], [10, 10], [11, 10]]) {
|
||
const ids = String(wv[index] ?? "").split(/[\s,,;;]+/).filter(Boolean);
|
||
for (let row = 0; row < maximum; row++) {
|
||
// 保留超限旧值供后端校验,不能在迁移时静默丢弃。
|
||
rows.push(row === maximum - 1 ? ids.slice(row).join(",") : (ids[row] ?? ""));
|
||
}
|
||
}
|
||
wv.splice(9, 3, ...rows);
|
||
},
|
||
},
|
||
{
|
||
type: "SeedanceAutoPass",
|
||
note: "将素材创建模式恢复为关闭/打开开关",
|
||
when: (wv) => wv.length === 62
|
||
&& SEEDANCE_AUTO_PASS_MODELS.has(wv[2])
|
||
&& (wv[8] === "自动创建" || wv[8] === "手动"),
|
||
apply: (wv) => { wv[8] = wv[8] === "手动" ? "打开" : "关闭"; },
|
||
},
|
||
{
|
||
type: "SeedanceAutoPassBatch",
|
||
note: "将 Seedance 全能生成视频批量节点的海外HC线路重命名为海外",
|
||
when: (wv) => {
|
||
const modelIndex = wv.findIndex(value => SEEDANCE_AUTO_PASS_MODELS.has(value));
|
||
return modelIndex >= 0 && wv[modelIndex + 1] === "海外HC";
|
||
},
|
||
apply: (wv) => {
|
||
const modelIndex = wv.findIndex(value => SEEDANCE_AUTO_PASS_MODELS.has(value));
|
||
wv[modelIndex + 1] = "海外";
|
||
},
|
||
},
|
||
{
|
||
type: "O1keyVideoGenerator",
|
||
note: "为 o1key 视频生成补齐素材创建模式",
|
||
when: (wv) => wv.length === 16,
|
||
apply: (wv) => wv.push("auto"),
|
||
},
|
||
{
|
||
type: "SeedanceMultiModal",
|
||
note: "将 Seedance 多模态模型线路的海外HC重命名为海外",
|
||
when: (wv) => SEEDANCE_AUTO_PASS_MODELS.has(wv[1]) && wv[2] === "海外HC",
|
||
apply: (wv) => { wv[2] = "海外"; },
|
||
},
|
||
{
|
||
type: "NanoBanana",
|
||
note: "移除 Nano Banana 已停用的色彩纠正参数",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& NANO_BANANA_COLOR_CORRECTION_MODES.has(wv[wv.length - 1]),
|
||
apply: (wv) => wv.pop(),
|
||
},
|
||
{
|
||
type: "NanoBanana",
|
||
note: "为 Nano Banana 补齐缩放图片参数",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& wv.length >= 8
|
||
&& !NANO_BANANA_RESIZE_MODES.has(wv[wv.length - 1]),
|
||
apply: (wv) => wv.push("不缩放"),
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "移除批量 Nano Banana 已停用的色彩纠正参数",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& wv.some(value => BATCH_NANO_IMAGE_FORMATS.has(value))
|
||
&& wv.some(value => BATCH_NANO_NAMING_RULES.has(value))
|
||
&& NANO_BANANA_COLOR_CORRECTION_MODES.has(wv[wv.length - 1]),
|
||
apply: (wv) => wv.pop(),
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "为批量 Nano Banana 补齐缩放图片参数",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& wv.some(value => BATCH_NANO_IMAGE_FORMATS.has(value))
|
||
&& wv.some(value => BATCH_NANO_NAMING_RULES.has(value))
|
||
&& !NANO_BANANA_RESIZE_MODES.has(wv[wv.length - 1])
|
||
&& !NANO_BANANA_RESIZE_MODES.has(wv[wv.length - 6]),
|
||
apply: (wv) => wv.push("不缩放"),
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "将批量 Nano Banana 的保存参数置底并将 seed 移到末端",
|
||
when: (wv) => NANO_BANANA_MODELS.has(wv[1])
|
||
&& Number.isInteger(wv[wv.length - 6])
|
||
&& BATCH_NANO_IMAGE_FORMATS.has(wv[wv.length - 5])
|
||
&& Number.isInteger(wv[wv.length - 4])
|
||
&& BATCH_NANO_NAMING_RULES.has(wv[wv.length - 3])
|
||
&& typeof wv[wv.length - 2] === "string"
|
||
&& NANO_BANANA_RESIZE_MODES.has(wv[wv.length - 1]),
|
||
apply: (wv) => {
|
||
const start = wv.length - 6;
|
||
const [seed, format, quality, naming, savePath, resize] = wv.slice(start);
|
||
wv.splice(start, 6, resize, format, quality, naming, savePath, seed);
|
||
},
|
||
},
|
||
{
|
||
type: "BatchNanoBananaPro",
|
||
note: "移除批量 Nano Banana 已停用的图片随机抽取参数",
|
||
when: (wv) => {
|
||
const index = batchNanoRandomWidgetIndex(wv);
|
||
return NANO_BANANA_MODELS.has(wv[1])
|
||
&& index >= 0
|
||
&& NANO_BANANA_RESIZE_MODES.has(wv[wv.length - 6])
|
||
&& Number.isInteger(wv[wv.length - 1])
|
||
&& (wv[index] === "关闭"
|
||
|| (typeof wv[index] === "string"
|
||
&& /^[1-5](?:[,,、 ]+[1-5])*$/.test(wv[index])));
|
||
},
|
||
apply: (wv) => wv.splice(batchNanoRandomWidgetIndex(wv), 1),
|
||
},
|
||
{
|
||
type: "O1keyGPTImage",
|
||
note: "移除独立 GPT Image 节点已停用的色彩纠正参数",
|
||
when: (wv) => GPT_IMAGE_MODELS.has(wv[1])
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[wv.length - 4])
|
||
&& GPT_IMAGE_COLOR_CORRECTION_MODES.has(wv[wv.length - 3])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[wv.length - 2])
|
||
&& GPT_IMAGE_MODERATION_MODES.has(wv[wv.length - 1]),
|
||
apply: (wv) => wv.splice(wv.length - 3, 1),
|
||
},
|
||
{
|
||
type: "O1keyGPTImage",
|
||
note: "为独立 GPT Image 节点补齐缩放和背景参数",
|
||
when: (wv) => GPT_IMAGE_MODELS.has(wv[1])
|
||
&& !(
|
||
(Number.isInteger(wv[wv.length - 3])
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[wv.length - 2])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[wv.length - 1]))
|
||
|| (GPT_IMAGE_RESIZE_MODES.has(wv[wv.length - 3])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[wv.length - 2])
|
||
&& GPT_IMAGE_MODERATION_MODES.has(wv[wv.length - 1]))
|
||
|| (GPT_IMAGE_BACKGROUNDS.has(wv[wv.length - 3])
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[wv.length - 2])
|
||
&& Number.isInteger(wv[wv.length - 1]))
|
||
),
|
||
apply: (wv) => wv.push("智能缩放", "auto", "自动"),
|
||
},
|
||
{
|
||
type: "O1keyGPTImage",
|
||
note: "移除独立 GPT Image 节点已删除的内容审查强度参数",
|
||
when: (wv) => GPT_IMAGE_MODELS.has(wv[1])
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[wv.length - 3])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[wv.length - 2])
|
||
&& GPT_IMAGE_MODERATION_MODES.has(wv[wv.length - 1]),
|
||
apply: (wv) => wv.pop(),
|
||
},
|
||
{
|
||
type: "O1keyGPTImage",
|
||
note: "调整独立 GPT Image 节点的背景、缩放和 seed 顺序",
|
||
when: (wv) => GPT_IMAGE_MODELS.has(wv[1])
|
||
&& Number.isInteger(wv[wv.length - 3])
|
||
&& GPT_IMAGE_RESIZE_MODES.has(wv[wv.length - 2])
|
||
&& GPT_IMAGE_BACKGROUNDS.has(wv[wv.length - 1]),
|
||
apply: (wv) => {
|
||
const start = wv.length - 3;
|
||
const [seed, resize, background] = wv.slice(start);
|
||
wv.splice(start, 3, background, resize, seed);
|
||
},
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "为批量 GPT Image 节点补齐缩放和背景参数",
|
||
when: (wv, node) => node.type === "O1keyGPTImageBatch"
|
||
&& GPT_IMAGE_MODELS.has(wv[1])
|
||
&& !isLegacyGPTImageBatchWidgetLayout(wv)
|
||
&& !isGPTImageBatchWidgetLayoutWithRandomSelection(wv)
|
||
&& !isCurrentGPTImageBatchWidgetLayout(wv),
|
||
apply: (wv) => wv.push("智能缩放", "不纠正", "auto", "自动"),
|
||
},
|
||
{
|
||
type: "O1keyGPTImageBatch",
|
||
note: "同步 GPT Image 批量节点的参数顺序并移除旧控件和随机抽取",
|
||
when: (wv) => GPT_IMAGE_MODELS.has(wv[1])
|
||
&& (isLegacyGPTImageBatchWidgetLayout(wv)
|
||
|| isGPTImageBatchWidgetLayoutWithRandomSelection(wv)),
|
||
apply: (wv) => {
|
||
if (isGPTImageBatchWidgetLayoutWithRandomSelection(wv)) {
|
||
const randomSelectionIndex = batchGPTImageRandomSelectionIndex(wv);
|
||
const concurrencyIndex = batchGPTImageConcurrencyIndex(wv);
|
||
const namingIndex = findLastIndex(wv, value => GPT_IMAGE_BATCH_NAMING_RULES.has(value));
|
||
const formatIndex = namingIndex - 2;
|
||
const hasLegacyConcurrency = Number.isInteger(wv[concurrencyIndex])
|
||
&& formatIndex === concurrencyIndex + 1;
|
||
wv.splice(randomSelectionIndex, hasLegacyConcurrency ? 2 : 1);
|
||
return;
|
||
}
|
||
const namingIndex = findLastIndex(wv, value => GPT_IMAGE_BATCH_NAMING_RULES.has(value));
|
||
const formatIndex = namingIndex - 1;
|
||
const seedIndex = formatIndex - 1;
|
||
const randomSelectionIndex = batchGPTImageRandomSelectionIndex(wv);
|
||
const savePathIndex = namingIndex + 1;
|
||
const resizeIndex = namingIndex + 2;
|
||
let backgroundIndex = resizeIndex + 1;
|
||
if (GPT_IMAGE_COLOR_CORRECTION_MODES.has(wv[backgroundIndex])) backgroundIndex++;
|
||
const [format, background, naming, savePath, resize, seed] = [
|
||
wv[formatIndex],
|
||
wv[backgroundIndex],
|
||
wv[namingIndex],
|
||
wv[savePathIndex],
|
||
wv[resizeIndex],
|
||
wv[seedIndex],
|
||
];
|
||
wv.splice(0, wv.length, ...wv.slice(0, randomSelectionIndex),
|
||
format, background, naming, savePath, resize, seed);
|
||
},
|
||
},
|
||
{
|
||
type: "*",
|
||
note: "将独立 Nano Banana 节点已移除的 512 分辨率迁移为 1K",
|
||
when: (wv, node) => NANO_BANANA_NODE_TYPES.has(node.type)
|
||
&& NANO_BANANA_MODELS.has(wv[1])
|
||
&& REMOVED_NANO_BANANA_RESOLUTIONS.has(wv[4]),
|
||
apply: (wv) => { wv[4] = "1K"; },
|
||
},
|
||
];
|
||
|
||
function _isSeedanceAutoPassMode(value) {
|
||
return value === "多模态"
|
||
|| value === "首尾帧"
|
||
|| value === "多模态参考生视频"
|
||
|| value === "图生视频-首帧"
|
||
|| value === "图生视频-首尾帧"
|
||
|| value === "文生视频";
|
||
}
|
||
|
||
function migrateSeedanceAutoPassInputPaths(node) {
|
||
if (node?.type !== "SeedanceAutoPass" || !Array.isArray(node.inputs)) return 0;
|
||
const groups = ["参考图片", "参考视频", "参考音频"];
|
||
const directInputs = new Set(["首帧图片", "尾帧图片", "图片素材ID", "视频素材ID", "音频素材ID"]);
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const name = input?.name;
|
||
if (typeof name !== "string") continue;
|
||
if (name === "素材创建") {
|
||
input.name = "素材创建模式";
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (name === "生成模式.提示词") {
|
||
input.name = "提示词";
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (name === "生成模式.联网搜索") {
|
||
input.name = "联网搜索";
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (name.startsWith("生成模式.")) {
|
||
input.name = name.slice("生成模式.".length);
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (name.startsWith("素材创建.")) {
|
||
const childName = name.slice("素材创建.".length);
|
||
if (directInputs.has(childName)) {
|
||
input.name = childName.endsWith("素材ID") ? `${childName}1` : childName;
|
||
migrated++;
|
||
}
|
||
continue;
|
||
}
|
||
if (name.endsWith("素材ID") && directInputs.has(name)) {
|
||
input.name = `${name}1`;
|
||
migrated++;
|
||
continue;
|
||
}
|
||
for (const group of groups) {
|
||
if (name.startsWith(`${group}.`)) break;
|
||
if (new RegExp(`^${group}\\d+$`).test(name)) {
|
||
input.name = `${group}.${name}`;
|
||
migrated++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateSeedanceMultiModalInputPaths(node) {
|
||
if (node?.type !== "SeedanceMultiModal" || !Array.isArray(node.inputs)) return 0;
|
||
const groups = ["参考图片", "参考视频", "参考音频"];
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const name = input?.name;
|
||
if (typeof name !== "string") continue;
|
||
const legacyImageAsset = /^真人素材ID([1-9]|[12]\d|30)$/.exec(name);
|
||
if (legacyImageAsset) {
|
||
input.name = `图片素材ID${legacyImageAsset[1]}`;
|
||
migrated++;
|
||
continue;
|
||
}
|
||
for (const group of groups) {
|
||
if (name.startsWith(`${group}.`)) break;
|
||
if (new RegExp(`^${group}\\d+$`).test(name)) {
|
||
input.name = `${group}.${name}`;
|
||
migrated++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
const SEEDANCE_ELEMENT_INPUT_RENAMES = new Map([
|
||
["真人照片", "照片"],
|
||
["真人视频", "视频"],
|
||
["真人音频", "音频"],
|
||
]);
|
||
|
||
function migrateSeedanceElementInputNames(node) {
|
||
if (node?.type !== "SeedanceElementCreate" || !Array.isArray(node.inputs)) return 0;
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const replacement = SEEDANCE_ELEMENT_INPUT_RENAMES.get(input?.name);
|
||
if (!replacement) continue;
|
||
input.name = replacement;
|
||
migrated++;
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateBatchNanoBananaInputPaths(node) {
|
||
if (node?.type !== "BatchNanoBananaPro" || !Array.isArray(node.inputs)) return 0;
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const name = input?.name;
|
||
if (typeof name !== "string" || name.startsWith("参考图组.")) continue;
|
||
const pathMatch = /^(?:图片文件夹数量\.|图片路径数量\.)?(?:图片路径|文件夹)([1-5])$/.exec(name);
|
||
if (pathMatch) {
|
||
const index = Number(pathMatch[1]);
|
||
input.name = `图片路径数量.${index === 1 ? "参考图1(主图)" : `参考图${index}`}`;
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (name === "图片配对模式" || name === "图片随机抽取") {
|
||
input.name = `图片路径数量.${name}`;
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (name.startsWith("图片文件夹数量.")) {
|
||
input.name = `图片路径数量.${name.slice("图片文件夹数量.".length)}`;
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (/^参考图[1-9]$/.test(name)) {
|
||
input.name = `参考图组.${name}`;
|
||
migrated++;
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateUniversalLLMInputPaths(node) {
|
||
if (node?.type !== "UniversalLLMChat" || !Array.isArray(node.inputs)) return 0;
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const name = input?.name;
|
||
if (typeof name !== "string" || name.startsWith("图片组.")) continue;
|
||
if (name === "图片") {
|
||
input.name = "图片组.图片1";
|
||
migrated++;
|
||
continue;
|
||
}
|
||
if (/^图片[1-9]$/.test(name)) {
|
||
input.name = `图片组.${name}`;
|
||
migrated++;
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateGPTImageInputPaths(node) {
|
||
if (node?.type !== "O1keyGPTImage" || !Array.isArray(node.inputs)) return 0;
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const name = input?.name;
|
||
if (typeof name !== "string" || name.startsWith("参考图组.")) continue;
|
||
if (/^参考图[1-9]$/.test(name)) {
|
||
input.name = `参考图组.${name}`;
|
||
migrated++;
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateGPTImageBatchInputPaths(node) {
|
||
if (node?.type !== "O1keyGPTImageBatch" || !Array.isArray(node.inputs)) return 0;
|
||
let migrated = 0;
|
||
for (let index = node.inputs.length - 1; index >= 0; index--) {
|
||
const input = node.inputs[index];
|
||
const name = input?.name;
|
||
if (typeof name !== "string") continue;
|
||
if (/^文件夹([1-5])$/.test(name)) {
|
||
const index = Number(name.match(/^文件夹([1-5])$/)[1]);
|
||
input.name = `图片路径数量.${index === 1 ? "参考图1(主图)" : `参考图${index}`}`;
|
||
migrated++;
|
||
} else if (name === "图片配对模式") {
|
||
input.name = "图片路径数量.图片配对模式";
|
||
migrated++;
|
||
} else if (name === "图片随机抽取") {
|
||
node.inputs.splice(index, 1);
|
||
migrated++;
|
||
} else if (/^参考图[1-9]$/.test(name)) {
|
||
input.name = `参考图组.${name}`;
|
||
migrated++;
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateGrokVideoInputNames(node) {
|
||
if (node?.type !== "O1keyGrokVideo" || !Array.isArray(node.inputs)) return 0;
|
||
let migrated = 0;
|
||
for (const input of node.inputs) {
|
||
const match = /^参考图([1-7])$/.exec(input?.name);
|
||
if (!match) continue;
|
||
input.name = `图片${match[1]}`;
|
||
migrated++;
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function collectNodes(graphData) {
|
||
const out = [];
|
||
if (!graphData || typeof graphData !== "object") return out;
|
||
if (Array.isArray(graphData.nodes)) out.push(...graphData.nodes);
|
||
const subgraphs = graphData.definitions?.subgraphs;
|
||
if (Array.isArray(subgraphs)) {
|
||
for (const sub of subgraphs) {
|
||
if (Array.isArray(sub?.nodes)) out.push(...sub.nodes);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function collectGraphContainers(graphData) {
|
||
const out = [];
|
||
if (!graphData || typeof graphData !== "object") return out;
|
||
if (Array.isArray(graphData.nodes)) out.push(graphData);
|
||
const subgraphs = graphData.definitions?.subgraphs;
|
||
if (Array.isArray(subgraphs)) {
|
||
for (const subgraph of subgraphs) {
|
||
if (Array.isArray(subgraph?.nodes)) out.push(subgraph);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function linkedUnifiedGenerator(container, saveNode, nodesById) {
|
||
const propertyId = saveNode?.properties?.o1keyGeneratorNodeId;
|
||
const propertyNode = nodesById.get(String(propertyId));
|
||
if (propertyNode?.type === "O1keyImageGenerator") return propertyNode;
|
||
|
||
const linkId = saveNode?.inputs?.[0]?.link;
|
||
if (linkId == null || !Array.isArray(container?.links)) return null;
|
||
const link = container.links.find((item) => String(
|
||
Array.isArray(item) ? item[0] : item?.id,
|
||
) === String(linkId));
|
||
const originId = Array.isArray(link) ? link[1] : link?.origin_id;
|
||
const originNode = nodesById.get(String(originId));
|
||
return originNode?.type === "O1keyImageGenerator" ? originNode : null;
|
||
}
|
||
|
||
function migrateUnifiedExternalPromptInput(container) {
|
||
const nodes = Array.isArray(container?.nodes) ? container.nodes : [];
|
||
const removedLinks = new Set();
|
||
let migrated = 0;
|
||
for (const node of nodes.filter((item) => item?.type === "O1keyImageGenerator")) {
|
||
if (!Array.isArray(node.inputs)) continue;
|
||
for (let index = node.inputs.length - 1; index >= 0; index--) {
|
||
const input = node.inputs[index];
|
||
if (input?.name !== "external_prompt") continue;
|
||
if (input.link != null) removedLinks.add(String(input.link));
|
||
node.inputs.splice(index, 1);
|
||
migrated++;
|
||
}
|
||
}
|
||
if (!removedLinks.size) return migrated;
|
||
if (Array.isArray(container.links)) {
|
||
container.links = container.links.filter((link) => !removedLinks.has(String(
|
||
Array.isArray(link) ? link[0] : link?.id,
|
||
)));
|
||
}
|
||
for (const node of nodes) {
|
||
for (const output of Array.isArray(node?.outputs) ? node.outputs : []) {
|
||
if (Array.isArray(output?.links)) {
|
||
output.links = output.links.filter((id) => !removedLinks.has(String(id)));
|
||
}
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateBatchNanoRandomInput(container) {
|
||
const nodes = Array.isArray(container?.nodes) ? container.nodes : [];
|
||
const removedLinks = new Set();
|
||
let migrated = 0;
|
||
for (const node of nodes.filter((item) => item?.type === "BatchNanoBananaPro")) {
|
||
if (!Array.isArray(node.inputs)) continue;
|
||
for (let index = node.inputs.length - 1; index >= 0; index--) {
|
||
const input = node.inputs[index];
|
||
if (!/(?:^|\.)图片随机抽取$/.test(String(input?.name ?? ""))) continue;
|
||
if (input.link != null) removedLinks.add(String(input.link));
|
||
node.inputs.splice(index, 1);
|
||
for (const link of Array.isArray(container.links) ? container.links : []) {
|
||
const targetId = Array.isArray(link) ? link[3] : link?.target_id;
|
||
const targetSlot = Array.isArray(link) ? link[4] : link?.target_slot;
|
||
if (String(targetId) !== String(node.id) || !Number.isInteger(targetSlot) || targetSlot <= index) continue;
|
||
if (Array.isArray(link)) link[4]--;
|
||
else link.target_slot--;
|
||
}
|
||
migrated++;
|
||
}
|
||
}
|
||
if (!removedLinks.size) return migrated;
|
||
if (Array.isArray(container.links)) {
|
||
container.links = container.links.filter((link) => !removedLinks.has(String(
|
||
Array.isArray(link) ? link[0] : link?.id,
|
||
)));
|
||
}
|
||
for (const node of nodes) {
|
||
for (const output of Array.isArray(node?.outputs) ? node.outputs : []) {
|
||
if (Array.isArray(output?.links)) {
|
||
output.links = output.links.filter((id) => !removedLinks.has(String(id)));
|
||
}
|
||
}
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateUnifiedSaveSettings(container) {
|
||
const nodes = Array.isArray(container?.nodes) ? container.nodes : [];
|
||
const nodesById = new Map(nodes.map((node) => [String(node?.id), node]));
|
||
const defaults = ["自定义前缀", "o1key", "原始", ""];
|
||
let migrated = 0;
|
||
|
||
for (const saveNode of nodes.filter((node) => node?.type === "O1keyImageSave")) {
|
||
const values = saveNode?.widgets_values;
|
||
if (!Array.isArray(values) || values.length === 0) continue;
|
||
const legacy = [...values];
|
||
if (legacy.length === 1) legacy.push("原始");
|
||
if (legacy.length === 2) legacy.push("");
|
||
if (legacy.length === 3) legacy.push("自定义前缀");
|
||
const generator = linkedUnifiedGenerator(container, saveNode, nodesById);
|
||
const generatorValues = generator?.widgets_values;
|
||
if (!Array.isArray(generatorValues) || generatorValues.length < 18) continue;
|
||
if (generatorValues.length === 18) {
|
||
generatorValues.push(legacy[3], legacy[0], legacy[1], legacy[2]);
|
||
} else {
|
||
while (generatorValues.length < 22) {
|
||
generatorValues.push(defaults[generatorValues.length - 18]);
|
||
}
|
||
}
|
||
values.splice(0, values.length);
|
||
migrated++;
|
||
}
|
||
|
||
for (const generator of nodes.filter((node) => node?.type === "O1keyImageGenerator")) {
|
||
const values = generator?.widgets_values;
|
||
if (!Array.isArray(values) || values.length < 18) continue;
|
||
const before = values.length;
|
||
while (values.length < 22) values.push(defaults[values.length - 18]);
|
||
if (values.length !== before) migrated++;
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateUnifiedOnlineSearch(container) {
|
||
const nodes = Array.isArray(container?.nodes) ? container.nodes : [];
|
||
let migrated = 0;
|
||
for (const node of nodes.filter((item) => item?.type === "O1keyImageGenerator")) {
|
||
const values = node?.widgets_values;
|
||
if (!Array.isArray(values)) continue;
|
||
const expectedLength = GPT_IMAGE_MODERATION_MODES.has(values[14]) ? 22 : 21;
|
||
if (values.length !== expectedLength) continue;
|
||
values.push("关闭");
|
||
migrated++;
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateUnifiedLayerDecomposition(container) {
|
||
const nodes = Array.isArray(container?.nodes) ? container.nodes : [];
|
||
let migrated = 0;
|
||
for (const node of nodes.filter((item) => item?.type === "O1keyImageGenerator")) {
|
||
const values = node?.widgets_values;
|
||
if (!Array.isArray(values)) continue;
|
||
const expectedLength = GPT_IMAGE_MODERATION_MODES.has(values[14]) ? 23 : 22;
|
||
if (values.length !== expectedLength) continue;
|
||
values.push(false);
|
||
migrated++;
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
function migrateUnifiedModeration(container) {
|
||
let migrated = 0;
|
||
for (const node of (container?.nodes || []).filter((item) => item?.type === "O1keyImageGenerator")) {
|
||
const values = node?.widgets_values;
|
||
// The retired widget occupied index 14, before batch and save settings.
|
||
if (!Array.isArray(values) || !GPT_IMAGE_MODERATION_MODES.has(values[14])) continue;
|
||
values.splice(14, 1);
|
||
migrated++;
|
||
}
|
||
return migrated;
|
||
}
|
||
|
||
app.registerExtension({
|
||
name: "o1key.workflowMigration",
|
||
|
||
beforeConfigureGraph(graphData) {
|
||
let migrated = 0;
|
||
for (const node of collectNodes(graphData)) {
|
||
migrated += migrateSeedanceAutoPassInputPaths(node);
|
||
migrated += migrateSeedanceMultiModalInputPaths(node);
|
||
migrated += migrateSeedanceElementInputNames(node);
|
||
migrated += migrateBatchNanoBananaInputPaths(node);
|
||
migrated += migrateUniversalLLMInputPaths(node);
|
||
migrated += migrateGPTImageInputPaths(node);
|
||
migrated += migrateGPTImageBatchInputPaths(node);
|
||
migrated += migrateGrokVideoInputNames(node);
|
||
const wv = node?.widgets_values;
|
||
if (!Array.isArray(wv)) continue;
|
||
for (const rule of MIGRATIONS) {
|
||
if (rule.type !== "*" && node.type !== rule.type) continue;
|
||
try {
|
||
if (rule.when(wv, node)) {
|
||
rule.apply(wv, node);
|
||
migrated++;
|
||
}
|
||
} catch (e) {
|
||
console.warn(`[o1key] 工作流迁移失败: ${rule.note}`, e);
|
||
}
|
||
}
|
||
}
|
||
for (const container of collectGraphContainers(graphData)) {
|
||
migrated += migrateBatchNanoRandomInput(container);
|
||
migrated += migrateUnifiedExternalPromptInput(container);
|
||
migrated += migrateUnifiedSaveSettings(container);
|
||
migrated += migrateUnifiedOnlineSearch(container);
|
||
migrated += migrateUnifiedLayerDecomposition(container);
|
||
migrated += migrateUnifiedModeration(container);
|
||
}
|
||
if (migrated > 0) {
|
||
console.log(`[o1key] 工作流参数迁移:已修复 ${migrated} 处参数或端口`);
|
||
}
|
||
},
|
||
});
|