feat: 实现一键同步网站账号上游模型功能

This commit is contained in:
liumangmang
2026-07-02 15:43:00 +08:00
parent c70b68d814
commit 63cbdf4dca
6 changed files with 548 additions and 0 deletions
+161
View File
@@ -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,