bizydraft 0.2.49__py3-none-any.whl → 0.2.87__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bizydraft might be problematic. Click here for more details.

@@ -0,0 +1,684 @@
1
+ // 媒体节点配置获取与工具函数(与 hookLoad/model.js 结构一致,面向 media_load_nodes)
2
+ import { fetchMediaConfig } from "./configLoader.js";
3
+ import { getCookie, computeExt, hideWidget } from "../tool.js";
4
+
5
+ // 动态配置缓存(仅缓存媒体部分)
6
+ let mediaConfigCache = null;
7
+ let mediaConfigLoadPromise = null;
8
+
9
+ export const mediaNodeList = [
10
+ "LoadImage",
11
+ "LoadImageMask",
12
+ "LoadAudio",
13
+ "LoadVideo",
14
+ "Load3D",
15
+ "VHS_LoadVideo",
16
+ "VHS_LoadAudioUpload",
17
+ ];
18
+ // 常见的媒体输入字段名(作为回退匹配)
19
+ export const possibleMediaWidgetNames = [
20
+ "image",
21
+ "file",
22
+ "audio",
23
+ "video",
24
+ "model_file",
25
+ ];
26
+
27
+ // 获取媒体配置的API函数(使用共享配置加载器)
28
+ export async function fetchMediaConfigWithCache() {
29
+ if (mediaConfigCache) return mediaConfigCache;
30
+ if (mediaConfigLoadPromise) return mediaConfigLoadPromise;
31
+
32
+ mediaConfigLoadPromise = (async () => {
33
+ const config = await fetchMediaConfig();
34
+ if (config) {
35
+ mediaConfigCache = config;
36
+ }
37
+ return config;
38
+ })();
39
+
40
+ return mediaConfigLoadPromise;
41
+ }
42
+
43
+ // 根据节点名称获取媒体节点配置(仅使用缓存,不阻塞返回;触发后台预取)
44
+ export async function getMediaNodeConfig(nodeName) {
45
+ // 后台触发一次预取
46
+ if (!mediaConfigLoadPromise) {
47
+ try {
48
+ void fetchMediaConfigWithCache();
49
+ } catch (e) {}
50
+ }
51
+
52
+ if (mediaConfigCache && mediaConfigCache[nodeName]) {
53
+ return { nodeName, config: mediaConfigCache[nodeName] };
54
+ }
55
+ return null;
56
+ }
57
+
58
+ // 从媒体配置中提取此节点的输入键(过滤 disable_comfyagent)
59
+ export function getMediaInputKeys(mediaNodeConfig) {
60
+ if (
61
+ !mediaNodeConfig ||
62
+ !mediaNodeConfig.config ||
63
+ !mediaNodeConfig.config.inputs
64
+ )
65
+ return [];
66
+ const inputs = mediaNodeConfig.config.inputs;
67
+ const keys = [];
68
+ for (const key of Object.keys(inputs)) {
69
+ const cfg = inputs[key];
70
+ if (cfg && !cfg.disable_comfyagent) keys.push(key);
71
+ }
72
+ return keys;
73
+ }
74
+
75
+ export async function computeIsMediaNode(nodeName) {
76
+ if (mediaNodeList.includes(nodeName)) {
77
+ return true;
78
+ }
79
+
80
+ // 2. 检查media_load_nodes的keys
81
+ const config = await fetchMediaConfigWithCache();
82
+ if (config) {
83
+ if (config.hasOwnProperty(nodeName)) {
84
+ return true;
85
+ }
86
+ }
87
+
88
+ return false;
89
+ }
90
+
91
+ // 启动时后台预取(不阻塞后续逻辑)
92
+ try {
93
+ void fetchMediaConfigWithCache();
94
+ } catch (e) {}
95
+
96
+ // ==================== 媒体 Widget 处理函数 ====================
97
+
98
+ /**
99
+ * 查找单个媒体 widget(用于 media_widget)
100
+ */
101
+ export function findMediaWidget(nodeWidgets, apiInputKeys) {
102
+ // 优先使用 API 配置的 keys
103
+ if (apiInputKeys && apiInputKeys.length > 0) {
104
+ for (const key of apiInputKeys) {
105
+ const w = nodeWidgets.find((x) => x.name === key);
106
+ if (w) return w;
107
+ }
108
+ }
109
+ // 回退到常见媒体 widget 名称
110
+ return (
111
+ nodeWidgets.find((w) => possibleMediaWidgetNames.includes(w.name)) || null
112
+ );
113
+ }
114
+
115
+ /**
116
+ * 查找所有媒体 widget(用于 va_widgets)
117
+ */
118
+ export function findMediaWidgets(nodeWidgets, apiInputKeys) {
119
+ const widgets = [];
120
+
121
+ // 优先使用 API 配置的 keys
122
+ if (apiInputKeys && apiInputKeys.length > 0) {
123
+ for (const key of apiInputKeys) {
124
+ const w = nodeWidgets.find((x) => x.name === key);
125
+ if (w) widgets.push(w);
126
+ }
127
+ }
128
+
129
+ // 如果 API 配置没找到,使用回退逻辑
130
+ if (widgets.length === 0) {
131
+ for (const widgetName of possibleMediaWidgetNames) {
132
+ const w = nodeWidgets.find((x) => x.name === widgetName);
133
+ if (w) widgets.push(w);
134
+ }
135
+ }
136
+
137
+ return widgets;
138
+ }
139
+
140
+ /**
141
+ * 添加新文件到列表并更新相关数据
142
+ */
143
+ export function addNewFileToList(url, image_list, urlToNameMap, nameToItemMap) {
144
+ const fileName = url.split("/").pop();
145
+ const newItem = { name: fileName, url: url };
146
+ image_list.push(newItem);
147
+ urlToNameMap.set(url, fileName);
148
+ nameToItemMap.set(fileName, newItem);
149
+ return fileName;
150
+ }
151
+
152
+ /**
153
+ * 更新所有相关 widget 的选项列表
154
+ */
155
+ export function updateWidgetsOptions(widgets, image_list) {
156
+ const names = image_list.map((item) => item.name);
157
+ widgets.forEach((widget) => {
158
+ if (widget && widget.options) {
159
+ widget.options.values = names;
160
+ }
161
+ });
162
+ }
163
+
164
+ /**
165
+ * 处理新上传的文件:添加到列表并更新所有 widget
166
+ */
167
+ export function handleNewUploadedFile(
168
+ url,
169
+ image_list,
170
+ urlToNameMap,
171
+ nameToItemMap,
172
+ image_name_widget,
173
+ actualMediaWidget
174
+ ) {
175
+ const fileName = addNewFileToList(
176
+ url,
177
+ image_list,
178
+ urlToNameMap,
179
+ nameToItemMap
180
+ );
181
+
182
+ // 更新所有相关 widget 的选项列表
183
+ const widgetsToUpdate = [image_name_widget, actualMediaWidget].filter(
184
+ Boolean
185
+ );
186
+ updateWidgetsOptions(widgetsToUpdate, image_list);
187
+
188
+ return fileName;
189
+ }
190
+
191
+ /**
192
+ * 从输入中提取 URL(支持 string 和 array 格式)
193
+ */
194
+ export function extractUrlFromInput(input) {
195
+ if (typeof input === "string") {
196
+ return input;
197
+ } else if (Array.isArray(input) && input.length > 0) {
198
+ return typeof input[0] === "string" ? input[0] : input[0];
199
+ }
200
+ return null;
201
+ }
202
+
203
+ /**
204
+ * 初始化 Map 映射
205
+ */
206
+ export function initMaps(image_list) {
207
+ const urlToNameMap = new Map();
208
+ const nameToItemMap = new Map();
209
+ image_list.forEach((item) => {
210
+ urlToNameMap.set(item.url, item.name);
211
+ nameToItemMap.set(item.name, item);
212
+ });
213
+ return { urlToNameMap, nameToItemMap };
214
+ }
215
+
216
+ /**
217
+ * 创建 image_name_widget 的 callback(用于 va_widgets)
218
+ */
219
+ export function createImageNameWidgetCallbackForVaWidgets(
220
+ nameToItemMap,
221
+ va_widgets,
222
+ isBatchUpdating
223
+ ) {
224
+ return function (e) {
225
+ const item = nameToItemMap.get(e);
226
+ if (item) {
227
+ const image_url = decodeURIComponent(item.url);
228
+ isBatchUpdating.value = true;
229
+ va_widgets.forEach((va_widget) => {
230
+ va_widget.value = image_url;
231
+ if (va_widget.callback) {
232
+ va_widget.callback(image_url);
233
+ }
234
+ });
235
+ isBatchUpdating.value = false;
236
+ }
237
+ };
238
+ }
239
+
240
+ /**
241
+ * 创建 image_name_widget 的 callback(用于 media_widget)
242
+ */
243
+ export function createImageNameWidgetCallbackForMediaWidget(
244
+ nameToItemMap,
245
+ media_widget
246
+ ) {
247
+ return function (e) {
248
+ const item = nameToItemMap.get(e);
249
+ if (item) {
250
+ const image_url = decodeURIComponent(item.url);
251
+ media_widget.value = image_url;
252
+ if (media_widget.callback) {
253
+ media_widget.callback(image_url);
254
+ }
255
+ }
256
+ };
257
+ }
258
+
259
+ /**
260
+ * 创建 value setter(用于 va_widget)
261
+ */
262
+ export function createVaWidgetValueSetter(
263
+ va_widget,
264
+ urlToNameMap,
265
+ nameToItemMap,
266
+ image_list,
267
+ image_name_widget,
268
+ actualMediaWidget,
269
+ isBatchUpdating
270
+ ) {
271
+ let _value = va_widget.value;
272
+
273
+ return {
274
+ get: () => _value,
275
+ set: function (newValue) {
276
+ const oldValue = _value;
277
+ _value = newValue;
278
+ console.log(
279
+ `[hookLoadMedia] va_widget.value 被设置, widget.name=${va_widget.name}, oldValue=`,
280
+ oldValue,
281
+ ", newValue=",
282
+ newValue
283
+ );
284
+
285
+ // 批量更新时跳过监听逻辑
286
+ if (isBatchUpdating.value) {
287
+ console.log(
288
+ `[hookLoadMedia] 批量更新中,跳过处理, widget.name=${va_widget.name}`
289
+ );
290
+ return;
291
+ }
292
+
293
+ // 如果值没有变化,不需要处理
294
+ if (oldValue === newValue) {
295
+ console.log(
296
+ `[hookLoadMedia] 值未变化,跳过处理, widget.name=${va_widget.name}`
297
+ );
298
+ return;
299
+ }
300
+
301
+ // 使用 Map 快速查找(O(1))
302
+ const name = urlToNameMap.get(newValue);
303
+ if (name && image_name_widget) {
304
+ console.log(
305
+ `[hookLoadMedia] 找到匹配的name, widget.name=${va_widget.name}, name=`,
306
+ name
307
+ );
308
+ image_name_widget.value = name;
309
+ } else if (
310
+ image_name_widget &&
311
+ newValue &&
312
+ typeof newValue === "string" &&
313
+ newValue.includes("/")
314
+ ) {
315
+ // 如果没找到,可能是新上传的文件,需要添加到列表
316
+ const fileName = newValue.split("/").pop();
317
+ console.log(
318
+ `[hookLoadMedia] 未找到匹配的name, widget.name=${va_widget.name}, 可能是新文件, fileName=${fileName}`
319
+ );
320
+
321
+ // 检查是否真的是新文件(URL格式)
322
+ if (newValue.startsWith("http") || newValue.startsWith("/")) {
323
+ handleNewUploadedFile(
324
+ newValue,
325
+ image_list,
326
+ urlToNameMap,
327
+ nameToItemMap,
328
+ image_name_widget,
329
+ actualMediaWidget
330
+ );
331
+ console.log(
332
+ `[hookLoadMedia] 新文件已通过value setter添加到列表, 当前列表长度=${image_list.length}`
333
+ );
334
+ }
335
+
336
+ image_name_widget.value = fileName;
337
+ }
338
+ },
339
+ };
340
+ }
341
+
342
+ /**
343
+ * 创建 va_widget 的 callback
344
+ */
345
+ export function createVaWidgetCallback(
346
+ va_widget,
347
+ urlToNameMap,
348
+ nameToItemMap,
349
+ image_list,
350
+ image_name_widget,
351
+ actualMediaWidget,
352
+ originalVaCallback
353
+ ) {
354
+ return function (e) {
355
+ console.log(
356
+ `[hookLoadMedia] va_widget.callback 被触发, widget.name=${va_widget.name}, e=`,
357
+ e
358
+ );
359
+
360
+ if (image_name_widget) {
361
+ const url = extractUrlFromInput(e);
362
+ if (url) {
363
+ const name = urlToNameMap.get(url);
364
+ if (name) {
365
+ image_name_widget.value = name;
366
+ } else {
367
+ // 如果没找到,可能是新上传的文件
368
+ if (
369
+ typeof e === "string" &&
370
+ !e.startsWith("http") &&
371
+ !e.startsWith("/")
372
+ ) {
373
+ // 不是 URL 格式,直接使用文件名
374
+ image_name_widget.value = e.split("/").pop();
375
+ } else {
376
+ // 是新上传的文件,添加到列表
377
+ const fileName = handleNewUploadedFile(
378
+ url,
379
+ image_list,
380
+ urlToNameMap,
381
+ nameToItemMap,
382
+ image_name_widget,
383
+ actualMediaWidget
384
+ );
385
+ console.log(
386
+ `[hookLoadMedia] 检测到新上传的文件, url=${url}, fileName=${fileName}, 当前列表长度=${image_list.length}`
387
+ );
388
+ image_name_widget.value = fileName;
389
+ }
390
+ }
391
+ }
392
+ }
393
+
394
+ // 调用原始callback
395
+ if (originalVaCallback) {
396
+ console.log(
397
+ `[hookLoadMedia] 调用原始callback, widget.name=${va_widget.name}`
398
+ );
399
+ originalVaCallback(e);
400
+ } else {
401
+ console.log(
402
+ `[hookLoadMedia] 原始callback不存在, widget.name=${va_widget.name}`
403
+ );
404
+ }
405
+ };
406
+ }
407
+
408
+ /**
409
+ * 创建 media_widget 的 callback
410
+ */
411
+ export function createMediaWidgetCallback(
412
+ media_widget,
413
+ urlToNameMap,
414
+ nameToItemMap,
415
+ image_list,
416
+ image_name_widget,
417
+ actualMediaWidget,
418
+ originalCallback
419
+ ) {
420
+ return function (e) {
421
+ console.log("media_widget.callback", e);
422
+ if (typeof e == "string") {
423
+ // 使用 Map 快速查找(O(1))
424
+ const item = e.includes("http")
425
+ ? urlToNameMap.has(e)
426
+ ? { url: e, name: urlToNameMap.get(e) }
427
+ : null
428
+ : nameToItemMap
429
+ ? nameToItemMap.get(e)
430
+ : null;
431
+
432
+ const image_url = item ? decodeURIComponent(item.url) : e;
433
+
434
+ image_name_widget.value = item ? item.name : e;
435
+ media_widget.value = image_url;
436
+ if (originalCallback) {
437
+ originalCallback([image_url]);
438
+ }
439
+ } else {
440
+ // 处理数组格式的输入(如文件上传)
441
+ const url = extractUrlFromInput(e);
442
+ if (url) {
443
+ const existingName = urlToNameMap.get(url);
444
+
445
+ if (existingName) {
446
+ // 如果已经在列表中,直接使用
447
+ image_name_widget.value = existingName;
448
+ media_widget.value = url;
449
+ } else {
450
+ // 如果不在列表中,说明是新上传的文件,需要添加到列表
451
+ const fileName = handleNewUploadedFile(
452
+ url,
453
+ image_list,
454
+ urlToNameMap,
455
+ nameToItemMap,
456
+ image_name_widget,
457
+ actualMediaWidget
458
+ );
459
+ console.log(
460
+ `[hookLoadMedia] 检测到新上传的文件(media_widget分支), url=${url}, fileName=${fileName}, 当前列表长度=${image_list.length}`
461
+ );
462
+ image_name_widget.value = fileName;
463
+ media_widget.value = url;
464
+ }
465
+ }
466
+
467
+ if (originalCallback) {
468
+ originalCallback(e);
469
+ }
470
+ }
471
+ };
472
+ }
473
+
474
+ /**
475
+ * 设置 va_widget 的 value setter
476
+ */
477
+ export function setupVaWidgetValueSetter(
478
+ va_widget,
479
+ urlToNameMap,
480
+ nameToItemMap,
481
+ image_list,
482
+ image_name_widget,
483
+ actualMediaWidget,
484
+ isBatchUpdating
485
+ ) {
486
+ const existingDescriptor = Object.getOwnPropertyDescriptor(
487
+ va_widget,
488
+ "value"
489
+ );
490
+
491
+ if (existingDescriptor && !existingDescriptor.configurable) {
492
+ return; // 跳过不可配置的属性
493
+ }
494
+
495
+ if (existingDescriptor) {
496
+ delete va_widget.value;
497
+ }
498
+
499
+ const valueSetter = createVaWidgetValueSetter(
500
+ va_widget,
501
+ urlToNameMap,
502
+ nameToItemMap,
503
+ image_list,
504
+ image_name_widget,
505
+ actualMediaWidget,
506
+ isBatchUpdating
507
+ );
508
+
509
+ Object.defineProperty(va_widget, "value", {
510
+ get: valueSetter.get,
511
+ set: valueSetter.set,
512
+ enumerable: true,
513
+ configurable: true,
514
+ });
515
+ }
516
+
517
+ /**
518
+ * 处理 va_widgets
519
+ */
520
+ export function setupVaWidgets(
521
+ node,
522
+ va_widgets,
523
+ image_list,
524
+ urlToNameMap,
525
+ nameToItemMap,
526
+ image_name_widget,
527
+ actualMediaWidget
528
+ ) {
529
+ const isBatchUpdating = { value: false };
530
+
531
+ // 创建 image_name_widget
532
+ if (!image_name_widget) {
533
+ image_name_widget = node.addWidget(
534
+ "combo",
535
+ "image_name",
536
+ "",
537
+ createImageNameWidgetCallbackForVaWidgets(
538
+ nameToItemMap,
539
+ va_widgets,
540
+ isBatchUpdating
541
+ ),
542
+ {
543
+ serialize: true,
544
+ values: image_list.map((item) => item.name),
545
+ }
546
+ );
547
+ }
548
+
549
+ // 隐藏所有 va_widgets 并设置监听
550
+ va_widgets.forEach((va_widget) => {
551
+ hideWidget(node, va_widget.name);
552
+ setupVaWidgetValueSetter(
553
+ va_widget,
554
+ urlToNameMap,
555
+ nameToItemMap,
556
+ image_list,
557
+ image_name_widget,
558
+ actualMediaWidget,
559
+ isBatchUpdating
560
+ );
561
+
562
+ // 重写 callback
563
+ const originalVaCallback = va_widget.callback;
564
+ console.log(
565
+ `[hookLoadMedia] 重写 va_widget.callback, widget.name=${
566
+ va_widget.name
567
+ }, 原始callback=${originalVaCallback ? "存在" : "不存在"}`
568
+ );
569
+ va_widget.callback = createVaWidgetCallback(
570
+ va_widget,
571
+ urlToNameMap,
572
+ nameToItemMap,
573
+ image_list,
574
+ image_name_widget,
575
+ actualMediaWidget,
576
+ originalVaCallback
577
+ );
578
+ });
579
+
580
+ return image_name_widget;
581
+ }
582
+
583
+ /**
584
+ * 应用工作流图像设置
585
+ */
586
+ export async function applyWorkflowImageSettings(
587
+ workflowParams,
588
+ image_list,
589
+ media_widget,
590
+ image_name_widget,
591
+ currentNodeId,
592
+ va_widgets,
593
+ actualMediaWidget
594
+ ) {
595
+ if (workflowParams && workflowParams.nodes) {
596
+ // 先获取配置,然后将 mediaNodeList 和配置的 keys 合并
597
+ const config = await fetchMediaConfigWithCache();
598
+ const allMediaNodeTypes = new Set(mediaNodeList);
599
+ if (config) {
600
+ // 将配置中的 keys 添加到集合中
601
+ for (const key of Object.keys(config)) {
602
+ allMediaNodeTypes.add(key);
603
+ }
604
+ }
605
+
606
+ // 使用同步的 includes 查找匹配的节点(完全避免循环中的异步)
607
+ const imageNode = workflowParams.nodes.find(
608
+ (item) => item.id === currentNodeId && allMediaNodeTypes.has(item.type)
609
+ );
610
+
611
+ if (imageNode && imageNode.widgets_values && imageNode.widgets_values[0]) {
612
+ const item = imageNode.widgets_values[0].split("/");
613
+ image_list.push({
614
+ name: item[item.length - 1],
615
+ url: imageNode.widgets_values[0],
616
+ });
617
+
618
+ // 使用 actualMediaWidget 而不是 media_widget
619
+ const targetWidget = actualMediaWidget || media_widget;
620
+
621
+ if (targetWidget) {
622
+ targetWidget.value = imageNode.widgets_values[0];
623
+
624
+ if (targetWidget.options) {
625
+ targetWidget.options.values = image_list.map((item) => item.url);
626
+ }
627
+
628
+ if (image_name_widget) {
629
+ image_name_widget.options.values = image_list.map(
630
+ (item) => item.name
631
+ );
632
+ }
633
+
634
+ // 触发 callback
635
+ if (targetWidget.callback) {
636
+ targetWidget.callback(imageNode.widgets_values[0]);
637
+ }
638
+
639
+ // 如果是 va_widgets 的情况,需要同步更新所有 va_widgets
640
+ if (va_widgets && va_widgets.length > 0) {
641
+ va_widgets.forEach((va_widget) => {
642
+ if (va_widget !== targetWidget) {
643
+ va_widget.value = imageNode.widgets_values[0];
644
+ if (va_widget.callback) {
645
+ va_widget.callback(imageNode.widgets_values[0]);
646
+ }
647
+ }
648
+ });
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+
655
+ /**
656
+ * 获取文件列表数据
657
+ */
658
+ export async function fetchImageList(apiHost, nodeName) {
659
+ const res = await fetch(
660
+ `${apiHost}/special/community/commit_input_resource?${new URLSearchParams({
661
+ ext: computeExt(nodeName),
662
+ current: 1,
663
+ page_size: 100,
664
+ }).toString()}`,
665
+ {
666
+ method: "GET",
667
+ headers: {
668
+ "Content-Type": "application/json",
669
+ Authorization: `Bearer ${getCookie("bizy_token")}`,
670
+ },
671
+ }
672
+ );
673
+
674
+ const { data } = await res.json();
675
+ const list =
676
+ (data && data.data && data.data.data && data.data.data.list) || [];
677
+ return list
678
+ .filter((item) => item.name)
679
+ .map((item) => ({
680
+ url: item.url,
681
+ id: item.id,
682
+ name: item.name,
683
+ }));
684
+ }