bizydraft 0.1.0__tar.gz
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.
- bizydraft-0.1.0/PKG-INFO +3 -0
- bizydraft-0.1.0/README.md +50 -0
- bizydraft-0.1.0/bizydraft.egg-info/PKG-INFO +3 -0
- bizydraft-0.1.0/bizydraft.egg-info/SOURCES.txt +19 -0
- bizydraft-0.1.0/bizydraft.egg-info/dependency_links.txt +1 -0
- bizydraft-0.1.0/bizydraft.egg-info/top_level.txt +1 -0
- bizydraft-0.1.0/pyproject.toml +22 -0
- bizydraft-0.1.0/setup.cfg +4 -0
- bizydraft-0.1.0/src/bizydraft/__init__.py +6 -0
- bizydraft-0.1.0/src/bizydraft/hijack_nodes.py +27 -0
- bizydraft-0.1.0/src/bizydraft/hijack_routes.py +165 -0
- bizydraft-0.1.0/src/bizydraft/resp.py +17 -0
- bizydraft-0.1.0/src/bizydraft/server.py +41 -0
- bizydraft-0.1.0/src/bizydraft/static/js/handleStyle.js +49 -0
- bizydraft-0.1.0/src/bizydraft/static/js/hookLoadImage.js +99 -0
- bizydraft-0.1.0/src/bizydraft/static/js/main.js +5 -0
- bizydraft-0.1.0/src/bizydraft/static/js/postEvent.js +412 -0
- bizydraft-0.1.0/src/bizydraft/static/js/socket.js +120 -0
- bizydraft-0.1.0/src/bizydraft/static/js/tool.js +6 -0
- bizydraft-0.1.0/src/bizydraft/static/js/uploadFile.js +165 -0
- bizydraft-0.1.0/version.txt +1 -0
bizydraft-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## CI 镜像打包
|
|
2
|
+
|
|
3
|
+
在 Actions 标签页选择 "Build and Push Docker Images (Manual Trigger)" 点 "Run workflow" 即可。
|
|
4
|
+
|
|
5
|
+
## 本地镜像打包
|
|
6
|
+
|
|
7
|
+
物理机上准备环境:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python tools/setupComfyUI.py --config comfyui_setup.json # 下载 ComfyUI 及插件
|
|
11
|
+
python tools/generateRequirements.py --config comfyui_setup.json # 生成总的依赖文件
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
打包:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bash build.sh
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 启动命令
|
|
21
|
+
|
|
22
|
+
**本地测试**
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
IMG=hub.6scloud.com/supertester/bizydraft-backend-comfyui:v202506062349
|
|
26
|
+
docker run -it --rm --network host \
|
|
27
|
+
$IMG \
|
|
28
|
+
bash -c "pip install --upgrade bizyui bizyengine && python3 /app/ComfyUI/main.py --cpu --listen 0.0.0.0 --disable-metadata --port 8188"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**comfyagent backend 启动**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
IMG=hub.6scloud.com/supertester/bizydraft-backend-comfyui:v202506062349
|
|
35
|
+
docker run --rm --network host \
|
|
36
|
+
-e BIZYAIR_SERVER_MODE=True \
|
|
37
|
+
$IMG \
|
|
38
|
+
bash -c "pip install --upgrade bizyui bizyengine && python3 /app/ComfyUI/main.py --cpu --listen 0.0.0.0 --disable-metadata --port 8188"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**BizyDraft 拖拉拽启动**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
IMG=sf-acr-registry.cn-shanghai.cr.aliyuncs.com/siliconflow/maas/bizyair-draft:v202506062349
|
|
45
|
+
docker run --rm --network host \
|
|
46
|
+
-e BIZYAIR_SERVER_MODE=True \
|
|
47
|
+
$IMG \
|
|
48
|
+
bash -c "pip install --upgrade bizyui bizyengine && python3 /app/ComfyUI/main.py --cpu --listen 0.0.0.0 --disable-metadata --port 8188"
|
|
49
|
+
```
|
|
50
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
version.txt
|
|
4
|
+
bizydraft.egg-info/PKG-INFO
|
|
5
|
+
bizydraft.egg-info/SOURCES.txt
|
|
6
|
+
bizydraft.egg-info/dependency_links.txt
|
|
7
|
+
bizydraft.egg-info/top_level.txt
|
|
8
|
+
src/bizydraft/__init__.py
|
|
9
|
+
src/bizydraft/hijack_nodes.py
|
|
10
|
+
src/bizydraft/hijack_routes.py
|
|
11
|
+
src/bizydraft/resp.py
|
|
12
|
+
src/bizydraft/server.py
|
|
13
|
+
src/bizydraft/static/js/handleStyle.js
|
|
14
|
+
src/bizydraft/static/js/hookLoadImage.js
|
|
15
|
+
src/bizydraft/static/js/main.js
|
|
16
|
+
src/bizydraft/static/js/postEvent.js
|
|
17
|
+
src/bizydraft/static/js/socket.js
|
|
18
|
+
src/bizydraft/static/js/tool.js
|
|
19
|
+
src/bizydraft/static/js/uploadFile.js
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
bizydraft
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=42", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bizydraft"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
|
|
9
|
+
[tool.setuptools]
|
|
10
|
+
packages = ["bizydraft"]
|
|
11
|
+
package-dir = { bizydraft = "src/bizydraft" }
|
|
12
|
+
include-package-data = true
|
|
13
|
+
|
|
14
|
+
[tool.setuptools.dynamic]
|
|
15
|
+
version = { file = ["version.txt"] }
|
|
16
|
+
|
|
17
|
+
[tool.setuptools.package-data]
|
|
18
|
+
# 修改为相对 bizydraft 包的路径(不需要包含 src/)
|
|
19
|
+
"bizydraft" = ["static/js/*.js", "static/js/*"]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.exclude-package-data]
|
|
22
|
+
"*" = ["__pycache__/*", "*/__pycache__/*"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from loguru import logger
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from nodes import NODE_CLASS_MAPPINGS, LoadImage
|
|
5
|
+
except ImportError:
|
|
6
|
+
logger.error(
|
|
7
|
+
"failed to import ComfyUI nodes modules, ensure PYTHONPATH is set correctly. (export PYTHONPATH=$PYTHONPATH:/path/to/ComfyUI)"
|
|
8
|
+
)
|
|
9
|
+
exit(1)
|
|
10
|
+
|
|
11
|
+
class BizyDraftLoadImage(LoadImage):
|
|
12
|
+
def __init__(self, *args, **kwargs):
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def VALIDATE_INPUTS(s, image, *args, **kwargs):
|
|
17
|
+
return True
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def hijack_nodes():
|
|
21
|
+
if "LoadImage" in NODE_CLASS_MAPPINGS:
|
|
22
|
+
del NODE_CLASS_MAPPINGS["LoadImage"]
|
|
23
|
+
NODE_CLASS_MAPPINGS["LoadImage"] = BizyDraftLoadImage
|
|
24
|
+
|
|
25
|
+
logger.info(
|
|
26
|
+
"[BizyDraft] Hijacked LoadImage node to BizyDraftLoadImage."
|
|
27
|
+
)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
import asyncio
|
|
4
|
+
import mimetypes
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from aiohttp import web, ClientSession, ClientTimeout
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from server import PromptServer
|
|
12
|
+
import execution
|
|
13
|
+
|
|
14
|
+
comfy_server = PromptServer.instance
|
|
15
|
+
except ImportError:
|
|
16
|
+
logger.error(
|
|
17
|
+
"failed to import ComfyUI modules, ensure PYTHONPATH is set correctly. (export PYTHONPATH=$PYTHONPATH:/path/to/ComfyUI)"
|
|
18
|
+
)
|
|
19
|
+
exit(1)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
BIZYDRAFT_MAX_FILE_SIZE = int(
|
|
23
|
+
os.getenv("BIZYDRAFT_MAX_FILE_SIZE", 100 * 1024 * 1024)
|
|
24
|
+
) # 100MB
|
|
25
|
+
BIZYDRAFT_REQUEST_TIMEOUT = int(
|
|
26
|
+
os.getenv("BIZYDRAFT_REQUEST_TIMEOUT", 20 * 60)
|
|
27
|
+
) # 20分钟
|
|
28
|
+
BIZYDRAFT_CHUNK_SIZE = int(os.getenv("BIZYDRAFT_CHUNK_SIZE", 1024 * 16)) # 16KB
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def view_image(request, old_handler):
|
|
32
|
+
logger.debug(
|
|
33
|
+
f"Received request for /view with query: {request.rel_url.query}"
|
|
34
|
+
)
|
|
35
|
+
if "filename" not in request.rel_url.query:
|
|
36
|
+
logger.warning("'filename' not provided in query string, returning 404")
|
|
37
|
+
return web.Response(status=404, text="'filename' not provided in query string")
|
|
38
|
+
|
|
39
|
+
filename = request.rel_url.query["filename"]
|
|
40
|
+
subfolder = request.rel_url.query.get("subfolder", "")
|
|
41
|
+
|
|
42
|
+
if not filename.startswith(("http://", "https://")) and not subfolder.startswith(
|
|
43
|
+
("http://", "https://")
|
|
44
|
+
):
|
|
45
|
+
logger.warning(f"Invalid filename format: {filename}, only URLs are supported")
|
|
46
|
+
return web.Response(
|
|
47
|
+
status=400, text="Invalid filename format(only url supported)"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
filename = (
|
|
52
|
+
f"{subfolder}/{filename}"
|
|
53
|
+
if not filename.startswith(("http://", "https://"))
|
|
54
|
+
else filename
|
|
55
|
+
) # 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
|
|
56
|
+
|
|
57
|
+
content_type, _ = mimetypes.guess_type(filename)
|
|
58
|
+
if content_type and any(x in content_type for x in ("image", "video")):
|
|
59
|
+
return web.HTTPFound(filename)
|
|
60
|
+
|
|
61
|
+
timeout = ClientTimeout(total=BIZYDRAFT_REQUEST_TIMEOUT)
|
|
62
|
+
async with ClientSession(timeout=timeout) as session:
|
|
63
|
+
async with session.get(filename) as resp:
|
|
64
|
+
resp.raise_for_status()
|
|
65
|
+
content_length = int(resp.headers.get("Content-Length", 0))
|
|
66
|
+
if content_length > BIZYDRAFT_MAX_FILE_SIZE:
|
|
67
|
+
logger.warning(
|
|
68
|
+
f"File size {human_readable_size(content_length)} exceeds limit {human_readable_size(BIZYDRAFT_MAX_FILE_SIZE)}"
|
|
69
|
+
)
|
|
70
|
+
return web.Response(
|
|
71
|
+
status=413,
|
|
72
|
+
text=f"File size exceeds limit ({human_readable_size(BIZYDRAFT_MAX_FILE_SIZE)})",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
headers = {
|
|
76
|
+
"Content-Disposition": f'attachment; filename="{uuid.uuid4()}"',
|
|
77
|
+
"Content-Type": "application/octet-stream",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
proxy_response = web.StreamResponse(headers=headers)
|
|
81
|
+
await proxy_response.prepare(request)
|
|
82
|
+
|
|
83
|
+
total_bytes = 0
|
|
84
|
+
async for chunk in resp.content.iter_chunked(BIZYDRAFT_CHUNK_SIZE):
|
|
85
|
+
total_bytes += len(chunk)
|
|
86
|
+
if total_bytes > BIZYDRAFT_MAX_FILE_SIZE:
|
|
87
|
+
await proxy_response.write(b"")
|
|
88
|
+
return web.Response(
|
|
89
|
+
status=413,
|
|
90
|
+
text=f"File size exceeds limit during streaming ({human_readable_size(BIZYDRAFT_MAX_FILE_SIZE)})",
|
|
91
|
+
)
|
|
92
|
+
await proxy_response.write(chunk)
|
|
93
|
+
|
|
94
|
+
return proxy_response
|
|
95
|
+
|
|
96
|
+
except asyncio.TimeoutError:
|
|
97
|
+
return web.Response(
|
|
98
|
+
status=504,
|
|
99
|
+
text=f"Request timed out (max {BIZYDRAFT_REQUEST_TIMEOUT//60} minutes)",
|
|
100
|
+
)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
return web.Response(
|
|
103
|
+
status=502, text=f"Failed to fetch remote resource: {str(e)}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def human_readable_size(size_bytes):
|
|
108
|
+
if size_bytes == 0:
|
|
109
|
+
return "0B"
|
|
110
|
+
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
111
|
+
i = int(math.floor(math.log(size_bytes, 1024)))
|
|
112
|
+
p = math.pow(1024, i)
|
|
113
|
+
s = round(size_bytes / p, 2)
|
|
114
|
+
return f"{s} {size_name[i]}"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def post_prompt(request):
|
|
118
|
+
json_data = await request.json()
|
|
119
|
+
logger.debug(f"Received POST request to /prompt with data: {json_data}")
|
|
120
|
+
json_data = comfy_server.trigger_on_prompt(json_data)
|
|
121
|
+
|
|
122
|
+
if "prompt" in json_data:
|
|
123
|
+
prompt = json_data["prompt"]
|
|
124
|
+
valid = execution.validate_prompt(prompt)
|
|
125
|
+
if valid[0]:
|
|
126
|
+
response = {
|
|
127
|
+
"prompt_id": None,
|
|
128
|
+
"number": None,
|
|
129
|
+
"node_errors": valid[3],
|
|
130
|
+
}
|
|
131
|
+
return web.json_response(response)
|
|
132
|
+
else:
|
|
133
|
+
return web.json_response(
|
|
134
|
+
{"error": valid[1], "node_errors": valid[3]}, status=400
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
error = {
|
|
138
|
+
"type": "no_prompt",
|
|
139
|
+
"message": "No prompt provided",
|
|
140
|
+
"details": "No prompt provided",
|
|
141
|
+
"extra_info": {},
|
|
142
|
+
}
|
|
143
|
+
return web.json_response({"error": error, "node_errors": {}}, status=400)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def hijack_routes():
|
|
147
|
+
routes = comfy_server.routes
|
|
148
|
+
for idx, route in enumerate(routes._items):
|
|
149
|
+
if route.path == "/view" and route.method == "GET":
|
|
150
|
+
old_handler = route.handler
|
|
151
|
+
|
|
152
|
+
async def new_handler(request):
|
|
153
|
+
return await view_image(request, old_handler)
|
|
154
|
+
|
|
155
|
+
routes._items[idx] = web.get("/view", new_handler)
|
|
156
|
+
routes._items[idx].kwargs.clear()
|
|
157
|
+
logger.info(
|
|
158
|
+
"Hijacked /view route to handle image, video and 3D streaming"
|
|
159
|
+
)
|
|
160
|
+
break
|
|
161
|
+
for idx, route in enumerate(routes._items):
|
|
162
|
+
if route.path == "/prompt" and route.method == "POST":
|
|
163
|
+
routes._items[idx] = web.post("/prompt", post_prompt)
|
|
164
|
+
logger.info("Hijacked /prompt route to handle prompt validation but not execution")
|
|
165
|
+
break
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from aiohttp import web
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def JsonResponse(http_status_code, data):
|
|
5
|
+
return web.json_response(
|
|
6
|
+
data,
|
|
7
|
+
status=http_status_code,
|
|
8
|
+
content_type="application/json",
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def OKResponse():
|
|
13
|
+
return JsonResponse(200, {"message": "success", "data": {}})
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def ErrResponse(err_code):
|
|
17
|
+
return JsonResponse(err_code, {"message": "", "data": {}})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from loguru import logger
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from server import PromptServer
|
|
7
|
+
except ImportError:
|
|
8
|
+
logger.error(
|
|
9
|
+
"Failed to import ComfyUI server modules, ensure PYTHONPATH is set correctly. (export PYTHONPATH=$PYTHONPATH:/path/to/ComfyUI)"
|
|
10
|
+
)
|
|
11
|
+
exit(1)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
from .resp import OKResponse, ErrResponse
|
|
15
|
+
|
|
16
|
+
_API_PREFIX = "bizyair"
|
|
17
|
+
_SERVER_MODE_HC_FLAG = True
|
|
18
|
+
|
|
19
|
+
BIZYAIR_MAGIC_STRING = os.getenv("BIZYAIR_MAGIC_STRING", "QtDtsxAc8JI1bTb7")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BizyDraftServer:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
BizyDraftServer.instance = self
|
|
25
|
+
self.prompt_server = PromptServer.instance
|
|
26
|
+
self.setup_routes()
|
|
27
|
+
|
|
28
|
+
def setup_routes(self):
|
|
29
|
+
@self.prompt_server.routes.get(f"/{_API_PREFIX}/are_you_alive")
|
|
30
|
+
async def are_you_alive(request):
|
|
31
|
+
if _SERVER_MODE_HC_FLAG:
|
|
32
|
+
return OKResponse()
|
|
33
|
+
return ErrResponse(500)
|
|
34
|
+
|
|
35
|
+
@self.prompt_server.routes.post(
|
|
36
|
+
f"/{_API_PREFIX}/are_you_alive_{BIZYAIR_MAGIC_STRING}"
|
|
37
|
+
)
|
|
38
|
+
async def toggle_are_you_alive(request):
|
|
39
|
+
global _SERVER_MODE_HC_FLAG
|
|
40
|
+
_SERVER_MODE_HC_FLAG = not _SERVER_MODE_HC_FLAG
|
|
41
|
+
return OKResponse()
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { app } from "../../scripts/app.js";
|
|
2
|
+
import { $el } from "../../scripts/ui.js";
|
|
3
|
+
const styleMenus = `
|
|
4
|
+
.p-panel-content-container{
|
|
5
|
+
display: none;
|
|
6
|
+
}
|
|
7
|
+
// .side-tool-bar-container.small-sidebar{
|
|
8
|
+
// display: none;
|
|
9
|
+
// }
|
|
10
|
+
.comfyui-menu.flex.items-center{
|
|
11
|
+
display: none;
|
|
12
|
+
}
|
|
13
|
+
.p-dialog-mask.p-overlay-mask.p-overlay-mask-enter.p-dialog-bottomright{
|
|
14
|
+
display: none !important;
|
|
15
|
+
}
|
|
16
|
+
body .bizyair-comfy-floating-button{
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
.bizy-select-title-container{
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
.p-button.p-component.p-button-outlined.p-button-sm{
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
.workflow-tabs-container{
|
|
26
|
+
display: none;
|
|
27
|
+
}
|
|
28
|
+
body .comfyui-body-bottom{
|
|
29
|
+
display: none;
|
|
30
|
+
}
|
|
31
|
+
#comfyui-body-bottom{
|
|
32
|
+
display: none;
|
|
33
|
+
}
|
|
34
|
+
.p-dialog-mask.p-overlay-mask.p-overlay-mask-enter{
|
|
35
|
+
display: none !important;
|
|
36
|
+
}
|
|
37
|
+
`
|
|
38
|
+
app.registerExtension({
|
|
39
|
+
name: "comfy.BizyAir.Style",
|
|
40
|
+
async setup() {
|
|
41
|
+
$el("style", {
|
|
42
|
+
textContent: styleMenus,
|
|
43
|
+
parent: document.head,
|
|
44
|
+
});
|
|
45
|
+
window.addEventListener('load', () => {
|
|
46
|
+
document.querySelector('[data-pc-section=mask]').style.display = 'none'
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { app } from "../../scripts/app.js";
|
|
2
|
+
import { fileToOss } from "./uploadFile.js";
|
|
3
|
+
import { getCookie } from './tool.js';
|
|
4
|
+
|
|
5
|
+
app.registerExtension({
|
|
6
|
+
name: "bizyair.image.to.oss",
|
|
7
|
+
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
8
|
+
if (nodeData.name === 'LoadImage') {
|
|
9
|
+
nodeType.prototype.onNodeCreated = async function() {
|
|
10
|
+
// const apiHost = 'http://localhost:3000/api'
|
|
11
|
+
const apiHost = 'https://uat87.bizyair.cn/api'
|
|
12
|
+
const getData = async () => {
|
|
13
|
+
const res = await fetch(`${apiHost}/community/commit_input_resource?${
|
|
14
|
+
new URLSearchParams({
|
|
15
|
+
url: '',
|
|
16
|
+
ext: '',
|
|
17
|
+
current: 1,
|
|
18
|
+
page_size: 100
|
|
19
|
+
|
|
20
|
+
}).toString()
|
|
21
|
+
}`, {
|
|
22
|
+
method: 'GET',
|
|
23
|
+
headers: {
|
|
24
|
+
'Content-Type': 'application/json',
|
|
25
|
+
'Authorization': `Bearer ${getCookie('bizy_token')}`
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
const {data} = await res.json()
|
|
29
|
+
const list = data.data.data.list || []
|
|
30
|
+
const image_list = list.filter(item => item.name).map(item => {
|
|
31
|
+
return {
|
|
32
|
+
url: item.url,
|
|
33
|
+
name: item.name
|
|
34
|
+
}
|
|
35
|
+
// return item.url
|
|
36
|
+
})
|
|
37
|
+
const image_widget = this.widgets.find(w => w.name === 'image');
|
|
38
|
+
|
|
39
|
+
const node = this;
|
|
40
|
+
|
|
41
|
+
image_widget.options.values = image_list.map(item => item.name);
|
|
42
|
+
|
|
43
|
+
// image_widget.value = image_list[0].url;
|
|
44
|
+
// image_widget.value = image_list[0];
|
|
45
|
+
if (image_list[0] && image_list[0].url) {
|
|
46
|
+
previewImage(node, decodeURIComponent(image_list[0].url))
|
|
47
|
+
}
|
|
48
|
+
image_widget.callback = function(e) {
|
|
49
|
+
const image_url = decodeURIComponent(image_list.find(item => item.name === e).url);
|
|
50
|
+
previewImage(node, image_url)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
getData()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
const upload_widget = this.widgets.find(w => w.name === 'upload');
|
|
57
|
+
upload_widget.callback = async function() {
|
|
58
|
+
const input = document.createElement('input');
|
|
59
|
+
input.type = 'file';
|
|
60
|
+
input.accept = 'image/*';
|
|
61
|
+
input.onchange = async (e) => {
|
|
62
|
+
const file = e.target.files[0];
|
|
63
|
+
await fileToOss(file);
|
|
64
|
+
|
|
65
|
+
getData()
|
|
66
|
+
}
|
|
67
|
+
input.click();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
function previewImage(node, image_url) {
|
|
75
|
+
const img = new Image();
|
|
76
|
+
img.onload = function() {
|
|
77
|
+
node.imgs = [img];
|
|
78
|
+
if (node.graph) {
|
|
79
|
+
node.graph.setDirtyCanvas(true);
|
|
80
|
+
} else {
|
|
81
|
+
console.warn('[BizyAir] 无法访问graph对象进行重绘');
|
|
82
|
+
}
|
|
83
|
+
const imageOutputStore =
|
|
84
|
+
node.nodeOutputStore ||
|
|
85
|
+
window.app?.nodeOutputStore ||
|
|
86
|
+
app?.nodeOutputStore;
|
|
87
|
+
|
|
88
|
+
if (imageOutputStore) {
|
|
89
|
+
console.log('[BizyAir] 设置节点输出数据');
|
|
90
|
+
imageOutputStore.setNodeOutputs(node, image_url, { isAnimated: false });
|
|
91
|
+
} else {
|
|
92
|
+
console.warn('[BizyAir] 未找到nodeOutputStore');
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
img.onerror = function(err) {
|
|
96
|
+
console.error('[BizyAir] 图片加载失败:', image_url, err);
|
|
97
|
+
};
|
|
98
|
+
img.src = image_url;
|
|
99
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import { app } from "../../scripts/app.js";
|
|
2
|
+
// import { $el } from "../../scripts/ui.js";
|
|
3
|
+
|
|
4
|
+
// import { WebSocketClient } from './socket.js';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
app.registerExtension({
|
|
9
|
+
name: "comfy.BizyAir.Socket",
|
|
10
|
+
|
|
11
|
+
dispatchCustomEvent(type, detail) {
|
|
12
|
+
app.api.dispatchCustomEvent(type, detail);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
customSocket(callback, customUrl) {
|
|
16
|
+
const url = customUrl || app.api.socket.url;
|
|
17
|
+
// const clientId = 'e07abdf5f465462f8dc43ca0812e9284';
|
|
18
|
+
// const socket = new WebSocket(url + "?clientId=" + sessionStorage.getItem("clientId"));
|
|
19
|
+
// const socket = new WebSocket(url + "?clientId=" + clientId);
|
|
20
|
+
const socket = new WebSocket(url);
|
|
21
|
+
|
|
22
|
+
const dispatchCustomEvent = this.dispatchCustomEvent;
|
|
23
|
+
|
|
24
|
+
socket.onmessage = function (event) {
|
|
25
|
+
try {
|
|
26
|
+
if (event.data instanceof ArrayBuffer) {
|
|
27
|
+
const view = new DataView(event.data);
|
|
28
|
+
const eventType = view.getUint32(0);
|
|
29
|
+
|
|
30
|
+
let imageMime;
|
|
31
|
+
switch (eventType) {
|
|
32
|
+
case 3:
|
|
33
|
+
const decoder = new TextDecoder();
|
|
34
|
+
const data = event.data.slice(4);
|
|
35
|
+
const nodeIdLength = view.getUint32(4);
|
|
36
|
+
dispatchCustomEvent('progress_text', {
|
|
37
|
+
nodeId: decoder.decode(data.slice(4, 4 + nodeIdLength)),
|
|
38
|
+
text: decoder.decode(data.slice(4 + nodeIdLength))
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
case 1:
|
|
42
|
+
const imageType = view.getUint32(4);
|
|
43
|
+
const imageData = event.data.slice(8);
|
|
44
|
+
switch (imageType) {
|
|
45
|
+
case 2:
|
|
46
|
+
imageMime = 'image/png';
|
|
47
|
+
break;
|
|
48
|
+
case 1:
|
|
49
|
+
default:
|
|
50
|
+
imageMime = 'image/jpeg';
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
const imageBlob = new Blob([imageData], {
|
|
54
|
+
type: imageMime
|
|
55
|
+
});
|
|
56
|
+
dispatchCustomEvent('b_preview', imageBlob);
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Unknown binary websocket message of type ${eventType}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
const msg = JSON.parse(event.data);
|
|
65
|
+
switch (msg.type) {
|
|
66
|
+
case 'status':
|
|
67
|
+
if (msg.data.sid) {
|
|
68
|
+
const clientId = msg.data.sid;
|
|
69
|
+
window.name = clientId; // use window name so it isnt reused when duplicating tabs
|
|
70
|
+
sessionStorage.setItem('clientId', clientId); // store in session storage so duplicate tab can load correct workflow
|
|
71
|
+
socket.clientId = clientId;
|
|
72
|
+
}
|
|
73
|
+
dispatchCustomEvent('status', msg.data.status ?? null);
|
|
74
|
+
break;
|
|
75
|
+
case 'executing':
|
|
76
|
+
// msg.data.prompt_id && (msg.data.prompt_id = '');
|
|
77
|
+
|
|
78
|
+
dispatchCustomEvent(
|
|
79
|
+
'executing',
|
|
80
|
+
msg.data.display_node || msg.data.node
|
|
81
|
+
);
|
|
82
|
+
break;
|
|
83
|
+
case 'execution_start':
|
|
84
|
+
case 'execution_error':
|
|
85
|
+
case 'execution_interrupted':
|
|
86
|
+
case 'execution_cached':
|
|
87
|
+
case 'execution_success':
|
|
88
|
+
case 'progress':
|
|
89
|
+
case 'executed':
|
|
90
|
+
case 'graphChanged':
|
|
91
|
+
case 'promptQueued':
|
|
92
|
+
case 'logs':
|
|
93
|
+
case 'b_preview':
|
|
94
|
+
dispatchCustomEvent(msg.type, msg.data);
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
const registeredTypes = socket.registeredTypes || new Set();
|
|
98
|
+
const reportedUnknownMessageTypes = socket.reportedUnknownMessageTypes || new Set();
|
|
99
|
+
|
|
100
|
+
if (registeredTypes.has(msg.type)) {
|
|
101
|
+
app.dispatchEvent(
|
|
102
|
+
new CustomEvent(msg.type, { detail: msg.data })
|
|
103
|
+
);
|
|
104
|
+
} else if (!reportedUnknownMessageTypes.has(msg.type)) {
|
|
105
|
+
reportedUnknownMessageTypes.add(msg.type);
|
|
106
|
+
console.warn(`Unknown message type ${msg.type}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.warn('Unhandled message:', event.data, error);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
socket.registeredTypes = new Set();
|
|
116
|
+
socket.reportedUnknownMessageTypes = new Set();
|
|
117
|
+
|
|
118
|
+
// 替换app.api.socket
|
|
119
|
+
app.api.socket = socket;
|
|
120
|
+
|
|
121
|
+
if (typeof callback === 'function') {
|
|
122
|
+
callback(socket);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return socket;
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
startSocket(callback) {
|
|
129
|
+
if (app.api.socket.readyState === WebSocket.CLOSED || app.api.socket.readyState === WebSocket.CLOSING) {
|
|
130
|
+
return this.customSocket(callback);
|
|
131
|
+
}
|
|
132
|
+
return app.api.socket;
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
closeSocket() {
|
|
136
|
+
if (app.api.socket && (app.api.socket.readyState === WebSocket.OPEN || app.api.socket.readyState === WebSocket.CONNECTING)) {
|
|
137
|
+
app.api.socket.close();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
changeSocketUrl(newUrl, callback) {
|
|
144
|
+
this.closeSocket();
|
|
145
|
+
const clientId = sessionStorage.getItem("clientId");
|
|
146
|
+
const socket = new WebSocket(newUrl + "?clientId=" + clientId + "&a=1");
|
|
147
|
+
const send = app.api.socket.send;
|
|
148
|
+
const onopen = app.api.socket.onopen;
|
|
149
|
+
const onmessage = app.api.socket.onmessage;
|
|
150
|
+
const onerror = app.api.socket.onerror;
|
|
151
|
+
const onclose = app.api.socket.onclose;
|
|
152
|
+
|
|
153
|
+
app.api.socket = socket;
|
|
154
|
+
app.api.socket.send = send;
|
|
155
|
+
app.api.socket.onopen = onopen;
|
|
156
|
+
app.api.socket.onmessage = onmessage;
|
|
157
|
+
app.api.socket.onerror = onerror;
|
|
158
|
+
app.api.socket.onclose = onclose;
|
|
159
|
+
|
|
160
|
+
if (typeof callback === 'function') {
|
|
161
|
+
callback(socket);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return socket;
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
sendSocketMessage(message) {
|
|
168
|
+
if (app.api.socket && app.api.socket.readyState === WebSocket.OPEN) {
|
|
169
|
+
app.api.socket.send(typeof message === 'string' ? message : JSON.stringify(message));
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
sendPrompt(prompt) {
|
|
176
|
+
app.queuePrompt(prompt);
|
|
177
|
+
},
|
|
178
|
+
getCookie(name) {
|
|
179
|
+
const value = `; ${document.cookie}`;
|
|
180
|
+
const parts = value.split(`; ${name}=`);
|
|
181
|
+
if (parts.length === 2) return parts.pop().split(';').shift();
|
|
182
|
+
},
|
|
183
|
+
// doSendSocket() {
|
|
184
|
+
// const socket = new WebSocketClient(`/w/v1/comfy/draft/ws?clientId=${sessionStorage.getItem("clientId")
|
|
185
|
+
// }&userId=${this.getCookie("user_id")
|
|
186
|
+
// }`);
|
|
187
|
+
// const customSocket = this.customSocket.bind(this);
|
|
188
|
+
// socket.on('message', (message) => {
|
|
189
|
+
// let data = JSON.parse(message.data);
|
|
190
|
+
// if (data.status == "Running") {
|
|
191
|
+
// customSocket(() => {}, data.wsUrl);
|
|
192
|
+
// }
|
|
193
|
+
// });
|
|
194
|
+
// },
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
async setup() {
|
|
198
|
+
const customSocket = this.customSocket.bind(this);
|
|
199
|
+
const startSocket = this.startSocket.bind(this);
|
|
200
|
+
const closeSocket = this.closeSocket.bind(this);
|
|
201
|
+
const changeSocketUrl = this.changeSocketUrl.bind(this);
|
|
202
|
+
const sendSocketMessage = this.sendSocketMessage.bind(this);
|
|
203
|
+
// let iTimer = null;
|
|
204
|
+
|
|
205
|
+
// // 直接检查一次
|
|
206
|
+
// const initialUserId = this.getCookie("user_id");
|
|
207
|
+
// if (initialUserId) {
|
|
208
|
+
// // 初始检查到 userId
|
|
209
|
+
// this.doSendSocket();
|
|
210
|
+
// }
|
|
211
|
+
|
|
212
|
+
// // 使用定时器监听cookie变化
|
|
213
|
+
// let lastCookieValue = this.getCookie("user_id");
|
|
214
|
+
// iTimer = setInterval(() => {
|
|
215
|
+
// const currentCookieValue = this.getCookie("user_id");
|
|
216
|
+
// if (currentCookieValue && currentCookieValue !== lastCookieValue) {
|
|
217
|
+
// // 检测到 cookie user_id 变化
|
|
218
|
+
// lastCookieValue = currentCookieValue;
|
|
219
|
+
// this.doSendSocket();
|
|
220
|
+
// clearInterval(iTimer);
|
|
221
|
+
// }
|
|
222
|
+
// }, 300); // 每秒检查一次
|
|
223
|
+
|
|
224
|
+
const methods = {
|
|
225
|
+
customSocket: function (params) {
|
|
226
|
+
const callback = params.callback ? new Function('socket', params.callback) : null;
|
|
227
|
+
const socket = customSocket(callback, params.url);
|
|
228
|
+
window.parent.postMessage({
|
|
229
|
+
type: 'functionResult',
|
|
230
|
+
method: 'customSocket',
|
|
231
|
+
result: '自定义socket执行结果'
|
|
232
|
+
}, '*');
|
|
233
|
+
return socket;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
startSocket: function (params) {
|
|
237
|
+
const callback = params.callback ? new Function('socket', params.callback) : null;
|
|
238
|
+
const socket = startSocket(callback);
|
|
239
|
+
window.parent.postMessage({
|
|
240
|
+
type: 'functionResult',
|
|
241
|
+
method: 'startSocket',
|
|
242
|
+
result: 'Socket连接已启动'
|
|
243
|
+
}, '*');
|
|
244
|
+
return socket;
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
closeSocket: function () {
|
|
248
|
+
const result = closeSocket();
|
|
249
|
+
window.parent.postMessage({
|
|
250
|
+
type: 'functionResult',
|
|
251
|
+
method: 'closeSocket',
|
|
252
|
+
result: result ? 'Socket连接已关闭' : 'Socket连接关闭失败或已关闭'
|
|
253
|
+
}, '*');
|
|
254
|
+
return result;
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
changeSocketUrl: function (params) {
|
|
258
|
+
if (!params.url) {
|
|
259
|
+
console.error('缺少url参数');
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const callback = params.callback ? new Function('socket', params.callback) : null;
|
|
263
|
+
const socket = changeSocketUrl(params.url, callback);
|
|
264
|
+
window.parent.postMessage({
|
|
265
|
+
type: 'functionResult',
|
|
266
|
+
method: 'changeSocketUrl',
|
|
267
|
+
result: 'Socket URL已更改为' + params.url
|
|
268
|
+
}, '*');
|
|
269
|
+
return socket;
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
sendSocketMessage: function (params) {
|
|
273
|
+
if (!params.message) {
|
|
274
|
+
console.error('缺少message参数');
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const result = sendSocketMessage(params.message);
|
|
278
|
+
window.parent.postMessage({
|
|
279
|
+
type: 'functionResult',
|
|
280
|
+
method: 'sendSocketMessage',
|
|
281
|
+
result: result ? '消息发送成功' : '消息发送失败'
|
|
282
|
+
}, '*');
|
|
283
|
+
return result;
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
clearCanvas: function () {
|
|
287
|
+
app.graph.clear();
|
|
288
|
+
window.parent.postMessage({
|
|
289
|
+
type: 'functionResult',
|
|
290
|
+
method: 'clearCanvas',
|
|
291
|
+
result: true
|
|
292
|
+
}, '*');
|
|
293
|
+
return true;
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
loadWorkflow: function (params) {
|
|
297
|
+
app.graph.clear();
|
|
298
|
+
if (params.json.version) {
|
|
299
|
+
app.loadGraphData(params.json);
|
|
300
|
+
} else {
|
|
301
|
+
app.loadApiJson(params.json, 'bizyair');
|
|
302
|
+
}
|
|
303
|
+
console.log("-----------loadWorkflow-----------", params.json)
|
|
304
|
+
window.parent.postMessage({
|
|
305
|
+
type: 'functionResult',
|
|
306
|
+
method: 'loadWorkflow',
|
|
307
|
+
result: true
|
|
308
|
+
}, '*');
|
|
309
|
+
return true;
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
saveWorkflow: async function () {
|
|
313
|
+
const graph = await app.graphToPrompt();
|
|
314
|
+
window.parent.postMessage({
|
|
315
|
+
type: 'functionResult',
|
|
316
|
+
method: 'saveWorkflow',
|
|
317
|
+
result: graph.workflow
|
|
318
|
+
}, '*');
|
|
319
|
+
return graph.workflow;
|
|
320
|
+
},
|
|
321
|
+
getWorkflow: async function () {
|
|
322
|
+
const graph = await app.graphToPrompt();
|
|
323
|
+
window.parent.postMessage({
|
|
324
|
+
type: 'functionResult',
|
|
325
|
+
method: 'getWorkflow',
|
|
326
|
+
result: graph.workflow
|
|
327
|
+
}, '*');
|
|
328
|
+
return graph.workflow;
|
|
329
|
+
},
|
|
330
|
+
saveApiJson: async function (params) {
|
|
331
|
+
const graph = await app.graphToPrompt();
|
|
332
|
+
window.parent.postMessage({
|
|
333
|
+
type: 'functionResult',
|
|
334
|
+
method: 'saveApiJson',
|
|
335
|
+
result: graph.output
|
|
336
|
+
}, '*');
|
|
337
|
+
return graph.output;
|
|
338
|
+
},
|
|
339
|
+
getClientId: function () {
|
|
340
|
+
const clientId = sessionStorage.getItem("clientId");
|
|
341
|
+
window.parent.postMessage({
|
|
342
|
+
type: 'functionResult',
|
|
343
|
+
method: 'getClientId',
|
|
344
|
+
result: clientId
|
|
345
|
+
}, '*');
|
|
346
|
+
return clientId;
|
|
347
|
+
},
|
|
348
|
+
runWorkflow: async function () {
|
|
349
|
+
const graph = await app.graphToPrompt();
|
|
350
|
+
const res = await app.queuePrompt(graph.output);
|
|
351
|
+
console.log("-----------queuePrompt-----------", res)
|
|
352
|
+
const clientId = sessionStorage.getItem("clientId");
|
|
353
|
+
window.parent.postMessage({
|
|
354
|
+
type: 'functionResult',
|
|
355
|
+
method: 'runWorkflow',
|
|
356
|
+
result: {
|
|
357
|
+
clientId: clientId,
|
|
358
|
+
jsonWorkflow: graph.output,
|
|
359
|
+
workflow: graph.workflow
|
|
360
|
+
}
|
|
361
|
+
}, '*');
|
|
362
|
+
return true;
|
|
363
|
+
},
|
|
364
|
+
setCookie: function (params) {
|
|
365
|
+
const setCookie = (name, value, days) => {
|
|
366
|
+
let expires = "";
|
|
367
|
+
if (days) {
|
|
368
|
+
const date = new Date();
|
|
369
|
+
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
370
|
+
expires = "; expires=" + date.toUTCString();
|
|
371
|
+
}
|
|
372
|
+
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
|
373
|
+
};
|
|
374
|
+
console.log("-----------setCookie-----------", params)
|
|
375
|
+
setCookie(params.name, params.value, params.days);
|
|
376
|
+
// window.parent.postMessage({
|
|
377
|
+
// type: 'functionResult',
|
|
378
|
+
// method: 'setCookie',
|
|
379
|
+
// result: true
|
|
380
|
+
// }, '*');
|
|
381
|
+
return true;
|
|
382
|
+
},
|
|
383
|
+
fitView: function () {
|
|
384
|
+
// window.app.canvas.ds.offset = [0, 0];
|
|
385
|
+
// window.app.canvas.ds.scale = 1;
|
|
386
|
+
// window.app.canvas.setDirty(true, true);
|
|
387
|
+
app.canvas.fitViewToSelectionAnimated()
|
|
388
|
+
|
|
389
|
+
window.parent.postMessage({
|
|
390
|
+
type: 'functionResult',
|
|
391
|
+
method: 'fitView',
|
|
392
|
+
result: true
|
|
393
|
+
}, '*');
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
window.addEventListener('message', function (event) {
|
|
399
|
+
if (event.data && event.data.type === 'callMethod') {
|
|
400
|
+
const methodName = event.data.method;
|
|
401
|
+
const params = event.data.params || {};
|
|
402
|
+
|
|
403
|
+
if (methods[methodName]) {
|
|
404
|
+
methods[methodName](params);
|
|
405
|
+
} else {
|
|
406
|
+
console.error('方法不存在:', methodName);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
window.parent.postMessage({ type: 'iframeReady' }, '*');
|
|
411
|
+
}
|
|
412
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export class WebSocketClient {
|
|
2
|
+
constructor(url, protocols) {
|
|
3
|
+
|
|
4
|
+
const host = 'uat-api.bizyair.cn';
|
|
5
|
+
|
|
6
|
+
if (url.startsWith('ws://') || url.startsWith('wss://')) {
|
|
7
|
+
this.url = url;
|
|
8
|
+
} else {
|
|
9
|
+
this.url = `${location.protocol == 'http:' ? 'wss' : 'wss'}://${host}${url}`
|
|
10
|
+
}
|
|
11
|
+
this.protocols = protocols
|
|
12
|
+
this.reconnectDelay = 1000
|
|
13
|
+
this.maxReconnectDelay = 30000
|
|
14
|
+
this.keepAliveInterval = 10000
|
|
15
|
+
this.ws = null
|
|
16
|
+
this.keepAliveTimer = null
|
|
17
|
+
this.reconnectTimer = null
|
|
18
|
+
|
|
19
|
+
this.connect()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
connect() {
|
|
23
|
+
this.ws = new WebSocket(this.url, this.protocols)
|
|
24
|
+
this.ws.onopen = () => {
|
|
25
|
+
this.onOpen()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.ws.onmessage = (message) => {
|
|
29
|
+
if (message.data !== 'pong') {
|
|
30
|
+
this.onMessage(message)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.ws.onerror = (error) => {
|
|
35
|
+
this.onError(error)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
this.ws.onclose = () => {
|
|
39
|
+
console.warn('The WebSocket connection has been closed and is ready to be reconnected')
|
|
40
|
+
this.onClose()
|
|
41
|
+
this.scheduleReconnect()
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
startKeepAlive() {
|
|
46
|
+
if (this.keepAliveTimer) return
|
|
47
|
+
|
|
48
|
+
this.keepAliveTimer = setInterval(() => {
|
|
49
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
50
|
+
this.ws.send('ping')
|
|
51
|
+
}
|
|
52
|
+
}, this.keepAliveInterval)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
stopKeepAlive() {
|
|
56
|
+
if (this.keepAliveTimer) {
|
|
57
|
+
clearInterval(this.keepAliveTimer)
|
|
58
|
+
this.keepAliveTimer = null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
scheduleReconnect() {
|
|
63
|
+
if (this.reconnectTimer) return
|
|
64
|
+
|
|
65
|
+
this.reconnectTimer = setTimeout(() => {
|
|
66
|
+
console.log(`Attempt to reconnect...`)
|
|
67
|
+
this.connect()
|
|
68
|
+
this.reconnectTimer = null
|
|
69
|
+
|
|
70
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay)
|
|
71
|
+
}, this.reconnectDelay)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onOpen() {
|
|
75
|
+
this.reconnectDelay = 2000
|
|
76
|
+
this.startKeepAlive()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
onMessage(message) {
|
|
80
|
+
message
|
|
81
|
+
// const data = JSON.parse(message.data);
|
|
82
|
+
// if (data === 'pong') {
|
|
83
|
+
// // Do nothing
|
|
84
|
+
// } else {
|
|
85
|
+
// console.log('message:', data);
|
|
86
|
+
// }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onError(error) {
|
|
90
|
+
console.error('WebSocket Error: ', error)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
onClose() {
|
|
94
|
+
this.stopKeepAlive()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
close() {
|
|
98
|
+
if (this.ws) {
|
|
99
|
+
this.ws.close()
|
|
100
|
+
}
|
|
101
|
+
this.stopKeepAlive()
|
|
102
|
+
if (this.reconnectTimer) {
|
|
103
|
+
clearTimeout(this.reconnectTimer)
|
|
104
|
+
this.reconnectTimer = null
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
on(event, callback) {
|
|
109
|
+
if (event === 'message') {
|
|
110
|
+
this.onMessage = callback;
|
|
111
|
+
} else if (event === 'open') {
|
|
112
|
+
this.onOpen = callback;
|
|
113
|
+
} else if (event === 'error') {
|
|
114
|
+
this.onError = callback;
|
|
115
|
+
} else if (event === 'close') {
|
|
116
|
+
this.onClose = callback;
|
|
117
|
+
}
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { getCookie } from './tool.js';
|
|
2
|
+
export async function fileToOss(file) {
|
|
3
|
+
try {
|
|
4
|
+
|
|
5
|
+
// 检查token是否存在
|
|
6
|
+
const apiHost = 'https://uat87.bizyair.cn/api'
|
|
7
|
+
// const apiHost = 'http://localhost:3000/api'
|
|
8
|
+
const authToken = getCookie('bizy_token')
|
|
9
|
+
console.log(authToken)
|
|
10
|
+
if (!authToken) {
|
|
11
|
+
throw new Error('未找到认证Token,请先登录');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 获取上传凭证
|
|
15
|
+
const uploadToken = await fetch(`${apiHost}/community/upload_token?file_name=${encodeURIComponent(file.name)}&file_type=inputs`, {
|
|
16
|
+
method: 'GET',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
'Authorization': `Bearer ${authToken}`
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// 检查响应状态
|
|
24
|
+
if (!uploadToken.ok) {
|
|
25
|
+
const errorText = await uploadToken.text();
|
|
26
|
+
console.error('获取上传凭证失败:', uploadToken.status, errorText);
|
|
27
|
+
throw new Error(`获取上传凭证失败: ${uploadToken.status} ${uploadToken.statusText}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const {data} = await uploadToken.json();
|
|
31
|
+
console.log('上传凭证响应:', data);
|
|
32
|
+
|
|
33
|
+
// 使用STS凭证上传
|
|
34
|
+
const ossConfig = {
|
|
35
|
+
accessKeyId: data.data.file.access_key_id,
|
|
36
|
+
accessKeySecret: data.data.file.access_key_secret,
|
|
37
|
+
securityToken: data.data.file.security_token,
|
|
38
|
+
bucket: data.data.storage.bucket,
|
|
39
|
+
region: data.data.storage.region,
|
|
40
|
+
objectKey: data.data.file.object_key
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
console.log('OSS配置:', ossConfig);
|
|
44
|
+
|
|
45
|
+
// 改用官方推荐的表单上传方式
|
|
46
|
+
const formData = new FormData();
|
|
47
|
+
|
|
48
|
+
// 构建Policy
|
|
49
|
+
const expiration = new Date();
|
|
50
|
+
expiration.setHours(expiration.getHours() + 1); // Policy过期时间1小时
|
|
51
|
+
|
|
52
|
+
const policyObj = {
|
|
53
|
+
expiration: expiration.toISOString(),
|
|
54
|
+
conditions: [
|
|
55
|
+
// 文件大小限制
|
|
56
|
+
['content-length-range', 0, 1048576000], // 最大1000MB
|
|
57
|
+
// 指定允许的文件名前缀
|
|
58
|
+
['starts-with', '$key', ossConfig.objectKey.split('/')[0]]
|
|
59
|
+
]
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Policy Base64编码
|
|
63
|
+
const policy = btoa(JSON.stringify(policyObj));
|
|
64
|
+
console.log('Policy:', policy);
|
|
65
|
+
|
|
66
|
+
// 构建表单字段
|
|
67
|
+
formData.append('key', ossConfig.objectKey);
|
|
68
|
+
formData.append('OSSAccessKeyId', ossConfig.accessKeyId);
|
|
69
|
+
formData.append('policy', policy);
|
|
70
|
+
formData.append('success_action_status', '200');
|
|
71
|
+
|
|
72
|
+
// 如果有临时token,需要添加
|
|
73
|
+
if (ossConfig.securityToken) {
|
|
74
|
+
formData.append('x-oss-security-token', ossConfig.securityToken);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 计算签名 - 阿里云官方要求使用HMAC-SHA1
|
|
78
|
+
const signature = await hmacSha1(policy, ossConfig.accessKeySecret);
|
|
79
|
+
console.log('计算的签名:', signature);
|
|
80
|
+
formData.append('signature', signature);
|
|
81
|
+
|
|
82
|
+
// 最后添加文件内容
|
|
83
|
+
formData.append('file', file);
|
|
84
|
+
|
|
85
|
+
// OSS服务端点
|
|
86
|
+
const host = `https://${ossConfig.bucket}.${ossConfig.region}.aliyuncs.com`;
|
|
87
|
+
console.log('上传地址:', host);
|
|
88
|
+
|
|
89
|
+
// 开始上传
|
|
90
|
+
const uploadResponse = await fetch(host, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
body: formData
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// 检查响应
|
|
96
|
+
if (!uploadResponse.ok) {
|
|
97
|
+
const errorText = await uploadResponse.text();
|
|
98
|
+
console.error('上传失败:', uploadResponse.status, errorText);
|
|
99
|
+
throw new Error(`上传失败: ${uploadResponse.status} ${uploadResponse.statusText}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 构建公开访问URL
|
|
103
|
+
const fileUrl = `${host}/${ossConfig.objectKey}`;
|
|
104
|
+
|
|
105
|
+
// 提交资源
|
|
106
|
+
await fetch(`${apiHost}/community/commit_input_resource`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
'Authorization': `Bearer ${authToken}`
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
object_key: data.data.file.object_key,
|
|
114
|
+
name: file.name,
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
return {
|
|
118
|
+
url: fileUrl,
|
|
119
|
+
ossTokenFile: data.data.file,
|
|
120
|
+
ossTokenStorage: data.data.storage
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('文件上传到OSS失败:', error);
|
|
124
|
+
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 使用标准的HMAC-SHA1签名算法
|
|
130
|
+
async function hmacSha1(message, key) {
|
|
131
|
+
// 使用浏览器原生的SubtleCrypto API
|
|
132
|
+
const encoder = new TextEncoder();
|
|
133
|
+
const keyData = encoder.encode(key);
|
|
134
|
+
const messageData = encoder.encode(message);
|
|
135
|
+
|
|
136
|
+
// 导入密钥
|
|
137
|
+
const cryptoKey = await window.crypto.subtle.importKey(
|
|
138
|
+
'raw',
|
|
139
|
+
keyData,
|
|
140
|
+
{ name: 'HMAC', hash: 'SHA-1' },
|
|
141
|
+
false,
|
|
142
|
+
['sign']
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// 计算签名
|
|
146
|
+
const signature = await window.crypto.subtle.sign(
|
|
147
|
+
'HMAC',
|
|
148
|
+
cryptoKey,
|
|
149
|
+
messageData
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// 转换为Base64编码
|
|
153
|
+
const base64Signature = arrayBufferToBase64(signature);
|
|
154
|
+
return base64Signature;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 将ArrayBuffer转换为Base64字符串
|
|
158
|
+
function arrayBufferToBase64(buffer) {
|
|
159
|
+
const bytes = new Uint8Array(buffer);
|
|
160
|
+
let binary = '';
|
|
161
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
162
|
+
binary += String.fromCharCode(bytes[i]);
|
|
163
|
+
}
|
|
164
|
+
return btoa(binary);
|
|
165
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.0
|