Spaces:
Sleeping
Sleeping
Upload main.py
Browse files
main.py
CHANGED
|
@@ -781,7 +781,7 @@ async def admin_disable_account(request: Request, account_id: str):
|
|
| 781 |
@app.put("/admin/accounts/{account_id}/enable")
|
| 782 |
@require_login()
|
| 783 |
async def admin_enable_account(request: Request, account_id: str):
|
| 784 |
-
"""启用账户"""
|
| 785 |
global multi_account_mgr
|
| 786 |
try:
|
| 787 |
multi_account_mgr = _update_account_disabled_status(
|
|
@@ -789,6 +789,15 @@ async def admin_enable_account(request: Request, account_id: str):
|
|
| 789 |
ACCOUNT_FAILURE_THRESHOLD, RATE_LIMIT_COOLDOWN_SECONDS,
|
| 790 |
SESSION_CACHE_TTL_SECONDS, global_stats
|
| 791 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 792 |
return {"status": "success", "message": f"账户 {account_id} 已启用", "account_count": len(multi_account_mgr.accounts)}
|
| 793 |
except Exception as e:
|
| 794 |
logger.error(f"[CONFIG] 启用账户失败: {str(e)}")
|
|
@@ -882,6 +891,8 @@ async def admin_update_settings(request: Request, new_settings: dict = Body(...)
|
|
| 882 |
max_connections=200
|
| 883 |
)
|
| 884 |
)
|
|
|
|
|
|
|
| 885 |
|
| 886 |
# 检查是否需要更新账户管理器配置(重试策略变化)
|
| 887 |
retry_changed = (
|
|
@@ -893,7 +904,7 @@ async def admin_update_settings(request: Request, new_settings: dict = Body(...)
|
|
| 893 |
if retry_changed:
|
| 894 |
logger.info(f"[CONFIG] 重试策略已变化,更新账户管理器配置")
|
| 895 |
# 更新所有账户管理器的配置
|
| 896 |
-
multi_account_mgr.
|
| 897 |
for account_id, account_mgr in multi_account_mgr.accounts.items():
|
| 898 |
account_mgr.account_failure_threshold = ACCOUNT_FAILURE_THRESHOLD
|
| 899 |
account_mgr.rate_limit_cooldown_seconds = RATE_LIMIT_COOLDOWN_SECONDS
|
|
@@ -1501,6 +1512,9 @@ async def stream_chat_generator(session: str, text_content: str, file_ids: List[
|
|
| 1501 |
yield f"data: {chunk}\n\n"
|
| 1502 |
|
| 1503 |
# 使用流式请求
|
|
|
|
|
|
|
|
|
|
| 1504 |
async with http_client.stream(
|
| 1505 |
"POST",
|
| 1506 |
"https://biz-discoveryengine.googleapis.com/v1alpha/locations/global/widgetStreamAssist",
|
|
@@ -1512,7 +1526,6 @@ async def stream_chat_generator(session: str, text_content: str, file_ids: List[
|
|
| 1512 |
raise HTTPException(status_code=r.status_code, detail=f"Upstream Error {error_text.decode()}")
|
| 1513 |
|
| 1514 |
# 使用异步解析器处理 JSON 数组流
|
| 1515 |
-
json_objects = [] # 收集所有响应对象用于图片解析
|
| 1516 |
try:
|
| 1517 |
async for json_obj in parse_json_array_stream_async(r.aiter_lines()):
|
| 1518 |
json_objects.append(json_obj) # 收集响应
|
|
@@ -1535,45 +1548,13 @@ async def stream_chat_generator(session: str, text_content: str, file_ids: List[
|
|
| 1535 |
chunk = create_chunk(chat_id, created_time, model_name, {"content": text}, None)
|
| 1536 |
yield f"data: {chunk}\n\n"
|
| 1537 |
|
| 1538 |
-
#
|
| 1539 |
if json_objects:
|
| 1540 |
file_ids, session_name = parse_images_from_response(json_objects)
|
| 1541 |
-
|
| 1542 |
if file_ids and session_name:
|
|
|
|
| 1543 |
logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 检测到{len(file_ids)}张生成图片")
|
| 1544 |
|
| 1545 |
-
try:
|
| 1546 |
-
base_url = get_base_url(request) if request else ""
|
| 1547 |
-
file_metadata = await get_session_file_metadata(account_manager, session_name, http_client, USER_AGENT, request_id)
|
| 1548 |
-
|
| 1549 |
-
# 并行下载所有图片
|
| 1550 |
-
download_tasks = []
|
| 1551 |
-
for file_info in file_ids:
|
| 1552 |
-
fid = file_info["fileId"]
|
| 1553 |
-
mime = file_info["mimeType"]
|
| 1554 |
-
meta = file_metadata.get(fid, {})
|
| 1555 |
-
correct_session = meta.get("session") or session_name
|
| 1556 |
-
task = download_image_with_jwt(account_manager, correct_session, fid, http_client, USER_AGENT, request_id)
|
| 1557 |
-
download_tasks.append((fid, mime, task))
|
| 1558 |
-
|
| 1559 |
-
results = await asyncio.gather(*[task for _, _, task in download_tasks], return_exceptions=True)
|
| 1560 |
-
|
| 1561 |
-
# 处��下载结果
|
| 1562 |
-
for idx, ((fid, mime, _), result) in enumerate(zip(download_tasks, results), 1):
|
| 1563 |
-
if isinstance(result, Exception):
|
| 1564 |
-
logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片{idx}下载失败: {type(result).__name__}")
|
| 1565 |
-
continue
|
| 1566 |
-
|
| 1567 |
-
image_url = save_image_to_hf(result, chat_id, fid, mime, base_url, IMAGE_DIR)
|
| 1568 |
-
logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片{idx}已保存: {image_url}")
|
| 1569 |
-
|
| 1570 |
-
markdown = f"\n\n\n\n"
|
| 1571 |
-
chunk = create_chunk(chat_id, created_time, model_name, {"content": markdown}, None)
|
| 1572 |
-
yield f"data: {chunk}\n\n"
|
| 1573 |
-
|
| 1574 |
-
except Exception as e:
|
| 1575 |
-
logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片处理失败: {str(e)}")
|
| 1576 |
-
|
| 1577 |
except ValueError as e:
|
| 1578 |
logger.error(f"[API] [{account_manager.config.account_id}] [req_{request_id}] JSON解析失败: {str(e)}")
|
| 1579 |
except Exception as e:
|
|
@@ -1581,8 +1562,61 @@ async def stream_chat_generator(session: str, text_content: str, file_ids: List[
|
|
| 1581 |
logger.error(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 流处理错误 ({error_type}): {str(e)}")
|
| 1582 |
raise
|
| 1583 |
|
| 1584 |
-
|
| 1585 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1586 |
|
| 1587 |
if is_stream:
|
| 1588 |
final_chunk = create_chunk(chat_id, created_time, model_name, {}, "stop")
|
|
|
|
| 781 |
@app.put("/admin/accounts/{account_id}/enable")
|
| 782 |
@require_login()
|
| 783 |
async def admin_enable_account(request: Request, account_id: str):
|
| 784 |
+
"""启用账户(同时重置错误禁用状态)"""
|
| 785 |
global multi_account_mgr
|
| 786 |
try:
|
| 787 |
multi_account_mgr = _update_account_disabled_status(
|
|
|
|
| 789 |
ACCOUNT_FAILURE_THRESHOLD, RATE_LIMIT_COOLDOWN_SECONDS,
|
| 790 |
SESSION_CACHE_TTL_SECONDS, global_stats
|
| 791 |
)
|
| 792 |
+
|
| 793 |
+
# 重置运行时错误状态(允许手动恢复错误禁用的账户)
|
| 794 |
+
if account_id in multi_account_mgr.accounts:
|
| 795 |
+
account_mgr = multi_account_mgr.accounts[account_id]
|
| 796 |
+
account_mgr.is_available = True
|
| 797 |
+
account_mgr.error_count = 0
|
| 798 |
+
account_mgr.last_429_time = 0.0
|
| 799 |
+
logger.info(f"[CONFIG] 账户 {account_id} 错误状态已重置")
|
| 800 |
+
|
| 801 |
return {"status": "success", "message": f"账户 {account_id} 已启用", "account_count": len(multi_account_mgr.accounts)}
|
| 802 |
except Exception as e:
|
| 803 |
logger.error(f"[CONFIG] 启用账户失败: {str(e)}")
|
|
|
|
| 891 |
max_connections=200
|
| 892 |
)
|
| 893 |
)
|
| 894 |
+
# 更新所有账户的 http_client 引用
|
| 895 |
+
multi_account_mgr.update_http_client(http_client)
|
| 896 |
|
| 897 |
# 检查是否需要更新账户管理器配置(重试策略变化)
|
| 898 |
retry_changed = (
|
|
|
|
| 904 |
if retry_changed:
|
| 905 |
logger.info(f"[CONFIG] 重试策略已变化,更新账户管理器配置")
|
| 906 |
# 更新所有账户管理器的配置
|
| 907 |
+
multi_account_mgr.cache_ttl = SESSION_CACHE_TTL_SECONDS
|
| 908 |
for account_id, account_mgr in multi_account_mgr.accounts.items():
|
| 909 |
account_mgr.account_failure_threshold = ACCOUNT_FAILURE_THRESHOLD
|
| 910 |
account_mgr.rate_limit_cooldown_seconds = RATE_LIMIT_COOLDOWN_SECONDS
|
|
|
|
| 1512 |
yield f"data: {chunk}\n\n"
|
| 1513 |
|
| 1514 |
# 使用流式请求
|
| 1515 |
+
json_objects = [] # 收集所有响应对象用于图片解析
|
| 1516 |
+
file_ids_info = None # 保存图片信息
|
| 1517 |
+
|
| 1518 |
async with http_client.stream(
|
| 1519 |
"POST",
|
| 1520 |
"https://biz-discoveryengine.googleapis.com/v1alpha/locations/global/widgetStreamAssist",
|
|
|
|
| 1526 |
raise HTTPException(status_code=r.status_code, detail=f"Upstream Error {error_text.decode()}")
|
| 1527 |
|
| 1528 |
# 使用异步解析器处理 JSON 数组流
|
|
|
|
| 1529 |
try:
|
| 1530 |
async for json_obj in parse_json_array_stream_async(r.aiter_lines()):
|
| 1531 |
json_objects.append(json_obj) # 收集响应
|
|
|
|
| 1548 |
chunk = create_chunk(chat_id, created_time, model_name, {"content": text}, None)
|
| 1549 |
yield f"data: {chunk}\n\n"
|
| 1550 |
|
| 1551 |
+
# 提取图片信息(在 async with 块内)
|
| 1552 |
if json_objects:
|
| 1553 |
file_ids, session_name = parse_images_from_response(json_objects)
|
|
|
|
| 1554 |
if file_ids and session_name:
|
| 1555 |
+
file_ids_info = (file_ids, session_name)
|
| 1556 |
logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 检测到{len(file_ids)}张生成图片")
|
| 1557 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1558 |
except ValueError as e:
|
| 1559 |
logger.error(f"[API] [{account_manager.config.account_id}] [req_{request_id}] JSON解析失败: {str(e)}")
|
| 1560 |
except Exception as e:
|
|
|
|
| 1562 |
logger.error(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 流处理错误 ({error_type}): {str(e)}")
|
| 1563 |
raise
|
| 1564 |
|
| 1565 |
+
# 在 async with 块外处理图片下载(避免占用上游连接)
|
| 1566 |
+
if file_ids_info:
|
| 1567 |
+
file_ids, session_name = file_ids_info
|
| 1568 |
+
try:
|
| 1569 |
+
base_url = get_base_url(request) if request else ""
|
| 1570 |
+
file_metadata = await get_session_file_metadata(account_manager, session_name, http_client, USER_AGENT, request_id)
|
| 1571 |
+
|
| 1572 |
+
# 并行下载所有图片
|
| 1573 |
+
download_tasks = []
|
| 1574 |
+
for file_info in file_ids:
|
| 1575 |
+
fid = file_info["fileId"]
|
| 1576 |
+
mime = file_info["mimeType"]
|
| 1577 |
+
meta = file_metadata.get(fid, {})
|
| 1578 |
+
correct_session = meta.get("session") or session_name
|
| 1579 |
+
task = download_image_with_jwt(account_manager, correct_session, fid, http_client, USER_AGENT, request_id)
|
| 1580 |
+
download_tasks.append((fid, mime, task))
|
| 1581 |
+
|
| 1582 |
+
results = await asyncio.gather(*[task for _, _, task in download_tasks], return_exceptions=True)
|
| 1583 |
+
|
| 1584 |
+
# 处理下载结果
|
| 1585 |
+
success_count = 0
|
| 1586 |
+
for idx, ((fid, mime, _), result) in enumerate(zip(download_tasks, results), 1):
|
| 1587 |
+
if isinstance(result, Exception):
|
| 1588 |
+
logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片{idx}下载失败: {type(result).__name__}: {str(result)[:100]}")
|
| 1589 |
+
# 降级处理:返回错误提示而不是静默失败
|
| 1590 |
+
error_msg = f"\n\n⚠️ 图片 {idx} 下载失败\n\n"
|
| 1591 |
+
chunk = create_chunk(chat_id, created_time, model_name, {"content": error_msg}, None)
|
| 1592 |
+
yield f"data: {chunk}\n\n"
|
| 1593 |
+
continue
|
| 1594 |
+
|
| 1595 |
+
try:
|
| 1596 |
+
image_url = save_image_to_hf(result, chat_id, fid, mime, base_url, IMAGE_DIR)
|
| 1597 |
+
logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片{idx}已保存: {image_url}")
|
| 1598 |
+
success_count += 1
|
| 1599 |
+
|
| 1600 |
+
markdown = f"\n\n\n\n"
|
| 1601 |
+
chunk = create_chunk(chat_id, created_time, model_name, {"content": markdown}, None)
|
| 1602 |
+
yield f"data: {chunk}\n\n"
|
| 1603 |
+
except Exception as save_error:
|
| 1604 |
+
logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片{idx}保存失败: {str(save_error)[:100]}")
|
| 1605 |
+
error_msg = f"\n\n⚠️ 图片 {idx} 保存失败\n\n"
|
| 1606 |
+
chunk = create_chunk(chat_id, created_time, model_name, {"content": error_msg}, None)
|
| 1607 |
+
yield f"data: {chunk}\n\n"
|
| 1608 |
+
|
| 1609 |
+
logger.info(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片处理完成: {success_count}/{len(file_ids)} 成功")
|
| 1610 |
+
|
| 1611 |
+
except Exception as e:
|
| 1612 |
+
logger.error(f"[IMAGE] [{account_manager.config.account_id}] [req_{request_id}] 图片处理失败: {type(e).__name__}: {str(e)[:100]}")
|
| 1613 |
+
# 降级处理:通知用户图片处理失败
|
| 1614 |
+
error_msg = f"\n\n⚠️ 图片处理失败: {type(e).__name__}\n\n"
|
| 1615 |
+
chunk = create_chunk(chat_id, created_time, model_name, {"content": error_msg}, None)
|
| 1616 |
+
yield f"data: {chunk}\n\n"
|
| 1617 |
+
|
| 1618 |
+
total_time = time.time() - start_time
|
| 1619 |
+
logger.info(f"[API] [{account_manager.config.account_id}] [req_{request_id}] 响应完成: {total_time:.2f}秒")
|
| 1620 |
|
| 1621 |
if is_stream:
|
| 1622 |
final_chunk = create_chunk(chat_id, created_time, model_name, {}, "stop")
|