bizydraft 0.2.78.dev20251030100819__py3-none-any.whl → 0.2.78.dev20251117024007__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.
bizydraft/oss_utils.py CHANGED
@@ -64,6 +64,9 @@ def decrypt(encrypted_message):
64
64
  if not encrypted_message or not isinstance(encrypted_message, str):
65
65
  raise ValueError("无效的加密消息")
66
66
 
67
+ if "v4.public" in encrypted_message:
68
+ return encrypted_message
69
+
67
70
  private_key = serialization.load_pem_private_key(
68
71
  private_key_pem.encode(), password=None, backend=default_backend()
69
72
  )
@@ -3,10 +3,12 @@ import math
3
3
  import mimetypes
4
4
  import os
5
5
  import uuid
6
+ from io import BytesIO
6
7
  from urllib.parse import unquote
7
8
 
8
9
  from aiohttp import ClientSession, ClientTimeout, web
9
10
  from loguru import logger
11
+ from PIL import Image
10
12
 
11
13
  try:
12
14
  import execution
@@ -37,6 +39,8 @@ async def view_image(request):
37
39
 
38
40
  filename = request.rel_url.query["filename"]
39
41
  subfolder = request.rel_url.query.get("subfolder", "")
42
+ channel = request.rel_url.query.get("channel", "rgba")
43
+ preview = request.rel_url.query.get("preview", None)
40
44
 
41
45
  http_prefix_options = ("http:", "https:")
42
46
 
@@ -56,17 +60,22 @@ async def view_image(request):
56
60
  subfolder = subfolder.replace("https:/", "https://", 1)
57
61
  if "http:/" in subfolder and not subfolder.startswith("http://"):
58
62
  subfolder = subfolder.replace("http:/", "http://", 1)
59
- filename = (
63
+
64
+ # 构建完整URL
65
+ full_url = (
60
66
  f"{subfolder}/{filename}"
61
67
  if not filename.startswith(http_prefix_options)
62
68
  else filename
63
- ) # preview 3d request: https://host:port/api/view?filename=filename.glb&type=output&subfolder=https://bizyair-dev.oss-cn-shanghai.aliyuncs.com/outputs&rand=0.5763957215362988
69
+ )
64
70
 
65
- content_type, _ = mimetypes.guess_type(filename)
71
+ # 获取原始文件名用于响应头
72
+ original_filename = filename.split("/")[-1] if "/" in filename else filename
73
+
74
+ content_type, _ = mimetypes.guess_type(full_url)
66
75
 
67
76
  timeout = ClientTimeout(total=BIZYDRAFT_REQUEST_TIMEOUT)
68
77
  async with ClientSession(timeout=timeout) as session:
69
- async with session.get(filename) as resp:
78
+ async with session.get(full_url) as resp:
70
79
  resp.raise_for_status()
71
80
 
72
81
  # 优先使用服务器返回的Content-Type,如果无法获取则使用猜测的类型
@@ -86,8 +95,98 @@ async def view_image(request):
86
95
  text=f"File size exceeds limit ({human_readable_size(BIZYDRAFT_MAX_FILE_SIZE)})",
87
96
  )
88
97
 
98
+ # 检查是否需要图像处理(preview或channel参数)
99
+ is_image = final_content_type and final_content_type.startswith(
100
+ "image/"
101
+ )
102
+ needs_processing = is_image and (
103
+ preview is not None or channel != "rgba"
104
+ )
105
+
106
+ if needs_processing:
107
+ logger.debug(f"Image processing requested: {channel=}, {preview=}")
108
+ # 下载完整图像到内存
109
+ image_data = await resp.read()
110
+
111
+ # 检查实际大小
112
+ if len(image_data) > BIZYDRAFT_MAX_FILE_SIZE:
113
+ return web.Response(
114
+ status=413,
115
+ text=f"File size exceeds limit ({human_readable_size(BIZYDRAFT_MAX_FILE_SIZE)})",
116
+ )
117
+
118
+ # 使用PIL处理图像
119
+ with Image.open(BytesIO(image_data)) as img:
120
+ # 处理preview参数
121
+ if preview is not None:
122
+ preview_info = preview.split(";")
123
+ image_format = preview_info[0]
124
+ if image_format not in ["webp", "jpeg"] or "a" in channel:
125
+ image_format = "webp"
126
+ quality = 90
127
+ if preview_info[-1].isdigit():
128
+ quality = int(preview_info[-1])
129
+
130
+ buffer = BytesIO()
131
+ if image_format in ["jpeg"] or channel == "rgb":
132
+ img = img.convert("RGB")
133
+ img.save(buffer, format=image_format, quality=quality)
134
+ buffer.seek(0)
135
+
136
+ return web.Response(
137
+ body=buffer.read(),
138
+ content_type=f"image/{image_format}",
139
+ headers={
140
+ "Content-Disposition": f'filename="{original_filename}"'
141
+ },
142
+ )
143
+
144
+ # 处理channel参数
145
+ if channel == "rgb":
146
+ logger.debug("Converting image to RGB (removing alpha)")
147
+ if img.mode == "RGBA":
148
+ r, g, b, a = img.split()
149
+ new_img = Image.merge("RGB", (r, g, b))
150
+ else:
151
+ new_img = img.convert("RGB")
152
+
153
+ buffer = BytesIO()
154
+ new_img.save(buffer, format="PNG")
155
+ buffer.seek(0)
156
+
157
+ return web.Response(
158
+ body=buffer.read(),
159
+ content_type="image/png",
160
+ headers={
161
+ "Content-Disposition": f'filename="{original_filename}"'
162
+ },
163
+ )
164
+
165
+ elif channel == "a":
166
+ logger.debug("Extracting alpha channel only")
167
+ if img.mode == "RGBA":
168
+ _, _, _, a = img.split()
169
+ else:
170
+ a = Image.new("L", img.size, 255)
171
+
172
+ # 创建alpha通道图像
173
+ alpha_img = Image.new("RGBA", img.size)
174
+ alpha_img.putalpha(a)
175
+ alpha_buffer = BytesIO()
176
+ alpha_img.save(alpha_buffer, format="PNG")
177
+ alpha_buffer.seek(0)
178
+
179
+ return web.Response(
180
+ body=alpha_buffer.read(),
181
+ content_type="image/png",
182
+ headers={
183
+ "Content-Disposition": f'filename="{original_filename}"'
184
+ },
185
+ )
186
+
187
+ # 默认流式传输(无需处理或非图像文件)
89
188
  headers = {
90
- "Content-Disposition": f'attachment; filename="{uuid.uuid4()}"',
189
+ "Content-Disposition": f'attachment; filename="{original_filename}"',
91
190
  "Content-Type": final_content_type,
92
191
  }
93
192
 
@@ -113,6 +212,7 @@ async def view_image(request):
113
212
  text=f"Request timed out (max {BIZYDRAFT_REQUEST_TIMEOUT//60} minutes)",
114
213
  )
115
214
  except Exception as e:
215
+ logger.error(f"Error in view_image: {str(e)}", exc_info=True)
116
216
  return web.Response(
117
217
  status=502, text=f"Failed to fetch remote resource: {str(e)}"
118
218
  )
@@ -69,13 +69,16 @@ app.registerExtension({
69
69
  parent: document.head,
70
70
  });
71
71
  const getCloseBtn = () => {
72
- let temp = null
73
- document.querySelectorAll('h3').forEach(e => {
74
- if (e.innerHTML == "<span>从模板开始</span>") {
75
- temp = e.parentNode.parentNode.parentNode.querySelector('.p-dialog-close-button')
76
- }
77
- })
78
- return temp
72
+ // let temp = null
73
+ // document.querySelectorAll('h2').forEach(e => {
74
+ // if (e.innerHTML == "<span>模板</span>") {
75
+ // const dialogContent = e.closest('.p-dialog-content')
76
+ // if (dialogContent) {
77
+ // temp = dialogContent.querySelector('i.pi.pi-times.text-sm')
78
+ // }
79
+ // }
80
+ // })
81
+ return document.querySelector('i.pi.pi-times.text-sm')
79
82
  }
80
83
  const getFengrossmentBtn = () => {
81
84
  let temp = null
@@ -1,5 +1,6 @@
1
1
  // 媒体节点配置获取与工具函数(与 hookLoad/model.js 结构一致,面向 media_load_nodes)
2
2
  import { fetchMediaConfig } from './configLoader.js'
3
+ import { getCookie, computeExt, hideWidget } from '../tool.js'
3
4
 
4
5
  // 动态配置缓存(仅缓存媒体部分)
5
6
  let mediaConfigCache = null;
@@ -81,3 +82,417 @@ export async function computeIsMediaNode(nodeName) {
81
82
 
82
83
  // 启动时后台预取(不阻塞后续逻辑)
83
84
  try { void fetchMediaConfigWithCache(); } catch (e) {}
85
+
86
+ // ==================== 媒体 Widget 处理函数 ====================
87
+
88
+ /**
89
+ * 查找单个媒体 widget(用于 media_widget)
90
+ */
91
+ export function findMediaWidget(nodeWidgets, apiInputKeys) {
92
+ // 优先使用 API 配置的 keys
93
+ if (apiInputKeys && apiInputKeys.length > 0) {
94
+ for (const key of apiInputKeys) {
95
+ const w = nodeWidgets.find(x => x.name === key);
96
+ if (w) return w;
97
+ }
98
+ }
99
+ // 回退到常见媒体 widget 名称
100
+ return nodeWidgets.find(w => possibleMediaWidgetNames.includes(w.name)) || null;
101
+ }
102
+
103
+ /**
104
+ * 查找所有媒体 widget(用于 va_widgets)
105
+ */
106
+ export function findMediaWidgets(nodeWidgets, apiInputKeys) {
107
+ const widgets = [];
108
+
109
+ // 优先使用 API 配置的 keys
110
+ if (apiInputKeys && apiInputKeys.length > 0) {
111
+ for (const key of apiInputKeys) {
112
+ const w = nodeWidgets.find(x => x.name === key);
113
+ if (w) widgets.push(w);
114
+ }
115
+ }
116
+
117
+ // 如果 API 配置没找到,使用回退逻辑
118
+ if (widgets.length === 0) {
119
+ for (const widgetName of possibleMediaWidgetNames) {
120
+ const w = nodeWidgets.find(x => x.name === widgetName);
121
+ if (w) widgets.push(w);
122
+ }
123
+ }
124
+
125
+ return widgets;
126
+ }
127
+
128
+ /**
129
+ * 添加新文件到列表并更新相关数据
130
+ */
131
+ export function addNewFileToList(url, image_list, urlToNameMap, nameToItemMap) {
132
+ const fileName = url.split('/').pop();
133
+ const newItem = { name: fileName, url: url };
134
+ image_list.push(newItem);
135
+ urlToNameMap.set(url, fileName);
136
+ nameToItemMap.set(fileName, newItem);
137
+ return fileName;
138
+ }
139
+
140
+ /**
141
+ * 更新所有相关 widget 的选项列表
142
+ */
143
+ export function updateWidgetsOptions(widgets, image_list) {
144
+ const names = image_list.map(item => item.name);
145
+ widgets.forEach(widget => {
146
+ if (widget && widget.options) {
147
+ widget.options.values = names;
148
+ }
149
+ });
150
+ }
151
+
152
+ /**
153
+ * 处理新上传的文件:添加到列表并更新所有 widget
154
+ */
155
+ export function handleNewUploadedFile(url, image_list, urlToNameMap, nameToItemMap, image_name_widget, actualMediaWidget) {
156
+ const fileName = addNewFileToList(url, image_list, urlToNameMap, nameToItemMap);
157
+
158
+ // 更新所有相关 widget 的选项列表
159
+ const widgetsToUpdate = [image_name_widget, actualMediaWidget].filter(Boolean);
160
+ updateWidgetsOptions(widgetsToUpdate, image_list);
161
+
162
+ return fileName;
163
+ }
164
+
165
+ /**
166
+ * 从输入中提取 URL(支持 string 和 array 格式)
167
+ */
168
+ export function extractUrlFromInput(input) {
169
+ if (typeof input === 'string') {
170
+ return input;
171
+ } else if (Array.isArray(input) && input.length > 0) {
172
+ return typeof input[0] === 'string' ? input[0] : input[0];
173
+ }
174
+ return null;
175
+ }
176
+
177
+ /**
178
+ * 初始化 Map 映射
179
+ */
180
+ export function initMaps(image_list) {
181
+ const urlToNameMap = new Map();
182
+ const nameToItemMap = new Map();
183
+ image_list.forEach(item => {
184
+ urlToNameMap.set(item.url, item.name);
185
+ nameToItemMap.set(item.name, item);
186
+ });
187
+ return { urlToNameMap, nameToItemMap };
188
+ }
189
+
190
+ /**
191
+ * 创建 image_name_widget 的 callback(用于 va_widgets)
192
+ */
193
+ export function createImageNameWidgetCallbackForVaWidgets(nameToItemMap, va_widgets, isBatchUpdating) {
194
+ return function(e) {
195
+ const item = nameToItemMap.get(e);
196
+ if (item) {
197
+ const image_url = decodeURIComponent(item.url);
198
+ isBatchUpdating.value = true;
199
+ va_widgets.forEach(va_widget => {
200
+ va_widget.value = image_url;
201
+ if (va_widget.callback) {
202
+ va_widget.callback(image_url);
203
+ }
204
+ });
205
+ isBatchUpdating.value = false;
206
+ }
207
+ };
208
+ }
209
+
210
+ /**
211
+ * 创建 image_name_widget 的 callback(用于 media_widget)
212
+ */
213
+ export function createImageNameWidgetCallbackForMediaWidget(nameToItemMap, media_widget) {
214
+ return function(e) {
215
+ const item = nameToItemMap.get(e);
216
+ if (item) {
217
+ const image_url = decodeURIComponent(item.url);
218
+ media_widget.value = image_url;
219
+ if (media_widget.callback) {
220
+ media_widget.callback(image_url);
221
+ }
222
+ }
223
+ };
224
+ }
225
+
226
+ /**
227
+ * 创建 value setter(用于 va_widget)
228
+ */
229
+ export function createVaWidgetValueSetter(va_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, isBatchUpdating) {
230
+ let _value = va_widget.value;
231
+
232
+ return {
233
+ get: () => _value,
234
+ set: function(newValue) {
235
+ const oldValue = _value;
236
+ _value = newValue;
237
+ console.log(`[hookLoadMedia] va_widget.value 被设置, widget.name=${va_widget.name}, oldValue=`, oldValue, ', newValue=', newValue);
238
+
239
+ // 批量更新时跳过监听逻辑
240
+ if (isBatchUpdating.value) {
241
+ console.log(`[hookLoadMedia] 批量更新中,跳过处理, widget.name=${va_widget.name}`);
242
+ return;
243
+ }
244
+
245
+ // 如果值没有变化,不需要处理
246
+ if (oldValue === newValue) {
247
+ console.log(`[hookLoadMedia] 值未变化,跳过处理, widget.name=${va_widget.name}`);
248
+ return;
249
+ }
250
+
251
+ // 使用 Map 快速查找(O(1))
252
+ const name = urlToNameMap.get(newValue);
253
+ if (name && image_name_widget) {
254
+ console.log(`[hookLoadMedia] 找到匹配的name, widget.name=${va_widget.name}, name=`, name);
255
+ image_name_widget.value = name;
256
+ } else if (image_name_widget && newValue && typeof newValue === 'string' && newValue.includes('/')) {
257
+ // 如果没找到,可能是新上传的文件,需要添加到列表
258
+ const fileName = newValue.split('/').pop();
259
+ console.log(`[hookLoadMedia] 未找到匹配的name, widget.name=${va_widget.name}, 可能是新文件, fileName=${fileName}`);
260
+
261
+ // 检查是否真的是新文件(URL格式)
262
+ if (newValue.startsWith('http') || newValue.startsWith('/')) {
263
+ handleNewUploadedFile(newValue, image_list, urlToNameMap, nameToItemMap, image_name_widget, actualMediaWidget);
264
+ console.log(`[hookLoadMedia] 新文件已通过value setter添加到列表, 当前列表长度=${image_list.length}`);
265
+ }
266
+
267
+ image_name_widget.value = fileName;
268
+ }
269
+ }
270
+ };
271
+ }
272
+
273
+ /**
274
+ * 创建 va_widget 的 callback
275
+ */
276
+ export function createVaWidgetCallback(va_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, originalVaCallback) {
277
+ return function(e) {
278
+ console.log(`[hookLoadMedia] va_widget.callback 被触发, widget.name=${va_widget.name}, e=`, e);
279
+
280
+ if (image_name_widget) {
281
+ const url = extractUrlFromInput(e);
282
+ if (url) {
283
+ const name = urlToNameMap.get(url);
284
+ if (name) {
285
+ image_name_widget.value = name;
286
+ } else {
287
+ // 如果没找到,可能是新上传的文件
288
+ if (typeof e === 'string' && !e.startsWith('http') && !e.startsWith('/')) {
289
+ // 不是 URL 格式,直接使用文件名
290
+ image_name_widget.value = e.split('/').pop();
291
+ } else {
292
+ // 是新上传的文件,添加到列表
293
+ const fileName = handleNewUploadedFile(url, image_list, urlToNameMap, nameToItemMap, image_name_widget, actualMediaWidget);
294
+ console.log(`[hookLoadMedia] 检测到新上传的文件, url=${url}, fileName=${fileName}, 当前列表长度=${image_list.length}`);
295
+ image_name_widget.value = fileName;
296
+ }
297
+ }
298
+ }
299
+ }
300
+
301
+ // 调用原始callback
302
+ if (originalVaCallback) {
303
+ console.log(`[hookLoadMedia] 调用原始callback, widget.name=${va_widget.name}`);
304
+ originalVaCallback(e);
305
+ } else {
306
+ console.log(`[hookLoadMedia] 原始callback不存在, widget.name=${va_widget.name}`);
307
+ }
308
+ };
309
+ }
310
+
311
+ /**
312
+ * 创建 media_widget 的 callback
313
+ */
314
+ export function createMediaWidgetCallback(media_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, originalCallback) {
315
+ return function(e) {
316
+ console.log('media_widget.callback', e);
317
+ if (typeof e == 'string') {
318
+ // 使用 Map 快速查找(O(1))
319
+ const item = e.includes('http') ?
320
+ (urlToNameMap.has(e) ? {url: e, name: urlToNameMap.get(e)} : null) :
321
+ (nameToItemMap ? nameToItemMap.get(e) : null);
322
+
323
+ const image_url = item ? decodeURIComponent(item.url) : e;
324
+
325
+ image_name_widget.value = item ? item.name : e;
326
+ media_widget.value = image_url;
327
+ if (originalCallback) {
328
+ originalCallback([image_url])
329
+ }
330
+ } else {
331
+ // 处理数组格式的输入(如文件上传)
332
+ const url = extractUrlFromInput(e);
333
+ if (url) {
334
+ const existingName = urlToNameMap.get(url);
335
+
336
+ if (existingName) {
337
+ // 如果已经在列表中,直接使用
338
+ image_name_widget.value = existingName;
339
+ media_widget.value = url;
340
+ } else {
341
+ // 如果不在列表中,说明是新上传的文件,需要添加到列表
342
+ const fileName = handleNewUploadedFile(url, image_list, urlToNameMap, nameToItemMap, image_name_widget, actualMediaWidget);
343
+ console.log(`[hookLoadMedia] 检测到新上传的文件(media_widget分支), url=${url}, fileName=${fileName}, 当前列表长度=${image_list.length}`);
344
+ image_name_widget.value = fileName;
345
+ media_widget.value = url;
346
+ }
347
+ }
348
+
349
+ if (originalCallback) {
350
+ originalCallback(e)
351
+ }
352
+ }
353
+ };
354
+ }
355
+
356
+ /**
357
+ * 设置 va_widget 的 value setter
358
+ */
359
+ export function setupVaWidgetValueSetter(va_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, isBatchUpdating) {
360
+ const existingDescriptor = Object.getOwnPropertyDescriptor(va_widget, 'value');
361
+
362
+ if (existingDescriptor && !existingDescriptor.configurable) {
363
+ return; // 跳过不可配置的属性
364
+ }
365
+
366
+ if (existingDescriptor) {
367
+ delete va_widget.value;
368
+ }
369
+
370
+ const valueSetter = createVaWidgetValueSetter(va_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, isBatchUpdating);
371
+
372
+ Object.defineProperty(va_widget, 'value', {
373
+ get: valueSetter.get,
374
+ set: valueSetter.set,
375
+ enumerable: true,
376
+ configurable: true
377
+ });
378
+ }
379
+
380
+ /**
381
+ * 处理 va_widgets
382
+ */
383
+ export function setupVaWidgets(node, va_widgets, image_list, urlToNameMap, nameToItemMap, image_name_widget, actualMediaWidget) {
384
+ const isBatchUpdating = { value: false };
385
+
386
+ // 创建 image_name_widget
387
+ if (!image_name_widget) {
388
+ image_name_widget = node.addWidget("combo", "image_name", "",
389
+ createImageNameWidgetCallbackForVaWidgets(nameToItemMap, va_widgets, isBatchUpdating),
390
+ {
391
+ serialize: true,
392
+ values: image_list.map(item => item.name)
393
+ }
394
+ );
395
+ }
396
+
397
+ // 隐藏所有 va_widgets 并设置监听
398
+ va_widgets.forEach(va_widget => {
399
+ hideWidget(node, va_widget.name);
400
+ setupVaWidgetValueSetter(va_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, isBatchUpdating);
401
+
402
+ // 重写 callback
403
+ const originalVaCallback = va_widget.callback;
404
+ console.log(`[hookLoadMedia] 重写 va_widget.callback, widget.name=${va_widget.name}, 原始callback=${originalVaCallback ? '存在' : '不存在'}`);
405
+ va_widget.callback = createVaWidgetCallback(va_widget, urlToNameMap, nameToItemMap, image_list, image_name_widget, actualMediaWidget, originalVaCallback);
406
+ });
407
+
408
+ return image_name_widget;
409
+ }
410
+
411
+ /**
412
+ * 应用工作流图像设置
413
+ */
414
+ export async function applyWorkflowImageSettings(workflowParams, image_list, media_widget, image_name_widget, currentNodeId, va_widgets, actualMediaWidget) {
415
+ if (workflowParams && workflowParams.nodes) {
416
+ // 先获取配置,然后将 mediaNodeList 和配置的 keys 合并
417
+ const config = await fetchMediaConfigWithCache();
418
+ const allMediaNodeTypes = new Set(mediaNodeList);
419
+ if (config) {
420
+ // 将配置中的 keys 添加到集合中
421
+ for (const key of Object.keys(config)) {
422
+ allMediaNodeTypes.add(key);
423
+ }
424
+ }
425
+
426
+ // 使用同步的 includes 查找匹配的节点(完全避免循环中的异步)
427
+ const imageNode = workflowParams.nodes.find(item =>
428
+ item.id === currentNodeId && allMediaNodeTypes.has(item.type)
429
+ )
430
+
431
+ if (imageNode && imageNode.widgets_values && imageNode.widgets_values[0]) {
432
+ const item = imageNode.widgets_values[0].split('/')
433
+ image_list.push({
434
+ name: item[item.length - 1],
435
+ url: imageNode.widgets_values[0]
436
+ })
437
+
438
+ // 使用 actualMediaWidget 而不是 media_widget
439
+ const targetWidget = actualMediaWidget || media_widget;
440
+
441
+ if (targetWidget) {
442
+ targetWidget.value = imageNode.widgets_values[0]
443
+
444
+ if (targetWidget.options) {
445
+ targetWidget.options.values = image_list.map(item => item.url)
446
+ }
447
+
448
+ if (image_name_widget) {
449
+ image_name_widget.options.values = image_list.map(item => item.name)
450
+ }
451
+
452
+ // 触发 callback
453
+ if (targetWidget.callback) {
454
+ targetWidget.callback(imageNode.widgets_values[0])
455
+ }
456
+
457
+ // 如果是 va_widgets 的情况,需要同步更新所有 va_widgets
458
+ if (va_widgets && va_widgets.length > 0) {
459
+ va_widgets.forEach(va_widget => {
460
+ if (va_widget !== targetWidget) {
461
+ va_widget.value = imageNode.widgets_values[0];
462
+ if (va_widget.callback) {
463
+ va_widget.callback(imageNode.widgets_values[0]);
464
+ }
465
+ }
466
+ });
467
+ }
468
+ }
469
+ }
470
+ }
471
+ }
472
+
473
+ /**
474
+ * 获取文件列表数据
475
+ */
476
+ export async function fetchImageList(apiHost, nodeName) {
477
+ const res = await fetch(`${apiHost}/special/community/commit_input_resource?${
478
+ new URLSearchParams({
479
+ ext: computeExt(nodeName),
480
+ current: 1,
481
+ page_size: 100
482
+ }).toString()
483
+ }`, {
484
+ method: 'GET',
485
+ headers: {
486
+ 'Content-Type': 'application/json',
487
+ 'Authorization': `Bearer ${getCookie('bizy_token')}`
488
+ }
489
+ });
490
+
491
+ const {data} = await res.json();
492
+ const list = (data && data.data && data.data.data && data.data.data.list) || [];
493
+ return list.filter(item => item.name).map(item => ({
494
+ url: item.url,
495
+ id: item.id,
496
+ name: item.name
497
+ }));
498
+ }
@@ -1,8 +1,23 @@
1
1
  import { app } from "../../scripts/app.js";
2
- import { getCookie, computeExt, hideWidget } from './tool.js';
3
- import { getMediaNodeConfig, getMediaInputKeys, possibleMediaWidgetNames, computeIsMediaNode, mediaNodeList, fetchMediaConfigWithCache } from './hookLoad/media.js';
4
-
5
-
2
+ import { hideWidget } from './tool.js';
3
+ import {
4
+ getMediaNodeConfig,
5
+ getMediaInputKeys,
6
+ computeIsMediaNode,
7
+ mediaNodeList,
8
+ fetchMediaConfigWithCache,
9
+ findMediaWidget,
10
+ findMediaWidgets,
11
+ updateWidgetsOptions,
12
+ handleNewUploadedFile,
13
+ extractUrlFromInput,
14
+ initMaps,
15
+ createImageNameWidgetCallbackForMediaWidget,
16
+ createMediaWidgetCallback,
17
+ setupVaWidgets,
18
+ applyWorkflowImageSettings,
19
+ fetchImageList
20
+ } from './hookLoad/media.js';
6
21
 
7
22
  app.registerExtension({
8
23
  name: "bizyair.image.to.oss",
@@ -33,307 +48,99 @@ app.registerExtension({
33
48
  });
34
49
  })
35
50
  nodeType.prototype.onNodeCreated = async function() {
36
- if (await computeIsMediaNode(nodeData.name)) {
37
- const apiHost = 'https://bizyair.cn/api'
38
- // 优先使用 API 的媒体输入键匹配到具体的 widget;若未命中则回退到原有字段集合
39
- let media_widget = null;
40
- const mediaNodeConfig = await getMediaNodeConfig(nodeData.name);
41
- const apiInputKeys = getMediaInputKeys(mediaNodeConfig);
42
- if (apiInputKeys && apiInputKeys.length > 0) {
43
- for (const key of apiInputKeys) {
44
- const w = this.widgets.find(x => x.name === key);
45
- if (w) { media_widget = w; break; }
46
- }
47
- }
48
- if (!media_widget) {
49
- media_widget = this.widgets.find(w => {
50
- return possibleMediaWidgetNames.includes(w.name);
51
- });
52
- }
53
- // 查找所有name等于接口配置中inputs下的字段的widget(如video、audio等)
54
- let va_widgets = [];
55
- if (apiInputKeys && apiInputKeys.length > 0) {
56
- for (const key of apiInputKeys) {
57
- const w = this.widgets.find(x => x.name === key);
58
- if (w) {
59
- va_widgets.push(w);
60
- }
61
- }
62
- }
63
-
64
- // 如果API配置没有找到,使用回退逻辑查找常见的媒体widget
65
- if (va_widgets.length === 0) {
66
- for (const widgetName of possibleMediaWidgetNames) {
67
- const w = this.widgets.find(x => x.name === widgetName);
68
- if (w) {
69
- va_widgets.push(w);
70
- }
71
- }
72
- }
73
- let image_name_widget = this.widgets.find(w => w.name === 'image_name');
74
- let image_list = []
75
- const getData = async () => {
76
- const res = await fetch(`${apiHost}/special/community/commit_input_resource?${
77
- new URLSearchParams({
78
- ext: computeExt(nodeData.name),
79
- current: 1,
80
- page_size: 100
81
-
82
- }).toString()
83
- }`, {
84
- method: 'GET',
85
- headers: {
86
- 'Content-Type': 'application/json',
87
- 'Authorization': `Bearer ${getCookie('bizy_token')}`
88
- }
89
- })
90
- const {data} = await res.json()
91
- const list = (data && data.data && data.data.data && data.data.data.list) || []
92
- image_list = list.filter(item => item.name).map(item => {
93
- return {
94
- url: item.url,
95
- id: item.id,
96
- name: item.name
97
- }
98
- })
99
-
100
- // 📊 方案:使用 Map 缓存 URL→Name 映射(O(1) 查找)
101
- const urlToNameMap = new Map();
102
- const nameToItemMap = new Map();
103
- image_list.forEach(item => {
104
- urlToNameMap.set(item.url, item.name);
105
- nameToItemMap.set(item.name, item);
106
- });
107
-
108
- // 如果找到va_widgets,处理它们
109
- if (va_widgets.length > 0) {
110
- // 标志位:防止批量更新时触发监听
111
- let isBatchUpdating = false;
112
-
113
- // 创建image_name_widget来替代显示
114
- if (!image_name_widget) {
115
- image_name_widget = this.addWidget("combo", "image_name", "", function(e){
116
- const item = nameToItemMap.get(e);
117
- if (item) {
118
- const image_url = decodeURIComponent(item.url);
119
- // 批量更新时跳过监听
120
- isBatchUpdating = true;
121
- va_widgets.forEach(va_widget => {
122
- va_widget.value = image_url;
123
- if (va_widget.callback) {
124
- va_widget.callback(image_url);
125
- }
126
- });
127
- isBatchUpdating = false;
128
- }
129
- }, {
130
- serialize: true,
131
- values: image_list.map(item => item.name)
132
- });
133
- }
134
-
135
- // 隐藏所有va_widgets 并设置监听
136
- va_widgets.forEach(va_widget => {
137
- hideWidget(this, va_widget.name);
138
- let _value = va_widget.value;
139
-
140
- // 检查并删除现有的 value 属性描述符(如果存在)
141
- const existingDescriptor = Object.getOwnPropertyDescriptor(va_widget, 'value');
142
- if (existingDescriptor && !existingDescriptor.configurable) {
143
- // 如果不可配置,跳过重新定义
144
- return;
145
- }
51
+ if (!(await computeIsMediaNode(nodeData.name))) {
52
+ return;
53
+ }
146
54
 
147
- // 删除现有属性(如果存在)
148
- if (existingDescriptor) {
149
- delete va_widget.value;
150
- }
55
+ const apiHost = 'https://bizyair.cn/api'
56
+ // const apiHost = 'http://localhost:3000/api'
151
57
 
152
- Object.defineProperty(va_widget, 'value', {
153
- get: function() {
154
- return _value;
155
- },
156
- set: function(newValue) {
157
- _value = newValue;
58
+ // 获取 API 配置
59
+ const mediaNodeConfig = await getMediaNodeConfig(nodeData.name);
60
+ const apiInputKeys = getMediaInputKeys(mediaNodeConfig);
158
61
 
159
- // 批量更新时跳过监听逻辑
160
- if (isBatchUpdating) {
161
- return;
162
- }
163
- // 使用 Map 快速查找(O(1))
164
- const name = urlToNameMap.get(newValue);
165
- if (name) {
166
- image_name_widget.value = name;
167
- } else {
168
- // 如果没找到,从URL提取文件名
169
- const fileName = newValue.split('/').pop();
170
- image_name_widget.value = fileName;
171
- }
172
- },
173
- enumerable: true,
174
- configurable: true
175
- });
176
- });
62
+ // 查找媒体 widget
63
+ const media_widget = findMediaWidget(this.widgets, apiInputKeys);
64
+ const va_widgets = findMediaWidgets(this.widgets, apiInputKeys);
65
+ let image_name_widget = this.widgets.find(w => w.name === 'image_name');
177
66
 
67
+ // 获取文件列表
68
+ const image_list = await fetchImageList(apiHost, nodeData.name);
178
69
 
179
- // 为每个va_widget重写callback
180
- va_widgets.forEach(va_widget => {
181
- // 保存va_widget的原始callback
182
- const originalVaCallback = va_widget.callback;
183
- // 重写va_widget的callback,当被触发时给image_name_widget赋值
184
- va_widget.callback = function(e) {
185
- if (image_name_widget) {
186
- if (typeof e === 'string') {
187
- // 使用 Map 快速查找(O(1))
188
- const name = urlToNameMap.get(e);
189
- if (name) {
190
- image_name_widget.value = name;
191
- } else {
192
- // 如果没找到,从URL提取文件名
193
- const fileName = e.split('/').pop();
194
- image_name_widget.value = fileName;
195
- }
196
- }
197
- }
70
+ // 初始化 Map 映射
71
+ const { urlToNameMap, nameToItemMap } = initMaps(image_list);
198
72
 
199
- // 调用原始callback
200
- if (originalVaCallback) {
201
- originalVaCallback(e);
202
- }
203
- };
204
- });
205
- }
73
+ // 如果找到va_widgets,处理它们
74
+ if (va_widgets.length > 0) {
75
+ image_name_widget = setupVaWidgets(this, va_widgets, image_list, urlToNameMap, nameToItemMap, image_name_widget, va_widgets[0]);
76
+ }
206
77
 
207
- // 如果va_widgets没有创建image_name_widget,使用原有逻辑创建
208
- if (!image_name_widget && media_widget) {
209
- image_name_widget = this.addWidget("combo", "image_name", "", function(e){
210
- const item = nameToItemMap.get(e);
211
- if (item) {
212
- const image_url = decodeURIComponent(item.url);
213
- media_widget.value = image_url;
214
- if (media_widget.callback) {
215
- media_widget.callback(image_url);
216
- }
217
- }
218
- }, {
219
- serialize: true,
220
- values: image_list.map(item => item.name)
221
- });
78
+ // 如果va_widgets没有创建image_name_widget,使用原有逻辑创建
79
+ if (!image_name_widget && media_widget) {
80
+ image_name_widget = this.addWidget("combo", "image_name", "",
81
+ createImageNameWidgetCallbackForMediaWidget(nameToItemMap, media_widget),
82
+ {
83
+ serialize: true,
84
+ values: image_list.map(item => item.name)
222
85
  }
86
+ );
87
+ }
223
88
 
224
- // 如果进入了va_widgets分支,使用va_widgets中第一个作为media_widget的替代
225
- const actualMediaWidget = va_widgets.length > 0 ? va_widgets[0] : media_widget;
226
-
227
- if (image_name_widget && actualMediaWidget) {
228
- const val = urlToNameMap.get(actualMediaWidget.value) || actualMediaWidget.value
229
- image_name_widget.label = actualMediaWidget.label
230
- image_name_widget.value = val
231
-
232
- const currentIndex = this.widgets.indexOf(image_name_widget);
233
- if (currentIndex > 1) {
234
- this.widgets.splice(currentIndex, 1);
235
- this.widgets.splice(1, 0, image_name_widget);
236
- }
237
-
238
- // 如果没有进入va_widgets分支,才隐藏media_widget
239
- if (va_widgets.length === 0) {
240
- hideWidget(this, media_widget.name)
241
- }
242
-
243
- actualMediaWidget.options.values = image_list.map(item => item.name);
244
-
245
- // 对于va_widgets的情况,callback已经在上面重写过了,不需要再次重写
246
- if (va_widgets.length === 0 && media_widget) {
247
- const callback = media_widget.callback
248
- media_widget.callback = function(e) {
249
- if (typeof e == 'string') {
250
- // 使用 Map 快速查找(O(1))
251
- const item = e.includes('http') ?
252
- (urlToNameMap.has(e) ? {url: e, name: urlToNameMap.get(e)} : null) :
253
- (nameToItemMap ? nameToItemMap.get(e) : null);
89
+ // 如果进入了va_widgets分支,使用va_widgets中第一个作为media_widget的替代
90
+ const actualMediaWidget = va_widgets.length > 0 ? va_widgets[0] : media_widget;
254
91
 
255
- const image_url = item ? decodeURIComponent(item.url) : e;
92
+ if (image_name_widget && actualMediaWidget) {
93
+ const val = urlToNameMap.get(actualMediaWidget.value) || actualMediaWidget.value;
94
+ image_name_widget.label = actualMediaWidget.label;
95
+ image_name_widget.value = val;
256
96
 
257
- image_name_widget.value = item ? item.name : e;
258
- media_widget.value = image_url;
259
- if (callback) {
260
- callback([image_url])
261
- }
262
- } else {
263
- const item = e[0].split('/')
264
- const fileName = item[item.length - 1];
265
- image_name_widget.options.values.pop()
266
- image_name_widget.options.values.push(fileName)
267
- image_name_widget.value = fileName
268
- image_list.push({
269
- name: fileName,
270
- url: e[0]
271
- })
272
- // 同步更新 Map
273
- urlToNameMap.set(e[0], fileName);
274
- if (nameToItemMap) {
275
- nameToItemMap.set(fileName, {url: e[0], name: fileName});
276
- }
277
- if (callback) {
278
- callback(e)
279
- }
280
- }
281
- }
282
- }
283
- }
284
- return true
97
+ // 调整 widget 顺序
98
+ const currentIndex = this.widgets.indexOf(image_name_widget);
99
+ if (currentIndex > 1) {
100
+ this.widgets.splice(currentIndex, 1);
101
+ this.widgets.splice(1, 0, image_name_widget);
285
102
  }
286
- await getData()
287
-
288
-
289
- async function applyWorkflowImageSettings(workflowParams, image_list, media_widget, image_name_widget, currentNodeId) {
290
- if (workflowParams && workflowParams.nodes) {
291
- // 先获取配置,然后将 mediaNodeList 和配置的 keys 合并
292
- const config = await fetchMediaConfigWithCache();
293
- const allMediaNodeTypes = new Set(mediaNodeList);
294
- if (config) {
295
- // 将配置中的 keys 添加到集合中
296
- for (const key of Object.keys(config)) {
297
- allMediaNodeTypes.add(key);
298
- }
299
- }
300
-
301
- // 使用同步的 includes 查找匹配的节点(完全避免循环中的异步)
302
- const imageNode = workflowParams.nodes.find(item =>
303
- item.id === currentNodeId && allMediaNodeTypes.has(item.type)
304
- )
305
103
 
306
- if (imageNode && imageNode.widgets_values && imageNode.widgets_values[0]) {
307
- const item = imageNode.widgets_values[0].split('/')
308
- image_list.push({
309
- name: item[item.length - 1],
310
- url: imageNode.widgets_values[0]
311
- })
312
- media_widget.value = imageNode.widgets_values[0]
104
+ // 如果没有进入va_widgets分支,才隐藏media_widget
105
+ if (va_widgets.length === 0) {
106
+ hideWidget(this, media_widget.name);
107
+ }
313
108
 
314
- media_widget.options.values = image_list.map(item => item.url)
315
- image_name_widget.options.values = image_list.map(item => item.name)
316
- media_widget.callback(imageNode.widgets_values[0])
317
- }
318
- }
109
+ // 更新 widget 选项列表
110
+ updateWidgetsOptions([actualMediaWidget], image_list);
111
+
112
+ // 对于va_widgets的情况,callback已经在上面重写过了,不需要再次重写
113
+ if (va_widgets.length === 0 && media_widget) {
114
+ const callback = media_widget.callback;
115
+ media_widget.callback = createMediaWidgetCallback(
116
+ media_widget,
117
+ urlToNameMap,
118
+ nameToItemMap,
119
+ image_list,
120
+ image_name_widget,
121
+ actualMediaWidget,
122
+ callback
123
+ );
319
124
  }
125
+ }
320
126
 
321
- // 如果有存储的工作流数据,应用图像设置
127
+ // 应用工作流设置
128
+ const workflowData = window.currentWorkflowData || workflowParams;
129
+ if (workflowData) {
130
+ await applyWorkflowImageSettings(workflowData, image_list, media_widget, image_name_widget, this.id, va_widgets, actualMediaWidget);
322
131
  if (window.currentWorkflowData) {
323
- await applyWorkflowImageSettings(window.currentWorkflowData, image_list, media_widget, image_name_widget, this.id);
324
- // 清除存储的数据,避免重复处理
325
132
  delete window.currentWorkflowData;
326
- } else {
327
- // 原有的调用
328
- await applyWorkflowImageSettings(workflowParams, image_list, media_widget, image_name_widget, this.id);
329
133
  }
330
- //在这里发个postmessage
331
- window.parent.postMessage({
332
- type: 'functionResult',
333
- method: 'hookLoadImageCompleted',
334
- params: {}
335
- }, '*');
134
+ } else {
135
+ await applyWorkflowImageSettings(workflowParams, image_list, media_widget, image_name_widget, this.id, va_widgets, actualMediaWidget);
336
136
  }
137
+
138
+ // 发送完成消息
139
+ window.parent.postMessage({
140
+ type: 'functionResult',
141
+ method: 'hookLoadImageCompleted',
142
+ params: {}
143
+ }, '*');
337
144
  }
338
145
  }
339
146
  })
@@ -29,9 +29,9 @@ app.registerExtension({
29
29
 
30
30
  // 异步获取节点配置
31
31
  const onNodeCreated = nodeType.prototype.onNodeCreated;
32
- nodeType.prototype.onNodeCreated = async function() {
33
- const nodeConfig = await getNodeConfig(nodeData.name);
34
- if (nodeConfig) {
32
+ const nodeConfig = await getNodeConfig(nodeData.name);
33
+ if (nodeConfig) {
34
+ nodeType.prototype.onNodeCreated = function() {
35
35
  try {
36
36
  const inputs = nodeConfig.config.inputs;
37
37
  const inputKeys = Object.keys(inputs);
@@ -140,5 +140,51 @@ app.registerExtension({
140
140
  if (nodeConfig) {
141
141
  setupNodeMouseBehavior(node, nodeConfig);
142
142
  }
143
+ },
144
+ async setup() {
145
+ const app = document.querySelector('#vue-app').__vue_app__
146
+ const pinia = app.config.globalProperties.$pinia
147
+ const settingStore = pinia._s.get('setting')
148
+ await settingStore.set('Comfy.Workflow.ShowMissingModelsWarning', false)
149
+
150
+
151
+
152
+
153
+ // 获取 toastStore
154
+ const toastStore = pinia._s.get('toast');
155
+
156
+ // 保存原始的 add 方法
157
+ const originalAdd = toastStore.add;
158
+
159
+ // 重写 add 方法,添加过滤逻辑
160
+ toastStore.add = function(message) {
161
+ const detail = (message.detail || '').toLowerCase();
162
+ const summary = (message.summary || '').toLowerCase();
163
+ const text = `${summary} ${detail}`;
164
+
165
+ // 检查是否包含阻止的关键词
166
+ const blockedKeywords = [
167
+ 'missing dependencies',
168
+ 'comfyui logs',
169
+ 'comfyullogs',
170
+ 'refer to the comfyui',
171
+ 'you may be missing'
172
+ ];
173
+
174
+ const shouldBlock = blockedKeywords.some(keyword =>
175
+ text.includes(keyword.toLowerCase())
176
+ );
177
+
178
+ // 如果包含阻止的关键词,则不添加 toast
179
+ if (shouldBlock) {
180
+ console.log('Blocked toast:', message);
181
+ return;
182
+ }
183
+
184
+ // 否则正常添加
185
+ return originalAdd.call(this, message);
186
+ };
187
+
188
+ console.log('Toast blocker activated!');
143
189
  }
144
190
  })
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bizydraft
3
- Version: 0.2.78.dev20251030100819
3
+ Version: 0.2.78.dev20251117024007
4
4
  Summary: bizydraft
5
5
  Requires-Dist: loguru
6
6
  Requires-Dist: aiohttp
@@ -3,8 +3,8 @@ bizydraft/block_nodes.py,sha256=Lqn3oSCaGDHR2OICc8a2iRoRCVVK9v1-9MM3r-qIZgA,1092
3
3
  bizydraft/env.py,sha256=VFmGopVL2TGWA6hwxyFhIglCEcQxy6iVvL_raMNd6u4,407
4
4
  bizydraft/hijack_nodes.py,sha256=riHsp4DhU60E60bfXUl8kVqjs1QvQWmx_FsgQAdWTa4,4197
5
5
  bizydraft/hijack_routes.py,sha256=TVK7zdSIe4wN5dusjnAr1we43K6B_Zup5-dWcBj5rxo,3545
6
- bizydraft/oss_utils.py,sha256=XM08pOXGo2jsJlJYmctjL9ZSLUZK8NZuhHCUROXt6GA,16171
7
- bizydraft/patch_handlers.py,sha256=JWIEK4yKOPpzs3KLAiBCEYfBohyoVUOdlsVOTt3ULeY,9657
6
+ bizydraft/oss_utils.py,sha256=IjWKFZ98KNvLw7bmFuqux5mEG36B8c9eQbQmL_gZo0M,16254
7
+ bizydraft/patch_handlers.py,sha256=cP6gIYBYghWXI72hBbz3BbQHP1O5IvslS8iNK29MEbI,14159
8
8
  bizydraft/postload.py,sha256=XFElKcmCajT_oO7SVJJBaN04XcWro54N5HB5cSCxfvI,1308
9
9
  bizydraft/prestartup_patch.py,sha256=4FGjmRcDHELjtlQOrfTfk2Un5OS89QIqfq-gEcB9WDs,998
10
10
  bizydraft/resp.py,sha256=8INvKOe5Dgai3peKfqKjrhUoYeuXWXn358w30-_cY-A,369
@@ -14,9 +14,9 @@ bizydraft/static/js/aiAppHandler.js,sha256=OQRhhoqvc8iZeCvHTtdaD2VTYBGzkeAGdCk1U
14
14
  bizydraft/static/js/clipspaceToOss.js,sha256=brfEPs71Tky5Dnc47UXNEFeFlESDE3kQvUH8ducpIew,14265
15
15
  bizydraft/static/js/disableComfyWebSocket.js,sha256=ZDOVlU3v3EvWpMRkD8s_AnO43vuWojVLLjkF2pNEVrA,1957
16
16
  bizydraft/static/js/freezeModeHandler.js,sha256=SjpHD2nYymR-E13B0YcqkA6e4WycZOVI3c48Ts9qvWE,18027
17
- bizydraft/static/js/handleStyle.js,sha256=YvafKzypgXH8nD-7Fwwf_lXUSU80OWk7D4Emqj-PV88,3700
18
- bizydraft/static/js/hookLoadMedia.js,sha256=JYKKqapidi6PUIyRhyPhdGNqQW90nnLANk6qnvojIlU,17725
19
- bizydraft/static/js/hookLoadModel.js,sha256=VeDjnL67ncMvQTAh7WmdchzB_bTbj6dqlDR83wXUDN4,6726
17
+ bizydraft/static/js/handleStyle.js,sha256=2zAD9zGktGwdi0VLw4cDvqkPGTh0yYBAniytIbfI9kU,3867
18
+ bizydraft/static/js/hookLoadMedia.js,sha256=EYDuitzOFvlNzM_e4IdE0-wXHQfnyCx7KrwzoxVTdr0,6363
19
+ bizydraft/static/js/hookLoadModel.js,sha256=r8ym8ih_w39bo_B0dZwnq-b00XOL-7dhM77R7_5J_Rc,8120
20
20
  bizydraft/static/js/main.js,sha256=-pMeAG8vkKO6yq9zXCeHI78KezGn0r9AhYm65DuZrVI,188
21
21
  bizydraft/static/js/nodeFocusHandler.js,sha256=24xXbS4Q-GjJdRqf11i-1pBo8MkOJ24F7MHFV44EG6Q,4683
22
22
  bizydraft/static/js/nodeParamsFilter.js,sha256=H7lBB0G8HNqoGhOCH1hNXqPU-rPlrFyTxg_f_JgLEMk,4168
@@ -26,9 +26,9 @@ bizydraft/static/js/tool.js,sha256=2Hhv2J18OaFZRWmHIlseahjd5Ot7ZVxUPb5z46YeXIo,2
26
26
  bizydraft/static/js/uploadFile.js,sha256=WvglKzHMeOzDhOH3P-fLcPHxCLbKOJpo4DntoRxeJtI,4908
27
27
  bizydraft/static/js/workflow_io.js,sha256=FWAjncvWhvy-3nN_legD2fpRwgnIncpRLHU5X016a-U,5236
28
28
  bizydraft/static/js/hookLoad/configLoader.js,sha256=R1k0GKdEmv4IIxdT2F-oOI9X9I19ECe77P_1tO3XxgY,2525
29
- bizydraft/static/js/hookLoad/media.js,sha256=_JhQhXmNiwfCEBtrQLrsIhEzdjL_mdY-9s5bXXjzB7Y,2439
29
+ bizydraft/static/js/hookLoad/media.js,sha256=iX_zoZ0OzLAdYZ3bn5GpzpnwU-YjcBuEozQt-MwNuwk,17936
30
30
  bizydraft/static/js/hookLoad/model.js,sha256=aHvEPt9k3CcrawHSYnQYcvbtTRwIztk-XDRA3lvgXvA,9890
31
- bizydraft-0.2.78.dev20251030100819.dist-info/METADATA,sha256=pe11bzcezv005LAhUDOgsK0C8MWfi8Oxrz9DUKJf494,180
32
- bizydraft-0.2.78.dev20251030100819.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
- bizydraft-0.2.78.dev20251030100819.dist-info/top_level.txt,sha256=XtoBq6hjZhXIM7aas4GtPDtAiKo8FdLzMABXW8qqQ8M,10
34
- bizydraft-0.2.78.dev20251030100819.dist-info/RECORD,,
31
+ bizydraft-0.2.78.dev20251117024007.dist-info/METADATA,sha256=UHr86vHNHIdSNQYagWYilP-23NkFtCZtyZxgGyHIRhQ,180
32
+ bizydraft-0.2.78.dev20251117024007.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
33
+ bizydraft-0.2.78.dev20251117024007.dist-info/top_level.txt,sha256=XtoBq6hjZhXIM7aas4GtPDtAiKo8FdLzMABXW8qqQ8M,10
34
+ bizydraft-0.2.78.dev20251117024007.dist-info/RECORD,,