bizydraft 0.2.78.dev20251030100819__py3-none-any.whl → 0.2.82.dev20251209023307__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/hijack_routes.py +28 -1
- bizydraft/oss_utils.py +14 -3
- bizydraft/patch_handlers.py +105 -5
- bizydraft/static/js/aspectRatio.js +751 -0
- bizydraft/static/js/clipspaceToOss.js +57 -26
- bizydraft/static/js/handleStyle.js +54 -12
- bizydraft/static/js/hookLoad/media.js +415 -0
- bizydraft/static/js/hookLoadMedia.js +92 -285
- bizydraft/static/js/hookLoadModel.js +49 -3
- bizydraft/static/js/imageUpload.js +146 -0
- bizydraft/static/js/limitTimeRange.js +129 -0
- bizydraft/static/js/main.js +4 -1
- {bizydraft-0.2.78.dev20251030100819.dist-info → bizydraft-0.2.82.dev20251209023307.dist-info}/METADATA +1 -1
- {bizydraft-0.2.78.dev20251030100819.dist-info → bizydraft-0.2.82.dev20251209023307.dist-info}/RECORD +16 -13
- {bizydraft-0.2.78.dev20251030100819.dist-info → bizydraft-0.2.82.dev20251209023307.dist-info}/WHEEL +0 -0
- {bizydraft-0.2.78.dev20251030100819.dist-info → bizydraft-0.2.82.dev20251209023307.dist-info}/top_level.txt +0 -0
|
@@ -62,7 +62,7 @@ function replaceClipspaceUrl(urlString) {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
const originalSetAttribute = HTMLImageElement.prototype.setAttribute;
|
|
65
|
-
HTMLImageElement.prototype.setAttribute = function(name, value) {
|
|
65
|
+
HTMLImageElement.prototype.setAttribute = function (name, value) {
|
|
66
66
|
if (name === 'src') {
|
|
67
67
|
const modifiedValue = replaceClipspaceUrl(value);
|
|
68
68
|
return originalSetAttribute.call(this, name, modifiedValue);
|
|
@@ -75,11 +75,11 @@ function replaceClipspaceUrl(urlString) {
|
|
|
75
75
|
// 拦截上传响应,保存映射并篡改返回值
|
|
76
76
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
77
77
|
const originalFetchApi = api.fetchApi;
|
|
78
|
-
api.fetchApi = async function(url, options) {
|
|
78
|
+
api.fetchApi = async function (url, options) {
|
|
79
79
|
const response = await originalFetchApi.call(this, url, options);
|
|
80
80
|
|
|
81
81
|
const isUploadApi = url === '/upload/image' || url === '/upload/mask'
|
|
82
|
-
|
|
82
|
+
|| url === '/api/upload/image' || url === '/api/upload/mask';
|
|
83
83
|
|
|
84
84
|
if (!isUploadApi || !response.ok) {
|
|
85
85
|
return response;
|
|
@@ -89,7 +89,7 @@ api.fetchApi = async function(url, options) {
|
|
|
89
89
|
|
|
90
90
|
// 检查是否是 OSS 上传响应
|
|
91
91
|
const isOssUpload = data.subfolder?.includes('http://') || data.subfolder?.includes('https://')
|
|
92
|
-
|
|
92
|
+
|| data.name?.startsWith('http://') || data.name?.startsWith('https://');
|
|
93
93
|
|
|
94
94
|
if (!isOssUpload) return response;
|
|
95
95
|
|
|
@@ -118,7 +118,7 @@ api.fetchApi = async function(url, options) {
|
|
|
118
118
|
finalUrl = ossUrl;
|
|
119
119
|
|
|
120
120
|
[`clipspace-mask-${baseId}.png`, `clipspace-paint-${baseId}.png`,
|
|
121
|
-
|
|
121
|
+
`clipspace-painted-${baseId}.png`, `clipspace-painted-masked-${baseId}.png`
|
|
122
122
|
].forEach(v => window.CLIPSPACE_TO_OSS_MAP[v] = ossUrl);
|
|
123
123
|
|
|
124
124
|
} else {
|
|
@@ -204,16 +204,29 @@ function convertClipspacePathsInPrompt(prompt) {
|
|
|
204
204
|
|
|
205
205
|
for (const [inputKey, inputValue] of Object.entries(node.inputs)) {
|
|
206
206
|
if (typeof inputValue === 'string' && inputValue.includes('clipspace')) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
// 1) 特殊情况:clipspace/https://... 或 clipspace/http://...
|
|
208
|
+
const ossUrlMatch = inputValue.match(/clipspace\/(https?:\/\/[^\s]+)/i);
|
|
209
|
+
if (ossUrlMatch) {
|
|
210
|
+
// 移除可能的 [input] 或 [output] 后缀
|
|
211
|
+
let cleanUrl = ossUrlMatch[1].replace(/\s*\[(input|output)\]$/i, '');
|
|
212
|
+
node.inputs[inputKey] = cleanUrl;
|
|
213
|
+
|
|
214
|
+
if (inputKey === 'image' && node.inputs['image_name']) {
|
|
215
|
+
node.inputs['image_name'] = cleanUrl.split('/').pop();
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
// 2) 常规情况:clipspace/xxx.png
|
|
219
|
+
const match = inputValue.match(/clipspace\/([\w-]+\.(?:png|jpg|jpeg|webp|gif))/i);
|
|
220
|
+
if (match) {
|
|
221
|
+
const filename = match[1];
|
|
222
|
+
const ossUrl = findOssUrl(filename);
|
|
211
223
|
|
|
212
|
-
|
|
213
|
-
|
|
224
|
+
if (ossUrl) {
|
|
225
|
+
node.inputs[inputKey] = ossUrl;
|
|
214
226
|
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
if (inputKey === 'image' && node.inputs['image_name']) {
|
|
228
|
+
node.inputs['image_name'] = ossUrl.split('/').pop();
|
|
229
|
+
}
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
}
|
|
@@ -232,7 +245,7 @@ function interceptPasteFromClipspace() {
|
|
|
232
245
|
if (!ComfyApp || !ComfyApp.pasteFromClipspace) return;
|
|
233
246
|
|
|
234
247
|
const originalPasteFromClipspace = ComfyApp.pasteFromClipspace;
|
|
235
|
-
ComfyApp.pasteFromClipspace = function(node) {
|
|
248
|
+
ComfyApp.pasteFromClipspace = function (node) {
|
|
236
249
|
// 调用原始函数
|
|
237
250
|
originalPasteFromClipspace.call(this, node);
|
|
238
251
|
|
|
@@ -244,14 +257,23 @@ function interceptPasteFromClipspace() {
|
|
|
244
257
|
|
|
245
258
|
// 1) 如果是 clipspace 路径格式,替换为 OSS URL
|
|
246
259
|
if (value.includes('clipspace/')) {
|
|
247
|
-
//
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
260
|
+
// 1.1) 特殊情况:clipspace/https://... 或 clipspace/http://...
|
|
261
|
+
// 这种情况是 OSS URL 被错误地加了 clipspace/ 前缀,直接移除前缀
|
|
262
|
+
const ossUrlMatch = value.match(/clipspace\/(https?:\/\/[^\s]+)/i);
|
|
263
|
+
if (ossUrlMatch) {
|
|
264
|
+
// 移除可能的 [input] 或 [output] 后缀
|
|
265
|
+
let cleanUrl = ossUrlMatch[1].replace(/\s*\[(input|output)\]$/i, '');
|
|
266
|
+
imageWidget.value = cleanUrl;
|
|
267
|
+
} else {
|
|
268
|
+
// 1.2) 常规情况:clipspace/xxx.png,提取文件名并查找映射
|
|
269
|
+
const match = value.match(/clipspace\/([\w-]+\.(?:png|jpg|jpeg|webp|gif))(\s\[(input|output)\])?/i);
|
|
270
|
+
if (match) {
|
|
271
|
+
const filename = match[1];
|
|
272
|
+
const ossUrl = findOssUrl(filename);
|
|
273
|
+
|
|
274
|
+
if (ossUrl) {
|
|
275
|
+
imageWidget.value = ossUrl;
|
|
276
|
+
}
|
|
255
277
|
}
|
|
256
278
|
}
|
|
257
279
|
}
|
|
@@ -273,22 +295,31 @@ app.registerExtension({
|
|
|
273
295
|
async setup() {
|
|
274
296
|
const originalGraphToPrompt = app.graphToPrompt;
|
|
275
297
|
|
|
276
|
-
// 在构建 Prompt 之前,先清理所有 widget
|
|
298
|
+
// 在构建 Prompt 之前,先清理所有 widget 的值,去掉多余的后缀和错误的 clipspace 前缀
|
|
277
299
|
function sanitizeGraphWidgets(graph) {
|
|
278
300
|
const nodes = graph?._nodes || [];
|
|
279
301
|
for (const node of nodes) {
|
|
280
302
|
if (!node?.widgets) continue;
|
|
281
303
|
for (const widget of node.widgets) {
|
|
282
304
|
if (typeof widget?.value === 'string') {
|
|
283
|
-
|
|
305
|
+
let value = widget.value;
|
|
306
|
+
// 先处理 clipspace/https://... 格式
|
|
307
|
+
if (value.includes('clipspace/')) {
|
|
308
|
+
const ossUrlMatch = value.match(/clipspace\/(https?:\/\/[^\s]+)/i);
|
|
309
|
+
if (ossUrlMatch) {
|
|
310
|
+
value = ossUrlMatch[1];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// 再移除类型后缀
|
|
314
|
+
widget.value = stripTypeSuffix(value);
|
|
284
315
|
}
|
|
285
316
|
}
|
|
286
317
|
}
|
|
287
318
|
}
|
|
288
319
|
|
|
289
|
-
app.graphToPrompt = async function(...args) {
|
|
320
|
+
app.graphToPrompt = async function (...args) {
|
|
290
321
|
// 预清理,避免 workflow.widgets_values 和 prompt 输入里包含 [input]/[output]
|
|
291
|
-
try { sanitizeGraphWidgets(app.graph); } catch (e) {}
|
|
322
|
+
try { sanitizeGraphWidgets(app.graph); } catch (e) { }
|
|
292
323
|
|
|
293
324
|
const result = await originalGraphToPrompt.apply(this, args);
|
|
294
325
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { app } from "../../scripts/app.js";
|
|
2
2
|
import { $el } from "../../scripts/ui.js";
|
|
3
3
|
const styleMenus = `
|
|
4
|
-
|
|
4
|
+
/* 隐藏Panel内容容器,但排除选择工具箱 */
|
|
5
|
+
.p-panel:not(.selection-toolbox) .p-panel-content-container{
|
|
5
6
|
display: none;
|
|
6
7
|
}
|
|
7
8
|
// .side-tool-bar-container.small-sidebar{
|
|
@@ -28,10 +29,11 @@ const styleMenus = `
|
|
|
28
29
|
#comfyui-body-bottom{
|
|
29
30
|
display: none;
|
|
30
31
|
}
|
|
31
|
-
|
|
32
|
+
/* 隐藏左侧的工作流按钮 */
|
|
32
33
|
.p-button.p-component.p-button-icon-only.p-button-text.workflows-tab-button.side-bar-button.p-button-secondary{
|
|
33
34
|
display: none;
|
|
34
35
|
}
|
|
36
|
+
/* 隐藏左侧的输入输出按钮 */
|
|
35
37
|
.p-button.p-component.p-button-icon-only.p-button-text.mtb-inputs-outputs-tab-button.side-bar-button.p-button-secondary{
|
|
36
38
|
display: none;
|
|
37
39
|
}
|
|
@@ -56,9 +58,46 @@ const styleMenus = `
|
|
|
56
58
|
.side-tool-bar-container.small-sidebar .side-bar-button-label{
|
|
57
59
|
display: none;
|
|
58
60
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
/* 隐藏整个comfy-menu-button-wrapper元素 */
|
|
62
|
+
.comfy-menu-button-wrapper{
|
|
63
|
+
display: none;
|
|
64
|
+
}
|
|
65
|
+
/* 隐藏帮助中心按钮 */
|
|
66
|
+
.comfy-help-center-btn{
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
/* 隐藏底部面板(Console)按钮 */
|
|
70
|
+
button[aria-label="底部面板"]{
|
|
71
|
+
display: none;
|
|
72
|
+
}
|
|
73
|
+
/* 隐藏键盘快捷键按钮 */
|
|
74
|
+
button[aria-label^="键盘快捷键"]{
|
|
75
|
+
display: none;
|
|
76
|
+
}
|
|
77
|
+
/* 隐藏右下角按钮组(包含鼠标指针、适应视图、缩放控制、小地图、隐藏链接等按钮) */
|
|
78
|
+
.p-buttongroup.absolute.right-0.bottom-0{
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
.pointer-events-auto.relative.w-full.h-10.bg-gradient-to-r.from-blue-600.to-blue-700.flex.items-center.justify-center.px-4 {
|
|
82
|
+
display: none;
|
|
83
|
+
}
|
|
84
|
+
.p-splitterpanel.p-splitterpanel-nested.flex.flex-col .ml-1.flex.pt-1{
|
|
85
|
+
display: none;
|
|
86
|
+
}
|
|
87
|
+
.p-button.p-component.p-button-text.size-8.bg-primary-background.text-white.p-0{
|
|
88
|
+
display: none;
|
|
89
|
+
}
|
|
90
|
+
.p-button.p-component.p-button-secondary.p-button-text.h-8.w-8.px-0{
|
|
91
|
+
display: none;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* 隐藏左侧的资产按钮 */
|
|
95
|
+
.p-button.p-component.p-button-icon-only.p-button-text.assets-tab-button.side-bar-button.p-button-secondary {
|
|
96
|
+
display: none;
|
|
97
|
+
}
|
|
98
|
+
/* 隐藏左侧的模型库按钮 */
|
|
99
|
+
.p-button.p-component.p-button-icon-only.p-button-text.model-library-tab-button.side-bar-button.p-button-secondary{
|
|
100
|
+
display: none;
|
|
62
101
|
}
|
|
63
102
|
`;
|
|
64
103
|
app.registerExtension({
|
|
@@ -69,13 +108,16 @@ app.registerExtension({
|
|
|
69
108
|
parent: document.head,
|
|
70
109
|
});
|
|
71
110
|
const getCloseBtn = () => {
|
|
72
|
-
let temp = null
|
|
73
|
-
document.querySelectorAll('
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
111
|
+
// let temp = null
|
|
112
|
+
// document.querySelectorAll('h2').forEach(e => {
|
|
113
|
+
// if (e.innerHTML == "<span>模板</span>") {
|
|
114
|
+
// const dialogContent = e.closest('.p-dialog-content')
|
|
115
|
+
// if (dialogContent) {
|
|
116
|
+
// temp = dialogContent.querySelector('i.pi.pi-times.text-sm')
|
|
117
|
+
// }
|
|
118
|
+
// }
|
|
119
|
+
// })
|
|
120
|
+
return document.querySelector('i.pi.pi-times.text-sm')
|
|
79
121
|
}
|
|
80
122
|
const getFengrossmentBtn = () => {
|
|
81
123
|
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
|
+
}
|