mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-05-07 05:42:55 +08:00
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.
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"),
|
||||
]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1246,19 +1246,22 @@ async function loadNewapiServices() {
|
||||
const services = await api.get('/newapi-services');
|
||||
renderNewapiServicesTable(services);
|
||||
} catch (e) {
|
||||
elements.newapiServicesTable.innerHTML = `<tr><td colspan="5" style="text-align:center;color:var(--danger-color);">${e.message}</td></tr>`;
|
||||
elements.newapiServicesTable.innerHTML = `<tr><td colspan="8" style="text-align:center;color:var(--danger-color);">${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderNewapiServicesTable(services) {
|
||||
if (!services || services.length === 0) {
|
||||
elements.newapiServicesTable.innerHTML = '<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">暂无 NEWAPI 服务,点击「添加服务」新增</td></tr>';
|
||||
elements.newapiServicesTable.innerHTML = '<tr><td colspan="8" style="text-align:center;color:var(--text-muted);padding:20px;">暂无 NEWAPI 服务,点击「添加服务」新增</td></tr>';
|
||||
return;
|
||||
}
|
||||
elements.newapiServicesTable.innerHTML = services.map(s => `
|
||||
<tr>
|
||||
<td>${escapeHtml(s.name)}</td>
|
||||
<td style="font-size:0.85rem;color:var(--text-muted);">${escapeHtml(s.api_url)}</td>
|
||||
<td style="text-align:center;">${s.channel_type || 57}</td>
|
||||
<td style="font-size:0.85rem;color:var(--text-muted);">${escapeHtml(s.channel_base_url || '')}</td>
|
||||
<td style="font-size:0.8rem;color:var(--text-muted);max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(s.channel_models || '')}">${escapeHtml(s.channel_models || '')}</td>
|
||||
<td style="text-align:center;" title="${s.enabled ? '已启用' : '已禁用'}">${s.enabled ? '✅' : '⭕'}</td>
|
||||
<td style="text-align:center;">${s.priority}</td>
|
||||
<td style="white-space:nowrap;">
|
||||
@@ -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) {
|
||||
|
||||
@@ -294,13 +294,16 @@
|
||||
<tr>
|
||||
<th style="width:150px;">名称</th>
|
||||
<th>API URL</th>
|
||||
<th style="width:90px;text-align:center;">Type</th>
|
||||
<th style="width:150px;">Base URL</th>
|
||||
<th>Models</th>
|
||||
<th style="width:80px;">状态</th>
|
||||
<th style="width:60px;text-align:center;">优先级</th>
|
||||
<th style="width:220px;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="newapi-services-table">
|
||||
<tr><td colspan="5" style="text-align:center;color:var(--text-muted);padding:20px;">加载中...</td></tr>
|
||||
<tr><td colspan="8" style="text-align:center;color:var(--text-muted);padding:20px;">加载中...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -470,6 +473,20 @@
|
||||
<label for="newapi-service-key">Root Token / API Key *</label>
|
||||
<input type="password" id="newapi-service-key" placeholder="编辑时留空则保持原值" autocomplete="new-password">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="newapi-service-channel-type">渠道 Type</label>
|
||||
<input type="number" id="newapi-service-channel-type" value="57" min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newapi-service-channel-base-url">渠道 Base URL</label>
|
||||
<input type="text" id="newapi-service-channel-base-url" placeholder="留空使用默认">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newapi-service-channel-models">渠道 Models</label>
|
||||
<input type="text" id="newapi-service-channel-models" placeholder="逗号分隔模型列表">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="newapi-service-priority">优先级</label>
|
||||
|
||||
Reference in New Issue
Block a user