feat: 实现一键同步网站账号上游模型功能
This commit is contained in:
@@ -44,6 +44,8 @@ from app.schemas.website import (
|
||||
SetConcurrencyRequest,
|
||||
SetConcurrencyItem,
|
||||
SetConcurrencyResponse,
|
||||
SyncUpstreamModelsItem,
|
||||
SyncUpstreamModelsResponse,
|
||||
)
|
||||
|
||||
from app.services.website_client import Sub2ApiWebsiteClient, _extract_id
|
||||
@@ -1280,6 +1282,165 @@ def set_website_accounts_concurrency(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/websites/{wid}/accounts/sync-upstream-models", response_model=SyncUpstreamModelsResponse)
|
||||
def sync_website_accounts_upstream_models(
|
||||
wid: int,
|
||||
db: Session = Depends(get_db),
|
||||
_=Depends(get_current_user),
|
||||
):
|
||||
"""一键同步上游模型"""
|
||||
website = db.query(Website).filter(Website.id == wid).first()
|
||||
if not website:
|
||||
raise HTTPException(404, "website not found")
|
||||
if website.site_type != "sub2api":
|
||||
raise HTTPException(400, "only sub2api site supports account model syncing")
|
||||
|
||||
with _client(website) as c:
|
||||
try:
|
||||
remote_accounts = c.list_accounts()
|
||||
except Exception as e:
|
||||
return SyncUpstreamModelsResponse(
|
||||
success=False,
|
||||
message=f"拉取远端账号列表失败: {e}",
|
||||
items=[]
|
||||
)
|
||||
|
||||
if remote_accounts is None:
|
||||
return SyncUpstreamModelsResponse(
|
||||
success=False,
|
||||
message="拉取远端账号列表失败,无法同步上游模型",
|
||||
items=[]
|
||||
)
|
||||
|
||||
remote_map = {}
|
||||
for acc in remote_accounts:
|
||||
aid = c.extract_id(acc)
|
||||
if aid:
|
||||
remote_map[aid] = acc
|
||||
|
||||
# 1. 查找候选账号:本地 UpstreamGeneratedKey.imported_website_id == wid 且 imported_account_id 非空
|
||||
keys = db.query(UpstreamGeneratedKey).filter(
|
||||
UpstreamGeneratedKey.imported_website_id == wid,
|
||||
UpstreamGeneratedKey.imported_account_id.isnot(None)
|
||||
).all()
|
||||
|
||||
candidates = {}
|
||||
for key in keys:
|
||||
aid = key.imported_account_id
|
||||
if aid not in candidates:
|
||||
candidates[aid] = {
|
||||
"account_id": aid,
|
||||
"db_key": key
|
||||
}
|
||||
|
||||
if not candidates:
|
||||
return SyncUpstreamModelsResponse(
|
||||
success=True,
|
||||
message="没有找到 SmartUp 导入的有效账号",
|
||||
items=[]
|
||||
)
|
||||
|
||||
items = []
|
||||
|
||||
for aid, cand in candidates.items():
|
||||
# 校验是否存在于远端
|
||||
remote_acc = remote_map.get(aid)
|
||||
if not remote_acc:
|
||||
items.append(SyncUpstreamModelsItem(
|
||||
account_id=aid,
|
||||
account_name=None,
|
||||
model_count=0,
|
||||
models=[],
|
||||
status="skipped",
|
||||
message="账号在远端已被删除或不存在"
|
||||
))
|
||||
continue
|
||||
|
||||
acc_name = remote_acc.get("name")
|
||||
|
||||
# 校验账号 ID 是否为数字
|
||||
try:
|
||||
int(aid)
|
||||
except ValueError:
|
||||
items.append(SyncUpstreamModelsItem(
|
||||
account_id=aid,
|
||||
account_name=acc_name,
|
||||
model_count=0,
|
||||
models=[],
|
||||
status="skipped",
|
||||
message="账号 ID 非数字,跳过同步"
|
||||
))
|
||||
continue
|
||||
|
||||
# 开始同步该账号的模型
|
||||
try:
|
||||
raw_models = c.sync_account_upstream_models(aid)
|
||||
# 过滤空、去重、排序
|
||||
valid_models = sorted(list(set(m.strip() for m in raw_models if m and m.strip())))
|
||||
|
||||
if not valid_models:
|
||||
# 如果返回空模型,保守处理为失败,不清空已有的模型白名单配置
|
||||
items.append(SyncUpstreamModelsItem(
|
||||
account_id=aid,
|
||||
account_name=acc_name,
|
||||
model_count=0,
|
||||
models=[],
|
||||
status="failed",
|
||||
message="上游同步返回模型列表为空"
|
||||
))
|
||||
continue
|
||||
|
||||
# 构造 model_mapping
|
||||
model_mapping = {m: m for m in valid_models}
|
||||
|
||||
# 优先读取账号现有的 credentials 并 merge,以防覆盖掉 api_key/base_url 等其他配置
|
||||
current_creds = remote_acc.get("credentials") or {}
|
||||
updated_creds = dict(current_creds)
|
||||
updated_creds["model_mapping"] = model_mapping
|
||||
|
||||
# 调用更新接口
|
||||
c.update_account(aid, {"credentials": updated_creds})
|
||||
|
||||
items.append(SyncUpstreamModelsItem(
|
||||
account_id=aid,
|
||||
account_name=acc_name,
|
||||
model_count=len(valid_models),
|
||||
models=valid_models,
|
||||
status="success",
|
||||
message=f"成功同步 {len(valid_models)} 个模型"
|
||||
))
|
||||
except Exception as e:
|
||||
items.append(SyncUpstreamModelsItem(
|
||||
account_id=aid,
|
||||
account_name=acc_name,
|
||||
model_count=0,
|
||||
models=[],
|
||||
status="failed",
|
||||
message=f"同步/保存上游模型失败: {e}"
|
||||
))
|
||||
|
||||
success_count = sum(1 for item in items if item.status == "success")
|
||||
failed_count = sum(1 for item in items if item.status == "failed")
|
||||
skip_count = sum(1 for item in items if item.status == "skipped")
|
||||
|
||||
success = (failed_count == 0)
|
||||
msg_parts = []
|
||||
if success_count:
|
||||
msg_parts.append(f"成功 {success_count} 个")
|
||||
if failed_count:
|
||||
msg_parts.append(f"失败 {failed_count} 个")
|
||||
if skip_count:
|
||||
msg_parts.append(f"跳过 {skip_count} 个")
|
||||
|
||||
message = "同步上游模型执行完毕:" + ",".join(msg_parts)
|
||||
|
||||
return SyncUpstreamModelsResponse(
|
||||
success=success,
|
||||
message=message,
|
||||
items=items
|
||||
)
|
||||
|
||||
|
||||
@router.post("/api/websites/{wid}/groups/organize", response_model=OrganizeGroupsResponse)
|
||||
def organize_website_groups(
|
||||
wid: int,
|
||||
|
||||
Reference in New Issue
Block a user