bizyengine 1.2.2__py3-none-any.whl → 1.2.4__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.
- bizyengine/bizy_server/api_client.py +142 -0
- bizyengine/bizy_server/errno.py +15 -0
- bizyengine/bizy_server/server.py +159 -0
- bizyengine/bizy_server/stream_response.py +268 -0
- bizyengine/bizyair_extras/__init__.py +1 -0
- bizyengine/bizyair_extras/nodes_nunchaku.py +193 -0
- bizyengine/core/commands/servers/prompt_server.py +2 -2
- bizyengine/core/common/client.py +44 -31
- bizyengine/core/common/env_var.py +6 -3
- bizyengine/core/configs/models.json +1 -0
- bizyengine/core/configs/models.yaml +11 -0
- bizyengine/misc/auth.py +15 -26
- bizyengine/misc/utils.py +2 -2
- bizyengine/version.txt +1 -1
- {bizyengine-1.2.2.dist-info → bizyengine-1.2.4.dist-info}/METADATA +1 -1
- {bizyengine-1.2.2.dist-info → bizyengine-1.2.4.dist-info}/RECORD +18 -16
- {bizyengine-1.2.2.dist-info → bizyengine-1.2.4.dist-info}/WHEEL +1 -1
- {bizyengine-1.2.2.dist-info → bizyengine-1.2.4.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
1
3
|
import os
|
|
2
4
|
import urllib
|
|
3
5
|
|
|
@@ -19,6 +21,10 @@ version_path = os.path.join(os.path.dirname(__file__), "..", "version.txt")
|
|
|
19
21
|
with open(version_path, "r") as file:
|
|
20
22
|
CLIENT_VERSION = file.read().strip()
|
|
21
23
|
|
|
24
|
+
# API请求地址
|
|
25
|
+
QWEN_MODEL_API_URL = "https://api.siliconflow.cn/v1/chat/completions"
|
|
26
|
+
QWEN_IMAGE_API_URL = "https://api.siliconflow.cn/v1/images/generations"
|
|
27
|
+
|
|
22
28
|
|
|
23
29
|
class APIClient:
|
|
24
30
|
def __init__(self):
|
|
@@ -1071,3 +1077,139 @@ class APIClient:
|
|
|
1071
1077
|
except Exception as e:
|
|
1072
1078
|
print(f"\033[31m[BizyAir]\033[0m Fail to get recent cost: {str(e)}")
|
|
1073
1079
|
return None, errnos.GET_RECENT_COST
|
|
1080
|
+
|
|
1081
|
+
async def forward_model_request(self, request_data):
|
|
1082
|
+
try:
|
|
1083
|
+
import asyncio
|
|
1084
|
+
import json
|
|
1085
|
+
|
|
1086
|
+
from .stream_response import StreamResponse
|
|
1087
|
+
|
|
1088
|
+
request_data["stream"] = True
|
|
1089
|
+
# 参数检查
|
|
1090
|
+
if "messages" not in request_data:
|
|
1091
|
+
return None, errnos.MODEL_API_ERROR
|
|
1092
|
+
|
|
1093
|
+
if (
|
|
1094
|
+
not isinstance(request_data["messages"], list)
|
|
1095
|
+
or len(request_data["messages"]) == 0
|
|
1096
|
+
):
|
|
1097
|
+
return None, errnos.MODEL_API_ERROR
|
|
1098
|
+
|
|
1099
|
+
# 获取用户API密钥
|
|
1100
|
+
api_key = get_api_key()
|
|
1101
|
+
|
|
1102
|
+
# 创建自定义流式响应对象,设置60秒超时
|
|
1103
|
+
response_stream = StreamResponse(request_data, timeout=60)
|
|
1104
|
+
|
|
1105
|
+
# 异步启动连接和数据请求
|
|
1106
|
+
asyncio.create_task(response_stream.connect_and_request(api_key))
|
|
1107
|
+
|
|
1108
|
+
# 返回流式响应对象
|
|
1109
|
+
return response_stream, None
|
|
1110
|
+
|
|
1111
|
+
except Exception as e:
|
|
1112
|
+
print(f"\033[31m[BizyAir]\033[0m Model API forwarding failed: {str(e)}")
|
|
1113
|
+
return None, errnos.MODEL_API_ERROR
|
|
1114
|
+
|
|
1115
|
+
async def stream_model_response(self, response):
|
|
1116
|
+
"""
|
|
1117
|
+
流式传输模型响应
|
|
1118
|
+
|
|
1119
|
+
Args:
|
|
1120
|
+
response: 从API获取的StreamResponse对象
|
|
1121
|
+
|
|
1122
|
+
Returns:
|
|
1123
|
+
一个异步生成器,用于流式传输响应数据
|
|
1124
|
+
"""
|
|
1125
|
+
# 生成请求ID用于日志
|
|
1126
|
+
req_id = f"stream-{id(response)}"
|
|
1127
|
+
|
|
1128
|
+
# 验证response对象是否有效
|
|
1129
|
+
if not response:
|
|
1130
|
+
print(f"\033[31m[BizyAir-{req_id}]\033[0m 无效的response对象 (为None)")
|
|
1131
|
+
yield b'data: {"error": "Invalid response object (None)"}\n\n'
|
|
1132
|
+
yield b"data: [DONE]\n\n"
|
|
1133
|
+
return
|
|
1134
|
+
|
|
1135
|
+
if not hasattr(response, "content") or not hasattr(
|
|
1136
|
+
response.content, "iter_any"
|
|
1137
|
+
):
|
|
1138
|
+
print(
|
|
1139
|
+
f"\033[31m[BizyAir-{req_id}]\033[0m 无效的response对象 (缺少必要方法)"
|
|
1140
|
+
)
|
|
1141
|
+
yield b'data: {"error": "Invalid response object (missing methods)"}\n\n'
|
|
1142
|
+
yield b"data: [DONE]\n\n"
|
|
1143
|
+
return
|
|
1144
|
+
|
|
1145
|
+
error_occurred = False
|
|
1146
|
+
chunks_forwarded = 0
|
|
1147
|
+
chunk = None # 定义在外部,以便finally块可以访问
|
|
1148
|
+
|
|
1149
|
+
try:
|
|
1150
|
+
# 使用StreamResponse.iter_any方法创建流式传输生成器
|
|
1151
|
+
async for chunk in response.content.iter_any():
|
|
1152
|
+
chunks_forwarded += 1
|
|
1153
|
+
|
|
1154
|
+
yield chunk
|
|
1155
|
+
# 是否是结束标记
|
|
1156
|
+
if b"data: [DONE]" in chunk:
|
|
1157
|
+
break
|
|
1158
|
+
# 是否包含错误
|
|
1159
|
+
if b'data: {"error"' in chunk or b'data: {"message":' in chunk:
|
|
1160
|
+
error_occurred = True
|
|
1161
|
+
|
|
1162
|
+
except Exception as e:
|
|
1163
|
+
error_occurred = True
|
|
1164
|
+
error_msg = f"读取流式数据失败: {str(e)}"
|
|
1165
|
+
print(f"\033[31m[BizyAir-{req_id}]\033[0m {error_msg}")
|
|
1166
|
+
|
|
1167
|
+
# 返回错误消息
|
|
1168
|
+
error_json = json.dumps({"error": error_msg})
|
|
1169
|
+
yield f"data: {error_json}\n\n".encode("utf-8")
|
|
1170
|
+
|
|
1171
|
+
# 如果还没有发送过DONE,则发送
|
|
1172
|
+
if chunks_forwarded == 0 or (chunk and not b"data: [DONE]" in chunk):
|
|
1173
|
+
yield b"data: [DONE]\n\n"
|
|
1174
|
+
|
|
1175
|
+
finally:
|
|
1176
|
+
# 如果没有发生错误,但也没有转发任何数据,发送一个空响应
|
|
1177
|
+
if not error_occurred and chunks_forwarded == 0:
|
|
1178
|
+
yield b'data: {"content": ""}\n\n'
|
|
1179
|
+
yield b"data: [DONE]\n\n"
|
|
1180
|
+
|
|
1181
|
+
async def release(self):
|
|
1182
|
+
"""关闭连接"""
|
|
1183
|
+
if self.writer and not self.closed:
|
|
1184
|
+
self.writer.close()
|
|
1185
|
+
try:
|
|
1186
|
+
await self.writer.wait_closed()
|
|
1187
|
+
except Exception:
|
|
1188
|
+
pass
|
|
1189
|
+
self.closed = True
|
|
1190
|
+
|
|
1191
|
+
async def forward_image_request(self, request_data):
|
|
1192
|
+
try:
|
|
1193
|
+
api_key = get_api_key()
|
|
1194
|
+
headers = {
|
|
1195
|
+
"Content-Type": "application/json",
|
|
1196
|
+
"Authorization": f"Bearer {api_key}",
|
|
1197
|
+
}
|
|
1198
|
+
# 创建异步HTTP会话
|
|
1199
|
+
async with aiohttp.ClientSession() as session:
|
|
1200
|
+
async with session.post(
|
|
1201
|
+
QWEN_IMAGE_API_URL, headers=headers, json=request_data
|
|
1202
|
+
) as response:
|
|
1203
|
+
# 读取并解析响应
|
|
1204
|
+
if response.status != 200:
|
|
1205
|
+
error_text = await response.text()
|
|
1206
|
+
print(
|
|
1207
|
+
f"\033[31m[BizyAir]\033[0m Image generation failed: {error_text}"
|
|
1208
|
+
)
|
|
1209
|
+
return None, errnos.MODEL_API_ERROR
|
|
1210
|
+
|
|
1211
|
+
result = await response.json()
|
|
1212
|
+
return result, None
|
|
1213
|
+
except Exception as e:
|
|
1214
|
+
print(f"\033[31m[BizyAir]\033[0m Image generation request failed: {str(e)}")
|
|
1215
|
+
return None, errnos.MODEL_API_ERROR
|
bizyengine/bizy_server/errno.py
CHANGED
|
@@ -442,3 +442,18 @@ class errnos:
|
|
|
442
442
|
GET_RECENT_COST = ErrorNo(
|
|
443
443
|
500, 500157, None, {"en": "Failed to get recent cost", "zh": "获取最近消费失败"}
|
|
444
444
|
)
|
|
445
|
+
|
|
446
|
+
MODEL_API_ERROR = ErrorNo(
|
|
447
|
+
500, 500158, None, {"en": "Failed to call model API", "zh": "调用模型API失败"}
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
MODEL_API_TIMEOUT = ErrorNo(
|
|
451
|
+
500, 500159, None, {"en": "Model API request timeout", "zh": "模型API请求超时"}
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
STREAMING_CONNECTION_ERROR = ErrorNo(
|
|
455
|
+
500,
|
|
456
|
+
500160,
|
|
457
|
+
None,
|
|
458
|
+
{"en": "Streaming connection closed unexpectedly", "zh": "流式连接异常关闭"},
|
|
459
|
+
)
|
bizyengine/bizy_server/server.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import configparser
|
|
3
|
+
import json
|
|
3
4
|
import logging
|
|
4
5
|
import os
|
|
5
6
|
import shutil
|
|
@@ -23,6 +24,7 @@ COMMUNITY_API = f"{API_PREFIX}/community"
|
|
|
23
24
|
MODEL_HOST_API = f"{API_PREFIX}/modelhost"
|
|
24
25
|
USER_API = f"{API_PREFIX}/user"
|
|
25
26
|
INVOICE_API = f"{API_PREFIX}/invoices"
|
|
27
|
+
MODEL_API = f"{API_PREFIX}/model"
|
|
26
28
|
|
|
27
29
|
logging.basicConfig(level=logging.DEBUG)
|
|
28
30
|
|
|
@@ -957,6 +959,163 @@ class BizyAirServer:
|
|
|
957
959
|
|
|
958
960
|
return OKResponse(resp)
|
|
959
961
|
|
|
962
|
+
@self.prompt_server.routes.post(f"/{MODEL_API}/chat")
|
|
963
|
+
async def chat_completions(request):
|
|
964
|
+
response = None # 确保变量在退出前定义
|
|
965
|
+
resp = None # 响应对象引用
|
|
966
|
+
req_id = f"req-{id(request)}" # 为请求生成唯一ID
|
|
967
|
+
|
|
968
|
+
try:
|
|
969
|
+
# 解析请求数据
|
|
970
|
+
request_data = await request.json()
|
|
971
|
+
|
|
972
|
+
# 转发请求到模型服务
|
|
973
|
+
response, err = await self.api_client.forward_model_request(
|
|
974
|
+
request_data
|
|
975
|
+
)
|
|
976
|
+
if err is not None:
|
|
977
|
+
print(
|
|
978
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 转发请求失败: {err.message}"
|
|
979
|
+
)
|
|
980
|
+
return ErrResponse(err)
|
|
981
|
+
|
|
982
|
+
# 创建并准备流式响应
|
|
983
|
+
resp = aiohttp.web.StreamResponse(
|
|
984
|
+
status=200,
|
|
985
|
+
reason="OK",
|
|
986
|
+
headers={
|
|
987
|
+
"Content-Type": "text/event-stream",
|
|
988
|
+
"Cache-Control": "no-cache",
|
|
989
|
+
"Connection": "keep-alive",
|
|
990
|
+
"X-Accel-Buffering": "no", # 禁用Nginx缓冲
|
|
991
|
+
},
|
|
992
|
+
)
|
|
993
|
+
await resp.prepare(request)
|
|
994
|
+
|
|
995
|
+
# 确保异步生成器正确创建
|
|
996
|
+
generator = self.api_client.stream_model_response(response)
|
|
997
|
+
if not generator:
|
|
998
|
+
print(f"\033[31m[聊天请求-{req_id}]\033[0m 创建异步生成器失败")
|
|
999
|
+
# 返回错误响应
|
|
1000
|
+
if not resp.prepared:
|
|
1001
|
+
return ErrResponse(errnos.MODEL_API_ERROR)
|
|
1002
|
+
else:
|
|
1003
|
+
# 如果响应已准备,发送错误数据帧
|
|
1004
|
+
try:
|
|
1005
|
+
error_msg = json.dumps({"error": "创建数据流失败"})
|
|
1006
|
+
await resp.write(f"data: {error_msg}\n\n".encode("utf-8"))
|
|
1007
|
+
await resp.write(b"data: [DONE]\n\n")
|
|
1008
|
+
except Exception as e:
|
|
1009
|
+
print(
|
|
1010
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 写入错误消息时出错: {str(e)}"
|
|
1011
|
+
)
|
|
1012
|
+
finally:
|
|
1013
|
+
try:
|
|
1014
|
+
await resp.write_eof()
|
|
1015
|
+
except:
|
|
1016
|
+
pass
|
|
1017
|
+
return resp
|
|
1018
|
+
|
|
1019
|
+
# 开始流式传输
|
|
1020
|
+
any_chunk_sent = False # 跟踪是否发送了任何数据块
|
|
1021
|
+
|
|
1022
|
+
try:
|
|
1023
|
+
async for chunk in generator:
|
|
1024
|
+
if chunk:
|
|
1025
|
+
try:
|
|
1026
|
+
await resp.write(chunk)
|
|
1027
|
+
any_chunk_sent = True
|
|
1028
|
+
await resp.drain() # 确保数据被立即发送
|
|
1029
|
+
except (
|
|
1030
|
+
ConnectionResetError,
|
|
1031
|
+
ConnectionError,
|
|
1032
|
+
aiohttp.ClientOSError,
|
|
1033
|
+
) as e:
|
|
1034
|
+
print(
|
|
1035
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 流式传输中连接错误: {str(e)}"
|
|
1036
|
+
)
|
|
1037
|
+
break
|
|
1038
|
+
except Exception as e:
|
|
1039
|
+
print(f"\033[31m[聊天请求-{req_id}]\033[0m 流式传输错误: {str(e)}")
|
|
1040
|
+
# 如果尚未发送任何数据块,尝试发送错误信息
|
|
1041
|
+
if not any_chunk_sent and not resp.prepared:
|
|
1042
|
+
return ErrResponse(errnos.MODEL_API_ERROR)
|
|
1043
|
+
elif not any_chunk_sent:
|
|
1044
|
+
try:
|
|
1045
|
+
error_msg = json.dumps({"error": f"流式传输错误: {str(e)}"})
|
|
1046
|
+
await resp.write(f"data: {error_msg}\n\n".encode("utf-8"))
|
|
1047
|
+
await resp.write(b"data: [DONE]\n\n")
|
|
1048
|
+
except Exception as write_err:
|
|
1049
|
+
print(
|
|
1050
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 写入错误消息时出错: {str(write_err)}"
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
# 检查是否发送了数据
|
|
1054
|
+
if not any_chunk_sent:
|
|
1055
|
+
print(
|
|
1056
|
+
f"\033[33m[聊天请求-{req_id}]\033[0m 警告: 没有发送任何数据块"
|
|
1057
|
+
)
|
|
1058
|
+
try:
|
|
1059
|
+
await resp.write(b'data: {"content": ""}\n\n')
|
|
1060
|
+
await resp.write(b"data: [DONE]\n\n")
|
|
1061
|
+
except Exception as e:
|
|
1062
|
+
print(
|
|
1063
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 写入空响应时出错: {str(e)}"
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
try:
|
|
1067
|
+
await resp.write_eof()
|
|
1068
|
+
except Exception as e:
|
|
1069
|
+
print(
|
|
1070
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 结束响应时出错: {str(e)}"
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
return resp
|
|
1074
|
+
|
|
1075
|
+
except Exception as e:
|
|
1076
|
+
print(
|
|
1077
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 处理请求时发生错误: {str(e)}"
|
|
1078
|
+
)
|
|
1079
|
+
# 如果响应已经准备好,尝试发送错误信息
|
|
1080
|
+
if resp and resp.prepared:
|
|
1081
|
+
try:
|
|
1082
|
+
error_msg = json.dumps({"error": f"服务器错误: {str(e)}"})
|
|
1083
|
+
await resp.write(f"data: {error_msg}\n\n".encode("utf-8"))
|
|
1084
|
+
await resp.write(b"data: [DONE]\n\n")
|
|
1085
|
+
await resp.write_eof()
|
|
1086
|
+
except:
|
|
1087
|
+
pass
|
|
1088
|
+
return resp
|
|
1089
|
+
|
|
1090
|
+
return ErrResponse(errnos.MODEL_API_ERROR)
|
|
1091
|
+
|
|
1092
|
+
finally:
|
|
1093
|
+
# 确保所有资源被释放
|
|
1094
|
+
if response and hasattr(response, "release"):
|
|
1095
|
+
try:
|
|
1096
|
+
await response.release()
|
|
1097
|
+
except Exception as e:
|
|
1098
|
+
print(
|
|
1099
|
+
f"\033[31m[聊天请求-{req_id}]\033[0m 关闭连接时出错: {str(e)}"
|
|
1100
|
+
)
|
|
1101
|
+
|
|
1102
|
+
@self.prompt_server.routes.post(f"/{MODEL_API}/images")
|
|
1103
|
+
async def image_generations(request):
|
|
1104
|
+
try:
|
|
1105
|
+
# 解析请求数据
|
|
1106
|
+
request_data = await request.json()
|
|
1107
|
+
|
|
1108
|
+
# 转发图像生成请求
|
|
1109
|
+
result, err = await self.api_client.forward_image_request(request_data)
|
|
1110
|
+
if err is not None:
|
|
1111
|
+
return ErrResponse(err)
|
|
1112
|
+
|
|
1113
|
+
# 返回结果
|
|
1114
|
+
return OKResponse(result)
|
|
1115
|
+
|
|
1116
|
+
except Exception:
|
|
1117
|
+
return ErrResponse(errnos.MODEL_API_ERROR)
|
|
1118
|
+
|
|
960
1119
|
async def send_json(self, event, data, sid=None):
|
|
961
1120
|
message = {"type": event, "data": data}
|
|
962
1121
|
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import ssl
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ConnectionState:
|
|
8
|
+
"""连接状态枚举"""
|
|
9
|
+
|
|
10
|
+
INIT = "初始化"
|
|
11
|
+
CONNECTING = "连接中"
|
|
12
|
+
CONNECTED = "已连接"
|
|
13
|
+
READING = "读取中"
|
|
14
|
+
CLOSING = "关闭中"
|
|
15
|
+
CLOSED = "已关闭"
|
|
16
|
+
ERROR = "错误"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StreamResponse:
|
|
20
|
+
"""自定义流式响应类,支持状态管理和错误处理"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, request_data: Dict[str, Any], timeout: int = 60):
|
|
23
|
+
# 请求和连接数据
|
|
24
|
+
self.request_data = request_data
|
|
25
|
+
self.reader = None
|
|
26
|
+
self.writer = None
|
|
27
|
+
self.timeout = timeout
|
|
28
|
+
|
|
29
|
+
# 连接状态管理
|
|
30
|
+
self.state = ConnectionState.INIT
|
|
31
|
+
self.state_lock = asyncio.Lock() # 状态修改锁
|
|
32
|
+
self.connection_event = asyncio.Event()
|
|
33
|
+
self.error = None
|
|
34
|
+
|
|
35
|
+
# 数据相关
|
|
36
|
+
self.content = self
|
|
37
|
+
self.buffer = b""
|
|
38
|
+
self.chunks_received = 0
|
|
39
|
+
self.done_received = False
|
|
40
|
+
|
|
41
|
+
# 调试信息
|
|
42
|
+
self.debug_id = id(self) # 用于日志区分不同实例
|
|
43
|
+
|
|
44
|
+
async def _set_state(self, new_state: str, error: Exception = None) -> None:
|
|
45
|
+
"""线程安全地设置连接状态"""
|
|
46
|
+
async with self.state_lock:
|
|
47
|
+
self.state = new_state
|
|
48
|
+
if error:
|
|
49
|
+
self.error = error
|
|
50
|
+
|
|
51
|
+
# 如果状态为已连接,触发连接事件
|
|
52
|
+
if new_state == ConnectionState.CONNECTED:
|
|
53
|
+
self.connection_event.set()
|
|
54
|
+
|
|
55
|
+
# 如果状态为错误,也触发连接事件,让等待连接的代码继续执行并处理错误
|
|
56
|
+
if new_state == ConnectionState.ERROR:
|
|
57
|
+
self.connection_event.set()
|
|
58
|
+
|
|
59
|
+
async def connect_and_request(self, api_key: str):
|
|
60
|
+
"""建立连接并发送请求,包含严格的状态管理"""
|
|
61
|
+
# 设置状态为连接中
|
|
62
|
+
await self._set_state(ConnectionState.CONNECTING)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
# 建立SSL连接,添加超时控制
|
|
66
|
+
connect_coro = asyncio.open_connection("api.siliconflow.cn", 443, ssl=True)
|
|
67
|
+
self.reader, self.writer = await asyncio.wait_for(
|
|
68
|
+
connect_coro, timeout=self.timeout
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# 准备HTTP请求
|
|
72
|
+
json_data = json.dumps(self.request_data)
|
|
73
|
+
request = (
|
|
74
|
+
f"POST /v1/chat/completions HTTP/1.1\r\n"
|
|
75
|
+
f"Host: api.siliconflow.cn\r\n"
|
|
76
|
+
f"Authorization: Bearer {api_key}\r\n"
|
|
77
|
+
f"Accept: text/event-stream\r\n"
|
|
78
|
+
f"Content-Type: application/json\r\n"
|
|
79
|
+
f"Content-Length: {len(json_data)}\r\n"
|
|
80
|
+
f"Connection: keep-alive\r\n"
|
|
81
|
+
f"\r\n"
|
|
82
|
+
f"{json_data}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# 检查是否仍在连接状态
|
|
86
|
+
async with self.state_lock:
|
|
87
|
+
if self.state != ConnectionState.CONNECTING:
|
|
88
|
+
raise Exception(f"连接状态已变更为 {self.state},无法发送请求")
|
|
89
|
+
|
|
90
|
+
# 发送请求
|
|
91
|
+
self.writer.write(request.encode("utf-8"))
|
|
92
|
+
await asyncio.wait_for(self.writer.drain(), timeout=self.timeout)
|
|
93
|
+
|
|
94
|
+
# 读取HTTP响应头
|
|
95
|
+
response_line = await asyncio.wait_for(
|
|
96
|
+
self.reader.readline(), timeout=self.timeout
|
|
97
|
+
)
|
|
98
|
+
if not response_line:
|
|
99
|
+
raise Exception("服务器关闭了连接")
|
|
100
|
+
|
|
101
|
+
status_line = response_line.decode("utf-8").strip()
|
|
102
|
+
|
|
103
|
+
if not status_line.startswith("HTTP/1.1 200"):
|
|
104
|
+
raise Exception(f"API请求失败: {status_line}")
|
|
105
|
+
|
|
106
|
+
# 读取响应头
|
|
107
|
+
headers = {}
|
|
108
|
+
while True:
|
|
109
|
+
# 再次检查状态
|
|
110
|
+
async with self.state_lock:
|
|
111
|
+
if self.state != ConnectionState.CONNECTING:
|
|
112
|
+
raise Exception(
|
|
113
|
+
f"连接状态已变更为 {self.state},停止读取响应头"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
line = await asyncio.wait_for(
|
|
117
|
+
self.reader.readline(), timeout=self.timeout
|
|
118
|
+
)
|
|
119
|
+
if line == b"\r\n" or not line:
|
|
120
|
+
break
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
header_line = line.decode("utf-8").strip()
|
|
124
|
+
if ":" in header_line:
|
|
125
|
+
key, value = header_line.split(":", 1)
|
|
126
|
+
headers[key.strip()] = value.strip()
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
# 成功连接
|
|
131
|
+
await self._set_state(ConnectionState.CONNECTED)
|
|
132
|
+
|
|
133
|
+
except asyncio.TimeoutError as e:
|
|
134
|
+
await self._set_state(ConnectionState.ERROR, e)
|
|
135
|
+
await self.release()
|
|
136
|
+
raise Exception(f"连接超时 (>{self.timeout}秒)") from e
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
await self._set_state(ConnectionState.ERROR, e)
|
|
140
|
+
await self.release()
|
|
141
|
+
raise Exception(f"连接失败: {str(e)}") from e
|
|
142
|
+
|
|
143
|
+
async def iter_any(self):
|
|
144
|
+
"""模拟aiohttp的iter_any方法"""
|
|
145
|
+
# 等待连接完成
|
|
146
|
+
if not self.connection_event.is_set():
|
|
147
|
+
try:
|
|
148
|
+
await asyncio.wait_for(
|
|
149
|
+
self.connection_event.wait(), timeout=self.timeout
|
|
150
|
+
)
|
|
151
|
+
except asyncio.TimeoutError as e:
|
|
152
|
+
await self._set_state(ConnectionState.ERROR, e)
|
|
153
|
+
await self.release()
|
|
154
|
+
raise Exception(f"等待连接超时 (>{self.timeout}秒)") from e
|
|
155
|
+
|
|
156
|
+
# 检查连接状态
|
|
157
|
+
async with self.state_lock:
|
|
158
|
+
if self.state == ConnectionState.ERROR:
|
|
159
|
+
error_msg = f"连接发生错误: {str(self.error)}"
|
|
160
|
+
raise Exception(error_msg) from self.error
|
|
161
|
+
|
|
162
|
+
if self.state != ConnectionState.CONNECTED:
|
|
163
|
+
error_msg = f"连接状态为 {self.state},无法读取数据"
|
|
164
|
+
raise Exception(error_msg)
|
|
165
|
+
|
|
166
|
+
# 设置状态为读取中
|
|
167
|
+
self.state = ConnectionState.READING
|
|
168
|
+
|
|
169
|
+
# 读取数据
|
|
170
|
+
try:
|
|
171
|
+
max_empty_chunks = 3 # 连续空块计数,防止无限循环
|
|
172
|
+
empty_chunk_count = 0
|
|
173
|
+
|
|
174
|
+
while True:
|
|
175
|
+
# 检查状态
|
|
176
|
+
async with self.state_lock:
|
|
177
|
+
if self.state != ConnectionState.READING:
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
# 读取一段数据,添加超时控制
|
|
182
|
+
chunk = await asyncio.wait_for(
|
|
183
|
+
self.reader.read(1024), timeout=self.timeout
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# 检测连续空块,防止无限循环
|
|
187
|
+
if not chunk:
|
|
188
|
+
empty_chunk_count += 1
|
|
189
|
+
if empty_chunk_count >= max_empty_chunks:
|
|
190
|
+
break
|
|
191
|
+
# 给个短暂的休息,避免CPU过度使用
|
|
192
|
+
await asyncio.sleep(0.05)
|
|
193
|
+
continue
|
|
194
|
+
else:
|
|
195
|
+
empty_chunk_count = 0
|
|
196
|
+
|
|
197
|
+
# 更新计数并生成数据
|
|
198
|
+
self.chunks_received += 1
|
|
199
|
+
yield chunk
|
|
200
|
+
|
|
201
|
+
# 检查是否接收到了结束标记 [DONE]
|
|
202
|
+
if b"data: [DONE]" in chunk:
|
|
203
|
+
self.done_received = True
|
|
204
|
+
break
|
|
205
|
+
|
|
206
|
+
except asyncio.TimeoutError as e:
|
|
207
|
+
await self._set_state(ConnectionState.ERROR, e)
|
|
208
|
+
break
|
|
209
|
+
|
|
210
|
+
except (ConnectionError, OSError) as e:
|
|
211
|
+
await self._set_state(ConnectionState.ERROR, e)
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
await self._set_state(ConnectionState.ERROR, e)
|
|
216
|
+
error_msg = f"读取数据失败: {str(e)}"
|
|
217
|
+
raise
|
|
218
|
+
finally:
|
|
219
|
+
# 关闭连接
|
|
220
|
+
await self.release()
|
|
221
|
+
|
|
222
|
+
async def release(self):
|
|
223
|
+
"""安全关闭连接并释放资源"""
|
|
224
|
+
async with self.state_lock:
|
|
225
|
+
# 检查是否已经在关闭或已关闭状态
|
|
226
|
+
if self.state in [ConnectionState.CLOSING, ConnectionState.CLOSED]:
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
# 设置状态为关闭中
|
|
230
|
+
self.state = ConnectionState.CLOSING
|
|
231
|
+
|
|
232
|
+
# 关闭写入端
|
|
233
|
+
if self.writer:
|
|
234
|
+
writer = self.writer
|
|
235
|
+
self.writer = None # 清除引用
|
|
236
|
+
self.reader = None # 清除引用
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# 尝试优雅关闭
|
|
240
|
+
if not writer.is_closing():
|
|
241
|
+
# 先确保发送所有数据
|
|
242
|
+
try:
|
|
243
|
+
await asyncio.wait_for(writer.drain(), timeout=1.0)
|
|
244
|
+
except (asyncio.TimeoutError, ConnectionError, OSError):
|
|
245
|
+
# 忽略drain错误
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
# 写入EOF标记
|
|
249
|
+
try:
|
|
250
|
+
writer.write_eof()
|
|
251
|
+
except (OSError, RuntimeError):
|
|
252
|
+
# 忽略无法写入EOF的错误
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
# 关闭连接
|
|
256
|
+
writer.close()
|
|
257
|
+
|
|
258
|
+
# 等待连接关闭完成
|
|
259
|
+
try:
|
|
260
|
+
await asyncio.wait_for(writer.wait_closed(), timeout=2.0)
|
|
261
|
+
except (asyncio.TimeoutError, ConnectionError, OSError, RuntimeError):
|
|
262
|
+
# 忽略等待关闭的错误
|
|
263
|
+
pass
|
|
264
|
+
except Exception:
|
|
265
|
+
pass
|
|
266
|
+
|
|
267
|
+
# 设置为已关闭状态
|
|
268
|
+
await self._set_state(ConnectionState.CLOSED)
|
|
@@ -15,6 +15,7 @@ from .nodes_ipadapter_plus.nodes_ipadapter_plus import *
|
|
|
15
15
|
from .nodes_janus_pro import *
|
|
16
16
|
from .nodes_kolors_mz import *
|
|
17
17
|
from .nodes_model_advanced import *
|
|
18
|
+
from .nodes_nunchaku import *
|
|
18
19
|
from .nodes_sd3 import *
|
|
19
20
|
from .nodes_segment_anything import *
|
|
20
21
|
from .nodes_testing_utils import *
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
from bizyengine.core import BizyAirBaseNode, BizyAirNodeIO, data_types
|
|
2
|
+
from bizyengine.core.configs.conf import config_manager
|
|
3
|
+
from bizyengine.core.path_utils import path_manager as folder_paths
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NunchakuFluxDiTLoader(BizyAirBaseNode):
|
|
7
|
+
@classmethod
|
|
8
|
+
def INPUT_TYPES(s):
|
|
9
|
+
attention_options = ["nunchaku-fp16"]
|
|
10
|
+
dtype_options = ["bfloat16"]
|
|
11
|
+
return {
|
|
12
|
+
"required": {
|
|
13
|
+
"model_path": (
|
|
14
|
+
["svdq-int4-flux.1-dev"],
|
|
15
|
+
{"tooltip": "The SVDQuant quantized FLUX.1 models."},
|
|
16
|
+
),
|
|
17
|
+
"cache_threshold": (
|
|
18
|
+
"FLOAT",
|
|
19
|
+
{
|
|
20
|
+
"default": 0,
|
|
21
|
+
"min": 0,
|
|
22
|
+
"max": 1,
|
|
23
|
+
"step": 0.001,
|
|
24
|
+
"tooltip": "Adjusts the caching tolerance like `residual_diff_threshold` in WaveSpeed. "
|
|
25
|
+
"Increasing the value enhances speed at the cost of quality. "
|
|
26
|
+
"A typical setting is 0.12. Setting it to 0 disables the effect.",
|
|
27
|
+
},
|
|
28
|
+
),
|
|
29
|
+
"attention": (
|
|
30
|
+
attention_options,
|
|
31
|
+
{
|
|
32
|
+
"default": attention_options[0],
|
|
33
|
+
"tooltip": "Attention implementation. The default implementation is `flash-attention2`. "
|
|
34
|
+
"`nunchaku-fp16` use FP16 attention, offering ~1.2× speedup. "
|
|
35
|
+
"Note that 20-series GPUs can only use `nunchaku-fp16`.",
|
|
36
|
+
},
|
|
37
|
+
),
|
|
38
|
+
"cpu_offload": (
|
|
39
|
+
["disable"],
|
|
40
|
+
{
|
|
41
|
+
"default": "auto",
|
|
42
|
+
"tooltip": "Whether to enable CPU offload for the transformer model."
|
|
43
|
+
"auto' will enable it if the GPU memory is less than 14G.",
|
|
44
|
+
},
|
|
45
|
+
),
|
|
46
|
+
"device_id": (
|
|
47
|
+
"INT",
|
|
48
|
+
{
|
|
49
|
+
"default": 0,
|
|
50
|
+
"min": 0,
|
|
51
|
+
"max": 0,
|
|
52
|
+
"step": 1,
|
|
53
|
+
"display": "number",
|
|
54
|
+
"lazy": True,
|
|
55
|
+
"tooltip": "The GPU device ID to use for the model.",
|
|
56
|
+
},
|
|
57
|
+
),
|
|
58
|
+
"data_type": (
|
|
59
|
+
dtype_options,
|
|
60
|
+
{
|
|
61
|
+
"default": dtype_options[0],
|
|
62
|
+
"tooltip": "Specifies the model's data type. Default is `bfloat16`. "
|
|
63
|
+
"For 20-series GPUs, which do not support `bfloat16`, use `float16` instead.",
|
|
64
|
+
},
|
|
65
|
+
),
|
|
66
|
+
},
|
|
67
|
+
"optional": {
|
|
68
|
+
"i2f_mode": (
|
|
69
|
+
["enabled", "always"],
|
|
70
|
+
{
|
|
71
|
+
"default": "enabled",
|
|
72
|
+
"tooltip": "The GEMM implementation for 20-series GPUs"
|
|
73
|
+
"— this option is only applicable to these GPUs.",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
RETURN_TYPES = (data_types.MODEL,)
|
|
80
|
+
CATEGORY = "Nunchaku"
|
|
81
|
+
NODE_DISPLAY_NAME = "Nunchaku FLUX DiT Loader"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class NunchakuTextEncoderLoader(BizyAirBaseNode):
|
|
85
|
+
@classmethod
|
|
86
|
+
def INPUT_TYPES(s):
|
|
87
|
+
return {
|
|
88
|
+
"required": {
|
|
89
|
+
"model_type": (["flux"],),
|
|
90
|
+
"text_encoder1": (folder_paths.get_filename_list("text_encoders"),),
|
|
91
|
+
"text_encoder2": (folder_paths.get_filename_list("text_encoders"),),
|
|
92
|
+
"t5_min_length": (
|
|
93
|
+
"INT",
|
|
94
|
+
{
|
|
95
|
+
"default": 512,
|
|
96
|
+
"min": 256,
|
|
97
|
+
"max": 1024,
|
|
98
|
+
"step": 128,
|
|
99
|
+
"display": "number",
|
|
100
|
+
"lazy": True,
|
|
101
|
+
},
|
|
102
|
+
),
|
|
103
|
+
"use_4bit_t5": (["disable"],),
|
|
104
|
+
"int4_model": (
|
|
105
|
+
["none"],
|
|
106
|
+
{"tooltip": "The name of the 4-bit T5 model."},
|
|
107
|
+
),
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
RETURN_TYPES = (data_types.CLIP,)
|
|
112
|
+
# FUNCTION = "load_text_encoder"
|
|
113
|
+
CATEGORY = "Nunchaku"
|
|
114
|
+
NODE_DISPLAY_NAME = "Nunchaku Text Encoder Loader"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class NunchakuFluxLoraLoader(BizyAirBaseNode):
|
|
118
|
+
@classmethod
|
|
119
|
+
def INPUT_TYPES(s):
|
|
120
|
+
return {
|
|
121
|
+
"required": {
|
|
122
|
+
"model": (
|
|
123
|
+
data_types.MODEL,
|
|
124
|
+
{"tooltip": "The diffusion model the LoRA will be applied to."},
|
|
125
|
+
),
|
|
126
|
+
"lora_name": (
|
|
127
|
+
[
|
|
128
|
+
"to choose",
|
|
129
|
+
],
|
|
130
|
+
{"tooltip": "The name of the LoRA."},
|
|
131
|
+
),
|
|
132
|
+
"lora_strength": (
|
|
133
|
+
"FLOAT",
|
|
134
|
+
{
|
|
135
|
+
"default": 1.0,
|
|
136
|
+
"min": -100.0,
|
|
137
|
+
"max": 100.0,
|
|
138
|
+
"step": 0.01,
|
|
139
|
+
"tooltip": "How strongly to modify the diffusion model. This value can be negative.",
|
|
140
|
+
},
|
|
141
|
+
),
|
|
142
|
+
"model_version_id": (
|
|
143
|
+
"STRING",
|
|
144
|
+
{
|
|
145
|
+
"default": "",
|
|
146
|
+
},
|
|
147
|
+
),
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
RETURN_TYPES = (data_types.MODEL,)
|
|
152
|
+
OUTPUT_TOOLTIPS = ("The modified diffusion model.",)
|
|
153
|
+
FUNCTION = "load_lora"
|
|
154
|
+
NODE_DISPLAY_NAME = "Nunchaku FLUX.1 LoRA Loader"
|
|
155
|
+
|
|
156
|
+
CATEGORY = "Nunchaku"
|
|
157
|
+
DESCRIPTION = (
|
|
158
|
+
"LoRAs are used to modify the diffusion model, "
|
|
159
|
+
"altering the way in which latents are denoised such as applying styles. "
|
|
160
|
+
"You can link multiple LoRA nodes."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def VALIDATE_INPUTS(cls, lora_name, **kwargs):
|
|
165
|
+
if lora_name == "" or lora_name is None:
|
|
166
|
+
return False
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
def load_lora(
|
|
170
|
+
self,
|
|
171
|
+
model,
|
|
172
|
+
lora_name,
|
|
173
|
+
lora_strength,
|
|
174
|
+
model_version_id: str = None,
|
|
175
|
+
):
|
|
176
|
+
assigned_id = self.assigned_id
|
|
177
|
+
new_model: BizyAirNodeIO = model.copy(assigned_id)
|
|
178
|
+
|
|
179
|
+
if model_version_id is not None and model_version_id != "":
|
|
180
|
+
# use model version id as lora name
|
|
181
|
+
lora_name = (
|
|
182
|
+
f"{config_manager.get_model_version_id_prefix()}{model_version_id}"
|
|
183
|
+
)
|
|
184
|
+
new_model.add_node_data(
|
|
185
|
+
class_type="NunchakuFluxLoraLoader",
|
|
186
|
+
inputs={
|
|
187
|
+
"model": model,
|
|
188
|
+
"lora_name": lora_name,
|
|
189
|
+
"lora_strength": lora_strength,
|
|
190
|
+
},
|
|
191
|
+
outputs={"slot_index": 0},
|
|
192
|
+
)
|
|
193
|
+
return (new_model,)
|
|
@@ -104,7 +104,7 @@ class BizyAirTask:
|
|
|
104
104
|
return self.get_data(len(self.data_pool) - 1)
|
|
105
105
|
|
|
106
106
|
def do_task_until_completed(
|
|
107
|
-
self, *, timeout: int =
|
|
107
|
+
self, *, timeout: int = 3600, poll_interval: float = 1
|
|
108
108
|
) -> list[dict]:
|
|
109
109
|
offset = 0
|
|
110
110
|
start_time = time.time()
|
|
@@ -169,7 +169,7 @@ class PromptServer(Command):
|
|
|
169
169
|
if BizyAirTask.check_inputs(result):
|
|
170
170
|
self.cache_manager.set(cache_key, result)
|
|
171
171
|
bz_task = BizyAirTask.from_data(result, check_inputs=False)
|
|
172
|
-
bz_task.do_task_until_completed(timeout=
|
|
172
|
+
bz_task.do_task_until_completed(timeout=60 * 60) # 60 minutes
|
|
173
173
|
last_data = bz_task.get_last_data()
|
|
174
174
|
response_data = last_data.get("data")
|
|
175
175
|
out = response_data["payload"]
|
bizyengine/core/common/client.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
import pprint
|
|
4
5
|
import urllib.error
|
|
@@ -17,9 +18,12 @@ __all__ = ["send_request"]
|
|
|
17
18
|
|
|
18
19
|
from dataclasses import dataclass, field
|
|
19
20
|
|
|
20
|
-
from .env_var import
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
from .env_var import (
|
|
22
|
+
BIZYAIR_API_KEY,
|
|
23
|
+
BIZYAIR_DEBUG,
|
|
24
|
+
BIZYAIR_SERVER_ADDRESS,
|
|
25
|
+
create_api_key_file,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
version_path = os.path.join(os.path.dirname(__file__), "..", "..", "version.txt")
|
|
25
29
|
with open(version_path, "r") as file:
|
|
@@ -29,34 +33,39 @@ with open(version_path, "r") as file:
|
|
|
29
33
|
@dataclass
|
|
30
34
|
class APIKeyState:
|
|
31
35
|
current_api_key: str = field(default=None)
|
|
32
|
-
is_valid: bool = field(default=
|
|
36
|
+
is_valid: bool = field(default=False)
|
|
33
37
|
|
|
34
38
|
|
|
39
|
+
# Actual api key in use
|
|
35
40
|
api_key_state = APIKeyState()
|
|
36
41
|
|
|
37
42
|
|
|
38
|
-
def set_api_key(api_key: str = "YOUR_API_KEY", override: bool = False):
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
def set_api_key(api_key: str = "YOUR_API_KEY", override: bool = False) -> bool:
|
|
44
|
+
logging.debug("client.py set_api_key called")
|
|
45
|
+
global api_key_state
|
|
46
|
+
if api_key_state.is_valid and not override:
|
|
41
47
|
warnings.warn("API key has already been set and will not be overridden.")
|
|
42
|
-
return
|
|
48
|
+
return True
|
|
43
49
|
if validate_api_key(api_key):
|
|
44
|
-
|
|
50
|
+
create_api_key_file(api_key)
|
|
45
51
|
api_key_state.is_valid = True
|
|
46
|
-
|
|
52
|
+
api_key_state.current_api_key = api_key
|
|
53
|
+
logging.info("\033[92mAPI key is set successfully.\033[0m")
|
|
54
|
+
return True
|
|
47
55
|
else:
|
|
48
|
-
api_key_state.is_valid = False
|
|
49
56
|
warnings.warn("Invalid API key provided.")
|
|
57
|
+
return False
|
|
50
58
|
|
|
51
59
|
|
|
52
60
|
def validate_api_key(api_key: str = None) -> bool:
|
|
53
|
-
|
|
61
|
+
logging.debug("validating api key...")
|
|
54
62
|
if not api_key or not isinstance(api_key, str):
|
|
55
|
-
warnings.warn("
|
|
63
|
+
warnings.warn("invalid api_key")
|
|
56
64
|
return False
|
|
65
|
+
|
|
66
|
+
is_valid = False
|
|
57
67
|
# if api_key_state.current_api_key == api_key and api_key_state.is_valid is not None:
|
|
58
68
|
# return api_key_state.is_valid
|
|
59
|
-
api_key_state.current_api_key = api_key
|
|
60
69
|
url = f"{BIZYAIR_SERVER_ADDRESS}/user/info"
|
|
61
70
|
headers = {"accept": "application/json", "authorization": f"Bearer {api_key}"}
|
|
62
71
|
try:
|
|
@@ -64,34 +73,37 @@ def validate_api_key(api_key: str = None) -> bool:
|
|
|
64
73
|
method="GET", url=url, headers=headers, callback=None
|
|
65
74
|
)
|
|
66
75
|
if "message" not in response_data or response_data["message"] != "Ok":
|
|
67
|
-
api_key_state.is_valid = False
|
|
68
76
|
raise ValueError(
|
|
69
77
|
f"\033[91mAPI key validation failed. API Key: {api_key}\033[0m"
|
|
70
78
|
)
|
|
71
79
|
else:
|
|
72
|
-
|
|
80
|
+
is_valid = True
|
|
73
81
|
except ConnectionError as ce:
|
|
74
|
-
api_key_state.is_valid = False
|
|
75
82
|
raise ValueError(f"\033[91mConnection error: {ce}\033[0m")
|
|
76
83
|
except PermissionError as pe:
|
|
77
|
-
api_key_state.is_valid = False
|
|
78
84
|
raise ValueError(
|
|
79
85
|
f"\033[91mError validating API key: {api_key}, error: {pe}\033[0m"
|
|
80
86
|
)
|
|
81
87
|
except Exception as e:
|
|
82
|
-
api_key_state.is_valid = False
|
|
83
88
|
raise ValueError(f"\033[91mOther error: {e}\033[0m")
|
|
84
|
-
|
|
89
|
+
|
|
90
|
+
logging.debug(f"api key validated: {is_valid}")
|
|
91
|
+
return is_valid
|
|
85
92
|
|
|
86
93
|
|
|
87
94
|
def get_api_key() -> str:
|
|
88
|
-
|
|
95
|
+
logging.debug("client.py get_api_key called")
|
|
96
|
+
global api_key_state
|
|
89
97
|
try:
|
|
90
|
-
|
|
98
|
+
if not api_key_state.is_valid:
|
|
99
|
+
if validate_api_key(BIZYAIR_API_KEY):
|
|
100
|
+
api_key_state.is_valid = True
|
|
101
|
+
api_key_state.current_api_key = BIZYAIR_API_KEY
|
|
102
|
+
logging.info("API key set successfully")
|
|
91
103
|
except Exception as e:
|
|
92
|
-
|
|
104
|
+
logging.error(str(e))
|
|
93
105
|
raise ValueError(str(e))
|
|
94
|
-
return
|
|
106
|
+
return api_key_state.current_api_key
|
|
95
107
|
|
|
96
108
|
|
|
97
109
|
def _headers():
|
|
@@ -141,8 +153,8 @@ def send_request(
|
|
|
141
153
|
error_message = str(e)
|
|
142
154
|
response_body = e.read().decode("utf-8") if hasattr(e, "read") else "N/A"
|
|
143
155
|
if verbose:
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
logging.error(f"URLError encountered: {error_message}")
|
|
157
|
+
logging.info(f"Response Body: {response_data}")
|
|
146
158
|
code, message = "N/A", "N/A"
|
|
147
159
|
try:
|
|
148
160
|
response_dict = json.loads(response_body)
|
|
@@ -152,7 +164,7 @@ def send_request(
|
|
|
152
164
|
|
|
153
165
|
except json.JSONDecodeError:
|
|
154
166
|
if verbose:
|
|
155
|
-
|
|
167
|
+
logging.error("Failed to decode response body as JSON.")
|
|
156
168
|
|
|
157
169
|
if "Unauthorized" in error_message:
|
|
158
170
|
raise PermissionError(
|
|
@@ -234,7 +246,7 @@ async def async_send_request(
|
|
|
234
246
|
if response.status != 200:
|
|
235
247
|
error_message = f"HTTP Status {response.status}"
|
|
236
248
|
if verbose:
|
|
237
|
-
|
|
249
|
+
logging.error(f"Error encountered: {error_message}")
|
|
238
250
|
if response.status == 401:
|
|
239
251
|
raise PermissionError(
|
|
240
252
|
"Key is invalid, please refer to https://cloud.siliconflow.cn to get the API key.\n"
|
|
@@ -251,17 +263,18 @@ async def async_send_request(
|
|
|
251
263
|
return callback(json.loads(response_data))
|
|
252
264
|
return json.loads(response_data)
|
|
253
265
|
except aiohttp.ClientError as e:
|
|
254
|
-
|
|
266
|
+
logging.error(f"Error fetching data: {e}")
|
|
255
267
|
return {}
|
|
256
268
|
except Exception as e:
|
|
257
|
-
|
|
269
|
+
logging.error(f"Error fetching data: {str(e)}")
|
|
258
270
|
return {}
|
|
259
271
|
|
|
260
272
|
|
|
261
273
|
def fetch_models_by_type(
|
|
262
274
|
url: str, model_type: str, *, method="GET", verbose=False
|
|
263
275
|
) -> dict:
|
|
264
|
-
|
|
276
|
+
global api_key_state
|
|
277
|
+
if not api_key_state.is_valid:
|
|
265
278
|
return {}
|
|
266
279
|
|
|
267
280
|
payload = {"type": model_type}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import configparser
|
|
2
|
+
import logging
|
|
2
3
|
import os
|
|
3
4
|
from os import environ
|
|
4
5
|
from pathlib import Path
|
|
@@ -59,7 +60,7 @@ def env(key, type_, default=None):
|
|
|
59
60
|
|
|
60
61
|
|
|
61
62
|
def load_api_key():
|
|
62
|
-
|
|
63
|
+
logging.debug("load_api_key called")
|
|
63
64
|
file_path = BIZYAIR_COMFYUI_PATH / "api_key.ini"
|
|
64
65
|
|
|
65
66
|
if file_path.is_file() and file_path.exists():
|
|
@@ -73,6 +74,7 @@ def load_api_key():
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
def create_api_key_file(api_key):
|
|
77
|
+
logging.debug("create_api_key_file called")
|
|
76
78
|
config = configparser.ConfigParser()
|
|
77
79
|
config["auth"] = {"api_key": api_key}
|
|
78
80
|
file_path = BIZYAIR_COMFYUI_PATH / "api_key.ini"
|
|
@@ -84,9 +86,9 @@ def create_api_key_file(api_key):
|
|
|
84
86
|
|
|
85
87
|
|
|
86
88
|
# production:
|
|
87
|
-
# service_address: https://
|
|
89
|
+
# service_address: https://api.bizyair.cn/x/v1
|
|
88
90
|
# uat:
|
|
89
|
-
# service_address: https://uat-
|
|
91
|
+
# service_address: https://uat-api.bizyair.cn/x/v1
|
|
90
92
|
_BIZYAIR_DOMAIN = os.getenv("BIZYAIR_DOMAIN", "https://api.bizyair.cn")
|
|
91
93
|
BIZYAIR_DOMAIN = ServerAddress(_BIZYAIR_DOMAIN)
|
|
92
94
|
BIZYAIR_X_SERVER = f"{_BIZYAIR_DOMAIN}/x/v1"
|
|
@@ -94,6 +96,7 @@ BIZYAIR_Y_SERVER = f"{_BIZYAIR_DOMAIN}/y/v1"
|
|
|
94
96
|
|
|
95
97
|
BIZYAIR_SERVER_ADDRESS = ServerAddress(BIZYAIR_X_SERVER)
|
|
96
98
|
|
|
99
|
+
# Initial value, DO NOT CHANGE IN ACTUAL CODE!!!
|
|
97
100
|
BIZYAIR_API_KEY = env("BIZYAIR_API_KEY", str, load_api_key()[1])
|
|
98
101
|
# Development Settings
|
|
99
102
|
BIZYAIR_DEV_REQUEST_URL = env("BIZYAIR_DEV_REQUEST_URL", str, None)
|
|
@@ -52,6 +52,17 @@ model_rules:
|
|
|
52
52
|
unet_name:
|
|
53
53
|
- ^flux/flux1-schnell.sft$
|
|
54
54
|
|
|
55
|
+
- mode_type: unet
|
|
56
|
+
base_model: FLUX
|
|
57
|
+
describe: NunchakuFluxDiT
|
|
58
|
+
score: 5
|
|
59
|
+
route: /supernode/bizyair-flux-nunchaku1-unet
|
|
60
|
+
nodes:
|
|
61
|
+
- class_type: NunchakuFluxDiTLoader
|
|
62
|
+
inputs:
|
|
63
|
+
model_path:
|
|
64
|
+
- '.*'
|
|
65
|
+
|
|
55
66
|
- mode_type: unet
|
|
56
67
|
base_model: Shuttle
|
|
57
68
|
describe: shuttle-3.1-aesthetic
|
bizyengine/misc/auth.py
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
import uuid
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
|
-
import bizyengine.core
|
|
6
|
+
import bizyengine.core.common
|
|
6
7
|
import server
|
|
7
8
|
from aiohttp import web
|
|
8
|
-
from bizyengine.core.common import create_api_key_file, load_api_key, validate_api_key
|
|
9
9
|
|
|
10
|
-
API_KEY = None
|
|
11
10
|
# html_file_path = Path(os.path.dirname(os.path.abspath(__file__))) / "set_api_key.html"
|
|
12
11
|
# with open(html_file_path, "r", encoding="utf-8") as htmlfile:
|
|
13
12
|
# set_api_key_html = htmlfile.read()
|
|
14
13
|
|
|
15
14
|
|
|
16
|
-
has_key, api_key = load_api_key()
|
|
17
|
-
if has_key:
|
|
18
|
-
API_KEY = api_key
|
|
19
|
-
|
|
20
|
-
|
|
21
15
|
# @server.PromptServer.instance.routes.get("/bizyair/set-api-key")
|
|
22
16
|
# async def set_api_key_page(request):
|
|
23
17
|
# return web.Response(text=set_api_key_html, content_type="text/html")
|
|
@@ -25,55 +19,50 @@ if has_key:
|
|
|
25
19
|
|
|
26
20
|
@server.PromptServer.instance.routes.post("/bizyair/set_api_key")
|
|
27
21
|
async def set_api_key(request):
|
|
28
|
-
global API_KEY
|
|
29
22
|
try:
|
|
30
23
|
data = await request.post()
|
|
31
24
|
api_key = data.get("api_key")
|
|
32
25
|
if api_key:
|
|
33
|
-
if not
|
|
26
|
+
if not bizyengine.core.common.set_api_key(api_key, True):
|
|
34
27
|
error_msg = "Wrong API key provided, please refer to cloud.siliconflow.cn to get the key"
|
|
35
|
-
|
|
28
|
+
logging.error("set_api_key:", error_msg)
|
|
36
29
|
return web.Response(
|
|
37
30
|
text=error_msg,
|
|
38
31
|
status=400,
|
|
39
32
|
)
|
|
40
|
-
|
|
41
|
-
API_KEY = api_key
|
|
42
|
-
bizyengine.core.set_api_key(API_KEY, override=True)
|
|
43
|
-
print("Set the key sucessfully.")
|
|
33
|
+
logging.info("Set api key sucessfully.")
|
|
44
34
|
return web.Response(text="ok")
|
|
45
35
|
else:
|
|
46
36
|
error_msg = "No API key provided, please refer to cloud.siliconflow.cn to get the key"
|
|
47
|
-
|
|
37
|
+
logging.error("set_api_key:", error_msg)
|
|
48
38
|
return web.Response(
|
|
49
39
|
text=error_msg,
|
|
50
40
|
status=400,
|
|
51
41
|
)
|
|
52
42
|
except Exception as e:
|
|
53
|
-
|
|
43
|
+
logging.error(f"set api key error: {str(e)}")
|
|
54
44
|
return web.Response(text=str(e), status=500)
|
|
55
45
|
|
|
56
46
|
|
|
57
47
|
@server.PromptServer.instance.routes.get("/bizyair/get_api_key")
|
|
58
48
|
async def get_api_key(request):
|
|
59
|
-
|
|
49
|
+
logging.debug("auth.py get_api_key called")
|
|
60
50
|
try:
|
|
61
|
-
|
|
62
|
-
if has_key:
|
|
63
|
-
API_KEY = api_key
|
|
64
|
-
bizyengine.core.set_api_key(API_KEY)
|
|
51
|
+
if bizyengine.core.common.get_api_key():
|
|
65
52
|
return web.Response(text="Key has been loaded from the api_key.ini file")
|
|
66
53
|
else:
|
|
54
|
+
logging.debug("getting api key from cookie")
|
|
67
55
|
api_key = request.cookies.get("api_key")
|
|
68
56
|
if not api_key:
|
|
69
|
-
|
|
57
|
+
logging.error("No api key found in cookies")
|
|
70
58
|
return web.Response(
|
|
71
59
|
text="No api key found in cookies, please refer to cloud.siliconflow.cn to get the key",
|
|
72
60
|
status=404,
|
|
73
61
|
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
if bizyengine.core.common.set_api_key(api_key):
|
|
63
|
+
return web.Response(text="Key has been loaded from the cookies")
|
|
64
|
+
logging.error("cannot set api key")
|
|
65
|
+
return web.Response(text="Cannot set api key", status=500)
|
|
77
66
|
|
|
78
67
|
except Exception as e:
|
|
79
68
|
return web.Response(text=str(e), status=500)
|
bizyengine/misc/utils.py
CHANGED
|
@@ -123,9 +123,9 @@ def format_bytes(num_bytes: int) -> str:
|
|
|
123
123
|
|
|
124
124
|
|
|
125
125
|
def get_api_key():
|
|
126
|
-
from .
|
|
126
|
+
from bizyengine.core.common import get_api_key as bcc_get_api_key
|
|
127
127
|
|
|
128
|
-
return
|
|
128
|
+
return bcc_get_api_key()
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
def get_llm_response(
|
bizyengine/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.2.
|
|
1
|
+
1.2.4
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bizyengine
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.4
|
|
4
4
|
Summary: [a/BizyAir](https://github.com/siliconflow/BizyAir) Comfy Nodes that can run in any environment.
|
|
5
5
|
Author-email: SiliconFlow <yaochi@siliconflow.cn>
|
|
6
6
|
Project-URL: Repository, https://github.com/siliconflow/BizyAir
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
bizyengine/__init__.py,sha256=GP9V-JM07fz7uv_qTB43QEA2rKdrVJxi5I7LRnn_3ZQ,914
|
|
2
|
-
bizyengine/version.txt,sha256=
|
|
2
|
+
bizyengine/version.txt,sha256=VjoLTuw8d7vDSsYw1PpSFPXGcmrgFGhAT9cERzEQCE4,6
|
|
3
3
|
bizyengine/bizy_server/__init__.py,sha256=SP9oSblnPo4KQyh7yOGD26YCskFAcQHAZy04nQBNRIw,200
|
|
4
|
-
bizyengine/bizy_server/api_client.py,sha256=
|
|
5
|
-
bizyengine/bizy_server/errno.py,sha256=
|
|
4
|
+
bizyengine/bizy_server/api_client.py,sha256=VDdAwCi4NL1HtqVuIOopUBjxhRjLjhxBKVFraOXI01M,42386
|
|
5
|
+
bizyengine/bizy_server/errno.py,sha256=nEr_A6ARwgIwlr1PFP8eg-HNAzz9r7l00fTKaq-ipxM,15826
|
|
6
6
|
bizyengine/bizy_server/error_handler.py,sha256=MGrfO1AEqbfEgMWPL8B6Ypew_zHiQAdYGlhN9bZohrY,167
|
|
7
7
|
bizyengine/bizy_server/execution.py,sha256=ayaEf6eGJKQsVZV-1_UlGlvwwmlH7FEek31Uq-MbUjA,1644
|
|
8
8
|
bizyengine/bizy_server/profile.py,sha256=f4juAzJ73gCm0AhagYpt9WnG8HEI6xze_U96-omBLqU,3044
|
|
9
9
|
bizyengine/bizy_server/resp.py,sha256=iOFT5Ud7VJBP2uqkojJIgc3y2ifMjjEXoj0ewneL9lc,710
|
|
10
|
-
bizyengine/bizy_server/server.py,sha256=
|
|
10
|
+
bizyengine/bizy_server/server.py,sha256=qpbvT55Hp3C_XlEyQcW_XQVecOkn4dRzjhE0nA3Wd0g,48518
|
|
11
|
+
bizyengine/bizy_server/stream_response.py,sha256=H2XHqlVRtQMhgdztAuG7l8-iV_Pm42u2x6WJ0gNVIW0,9654
|
|
11
12
|
bizyengine/bizy_server/utils.py,sha256=C5tnMhvdtrrvwuCex3oERIGWrTWVb5dkXD1Txb5sJaE,2568
|
|
12
|
-
bizyengine/bizyair_extras/__init__.py,sha256=
|
|
13
|
+
bizyengine/bizyair_extras/__init__.py,sha256=6Su2AZMMPlBcOdxgy-BWq89m9MJl7voLSWeDcdyagIo,922
|
|
13
14
|
bizyengine/bizyair_extras/nodes_advanced_refluxcontrol.py,sha256=cecfjrtnjJAty9aNkhz8BlmHUC1NImkFlUDiA0COEa4,2242
|
|
14
15
|
bizyengine/bizyair_extras/nodes_cogview4.py,sha256=Ni0TDOycczyDhYPvSR68TxGV_wE2uhaxd8MIj-J4-3o,2031
|
|
15
16
|
bizyengine/bizyair_extras/nodes_comfyui_detail_daemon.py,sha256=i71it24tiGvZ3h-XFWISr4CpZszUtPuz3UrZARYluLk,6169
|
|
@@ -25,6 +26,7 @@ bizyengine/bizyair_extras/nodes_image_utils.py,sha256=f49VDIwI6opHwCuM4n8GKGtute
|
|
|
25
26
|
bizyengine/bizyair_extras/nodes_ip2p.py,sha256=GSEFJvrs4f2tv0xwYkWqc8uhsXrzAJVPvvwcw0gTjR0,619
|
|
26
27
|
bizyengine/bizyair_extras/nodes_janus_pro.py,sha256=hAdMsS09RkRHZn9cNwpmyOaH7ODOMjVt9SbBsD-UvbM,2665
|
|
27
28
|
bizyengine/bizyair_extras/nodes_model_advanced.py,sha256=s-dbFRWCdsTxctFYaZtmVwZ8-xuPPHixtkHFCmR7zcs,1755
|
|
29
|
+
bizyengine/bizyair_extras/nodes_nunchaku.py,sha256=trvQyHwYcT3mxV9-t2SR0pXk_qj0UHLqirlj3htaRc8,6832
|
|
28
30
|
bizyengine/bizyair_extras/nodes_sd3.py,sha256=lZCxj0IFmuxk1fZTDcRKgVV5QWHjkUdpR4w9-DZbMf4,1727
|
|
29
31
|
bizyengine/bizyair_extras/nodes_segment_anything.py,sha256=9Aeu4A2c2hA-Gu6kY6cINmZ3Y6DVXhWJCLjcW4M_v5o,7231
|
|
30
32
|
bizyengine/bizyair_extras/nodes_segment_anything_utils.py,sha256=ZefAqrFrevDH3XY_wipr_VwKfeXrgpZEUFaqg_JGOdU,4714
|
|
@@ -49,20 +51,20 @@ bizyengine/core/commands/invoker.py,sha256=8wcIMd8k44o96LAvxFrIiKOlVtf1MW-AcMDXs
|
|
|
49
51
|
bizyengine/core/commands/processors/model_hosting_processor.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
50
52
|
bizyengine/core/commands/processors/prompt_processor.py,sha256=0PxSCvhI4gZmV3cEjJl8lKNb08EFemFVRRXrupedNdU,4467
|
|
51
53
|
bizyengine/core/commands/servers/model_server.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
-
bizyengine/core/commands/servers/prompt_server.py,sha256=
|
|
54
|
+
bizyengine/core/commands/servers/prompt_server.py,sha256=mGkwhY7qVeCDNFmmP-Bh-V_uX3cVkH7FAaIrjpcsEWg,8140
|
|
53
55
|
bizyengine/core/common/__init__.py,sha256=GicZw6YeAZk1PsKmFDt9dm1F75zPUlpia9Q_ki5vW1Y,179
|
|
54
56
|
bizyengine/core/common/caching.py,sha256=isliSZsQyrNjXmupW-BaZ2EoVF5G8t7aHAhbcELAn5M,6253
|
|
55
|
-
bizyengine/core/common/client.py,sha256=
|
|
56
|
-
bizyengine/core/common/env_var.py,sha256=
|
|
57
|
+
bizyengine/core/common/client.py,sha256=Bf_UAosHXyYJ1YJ93ltsvvzJDawIPPmCjVaMOW1Gw-o,10114
|
|
58
|
+
bizyengine/core/common/env_var.py,sha256=ZiJc5G84MgVMRj7yxI7WWt8lmmanJiGSWl23NyWLbg4,3266
|
|
57
59
|
bizyengine/core/common/utils.py,sha256=bm-XmSPy83AyjD0v5EfWp6jiO6_5p7rkZ_HQAuVmgmo,3086
|
|
58
60
|
bizyengine/core/configs/conf.py,sha256=D_UWG9SSJnK5EhbrfNFryJQ8hUwwdvhOGlq1TielwpI,3830
|
|
59
|
-
bizyengine/core/configs/models.json,sha256=
|
|
60
|
-
bizyengine/core/configs/models.yaml,sha256=
|
|
61
|
+
bizyengine/core/configs/models.json,sha256=jCrqQgjVeHugLb191Xay5rg0m3duTVISPp_GxVGQ3HA,2656
|
|
62
|
+
bizyengine/core/configs/models.yaml,sha256=Gbr5k-Yak_ybbPK50eK5wb6gKfDIWjdQ9QCVc7BvZ0Y,8399
|
|
61
63
|
bizyengine/core/path_utils/__init__.py,sha256=5K9n4sexva0rfuYj3HcxdYeWnA1TuLTjpGGMFBTgnhM,240
|
|
62
64
|
bizyengine/core/path_utils/path_manager.py,sha256=tRVAcpsYvfWD-tK7khLvNCZayB0wpU9L0tRTH4ZESzM,10549
|
|
63
65
|
bizyengine/core/path_utils/utils.py,sha256=ksgNPyQaqilOImscLkSYizbRfDQropfxpL8tqIXardM,881
|
|
64
66
|
bizyengine/misc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
|
-
bizyengine/misc/auth.py,sha256=
|
|
67
|
+
bizyengine/misc/auth.py,sha256=V7VoHZ9-ljT_gUUOOh7AeNnAyVuyiK8fiqSM8T8k948,2671
|
|
66
68
|
bizyengine/misc/llm.py,sha256=6QZZpZSrHY6rmA6bmB9_cuiu34zcDKw7mAdsGiElYWQ,14332
|
|
67
69
|
bizyengine/misc/mzkolors.py,sha256=jnOHNvHzvPDqlKYFhPv4KKCuPV4izbuPPbykFsOcH-E,2588
|
|
68
70
|
bizyengine/misc/nodes.py,sha256=9njflJfklynyY0XmOCCzxlq48EwaO9wfrQGyXMqxlXM,43186
|
|
@@ -71,8 +73,8 @@ bizyengine/misc/nodes_controlnet_union_sdxl.py,sha256=e6Zs7unfPU-18VCLGgZXFOa0x1
|
|
|
71
73
|
bizyengine/misc/route_sam.py,sha256=-bMIR2QalfnszipGxSxvDAHGJa5gPSrjkYPb5baaRg4,1561
|
|
72
74
|
bizyengine/misc/segment_anything.py,sha256=RRm8FOfDY9VxdVrLjcdzJRh2pSM-kmNcCySuYnx9l7w,8677
|
|
73
75
|
bizyengine/misc/supernode.py,sha256=MPoJN6H_oCV00lmv1LWtGdQwTlyQPI6gXdMDXgkFd7g,4197
|
|
74
|
-
bizyengine/misc/utils.py,sha256=
|
|
75
|
-
bizyengine-1.2.
|
|
76
|
-
bizyengine-1.2.
|
|
77
|
-
bizyengine-1.2.
|
|
78
|
-
bizyengine-1.2.
|
|
76
|
+
bizyengine/misc/utils.py,sha256=T9fPmnwUNYqYx0VYMFC_F478ru9m-8kMo_sZUZixM5s,6403
|
|
77
|
+
bizyengine-1.2.4.dist-info/METADATA,sha256=sH5YllXjbdg1c1r3s-I_AKIZ5Q7NKCPqSh7Pb_T6o0s,574
|
|
78
|
+
bizyengine-1.2.4.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
|
|
79
|
+
bizyengine-1.2.4.dist-info/top_level.txt,sha256=2zapzqxX-we5cRyJkGf9bd5JinRtXp3-_uDI-xCAnc0,11
|
|
80
|
+
bizyengine-1.2.4.dist-info/RECORD,,
|
|
File without changes
|