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,386 @@
1
+ import { app } from "../../scripts/app.js";
2
+ import { api } from "../../scripts/api.js";
3
+
4
+ window.CLIPSPACE_TO_OSS_MAP = window.CLIPSPACE_TO_OSS_MAP || {};
5
+
6
+ // ═══════════════════════════════════════════════════════════════════════════
7
+ // 工具函数:查找 clipspace 文件名对应的 OSS URL
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+ function findOssUrl(filename) {
10
+ return (
11
+ window.CLIPSPACE_TO_OSS_MAP[filename] ||
12
+ window.CLIPSPACE_TO_OSS_MAP[`${filename} [input]`] ||
13
+ window.CLIPSPACE_TO_OSS_MAP[`${filename} [output]`]
14
+ );
15
+ }
16
+
17
+ // 去掉末尾的 " [input]" 或 " [output]" 后缀
18
+ function stripTypeSuffix(value) {
19
+ if (!value || typeof value !== "string") return value;
20
+ return value.replace(/\s\[(input|output)\]$/i, "");
21
+ }
22
+ // ═══════════════════════════════════════════════════════════════════════════
23
+ // 工具函数:替换 clipspace URL 为 OSS URL
24
+ // ═══════════════════════════════════════════════════════════════════════════
25
+ function replaceClipspaceUrl(urlString) {
26
+ if (!urlString || typeof urlString !== "string") return urlString;
27
+ if (!urlString.includes("/view?") || !urlString.includes("clipspace"))
28
+ return urlString;
29
+
30
+ try {
31
+ const url = new URL(urlString, window.location.origin);
32
+ const filename = url.searchParams.get("filename");
33
+ const subfolder = url.searchParams.get("subfolder");
34
+
35
+ if (subfolder === "clipspace" && filename) {
36
+ const ossUrl = findOssUrl(filename);
37
+ if (ossUrl) {
38
+ url.searchParams.set("filename", ossUrl);
39
+ url.searchParams.set("subfolder", "");
40
+ return url.pathname + url.search;
41
+ }
42
+ }
43
+ } catch (e) {
44
+ console.error("[BizyDraft] Error replacing clipspace URL:", e);
45
+ }
46
+
47
+ return urlString;
48
+ }
49
+
50
+ // ═══════════════════════════════════════════════════════════════════════════
51
+ // 拦截图片加载请求,将 clipspace URL 替换为 OSS URL
52
+ // ═══════════════════════════════════════════════════════════════════════════
53
+ (function interceptImageLoading() {
54
+ const originalSrcDescriptor = Object.getOwnPropertyDescriptor(
55
+ Image.prototype,
56
+ "src"
57
+ );
58
+
59
+ Object.defineProperty(Image.prototype, "src", {
60
+ get() {
61
+ return originalSrcDescriptor.get.call(this);
62
+ },
63
+ set(value) {
64
+ const modifiedValue = replaceClipspaceUrl(value);
65
+ originalSrcDescriptor.set.call(this, modifiedValue);
66
+ },
67
+ configurable: true,
68
+ });
69
+
70
+ const originalSetAttribute = HTMLImageElement.prototype.setAttribute;
71
+ HTMLImageElement.prototype.setAttribute = function (name, value) {
72
+ if (name === "src") {
73
+ const modifiedValue = replaceClipspaceUrl(value);
74
+ return originalSetAttribute.call(this, name, modifiedValue);
75
+ }
76
+ return originalSetAttribute.call(this, name, value);
77
+ };
78
+ })();
79
+
80
+ // ═══════════════════════════════════════════════════════════════════════════
81
+ // 拦截上传响应,保存映射并篡改返回值
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ const originalFetchApi = api.fetchApi;
84
+ api.fetchApi = async function (url, options) {
85
+ const response = await originalFetchApi.call(this, url, options);
86
+
87
+ const isUploadApi =
88
+ url === "/upload/image" ||
89
+ url === "/upload/mask" ||
90
+ url === "/api/upload/image" ||
91
+ url === "/api/upload/mask";
92
+
93
+ if (!isUploadApi || !response.ok) {
94
+ return response;
95
+ }
96
+ try {
97
+ const data = await response.clone().json();
98
+
99
+ // 检查是否是 OSS 上传响应
100
+ const isOssUpload =
101
+ data.subfolder?.includes("http://") ||
102
+ data.subfolder?.includes("https://") ||
103
+ data.name?.startsWith("http://") ||
104
+ data.name?.startsWith("https://");
105
+
106
+ if (!isOssUpload) return response;
107
+
108
+ // 构造完整的 OSS URL
109
+ const ossUrl = data.subfolder?.includes("http")
110
+ ? `${data.subfolder}/${data.name}`
111
+ : data.name;
112
+
113
+ // 处理映射逻辑
114
+ let finalUrl = ossUrl;
115
+
116
+ if (options?.body instanceof FormData) {
117
+ const imageFile = options.body.get("image");
118
+ if (imageFile?.name) {
119
+ const filename = imageFile.name;
120
+ const idMatch = filename.match(/(\d+)/);
121
+ const baseId = idMatch?.[1];
122
+
123
+ // 第一次 /upload/mask 的结果是涂改后的完整图片
124
+ if (baseId && url.includes("/upload/mask")) {
125
+ const firstMaskKey = `__FIRST_MASK_${baseId}__`;
126
+
127
+ if (!window.CLIPSPACE_TO_OSS_MAP[firstMaskKey]) {
128
+ // 首次 mask 上传,保存到所有变体
129
+ window.CLIPSPACE_TO_OSS_MAP[firstMaskKey] = ossUrl;
130
+ finalUrl = ossUrl;
131
+
132
+ [
133
+ `clipspace-mask-${baseId}.png`,
134
+ `clipspace-paint-${baseId}.png`,
135
+ `clipspace-painted-${baseId}.png`,
136
+ `clipspace-painted-masked-${baseId}.png`,
137
+ ].forEach((v) => (window.CLIPSPACE_TO_OSS_MAP[v] = ossUrl));
138
+ } else {
139
+ // 后续 mask 上传,使用首次的 URL
140
+ finalUrl = window.CLIPSPACE_TO_OSS_MAP[firstMaskKey];
141
+ }
142
+ } else if (baseId) {
143
+ // /upload/image 的上传,如果已有 mask 则使用 mask 的 URL
144
+ const firstMaskUrl =
145
+ window.CLIPSPACE_TO_OSS_MAP[`__FIRST_MASK_${baseId}__`];
146
+ if (firstMaskUrl) {
147
+ finalUrl = firstMaskUrl;
148
+ }
149
+ }
150
+
151
+ // 保存映射
152
+ [filename, `${filename} [input]`, `${filename} [output]`].forEach(
153
+ (key) => {
154
+ window.CLIPSPACE_TO_OSS_MAP[key] = finalUrl;
155
+ }
156
+ );
157
+
158
+ const filenameWithoutSuffix = filename.replace(
159
+ / \[(input|output)\]$/,
160
+ ""
161
+ );
162
+ if (filenameWithoutSuffix !== filename) {
163
+ window.CLIPSPACE_TO_OSS_MAP[filenameWithoutSuffix] = finalUrl;
164
+ }
165
+ }
166
+ }
167
+
168
+ // 同时保存后端返回的文件名映射
169
+ window.CLIPSPACE_TO_OSS_MAP[data.name] = finalUrl;
170
+
171
+ // 🔧 修改 ComfyApp.clipspace,让它使用 OSS URL 而不是 clipspace 路径
172
+ if (window.app?.constructor?.clipspace) {
173
+ const clipspace = window.app.constructor.clipspace;
174
+
175
+ // 修改 clipspace.images
176
+ if (clipspace.images && clipspace.images.length > 0) {
177
+ const clipImage = clipspace.images[clipspace.selectedIndex || 0];
178
+ if (clipImage && clipImage.subfolder === "clipspace") {
179
+ clipspace.images[clipspace.selectedIndex || 0] = {
180
+ filename: finalUrl,
181
+ subfolder: "",
182
+ };
183
+ }
184
+ }
185
+
186
+ // 修改 clipspace.widgets
187
+ if (clipspace.widgets) {
188
+ const imageWidgetIndex = clipspace.widgets.findIndex(
189
+ (w) => w.name === "image"
190
+ );
191
+ if (imageWidgetIndex >= 0) {
192
+ const widgetValue = clipspace.widgets[imageWidgetIndex].value;
193
+ if (
194
+ widgetValue &&
195
+ typeof widgetValue === "object" &&
196
+ widgetValue.subfolder === "clipspace"
197
+ ) {
198
+ clipspace.widgets[imageWidgetIndex].value = {
199
+ filename: finalUrl,
200
+ subfolder: "",
201
+ };
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ // 篡改响应,让 ComfyUI 使用完整的 OSS URL
208
+ const modifiedData = { ...data, name: finalUrl, subfolder: "" };
209
+ return new Response(JSON.stringify(modifiedData), {
210
+ status: response.status,
211
+ statusText: response.statusText,
212
+ headers: response.headers,
213
+ });
214
+ } catch (e) {
215
+ console.error("[BizyDraft Upload] Error:", e);
216
+ return response;
217
+ }
218
+ };
219
+
220
+ // 转换 prompt 中的 clipspace 路径为 OSS URL
221
+ function convertClipspacePathsInPrompt(prompt) {
222
+ if (!prompt || typeof prompt !== "object") {
223
+ return prompt;
224
+ }
225
+
226
+ for (const [nodeId, node] of Object.entries(prompt)) {
227
+ if (!node?.inputs) continue;
228
+
229
+ for (const [inputKey, inputValue] of Object.entries(node.inputs)) {
230
+ if (typeof inputValue === "string" && inputValue.includes("clipspace")) {
231
+ // 1) 特殊情况:clipspace/https://... 或 clipspace/http://...
232
+ const ossUrlMatch = inputValue.match(/clipspace\/(https?:\/\/[^\s]+)/i);
233
+ if (ossUrlMatch) {
234
+ // 移除可能的 [input] 或 [output] 后缀
235
+ let cleanUrl = ossUrlMatch[1].replace(/\s*\[(input|output)\]$/i, "");
236
+ node.inputs[inputKey] = cleanUrl;
237
+
238
+ if (inputKey === "image" && node.inputs["image_name"]) {
239
+ node.inputs["image_name"] = cleanUrl.split("/").pop();
240
+ }
241
+ } else {
242
+ // 2) 常规情况:clipspace/xxx.png
243
+ const match = inputValue.match(
244
+ /clipspace\/([\w-]+\.(?:png|jpg|jpeg|webp|gif))/i
245
+ );
246
+ if (match) {
247
+ const filename = match[1];
248
+ const ossUrl = findOssUrl(filename);
249
+
250
+ if (ossUrl) {
251
+ node.inputs[inputKey] = ossUrl;
252
+
253
+ if (inputKey === "image" && node.inputs["image_name"]) {
254
+ node.inputs["image_name"] = ossUrl.split("/").pop();
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+
263
+ return prompt;
264
+ }
265
+
266
+ // ═══════════════════════════════════════════════════════════════════════════
267
+ // 拦截 pasteFromClipspace,确保 widget.value 使用 OSS URL
268
+ // ═══════════════════════════════════════════════════════════════════════════
269
+ function interceptPasteFromClipspace() {
270
+ const ComfyApp = window.app?.constructor;
271
+ if (!ComfyApp || !ComfyApp.pasteFromClipspace) return;
272
+
273
+ const originalPasteFromClipspace = ComfyApp.pasteFromClipspace;
274
+ ComfyApp.pasteFromClipspace = function (node) {
275
+ // 调用原始函数
276
+ originalPasteFromClipspace.call(this, node);
277
+
278
+ // 修正 widget.value
279
+ if (node.widgets) {
280
+ const imageWidget = node.widgets.find((w) => w.name === "image");
281
+ if (imageWidget && typeof imageWidget.value === "string") {
282
+ const value = imageWidget.value;
283
+
284
+ // 1) 如果是 clipspace 路径格式,替换为 OSS URL
285
+ if (value.includes("clipspace/")) {
286
+ // 1.1) 特殊情况:clipspace/https://... 或 clipspace/http://...
287
+ // 这种情况是 OSS URL 被错误地加了 clipspace/ 前缀,直接移除前缀
288
+ const ossUrlMatch = value.match(/clipspace\/(https?:\/\/[^\s]+)/i);
289
+ if (ossUrlMatch) {
290
+ // 移除可能的 [input] 或 [output] 后缀
291
+ let cleanUrl = ossUrlMatch[1].replace(
292
+ /\s*\[(input|output)\]$/i,
293
+ ""
294
+ );
295
+ imageWidget.value = cleanUrl;
296
+ } else {
297
+ // 1.2) 常规情况:clipspace/xxx.png,提取文件名并查找映射
298
+ const match = value.match(
299
+ /clipspace\/([\w-]+\.(?:png|jpg|jpeg|webp|gif))(\s\[(input|output)\])?/i
300
+ );
301
+ if (match) {
302
+ const filename = match[1];
303
+ const ossUrl = findOssUrl(filename);
304
+
305
+ if (ossUrl) {
306
+ imageWidget.value = ossUrl;
307
+ }
308
+ }
309
+ }
310
+ }
311
+ // 2) 如果是 "https://... [input]" 这样的字符串,移除后缀
312
+ else if (
313
+ /https?:\/\/.*\.(png|jpg|jpeg|webp|gif)\s\[(input|output)\]$/i.test(
314
+ value
315
+ )
316
+ ) {
317
+ const cleaned = stripTypeSuffix(value);
318
+ if (cleaned !== value) {
319
+ imageWidget.value = cleaned;
320
+ }
321
+ }
322
+ }
323
+ }
324
+ };
325
+ }
326
+ // 注册 ComfyUI 扩展
327
+ app.registerExtension({
328
+ name: "bizyair.clipspace.to.oss",
329
+
330
+ async setup() {
331
+ const originalGraphToPrompt = app.graphToPrompt;
332
+
333
+ // 在构建 Prompt 之前,先清理所有 widget 的值,去掉多余的后缀和错误的 clipspace 前缀
334
+ function sanitizeGraphWidgets(graph) {
335
+ const nodes = graph?._nodes || [];
336
+ for (const node of nodes) {
337
+ if (!node?.widgets) continue;
338
+ for (const widget of node.widgets) {
339
+ if (typeof widget?.value === "string") {
340
+ let value = widget.value;
341
+ // 先处理 clipspace/https://... 格式
342
+ if (value.includes("clipspace/")) {
343
+ const ossUrlMatch = value.match(
344
+ /clipspace\/(https?:\/\/[^\s]+)/i
345
+ );
346
+ if (ossUrlMatch) {
347
+ value = ossUrlMatch[1];
348
+ }
349
+ }
350
+ // 再移除类型后缀
351
+ widget.value = stripTypeSuffix(value);
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ app.graphToPrompt = async function (...args) {
358
+ // 预清理,避免 workflow.widgets_values 和 prompt 输入里包含 [input]/[output]
359
+ try {
360
+ sanitizeGraphWidgets(app.graph);
361
+ } catch (e) {}
362
+
363
+ const result = await originalGraphToPrompt.apply(this, args);
364
+
365
+ if (result?.output) {
366
+ // 二次清理并转换 clipspace
367
+ const cleaned = convertClipspacePathsInPrompt(result.output);
368
+ // 额外移除任何字符串输入中的类型后缀
369
+ for (const nodeId of Object.keys(cleaned || {})) {
370
+ const node = cleaned[nodeId];
371
+ if (!node?.inputs) continue;
372
+ for (const key of Object.keys(node.inputs)) {
373
+ const v = node.inputs[key];
374
+ node.inputs[key] = typeof v === "string" ? stripTypeSuffix(v) : v;
375
+ }
376
+ }
377
+ result.output = cleaned;
378
+ }
379
+
380
+ return result;
381
+ };
382
+
383
+ // 拦截 pasteFromClipspace
384
+ interceptPasteFromClipspace();
385
+ },
386
+ });
@@ -0,0 +1,64 @@
1
+ // 保存原始的 WebSocket 构造函数
2
+ const OriginalWebSocket = window.WebSocket;
3
+
4
+ // 保存原始的 fetch 函数
5
+ const OriginalFetch = window.fetch;
6
+
7
+ // 需要跳过的 URL 数组
8
+ const skipFetchUrls = ["manager/badge_mode", "pysssss/autocomplete"];
9
+
10
+ class FakeWebSocket {
11
+ constructor(url) {
12
+ this.url = url;
13
+ this.readyState = WebSocket.CONNECTING; // 核心:保持 CONNECTING 状态
14
+ console.warn("[BizyDraft] 已阻止 WebSocket 连接:", url);
15
+ }
16
+ send() {}
17
+ close() {}
18
+ addEventListener() {}
19
+ removeEventListener() {}
20
+ }
21
+
22
+ window.WebSocket = function (url, protocols) {
23
+ //精确拦截/ws请求
24
+ if (typeof url === "string" && /^wss?:\/\/[^/]+\/ws(\?.*)?$/.test(url)) {
25
+ return new FakeWebSocket(url);
26
+ }
27
+ // 其他连接正常创建,不影响
28
+ return new OriginalWebSocket(url, protocols);
29
+ };
30
+
31
+ // 保留 WebSocket 的静态属性和原型
32
+ Object.setPrototypeOf(window.WebSocket, OriginalWebSocket);
33
+ window.WebSocket.prototype = OriginalWebSocket.prototype;
34
+
35
+ // 复制静态常量(使用 defineProperty 避免只读属性错误)
36
+ ["CONNECTING", "OPEN", "CLOSING", "CLOSED"].forEach((prop) => {
37
+ Object.defineProperty(window.WebSocket, prop, {
38
+ value: OriginalWebSocket[prop],
39
+ writable: false,
40
+ enumerable: true,
41
+ configurable: true,
42
+ });
43
+ });
44
+
45
+ // 拦截 fetch 请求
46
+ window.fetch = function (url) {
47
+ // 将 URL 转换为字符串进行比较
48
+ const urlString = typeof url === "string" ? url : url.toString();
49
+ // 检查 URL 是否在跳过列表中
50
+ if (skipFetchUrls.some((skipUrl) => urlString.includes(skipUrl))) {
51
+ console.warn("[BizyDraft] 已阻止 fetch 请求:", urlString);
52
+ // 返回一个模拟的 Response 对象,状态为 200
53
+ return Promise.resolve(
54
+ new Response(null, {
55
+ status: 200,
56
+ statusText: "OK",
57
+ headers: new Headers({ "Content-Type": "application/json" }),
58
+ })
59
+ );
60
+ }
61
+
62
+ // 其他请求正常发送
63
+ return OriginalFetch.apply(this, arguments);
64
+ };