From 9c6e0d60362fb15b9a740439f3c594fec18efde7 Mon Sep 17 00:00:00 2001 From: Jay Hsueh Date: Wed, 25 Mar 2026 15:18:05 +0800 Subject: [PATCH] feat(newapi): enhance NEWAPI service management with channel configuration - Added channel_type, channel_base_url, and channel_models fields to the NewapiService model. - Updated CRUD operations to handle new fields for creating and updating NEWAPI services. - Modified upload functions to accept channel configuration parameters. - Enhanced front-end forms and tables to display and manage channel settings for NEWAPI services. - Improved error handling and user feedback in the UI for service management. --- src/core/upload/newapi_upload.py | 33 ++++++++++++++++++------ src/database/crud.py | 6 +++++ src/database/models.py | 3 +++ src/database/session.py | 3 +++ src/web/routes/accounts.py | 18 +++++++++++-- src/web/routes/registration.py | 9 ++++++- src/web/routes/upload/newapi_services.py | 21 +++++++++++++++ static/js/settings.js | 24 ++++++++++++++--- templates/settings.html | 19 +++++++++++++- 9 files changed, 121 insertions(+), 15 deletions(-) diff --git a/src/core/upload/newapi_upload.py b/src/core/upload/newapi_upload.py index 983cd07..3e94ee3 100644 --- a/src/core/upload/newapi_upload.py +++ b/src/core/upload/newapi_upload.py @@ -5,7 +5,7 @@ NEWAPI 上传功能 — 通过 PUT /api/channel/ 添加渠道 import json import logging from datetime import datetime -from typing import List, Tuple +from typing import List, Optional, Tuple from curl_cffi import requests as cffi_requests @@ -14,9 +14,9 @@ from ...database.session import get_db logger = logging.getLogger(__name__) -NEWAPI_TYPE_OPENAI = 57 -DEFAULT_BASE_URL = "" -DEFAULT_MODELS = "gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact" +DEFAULT_CHANNEL_TYPE = 1 +DEFAULT_CHANNEL_BASE_URL = "" +DEFAULT_CHANNEL_MODELS = "gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact" def _normalize_base(api_url: str) -> str: @@ -46,6 +46,9 @@ def upload_to_newapi( account: Account, api_url: str, api_key: str, + channel_type: Optional[int] = None, + channel_base_url: Optional[str] = None, + channel_models: Optional[str] = None, ) -> Tuple[bool, str]: base = _normalize_base(api_url) if not base: @@ -55,15 +58,19 @@ def upload_to_newapi( if not account.access_token: return False, "账号缺少 access_token" + resolved_channel_type = channel_type if isinstance(channel_type, int) and channel_type > 0 else DEFAULT_CHANNEL_TYPE + resolved_channel_base_url = (channel_base_url or DEFAULT_CHANNEL_BASE_URL).strip() + resolved_channel_models = (channel_models or DEFAULT_CHANNEL_MODELS).strip() or DEFAULT_CHANNEL_MODELS + url = f"{base}/api/channel/" account_name = account.email or "" channel = { "auto_ban": 1, "name": account.email or "", - "type": NEWAPI_TYPE_OPENAI, + "type": resolved_channel_type, "key": json.dumps({"access_token": account.access_token or "", "account_id": account_name}, ensure_ascii=False), - "base_url": DEFAULT_BASE_URL, - "models": DEFAULT_MODELS, + "base_url": resolved_channel_base_url, + "models": resolved_channel_models, "multi_key_mode": "random", "group": "default", "groups": ["default"], @@ -92,6 +99,9 @@ def batch_upload_to_newapi( account_ids: List[int], api_url: str, api_key: str, + channel_type: Optional[int] = None, + channel_base_url: Optional[str] = None, + channel_models: Optional[str] = None, ) -> dict: results = { "success_count": 0, @@ -112,7 +122,14 @@ def batch_upload_to_newapi( results["details"].append({"id": account_id, "email": account.email, "success": False, "error": "缺少 Token"}) continue - success, message = upload_to_newapi(account, api_url, api_key) + success, message = upload_to_newapi( + account, + api_url, + api_key, + channel_type=channel_type, + channel_base_url=channel_base_url, + channel_models=channel_models, + ) if success: account.newapi_uploaded = True account.newapi_uploaded_at = datetime.utcnow() diff --git a/src/database/crud.py b/src/database/crud.py index d4672a3..95de676 100644 --- a/src/database/crud.py +++ b/src/database/crud.py @@ -788,6 +788,9 @@ def create_newapi_service( api_key: str, enabled: bool = True, priority: int = 0, + channel_type: int = 57, + channel_base_url: str = "", + channel_models: str = "gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact", ) -> NewapiService: svc = NewapiService( name=name, @@ -795,6 +798,9 @@ def create_newapi_service( api_key=api_key, enabled=enabled, priority=priority, + channel_type=channel_type, + channel_base_url=channel_base_url, + channel_models=channel_models, ) db.add(svc) db.commit() diff --git a/src/database/models.py b/src/database/models.py index e7614dc..6c658cf 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -189,6 +189,9 @@ class NewapiService(Base): name = Column(String(100), nullable=False) api_url = Column(String(500), nullable=False) api_key = Column(Text, nullable=False) + channel_type = Column(Integer, default=57) + channel_base_url = Column(String(500), default="") + channel_models = Column(Text, default="gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact") enabled = Column(Boolean, default=True) priority = Column(Integer, default=0) created_at = Column(DateTime, default=datetime.utcnow) diff --git a/src/database/session.py b/src/database/session.py index 3eb148a..eefa228 100644 --- a/src/database/session.py +++ b/src/database/session.py @@ -114,6 +114,9 @@ class DatabaseSessionManager: ("accounts", "token_sync_updated_at", "DATETIME"), ("accounts", "newapi_uploaded", "BOOLEAN DEFAULT 0"), ("accounts", "newapi_uploaded_at", "DATETIME"), + ("newapi_services", "channel_type", "INTEGER DEFAULT 57"), + ("newapi_services", "channel_base_url", "VARCHAR(500) DEFAULT ''"), + ("newapi_services", "channel_models", "TEXT"), ("proxies", "is_default", "BOOLEAN DEFAULT 0"), ("cpa_services", "include_proxy_url", "BOOLEAN DEFAULT 0"), ] diff --git a/src/web/routes/accounts.py b/src/web/routes/accounts.py index 0a7091a..21a49c5 100644 --- a/src/web/routes/accounts.py +++ b/src/web/routes/accounts.py @@ -1014,7 +1014,14 @@ async def batch_upload_accounts_to_newapi(request: BatchUploadNewapiRequest): request.status_filter, request.email_service_filter, request.search_filter ) - results = batch_upload_to_newapi(ids, api_url, api_key) + results = batch_upload_to_newapi( + ids, + api_url, + api_key, + channel_type=svc.channel_type, + channel_base_url=svc.channel_base_url, + channel_models=svc.channel_models, + ) return results @@ -1035,7 +1042,14 @@ async def upload_account_to_newapi(account_id: int, request: Optional[UploadNewa account = crud.get_account_by_id(db, account_id) if not account: raise HTTPException(status_code=404, detail="账号不存在") - success, message = upload_to_newapi(account, svc.api_url, svc.api_key) + success, message = upload_to_newapi( + account, + svc.api_url, + svc.api_key, + channel_type=svc.channel_type, + channel_base_url=svc.channel_base_url, + channel_models=svc.channel_models, + ) if success: account.newapi_uploaded = True account.newapi_uploaded_at = datetime.utcnow() diff --git a/src/web/routes/registration.py b/src/web/routes/registration.py index 06f96ff..f1979d3 100644 --- a/src/web/routes/registration.py +++ b/src/web/routes/registration.py @@ -797,7 +797,14 @@ def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy: if not _svc: continue log_callback(f"[NEWAPI] 上传到服务: {_svc.name}") - _ok, _msg = upload_to_newapi(saved_account, _svc.api_url, _svc.api_key) + _ok, _msg = upload_to_newapi( + saved_account, + _svc.api_url, + _svc.api_key, + channel_type=_svc.channel_type, + channel_base_url=_svc.channel_base_url, + channel_models=_svc.channel_models, + ) if _ok: saved_account.newapi_uploaded = True saved_account.newapi_uploaded_at = datetime.utcnow() diff --git a/src/web/routes/upload/newapi_services.py b/src/web/routes/upload/newapi_services.py index 90dbf70..a36510f 100644 --- a/src/web/routes/upload/newapi_services.py +++ b/src/web/routes/upload/newapi_services.py @@ -16,6 +16,9 @@ class NewapiServiceCreate(BaseModel): name: str api_url: str api_key: str + channel_type: int = 57 + channel_base_url: str = "" + channel_models: str = "gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact" enabled: bool = True priority: int = 0 @@ -24,6 +27,9 @@ class NewapiServiceUpdate(BaseModel): name: Optional[str] = None api_url: Optional[str] = None api_key: Optional[str] = None + channel_type: Optional[int] = None + channel_base_url: Optional[str] = None + channel_models: Optional[str] = None enabled: Optional[bool] = None priority: Optional[int] = None @@ -33,6 +39,9 @@ class NewapiServiceResponse(BaseModel): name: str api_url: str has_key: bool + channel_type: int = 57 + channel_base_url: str = "" + channel_models: str = "" enabled: bool priority: int created_at: Optional[str] = None @@ -48,6 +57,9 @@ def _to_response(svc) -> NewapiServiceResponse: name=svc.name, api_url=svc.api_url, has_key=bool(svc.api_key), + channel_type=svc.channel_type if svc.channel_type is not None else 57, + channel_base_url=svc.channel_base_url or "", + channel_models=svc.channel_models or "", enabled=svc.enabled, priority=svc.priority, created_at=svc.created_at.isoformat() if svc.created_at else None, @@ -70,6 +82,9 @@ async def create_newapi_service(request: NewapiServiceCreate): name=request.name, api_url=request.api_url, api_key=request.api_key, + channel_type=request.channel_type, + channel_base_url=request.channel_base_url, + channel_models=request.channel_models, enabled=request.enabled, priority=request.priority, ) @@ -103,6 +118,12 @@ async def update_newapi_service(service_id: int, request: NewapiServiceUpdate): update_data["enabled"] = request.enabled if request.priority is not None: update_data["priority"] = request.priority + if request.channel_type is not None: + update_data["channel_type"] = request.channel_type + if request.channel_base_url is not None: + update_data["channel_base_url"] = request.channel_base_url + if request.channel_models is not None: + update_data["channel_models"] = request.channel_models svc = crud.update_newapi_service(db, service_id, **update_data) return _to_response(svc) diff --git a/static/js/settings.js b/static/js/settings.js index 3065ae7..d3546eb 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -1246,19 +1246,22 @@ async function loadNewapiServices() { const services = await api.get('/newapi-services'); renderNewapiServicesTable(services); } catch (e) { - elements.newapiServicesTable.innerHTML = `${e.message}`; + elements.newapiServicesTable.innerHTML = `${e.message}`; } } function renderNewapiServicesTable(services) { if (!services || services.length === 0) { - elements.newapiServicesTable.innerHTML = '暂无 NEWAPI 服务,点击「添加服务」新增'; + elements.newapiServicesTable.innerHTML = '暂无 NEWAPI 服务,点击「添加服务」新增'; return; } elements.newapiServicesTable.innerHTML = services.map(s => ` ${escapeHtml(s.name)} ${escapeHtml(s.api_url)} + ${s.channel_type || 57} + ${escapeHtml(s.channel_base_url || '')} + ${escapeHtml(s.channel_models || '')} ${s.enabled ? '✅' : '⭕'} ${s.priority} @@ -1270,10 +1273,14 @@ function renderNewapiServicesTable(services) { } function openNewapiServiceModal(service = null) { + const defaultModels = 'gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact'; document.getElementById('newapi-service-id').value = service ? service.id : ''; document.getElementById('newapi-service-name').value = service ? service.name : ''; document.getElementById('newapi-service-url').value = service ? service.api_url : ''; document.getElementById('newapi-service-key').value = ''; + document.getElementById('newapi-service-channel-type').value = service ? (service.channel_type || 57) : 57; + document.getElementById('newapi-service-channel-base-url').value = service ? (service.channel_base_url || '') : ''; + document.getElementById('newapi-service-channel-models').value = service ? (service.channel_models || defaultModels) : defaultModels; document.getElementById('newapi-service-priority').value = service ? service.priority : 0; document.getElementById('newapi-service-enabled').checked = service ? service.enabled : true; if (service) { @@ -1304,6 +1311,9 @@ async function handleSaveNewapiService(e) { const name = document.getElementById('newapi-service-name').value.trim(); const apiUrl = document.getElementById('newapi-service-url').value.trim(); const apiKey = document.getElementById('newapi-service-key').value.trim(); + const channelType = parseInt(document.getElementById('newapi-service-channel-type').value) || 57; + const channelBaseUrl = document.getElementById('newapi-service-channel-base-url').value.trim(); + const channelModels = document.getElementById('newapi-service-channel-models').value.trim(); const priority = parseInt(document.getElementById('newapi-service-priority').value) || 0; const enabled = document.getElementById('newapi-service-enabled').checked; @@ -1317,7 +1327,15 @@ async function handleSaveNewapiService(e) { } try { - const payload = { name, api_url: apiUrl, priority, enabled }; + const payload = { + name, + api_url: apiUrl, + priority, + enabled, + channel_type: channelType, + channel_base_url: channelBaseUrl, + channel_models: channelModels, + }; if (apiKey) payload.api_key = apiKey; if (id) { diff --git a/templates/settings.html b/templates/settings.html index 1d1bbe7..1d1dc65 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -294,13 +294,16 @@ 名称 API URL + Type + Base URL + Models 状态 优先级 操作 - 加载中... + 加载中... @@ -470,6 +473,20 @@ +
+
+ + +
+
+ + +
+
+
+ + +