-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathimage_upload.js
More file actions
255 lines (235 loc) · 8.13 KB
/
image_upload.js
File metadata and controls
255 lines (235 loc) · 8.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import { app } from "../../../scripts/app.js";
import { applyBadgeToNode } from './handle_load_nodes.js'
/*
BizyAir_Seedream4_5
BizyAir_NanoBananaPro
BizyAir_NanoBananaProOfficial
BizyAir_Seedream5
BizyAir_Seedance_2_0_REF_API
BizyAir_Seedance_2_0_Multimodal_API
BizyAir_Kling_O3_VI2V_REF_API
BizyAir_Kling_O3_VIDEO_EDIT_API
BizyAir_Wan_V2_7_IMAGE_API
以及所有包含 "inputcount" 属性的节点,添加多图片上传功能
*/
// 简单的链式callback实现
function chainCallback(originalCallback, newCallback) {
return function (...args) {
if (originalCallback) {
originalCallback.apply(this, args);
}
newCallback.apply(this, args);
};
}
function initializeDynamicInputs(node) {
// 检查是否包含 inputcount widget 或者是我们指定的节点
const hasInputCount = node.widgets?.some(w => w.name === "inputcount");
const nodeIdentifier = node.comfyClass || node.type;
const isHardcodedNode = [
"BizyAir_Seedream4_5",
"BizyAir_NanoBananaPro",
"BizyAir_NanoBananaProOfficial",
"BizyAir_NanoBanana2",
"BizyAir_Seedream5",
"BizyAir_VIDU_Q3_T2V_API",
"BizyAir_VIDU_Q3_I2V_API",
"BizyAir_Seedance_2_0_REF_API",
"BizyAir_Seedance_2_0_Multimodal_API",
"BizyAir_Kling_O3_VI2V_REF_API",
"BizyAir_Kling_O3_VIDEO_EDIT_API",
"BizyAir_Wan_V2_7_IMAGE_API"
].includes(nodeIdentifier);
if (!hasInputCount && !isHardcodedNode) {
return;
}
node._type = "IMAGE";
if (!node.inputs) {
node.inputs = [];
}
//VIDU 节点不需要 inputcount 和 Update inputs 按钮
const isVidu = nodeIdentifier === "BizyAir_VIDU_Q3_T2V_API" || nodeIdentifier === "BizyAir_VIDU_Q3_I2V_API";
if (!isVidu) {
// 先检查是否存在 inputcount widget
let inputCountWidget = node.widgets?.find(
(w) => w.name === "inputcount"
);
// 如果不存在,先创建 inputcount widget(在按钮之前创建,确保显示顺序)
if (!inputCountWidget) {
// 计算当前 IMAGE 类型输入数量
const currentImageInputs = node.inputs.filter(
(input) => input.type === node._type
).length;
// 创建 inputcount widget,默认值为当前输入数量或1
// 使用 precision: 0 确保是整数类型
inputCountWidget = node.addWidget(
"number",
"inputcount",
currentImageInputs || 1,
null,
{
min: 1,
max: 10,
step: 10, // 旧参数(兼容性),是 step2 的 10 倍
step2: 1, // 新参数,实际的步长
precision: 0, // 设置为 0 表示整数
}
);
}
// 检查是否已经存在 "Update inputs" 按钮,避免重复添加
let updateButton = node.widgets?.find(
(w) => w.name === "Update inputs(支持多图点我)"
);
if (!updateButton) {
// 然后添加 "Update inputs" 按钮(会在 inputcount 之后显示)
updateButton = node.addWidget("button", "Update inputs(支持多图点我)", null, () => {
if (!node.inputs) {
node.inputs = [];
}
// 查找 inputcount widget
const inputCountWidget = node.widgets?.find(
(w) => w.name === "inputcount"
);
// 如果仍然找不到,创建它
if (!inputCountWidget) {
const currentImageInputs = node.inputs.filter(
(input) => input.type === node._type
).length;
node.addWidget(
"number",
"inputcount",
currentImageInputs || 1,
null,
{
min: 1,
max: 10,
step: 10, // 旧参数(兼容性),是 step2 的 10 倍
step2: 1, // 新参数,实际的步长
precision: 0, // 设置为 0 表示整数
}
);
return;
}
const target_number_of_inputs = inputCountWidget.value || 1;
const num_inputs = node.inputs.filter(
(input) => input.type === node._type
).length;
if (target_number_of_inputs === num_inputs) {
return; // already set, do nothing
}
if (target_number_of_inputs < num_inputs) {
const inputs_to_remove = num_inputs - target_number_of_inputs;
for (let i = 0; i < inputs_to_remove; i++) {
node.removeInput(node.inputs.length - 1);
}
} else {
for (let i = num_inputs + 1; i <= target_number_of_inputs; ++i) {
node.addInput(`image_${i}`, node._type, { shape: 7 });
}
}
});
}
}
// 添加下拉选择 widget
if (
nodeIdentifier === "BizyAir_NanoBananaPro" ||
nodeIdentifier === "BizyAir_NanoBananaProOfficial" ||
nodeIdentifier === "BizyAir_NanoBanana2" ||
nodeIdentifier === "BizyAir_VIDU_Q3_T2V_API" ||
nodeIdentifier === "BizyAir_VIDU_Q3_I2V_API"
) {
const modeWidget = node.widgets?.find(w => w.name === "mode");
if (modeWidget) {
modeWidget._bizyairManualPriceRefresh = true;
modeWidget.callback = () => {
applyBadgeToNode(node, true);
};
// 调整顺序:将 mode 移动到最后(确保在 Update inputs 按钮下面)
const idx = node.widgets.indexOf(modeWidget);
if (idx !== -1) {
node.widgets.splice(idx, 1);
node.widgets.push(modeWidget);
}
} else {
const createdModeWidget = node.addWidget("combo", "mode", "third-party", () => {
applyBadgeToNode(node, true);
}, {
values: ["official", "third-party"],
tooltip: "官方渠道vs第三方渠道",
});
createdModeWidget._bizyairManualPriceRefresh = true;
}
}
}
app.registerExtension({
name: "bizyair.dynamicImageInputs",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// 检查是否有 inputcount 输入参数(从 nodeData 中判断)
let hasInputCountData = false;
if (nodeData.input) {
if (nodeData.input.required && nodeData.input.required.inputcount) {
hasInputCountData = true;
} else if (nodeData.input.optional && nodeData.input.optional.inputcount) {
hasInputCountData = true;
}
}
const isHardcodedNode = [
"BizyAir_Seedream4_5",
"BizyAir_NanoBananaPro",
"BizyAir_NanoBananaProOfficial",
"BizyAir_NanoBanana2",
"BizyAir_Seedream5",
"BizyAir_VIDU_Q3_T2V_API",
"BizyAir_VIDU_Q3_I2V_API",
"BizyAir_Seedance_2_0_REF_API",
"BizyAir_Seedance_2_0_Multimodal_API",
"BizyAir_Kling_O3_VI2V_REF_API",
"BizyAir_Kling_O3_VIDEO_EDIT_API",
"BizyAir_Wan_V2_7_IMAGE_API"
].includes(nodeData.name);
if (!hasInputCountData && !isHardcodedNode) {
return;
}
if (
nodeData.name === "BizyAir_NanoBananaPro" ||
nodeData.name === "BizyAir_NanoBananaProOfficial" ||
nodeData.name === "BizyAir_NanoBanana2" ||
nodeData.name === "BizyAir_VIDU_Q3_T2V_API" ||
nodeData.name === "BizyAir_VIDU_Q3_I2V_API"
) {
if (!nodeData.input) nodeData.input = {};
if (!nodeData.input.required) nodeData.input.required = {};
if (!nodeData.input.required.mode) {
nodeData.input.required.mode = [
["official", "third-party"],
{ default: "third-party", tooltip: "官方渠道vs第三方渠道" }
];
}
}
// 使用 onNodeCreated 原型方法(同步执行,确保在节点创建时立即初始化)
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
try {
const result = originalOnNodeCreated?.apply(this, arguments);
// 在节点创建时初始化 widgets
initializeDynamicInputs(this);
return result;
} catch (error) {
console.error("[bizyair.dynamicImageInputs] Error in onNodeCreated:", error);
}
};
// 使用 chainCallback 设置 onConfigure,确保在节点配置完成后也初始化 widgets(用于从 JSON 恢复时)
const originalOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = chainCallback(
originalOnConfigure,
function (info) {
// 在 configure 完成后初始化 widgets
initializeDynamicInputs(this);
}
);
},
nodeCreated(node) {
// 在节点创建时也初始化 widgets(作为备份,用于首次创建节点时)
// 注意:这是异步的,可能在其他扩展之后执行
initializeDynamicInputs(node);
},
});