From 8faf913c282c685da95dfa00b2669248d56ad49e Mon Sep 17 00:00:00 2001 From: hex2077 Date: Fri, 16 Jan 2026 16:13:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(credential):=20=E7=A7=BB=E9=99=A4=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E8=BF=9B=E7=A8=8B=E7=9A=84=E9=94=81=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E7=89=B9=E6=AE=8A=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs: 添加PROVIDER_ADAPTER_GUIDE.md文档说明接入流程 修改凭证缓存管理器,不再允许当前进程复用锁文件,确保同一时间只有一个实例运行。 同时新增提供商接入指南文档,详细说明后端到前端的全流程调整步骤。 --- PROVIDER_ADAPTER_GUIDE.md | 86 +++++++++++++++++++++++++++ src/utils/credential-cache-manager.js | 6 +- static/app/i18n.js | 6 ++ static/app/modal.js | 6 +- 4 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 PROVIDER_ADAPTER_GUIDE.md diff --git a/PROVIDER_ADAPTER_GUIDE.md b/PROVIDER_ADAPTER_GUIDE.md new file mode 100644 index 0000000..dba05c7 --- /dev/null +++ b/PROVIDER_ADAPTER_GUIDE.md @@ -0,0 +1,86 @@ +# AIClient2API Provider 接入指南 + +本文档详细说明了如何向 AIClient2API 项目接入全新的模型提供商(Provider),涵盖从后端核心逻辑到前端 UI 管理的全流程调整。 + +## 1. 接入流程概览 + +1. **后端常量定义**:在 `src/utils/common.js` 中添加标识。 +2. **核心 Service 开发**:在 `src/providers/` 实现 API 请求逻辑。 +3. **适配器注册**:在 `src/providers/adapter.js` 注册。 +4. **模型与号池配置**:在 `src/providers/provider-models.js` 和 `src/providers/provider-pool-manager.js` 配置。 +5. **前端 UI 全方位调整**: + * `static/app/provider-manager.js`:号池显示与顺序。 + * `static/app/file-upload.js`:上传路径映射。 + * `static/components/section-config.html`:配置按钮。 + * `static/components/section-guide.html`:使用指南。 +6. **系统级映射(必做)**:在 OAuth 处理器、凭据关联工具、用量统计等模块中建立映射。 + +--- + +## 2. 后端核心实现 + +### 2.1 定义常量 +修改 [`src/utils/common.js`](src/utils/common.js),在 `MODEL_PROVIDER` 中添加新 key(格式建议:`协议-名称-类型`)。 + +### 2.2 核心 Service (Core) +在 `src/providers/` 下创建新目录并实现 `NewProviderApiService` 类。 +**必选方法**:`constructor(config)`, `initialize()`, `listModels()`, `generateContent()`, `generateContentStream()`。 + +### 2.3 注册适配器 +在 [`src/providers/adapter.js`](src/providers/adapter.js) 中继承 `ApiServiceAdapter`,并在 `getServiceAdapter` 工厂方法中添加 `switch` 分支。 + +### 2.4 模型与号池默认配置 +* **模型列表**:在 [`src/providers/provider-models.js`](src/providers/provider-models.js) 的 `PROVIDER_MODELS` 对象中添加默认支持的模型 ID。 +* **健康检查默认值**:在 [`src/providers/provider-pool-manager.js`](src/providers/provider-pool-manager.js) 的 `DEFAULT_HEALTH_CHECK_MODELS` 中指定用于健康检查的默认模型。 + +--- + +## 3. 前端界面调整 + +### 3.1 号池显示逻辑 ([`static/app/provider-manager.js`](static/app/provider-manager.js)) +* **显示顺序**:将新标识添加到 `providerDisplayOrder` 数组。 +* **授权按钮**:若支持 OAuth,在 `generateAuthButton` 的 `oauthProviders` 数组中添加标识。 +* **路径提示**:在 `getAuthFilePath` 中返回凭据文件的默认建议路径。 + +### 3.2 凭据上传路由 ([`static/app/file-upload.js`](static/app/file-upload.js)) +* 修改 `getProviderKey`,建立提供商标识与 `configs/` 子目录名的映射(例如:`new-provider-api` -> `new-provider`)。 + +### 3.3 配置管理界面 ([`static/components/section-config.html`](static/components/section-config.html)) +* **必须添加**:在 `id="modelProvider"`(初始化提供商选择)容器中添加对应的 `provider-tag` 按钮。 +* **可选添加**:在 `id="proxyProviders"`(代理开关)中同步添加。 + +### 3.4 指南与教程 ([`static/components/section-guide.html`](static/components/section-guide.html)) +* 在“项目简介”和“客户端配置指南”中添加新提供商的调用示例(如 `{provider}/v1/chat/completions`)。 + +--- + +## 4. 全局系统映射 (关键) + +为确保新提供商的功能完整(如多账号自动切换、用量监控),**必须**在以下位置建立映射: + +### 4.1 凭据路径键名映射 ([`src/services/service-manager.js`](src/services/service-manager.js)) +在 `getServiceAdapter` 逻辑相关的 `credPathKey` 映射中,指定该提供商对应的配置文件路径键名。 + +### 4.2 自动关联工具 ([`src/utils/provider-utils.js`](src/utils/provider-utils.js)) +在 `CONFIG_FILE_PATTERNS` 数组中添加配置,以便系统能根据文件路径自动识别并关联凭据: +```javascript +{ + patterns: ['configs/new-dir/', '/new-dir/'], + providerType: 'new-provider-api', + credPathKey: 'NEW_PROVIDER_CREDS_FILE_PATH' +} +``` + +### 4.3 用量统计映射 ([`src/ui-modules/usage-api.js`](src/ui-modules/usage-api.js)) +* 将标识添加到 `supportedProviders` 数组。 +* 在 `credPathKey` 映射中添加路径键名,以便前端能展示每个账号的配额/用量。 + +### 4.4 OAuth 处理器 ([`src/ui-modules/oauth-api.js`](src/ui-modules/oauth-api.js)) +若支持 OAuth,需在 `handleGenerateAuthUrl` 中分发到相应的认证处理器。 + +--- + +## 5. 注意事项 +1. **协议对齐**:本项目内部默认使用 Gemini 协议。若上游为 OpenAI 协议,需在 `src/convert/` 实现转换。 +2. **安全**:不要在 Core 代码中硬编码 Key,始终从 `config` 中读取动态注入的凭据。 +3. **异常捕获**:Core 代码必须抛出标准错误(包含 status),以便号池管理器识别并自动隔离失效账号。 diff --git a/src/utils/credential-cache-manager.js b/src/utils/credential-cache-manager.js index 373372f..ea6f6f5 100644 --- a/src/utils/credential-cache-manager.js +++ b/src/utils/credential-cache-manager.js @@ -110,11 +110,7 @@ export class CredentialCacheManager { try { process.kill(pid, 0); // 0 信号仅检查进程存在性 // 如果进程存在,检查是否是当前进程 - if (pid === process.pid) { - console.log(`[CredentialCache] Lock file belongs to current process (PID: ${pid}), continuing...`); - } else { - throw new Error(`[CredentialCache] Another instance is running (PID: ${pid}). Please stop it first.`); - } + throw new Error(`[CredentialCache] Another instance is running (PID: ${pid}). Please stop it first.`); } catch (killError) { if (killError.code === 'ESRCH') { // 进程已死亡,可以继续 diff --git a/static/app/i18n.js b/static/app/i18n.js index 068a24f..c0b111f 100644 --- a/static/app/i18n.js +++ b/static/app/i18n.js @@ -467,6 +467,9 @@ const translations = { 'modal.provider.deleteUnhealthy.deleting': '正在删除...', 'modal.provider.deleteUnhealthy.success': '已删除 {count} 个节点', 'modal.provider.deleteUnhealthy.failed': '删除失败', + 'modal.provider.healthCheck.complete': '健康检查完成: {success} 变为健康', + 'modal.provider.healthCheck.abnormal': ', {fail} 异常', + 'modal.provider.healthCheck.skipped': ', {skipped} 跳过(未启用)', 'modal.provider.refreshUnhealthyUuids': '刷新不健康UUID', 'modal.provider.refreshUnhealthyUuidsBtn': '刷新UUID', 'modal.provider.refreshUnhealthyUuidsConfirm': '确定要刷新 {count} 个不健康节点的UUID吗?', @@ -1167,6 +1170,9 @@ const translations = { 'modal.provider.deleteUnhealthy.deleting': 'Deleting...', 'modal.provider.deleteUnhealthy.success': 'Deleted {count} node(s)', 'modal.provider.deleteUnhealthy.failed': 'Delete failed', + 'modal.provider.healthCheck.complete': 'Health check complete: {success} became healthy', + 'modal.provider.healthCheck.abnormal': ', {fail} abnormal', + 'modal.provider.healthCheck.skipped': ', {skipped} skipped (disabled)', 'modal.provider.refreshUnhealthyUuids': 'Refresh unhealthy UUIDs', 'modal.provider.refreshUnhealthyUuidsBtn': 'Refresh UUIDs', 'modal.provider.refreshUnhealthyUuidsConfirm': 'Refresh UUIDs for {count} unhealthy node(s)?', diff --git a/static/app/modal.js b/static/app/modal.js index c30540c..c20f83d 100644 --- a/static/app/modal.js +++ b/static/app/modal.js @@ -1362,9 +1362,9 @@ async function performHealthCheck(providerType) { // 统计跳过的数量(checkHealth 未启用的) const skippedCount = results ? results.filter(r => r.success === null).length : 0; - let message = `${t('modal.provider.healthCheck')}完成: ${successCount} 健康`; - if (failCount > 0) message += `, ${failCount} 异常`; - if (skippedCount > 0) message += `, ${skippedCount} 跳过(未启用)`; + let message = `${t('modal.provider.healthCheck.complete', { success: successCount })}`; + if (failCount > 0) message += t('modal.provider.healthCheck.abnormal', { fail: failCount }); + if (skippedCount > 0) message += t('modal.provider.healthCheck.skipped', { skipped: skippedCount }); showToast(t('common.info'), message, failCount > 0 ? 'warning' : 'success');