From b8a983a3cd9776d7a841577b1405367308130ada Mon Sep 17 00:00:00 2001 From: hex2077 Date: Sat, 4 Apr 2026 22:49:52 +0800 Subject: [PATCH] =?UTF-8?q?Revert=20"feat:=20=E6=94=AF=E6=8C=81=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E6=8F=90=E4=BE=9B=E5=95=86=E9=85=8D=E7=BD=AE=E7=BB=84?= =?UTF-8?q?=E5=92=8C=E5=89=8D=E7=BC=80=E5=8C=B9=E9=85=8D=E6=9C=BA=E5=88=B6?= =?UTF-8?q?"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 0c9d52f5379928b650e522a942eb7a95ce0497b3. --- package.json | 2 +- pnpm-lock.yaml | 22 +- src/core/config-manager.js | 15 - src/handlers/request-handler.js | 8 +- src/providers/adapter.js | 33 +- src/providers/claude/claude-core.js | 4 +- src/providers/claude/claude-kiro.js | 14 +- src/providers/forward/forward-core.js | 4 +- src/providers/gemini/antigravity-core.js | 28 +- src/providers/gemini/gemini-core.js | 19 +- src/providers/grok/grok-core.js | 26 +- src/providers/openai/codex-core.js | 12 +- src/providers/openai/openai-core.js | 4 +- src/providers/openai/openai-responses-core.js | 4 +- src/providers/openai/qwen-core.js | 14 +- src/providers/provider-models.js | 13 +- src/providers/provider-pool-manager.js | 32 +- src/services/service-manager.js | 16 +- src/services/ui-manager.js | 4 +- src/ui-modules/provider-api.js | 154 ++------- src/utils/common.js | 2 +- src/utils/proxy-utils.js | 20 +- static/app/app.js | 4 +- static/app/event-handlers.js | 12 - static/app/i18n.js | 13 - static/app/modal.js | 37 ++- static/app/models-manager.js | 38 +-- static/app/provider-manager.js | 307 ++---------------- static/app/routing-examples.js | 42 +-- static/app/utils.js | 94 ++---- static/components/section-providers.css | 22 -- static/components/section-providers.html | 3 - 32 files changed, 221 insertions(+), 801 deletions(-) diff --git a/package.json b/package.json index 5b1b23b..a967544 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "adm-zip": "^0.5.16", - "axios": "^1.14.0", + "axios": "^1.10.0", "deepmerge": "^4.3.1", "dotenv": "^16.4.5", "google-auth-library": "^10.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3da5f07..8570703 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^0.5.16 version: 0.5.16 axios: - specifier: ^1.14.0 - version: 1.14.0 + specifier: ^1.10.0 + version: 1.13.4 deepmerge: specifier: ^4.3.1 version: 4.3.1 @@ -883,8 +883,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - axios@1.14.0: - resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==} + axios@1.13.4: + resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} @@ -1308,12 +1308,11 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + deprecated: Glob versions prior to v9 are no longer supported google-auth-library@10.5.0: resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} @@ -1835,9 +1834,8 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - proxy-from-env@2.1.0: - resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} - engines: {node: '>=10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} @@ -3271,11 +3269,11 @@ snapshots: asynckit@0.4.0: {} - axios@1.14.0: + axios@1.13.4: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 - proxy-from-env: 2.1.0 + proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -4443,7 +4441,7 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - proxy-from-env@2.1.0: {} + proxy-from-env@1.1.0: {} pure-rand@6.1.0: {} diff --git a/src/core/config-manager.js b/src/core/config-manager.js index 0af5b88..364dd99 100644 --- a/src/core/config-manager.js +++ b/src/core/config-manager.js @@ -76,11 +76,6 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP LOGIN_MIN_INTERVAL: 5000, // 两次尝试之间的最小间隔(毫秒),默认1秒 PROVIDER_POOLS_FILE_PATH: null, // 新增号池配置文件路径 MAX_ERROR_COUNT: 10, // 提供商最大错误次数 - SCHEDULED_HEALTH_CHECK: { - enabled: false, - interval: 600000, - startupRun: false - }, providerFallbackChain: {}, // 跨类型 Fallback 链配置 LOG_ENABLED: true, LOG_OUTPUT_MODE: "all", @@ -131,8 +126,6 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP { flag: '--login-max-attempts', configKey: 'LOGIN_MAX_ATTEMPTS', type: 'int' }, { flag: '--login-lockout-duration', configKey: 'LOGIN_LOCKOUT_DURATION', type: 'int' }, { flag: '--login-min-interval', configKey: 'LOGIN_MIN_INTERVAL', type: 'int' }, - { flag: '--scheduled-health-check-enabled', configKey: 'SCHEDULE_HEALTH_CHECK_ENABLED', type: 'bool' }, - { flag: '--scheduled-health-check-interval', configKey: 'SCHEDULE_HEALTH_CHECK_INTERVAL', type: 'int' }, ]; // Parse command-line arguments using definitions @@ -167,14 +160,6 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP } } - // 合并定时健康检查的 CLI 配置 - if (currentConfig.SCHEDULE_HEALTH_CHECK_ENABLED !== undefined) { - currentConfig.SCHEDULED_HEALTH_CHECK.enabled = currentConfig.SCHEDULE_HEALTH_CHECK_ENABLED; - } - if (currentConfig.SCHEDULE_HEALTH_CHECK_INTERVAL !== undefined) { - currentConfig.SCHEDULED_HEALTH_CHECK.interval = currentConfig.SCHEDULE_HEALTH_CHECK_INTERVAL; - } - normalizeConfiguredProviders(currentConfig); if (!currentConfig.SYSTEM_PROMPT_FILE_PATH) { diff --git a/src/handlers/request-handler.js b/src/handlers/request-handler.js index 57e830b..ebc84da 100644 --- a/src/handlers/request-handler.js +++ b/src/handlers/request-handler.js @@ -6,7 +6,7 @@ import { handleAPIRequests } from '../services/api-manager.js'; import { getApiService, getProviderStatus } from '../services/service-manager.js'; import { getProviderPoolManager } from '../services/service-manager.js'; import { MODEL_PROVIDER } from '../utils/common.js'; -import { getRegisteredProviders, isRegisteredProvider } from '../providers/adapter.js'; +import { getRegisteredProviders } from '../providers/adapter.js'; import { countTokensAnthropic } from '../utils/token-utils.js'; import { PROMPT_LOG_FILENAME } from '../core/config-manager.js'; import { getPluginManager } from '../core/plugin-manager.js'; @@ -152,7 +152,8 @@ export function createRequestHandler(config, providerPoolManager) { // Allow overriding MODEL_PROVIDER via request header const modelProviderHeader = req.headers['model-provider']; if (modelProviderHeader) { - if (isRegisteredProvider(modelProviderHeader)) { + const registeredProviders = getRegisteredProviders(); + if (registeredProviders.includes(modelProviderHeader)) { currentConfig.MODEL_PROVIDER = modelProviderHeader; logger.info(`[Config] MODEL_PROVIDER overridden by header to: ${currentConfig.MODEL_PROVIDER}`); } else { @@ -168,7 +169,8 @@ export function createRequestHandler(config, providerPoolManager) { if (pathSegments.length > 0) { const firstSegment = pathSegments[0]; - const isValidProvider = isRegisteredProvider(firstSegment); + const registeredProviders = getRegisteredProviders(); + const isValidProvider = registeredProviders.includes(firstSegment); const isAutoMode = firstSegment === MODEL_PROVIDER.AUTO; if (firstSegment && (isValidProvider || isAutoMode)) { diff --git a/src/providers/adapter.js b/src/providers/adapter.js index 7f9436c..bfaceac 100644 --- a/src/providers/adapter.js +++ b/src/providers/adapter.js @@ -704,26 +704,6 @@ registerAdapter(MODEL_PROVIDER.GROK_CUSTOM, GrokApiServiceAdapter); // 用于存储服务适配器单例的映射 export const serviceInstances = {}; -/** - * 检查提供商是否已注册(支持前缀匹配) - * @param {string} provider - 提供商名称 - * @returns {boolean} - 是否有效 - */ -export function isRegisteredProvider(provider) { - if (adapterRegistry.has(provider)) { - return true; - } - - // 检查前缀 (例如 openai-custom-1 -> openai-custom) - for (const key of adapterRegistry.keys()) { - if (provider.startsWith(key + '-')) { - return true; - } - } - - return false; -} - // 服务适配器工厂 export function getServiceAdapter(config) { const customNameDisplay = config.customName ? ` (${config.customName})` : ''; @@ -732,18 +712,7 @@ export function getServiceAdapter(config) { const providerKey = config.uuid ? provider + config.uuid : provider; if (!serviceInstances[providerKey]) { - let AdapterClass = adapterRegistry.get(provider); - - // 如果没找到精确匹配,尝试通过前缀查找 (例如 openai-custom-1 -> openai-custom) - if (!AdapterClass) { - for (const [key, value] of adapterRegistry.entries()) { - if (provider === key || provider.startsWith(key + '-')) { - AdapterClass = value; - break; - } - } - } - + const AdapterClass = adapterRegistry.get(provider); if (AdapterClass) { serviceInstances[providerKey] = new AdapterClass(config); } else { diff --git a/src/providers/claude/claude-core.js b/src/providers/claude/claude-core.js index 89f5e22..e95e3b5 100644 --- a/src/providers/claude/claude-core.js +++ b/src/providers/claude/claude-core.js @@ -63,13 +63,13 @@ export class ClaudeApiService { } // 配置自定义代理 - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.CLAUDE_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.CLAUDE_CUSTOM); return axios.create(axiosConfig); } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.CLAUDE_CUSTOM, this.baseUrl); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.CLAUDE_CUSTOM, this.baseUrl); } /** diff --git a/src/providers/claude/claude-kiro.js b/src/providers/claude/claude-kiro.js index cd7fa0d..7c185e3 100644 --- a/src/providers/claude/claude-kiro.js +++ b/src/providers/claude/claude-kiro.js @@ -494,7 +494,7 @@ export class KiroApiService { } // 配置自定义代理 - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API); + configureAxiosProxy(axiosConfig, this.config, 'claude-kiro-oauth'); this.axiosInstance = axios.create(axiosConfig); @@ -505,7 +505,7 @@ export class KiroApiService { } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.KIRO_API); } /** @@ -744,7 +744,7 @@ async saveCredentialsToFile(filePath, newData) { // 刷新成功,重置 PoolManager 中的刷新状态并标记为健康 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.KIRO_API, this.uuid); } } else { throw new Error('Invalid refresh response: Missing accessToken'); @@ -1627,7 +1627,7 @@ async saveCredentialsToFile(filePath, newData) { _refreshUuid() { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - const newUuid = poolManager.refreshProviderUuid(this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API, { + const newUuid = poolManager.refreshProviderUuid(MODEL_PROVIDER.KIRO_API, { uuid: this.uuid }); return newUuid; @@ -1649,7 +1649,7 @@ async saveCredentialsToFile(filePath, newData) { if (poolManager && this.uuid) { logger.info(`[Kiro] Marking credential ${this.uuid} as needs refresh. Reason: ${reason}`); // 使用新的 markProviderNeedRefresh 方法代替 markProviderUnhealthyImmediately - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.KIRO_API, { uuid: this.uuid }); // Attach marker to error object to prevent duplicate marking in upper layers @@ -1674,7 +1674,7 @@ async saveCredentialsToFile(filePath, newData) { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Kiro] Marking credential ${this.uuid} as unhealthy. Reason: ${reason}`); - poolManager.markProviderUnhealthyImmediately(this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API, { + poolManager.markProviderUnhealthyImmediately(MODEL_PROVIDER.KIRO_API, { uuid: this.uuid }, reason); // Attach marker to error object to prevent duplicate marking in upper layers @@ -1701,7 +1701,7 @@ async saveCredentialsToFile(filePath, newData) { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Kiro] Marking credential ${this.uuid} as unhealthy with recovery time. Reason: ${reason}, Recovery: ${recoveryTime?.toISOString()}`); - poolManager.markProviderUnhealthyWithRecoveryTime(this.config.MODEL_PROVIDER || MODEL_PROVIDER.KIRO_API, { + poolManager.markProviderUnhealthyWithRecoveryTime(MODEL_PROVIDER.KIRO_API, { uuid: this.uuid }, reason, recoveryTime); // Attach marker to error object to prevent duplicate marking in upper layers diff --git a/src/providers/forward/forward-core.js b/src/providers/forward/forward-core.js index 260dcd2..8730ac3 100644 --- a/src/providers/forward/forward-core.js +++ b/src/providers/forward/forward-core.js @@ -56,13 +56,13 @@ export class ForwardApiService { axiosConfig.proxy = false; } - configureAxiosProxy(axiosConfig, config, config.MODEL_PROVIDER || MODEL_PROVIDER.FORWARD_API); + configureAxiosProxy(axiosConfig, config, MODEL_PROVIDER.FORWARD_API); this.axiosInstance = axios.create(axiosConfig); } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.FORWARD_API, this.baseUrl); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.FORWARD_API, this.baseUrl); } async callApi(endpoint, body, isRetry = false, retryCount = 0) { diff --git a/src/providers/gemini/antigravity-core.js b/src/providers/gemini/antigravity-core.js index c66fd2e..3b34ba0 100644 --- a/src/providers/gemini/antigravity-core.js +++ b/src/providers/gemini/antigravity-core.js @@ -697,7 +697,7 @@ export class AntigravityApiService { }); // 检查是否需要使用代理 - const proxyConfig = getGoogleAuthProxyConfig(config, config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY); + const proxyConfig = getGoogleAuthProxyConfig(config, 'gemini-antigravity'); // 配置 OAuth2Client 使用自定义的 HTTP agent const oauth2Options = { @@ -729,11 +729,11 @@ export class AntigravityApiService { this.baseURLs = this.getBaseURLFallbackOrder(config); // 保存代理配置供后续使用 - this.proxyConfig = getProxyConfigForProvider(config, config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY); + this.proxyConfig = getProxyConfigForProvider(config, 'gemini-antigravity'); } _applySidecar(requestOptions) { - return configureTLSSidecar(requestOptions, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY); + return configureTLSSidecar(requestOptions, this.config, MODEL_PROVIDER.ANTIGRAVITY); } /** @@ -821,7 +821,7 @@ export class AntigravityApiService { // 刷新成功,重置 PoolManager 中的刷新状态并标记为健康 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.ANTIGRAVITY, this.uuid); } } else { logger.info(`[Antigravity Auth] No access token or refresh token. Starting new authentication flow...`); @@ -832,7 +832,7 @@ export class AntigravityApiService { // 认证成功,重置状态 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.ANTIGRAVITY, this.uuid); } } } catch (error) { @@ -1109,7 +1109,7 @@ export class AntigravityApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Antigravity] Marking credential ${this.uuid} as needs refresh. Reason: 401/400 Unauthorized`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.ANTIGRAVITY, { uuid: this.uuid }); error.credentialMarkedUnhealthy = true; @@ -1212,7 +1212,7 @@ export class AntigravityApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Antigravity] Marking credential ${this.uuid} as needs refresh. Reason: 401/400 Unauthorized in stream`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.ANTIGRAVITY, { uuid: this.uuid }); error.credentialMarkedUnhealthy = true; @@ -1315,7 +1315,7 @@ export class AntigravityApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Antigravity] Token is near expiry, marking credential ${this.uuid} for refresh`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.ANTIGRAVITY, { uuid: this.uuid }); } @@ -1393,7 +1393,7 @@ export class AntigravityApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Antigravity] Token is near expiry, marking credential ${this.uuid} for refresh`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.ANTIGRAVITY, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.ANTIGRAVITY, { uuid: this.uuid }); } @@ -1442,6 +1442,13 @@ export class AntigravityApiService { async getUsageLimits() { if (!this.isInitialized) await this.initialize(); + // 注意:V2 架构下不再在 getUsageLimits 中同步刷新 token + // 如果 token 过期,PoolManager 后台会自动处理 + // if (this.isExpiryDateNear()) { + // logger.info('[Antigravity] Token is near expiry, refreshing before getUsageLimits request...'); + // await this.initializeAuth(true); + // } + try { const modelsWithQuotas = await this.getModelsWithQuotas(); return modelsWithQuotas; @@ -1480,12 +1487,15 @@ export class AntigravityApiService { this._applySidecar(requestOptions); const res = await this.authClient.request(requestOptions); + // logger.info(`[Antigravity] fetchAvailableModels success: ${JSON.stringify(res.data)}`); if (res.data) { + if (res.data.models) { const modelsData = res.data.models; // 遍历模型数据,提取配额信息 for (const [modelId, modelData] of Object.entries(modelsData)) { + // 参考 fetchAvailableModels 的逻辑修复 modelName2Alias 不存在的问题 if (!modelId || (!ANTIGRAVITY_MODELS.includes(modelId) && !modelId.startsWith('claude-'))) { continue; } diff --git a/src/providers/gemini/gemini-core.js b/src/providers/gemini/gemini-core.js index b4f386c..0033cbf 100644 --- a/src/providers/gemini/gemini-core.js +++ b/src/providers/gemini/gemini-core.js @@ -289,7 +289,7 @@ export class GeminiApiService { }); // 检查是否需要使用代理 - const proxyConfig = getGoogleAuthProxyConfig(config, config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI); + const proxyConfig = getGoogleAuthProxyConfig(config, 'gemini-cli-oauth'); // 配置 OAuth2Client 使用自定义的 HTTP agent const oauth2Options = { @@ -312,7 +312,6 @@ export class GeminiApiService { this.config = config; this.host = config.HOST; - this.uuid = config.uuid; this.oauthCredsBase64 = config.GEMINI_OAUTH_CREDS_BASE64; this.oauthCredsFilePath = config.GEMINI_OAUTH_CREDS_FILE_PATH; this.projectId = config.PROJECT_ID; @@ -321,7 +320,7 @@ export class GeminiApiService { this.apiVersion = DEFAULT_CODE_ASSIST_API_VERSION; // 保存代理配置供后续使用 - this.proxyConfig = getProxyConfigForProvider(config, config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI); + this.proxyConfig = getProxyConfigForProvider(config, 'gemini-cli-oauth'); } async initialize() { @@ -346,7 +345,7 @@ export class GeminiApiService { } _applySidecar(requestOptions) { - return configureTLSSidecar(requestOptions, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI); + return configureTLSSidecar(requestOptions, this.config, MODEL_PROVIDER.GEMINI_CLI); } /** @@ -413,7 +412,7 @@ export class GeminiApiService { // 刷新成功,重置 PoolManager 中的刷新状态并标记为健康 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.GEMINI_CLI, this.uuid); } } else { logger.info(`[Gemini Auth] No access token or refresh token. Starting new authentication flow...`); @@ -424,7 +423,7 @@ export class GeminiApiService { // 认证成功,重置状态 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.GEMINI_CLI, this.uuid); } } } catch (error) { @@ -599,7 +598,7 @@ export class GeminiApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Gemini] Marking credential ${this.uuid} as needs refresh. Reason: 401/400 Unauthorized`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.GEMINI_CLI, { uuid: this.uuid }); error.credentialMarkedUnhealthy = true; @@ -682,7 +681,7 @@ export class GeminiApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Gemini] Marking credential ${this.uuid} as needs refresh. Reason: 401/400 Unauthorized in stream`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.GEMINI_CLI, { uuid: this.uuid }); error.credentialMarkedUnhealthy = true; @@ -758,7 +757,7 @@ export class GeminiApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Gemini] Token is near expiry, marking credential ${this.uuid} for refresh`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.GEMINI_CLI, { uuid: this.uuid }); } @@ -797,7 +796,7 @@ export class GeminiApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Gemini] Token is near expiry, marking credential ${this.uuid} for refresh`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GEMINI_CLI, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.GEMINI_CLI, { uuid: this.uuid }); } diff --git a/src/providers/grok/grok-core.js b/src/providers/grok/grok-core.js index 8e34b3d..7fb962d 100644 --- a/src/providers/grok/grok-core.js +++ b/src/providers/grok/grok-core.js @@ -129,14 +129,14 @@ export class GrokApiService { async acceptTos() { const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/app-chat/accept-tos`, headers: this.buildHeaders(), data: {}, httpAgent, httpsAgent, timeout: 15000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { await axios(axiosConfig); } catch (e) { logger.debug(`[Grok TOS] ${e.message}`); } } async setBirthDate() { const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/app-chat/set-birth-date`, headers: this.buildHeaders(), data: { "birthDate": "1990-01-01" }, httpAgent, httpsAgent, timeout: 15000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { await axios(axiosConfig); } catch (e) { logger.debug(`[Grok Birth] ${e.message}`); } } @@ -167,13 +167,13 @@ export class GrokApiService { timeout: 15000, responseType: 'arraybuffer' }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { await axios(axiosConfig); } catch (e) { throw e; } } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); } async initialize() { @@ -190,7 +190,7 @@ export class GrokApiService { // await this.getUsageLimits(); return Promise.resolve(); const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.GROK_CUSTOM, this.uuid); } } catch (error) { logger.error('[Grok] Failed to initialize authentication:', error); @@ -202,7 +202,7 @@ export class GrokApiService { const headers = this.buildHeaders(); const payload = { "requestKind": "DEFAULT", "modelName": "grok-3" }; const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/rate-limits`, headers, data: payload, httpAgent, httpsAgent, timeout: 30000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { const response = await axios(axiosConfig); @@ -282,7 +282,7 @@ export class GrokApiService { if (mediaUrl && mediaUrl.trim()) payload.mediaUrl = mediaUrl; const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/media/post/create`, headers, data: payload, httpAgent, httpsAgent, timeout: 30000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { const response = await axios(axiosConfig); @@ -302,7 +302,7 @@ export class GrokApiService { if (!idMatch) return videoUrl; const videoId = idMatch[1]; const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/media/video/upscale`, headers: this.buildHeaders(), data: { videoId }, httpAgent, httpsAgent, timeout: 30000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { const response = await axios(axiosConfig); @@ -329,7 +329,7 @@ export class GrokApiService { httpsAgent, timeout: 15000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { const response = await axios(axiosConfig); @@ -455,7 +455,7 @@ export class GrokApiService { rolloutId: "", modelResponse: null, cardAttachment: null, - cardAttachments: [], + cardAttachments: [], // 收集所有的卡片附件 streamingImageGenerationResponse: null, streamingVideoGenerationResponse: null, finalVideoUrl: null, @@ -743,7 +743,7 @@ export class GrokApiService { } if (!b64) return null; const axiosConfig = { method: 'post', url: `${this.baseUrl}/rest/app-chat/upload-file`, headers: this.buildHeaders(), data: { fileName: `file.${mime.split("/")[1] || "bin"}`, fileMimeType: mime, content: b64 }, httpAgent, httpsAgent, timeout: 30000 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { return (await axios(axiosConfig)).data; } catch (error) { return null; } } @@ -763,7 +763,7 @@ export class GrokApiService { if (requestBody._requestBaseUrl) delete requestBody._requestBaseUrl; if (this.isExpiryDateNear() && getProviderPoolManager() && this.uuid) { - getProviderPoolManager().markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM, { uuid: this.uuid }); + getProviderPoolManager().markProviderNeedRefresh(MODEL_PROVIDER.GROK_CUSTOM, { uuid: this.uuid }); } const rawModel = typeof model === 'string' ? model : ''; @@ -841,7 +841,7 @@ export class GrokApiService { const payload = this.buildPayload(model, requestBody); const axiosConfig = { method: 'post', url: this.chatApi, headers: this.buildHeaders(), data: payload, responseType: 'stream', httpAgent, httpsAgent, timeout: 60000, maxRedirects: 0 }; - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.GROK_CUSTOM); + configureAxiosProxy(axiosConfig, this.config, MODEL_PROVIDER.GROK_CUSTOM); this._applySidecar(axiosConfig); try { diff --git a/src/providers/openai/codex-core.js b/src/providers/openai/codex-core.js index 909414f..3eceb68 100644 --- a/src/providers/openai/codex-core.js +++ b/src/providers/openai/codex-core.js @@ -40,7 +40,7 @@ export class CodexApiService { } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.CODEX_API, this.baseUrl); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.CODEX_API, this.baseUrl); } /** @@ -148,7 +148,7 @@ export class CodexApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Codex] Token is near expiry, marking credential ${this.uuid} for background refresh`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.CODEX_API, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.CODEX_API, { uuid: this.uuid }); } @@ -195,7 +195,7 @@ export class CodexApiService { }; // 配置代理 - const proxyConfig = getProxyConfigForProvider(this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.CODEX_API); + const proxyConfig = getProxyConfigForProvider(this.config, 'openai-codex-oauth'); if (proxyConfig) { config.httpAgent = proxyConfig.httpAgent; config.httpsAgent = proxyConfig.httpsAgent; @@ -272,7 +272,7 @@ export class CodexApiService { }; // 配置代理 - const proxyConfig = getProxyConfigForProvider(this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.CODEX_API); + const proxyConfig = getProxyConfigForProvider(this.config, 'openai-codex-oauth'); if (proxyConfig) { config.httpAgent = proxyConfig.httpAgent; config.httpsAgent = proxyConfig.httpsAgent; @@ -454,7 +454,7 @@ export class CodexApiService { // 刷新成功,重置 PoolManager 中的刷新状态并标记为健康 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.CODEX_API, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.CODEX_API, this.uuid); } logger.info('[Codex] Token refreshed successfully'); } catch (error) { @@ -688,7 +688,7 @@ export class CodexApiService { }; // 配置代理 - const proxyConfig = getProxyConfigForProvider(this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.CODEX_API); + const proxyConfig = getProxyConfigForProvider(this.config, 'openai-codex-oauth'); if (proxyConfig) { config.httpAgent = proxyConfig.httpAgent; config.httpsAgent = proxyConfig.httpsAgent; diff --git a/src/providers/openai/openai-core.js b/src/providers/openai/openai-core.js index 0485fd6..6fe800a 100644 --- a/src/providers/openai/openai-core.js +++ b/src/providers/openai/openai-core.js @@ -47,13 +47,13 @@ export class OpenAIApiService { } // 配置自定义代理 - configureAxiosProxy(axiosConfig, config, config.MODEL_PROVIDER || MODEL_PROVIDER.OPENAI_CUSTOM); + configureAxiosProxy(axiosConfig, config, MODEL_PROVIDER.OPENAI_CUSTOM); this.axiosInstance = axios.create(axiosConfig); } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.OPENAI_CUSTOM, this.baseUrl); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.OPENAI_CUSTOM, this.baseUrl); } async callApi(endpoint, body, isRetry = false, retryCount = 0) { diff --git a/src/providers/openai/openai-responses-core.js b/src/providers/openai/openai-responses-core.js index 853a3fd..d12cbad 100644 --- a/src/providers/openai/openai-responses-core.js +++ b/src/providers/openai/openai-responses-core.js @@ -47,13 +47,13 @@ export class OpenAIResponsesApiService { } // 配置自定义代理 (使用 openai-custom 的代理配置) - configureAxiosProxy(axiosConfig, config, config.MODEL_PROVIDER || MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES); + configureAxiosProxy(axiosConfig, config, MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES); this.axiosInstance = axios.create(axiosConfig); } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES, this.baseUrl); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES, this.baseUrl); } async callApi(endpoint, body, isRetry = false, retryCount = 0) { diff --git a/src/providers/openai/qwen-core.js b/src/providers/openai/qwen-core.js index e2621ca..8d31638 100644 --- a/src/providers/openai/qwen-core.js +++ b/src/providers/openai/qwen-core.js @@ -230,7 +230,7 @@ export class QwenApiService { } // 配置自定义代理 - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API); + configureAxiosProxy(axiosConfig, this.config, 'openai-qwen-oauth'); this.currentAxiosInstance = axios.create(axiosConfig); @@ -239,7 +239,7 @@ export class QwenApiService { } _applySidecar(axiosConfig) { - return configureTLSSidecar(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API, this.baseUrl); + return configureTLSSidecar(axiosConfig, this.config, MODEL_PROVIDER.QWEN_API, this.baseUrl); } /** @@ -278,7 +278,7 @@ export class QwenApiService { if (forceRefresh || (credentials && credentials.access_token)) { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.QWEN_API, this.uuid); } } } catch (error) { @@ -331,7 +331,7 @@ export class QwenApiService { // 认证成功,重置状态 const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { - poolManager.resetProviderRefreshStatus(this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API, this.uuid); + poolManager.resetProviderRefreshStatus(MODEL_PROVIDER.QWEN_API, this.uuid); } } } @@ -575,7 +575,7 @@ export class QwenApiService { } // 配置自定义代理 - configureAxiosProxy(axiosConfig, this.config, this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API); + configureAxiosProxy(axiosConfig, this.config, 'openai-qwen-oauth'); this.currentAxiosInstance = axios.create(axiosConfig); @@ -629,7 +629,7 @@ export class QwenApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Qwen] Marking credential ${this.uuid} as needs refresh. Reason: Auth Error ${status}`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.QWEN_API, { uuid: this.uuid }); error.credentialMarkedUnhealthy = true; @@ -677,7 +677,7 @@ export class QwenApiService { const poolManager = getProviderPoolManager(); if (poolManager && this.uuid) { logger.info(`[Qwen] Token is near expiry, marking credential ${this.uuid} for refresh`); - poolManager.markProviderNeedRefresh(this.config.MODEL_PROVIDER || MODEL_PROVIDER.QWEN_API, { + poolManager.markProviderNeedRefresh(MODEL_PROVIDER.QWEN_API, { uuid: this.uuid }); } diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index 8c815a0..faa044c 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -116,18 +116,7 @@ export const PROVIDER_MODELS = { * @returns {Array} 模型列表 */ export function getProviderModels(providerType) { - if (PROVIDER_MODELS[providerType]) { - return PROVIDER_MODELS[providerType]; - } - - // 尝试前缀匹配 (例如 openai-custom-1 -> openai-custom) - for (const key of Object.keys(PROVIDER_MODELS)) { - if (providerType.startsWith(key + '-')) { - return PROVIDER_MODELS[key]; - } - } - - return []; + return PROVIDER_MODELS[providerType] || []; } /** diff --git a/src/providers/provider-pool-manager.js b/src/providers/provider-pool-manager.js index 334c9d3..345badf 100644 --- a/src/providers/provider-pool-manager.js +++ b/src/providers/provider-pool-manager.js @@ -1841,11 +1841,7 @@ export class ProviderPoolManager { for (const { providerType, provider, uuid, customName } of providersToCheck) { const providerCheckStart = Date.now(); - const baseProviderType = this._getBaseProviderType(providerType); - const checkModelName = provider.config.checkModelName || - ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType] || - ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[baseProviderType] || - 'unknown'; + const checkModelName = provider.config.checkModelName || ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType] || 'unknown'; const displayName = customName || uuid.substring(0, 8); try { @@ -1907,7 +1903,7 @@ export class ProviderPoolManager { } // OpenAI Custom Responses 使用特殊格式 - if (this._getBaseProviderType(providerType) === MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES) { + if (providerType === MODEL_PROVIDER.OPENAI_CUSTOM_RESPONSES) { requests.push({ input: [baseMessage], model: modelName @@ -1924,26 +1920,6 @@ export class ProviderPoolManager { return requests; } - /** - * 根据提供商类型获取基准提供商类型(用于查找配置和模型) - * 例如:openai-custom-1 -> openai-custom - * @private - */ - _getBaseProviderType(providerType) { - if (ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType]) { - return providerType; - } - - // 尝试前缀匹配 - for (const key of Object.keys(ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS)) { - if (providerType === key || providerType.startsWith(key + '-')) { - return key; - } - } - - return providerType; - } - /** * Performs an actual health check for a specific provider. * @@ -1958,10 +1934,8 @@ export class ProviderPoolManager { */ async _checkProviderHealth(providerType, providerConfig) { // 确定健康检查使用的模型名称 - const baseProviderType = this._getBaseProviderType(providerType); const modelName = providerConfig.checkModelName || - ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType] || - ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[baseProviderType]; + ProviderPoolManager.DEFAULT_HEALTH_CHECK_MODELS[providerType]; if (!modelName) { this._log('warn', `Unknown provider type for health check: ${providerType}. Please check DEFAULT_HEALTH_CHECK_MODELS.`); diff --git a/src/services/service-manager.js b/src/services/service-manager.js index 47f1673..747518f 100644 --- a/src/services/service-manager.js +++ b/src/services/service-manager.js @@ -563,8 +563,7 @@ export async function getProviderStatus(config, options = {}) { 'gemini-antigravity': 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH', 'openai-iflow': 'IFLOW_TOKEN_FILE_PATH', 'forward-api': 'FORWARD_BASE_URL', - 'grok-custom': 'GROK_COOKIE_TOKEN', - 'openai-codex-oauth': 'CODEX_OAUTH_CREDS_FILE_PATH' + 'grok-custom': 'GROK_COOKIE_TOKEN' }; let providerPoolsSlim = []; let unhealthyProvideIdentifyList = []; @@ -576,18 +575,7 @@ export async function getProviderStatus(config, options = {}) { for (const key of Object.keys(providerPools)) { if (!Array.isArray(providerPools[key])) continue; if (filterProvider && key !== filterProvider) continue; - - let identifyField = identifyFieldMap[key] || null; - if (!identifyField) { - // 尝试通过前缀查找 identifyField (例如 openai-custom-1 -> openai-custom) - for (const [prefix, field] of Object.entries(identifyFieldMap)) { - if (key.startsWith(prefix + '-')) { - identifyField = field; - break; - } - } - } - + const identifyField = identifyFieldMap[key] || null; const slimArr = providerPools[key] .filter(item => { if (item.isDisabled) return false; diff --git a/src/services/ui-manager.js b/src/services/ui-manager.js index cd71bb9..91db632 100644 --- a/src/services/ui-manager.js +++ b/src/services/ui-manager.js @@ -125,7 +125,7 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo // Get supported provider types based on registered adapters if (method === 'GET' && pathParam === '/api/providers/supported') { - return await providerApi.handleGetSupportedProviders(req, res, currentConfig, providerPoolManager); + return await providerApi.handleGetSupportedProviders(req, res); } // Get specific provider type details @@ -137,7 +137,7 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo // Get available models for all providers or specific provider type if (method === 'GET' && pathParam === '/api/provider-models') { - return await providerApi.handleGetProviderModels(req, res, currentConfig, providerPoolManager); + return await providerApi.handleGetProviderModels(req, res); } // Get available models for a specific provider type diff --git a/src/ui-modules/provider-api.js b/src/ui-modules/provider-api.js index 600f4d0..1d4cf3d 100644 --- a/src/ui-modules/provider-api.js +++ b/src/ui-modules/provider-api.js @@ -9,53 +9,38 @@ import { getRegisteredProviders } from '../providers/adapter.js'; // 文件级互斥锁:防止并发读写导致数据丢失 // 安全净化:移除用户输入字段中的危险内容(script、事件处理器、javascript:协议等), // 存储原始文本。HTML 转义统一由前端 escHtml() 负责,避免双编码问题。 -// 安全净化:移除用户输入字段中的危险内容,并可选地过滤敏感 API 密钥 -function sanitizeProviderData(provider, maskSensitive = false) { +function sanitizeProviderData(provider) { if (!provider || typeof provider !== 'object') return provider; const sanitized = { ...provider }; - - // 1. 过滤敏感字段(API Keys, Tokens 等) - if (maskSensitive) { - const sensitiveKeys = [ - 'OPENAI_API_KEY', 'CLAUDE_API_KEY', 'FORWARD_API_KEY', - 'GROK_COOKIE_TOKEN', 'GROK_CF_CLEARANCE', - 'refreshToken', 'accessToken', 'clientSecret' - ]; - - sensitiveKeys.forEach(key => { - if (sanitized[key]) { - // 对密钥进行脱敏显示(只保留前 4 位和后 4 位) - const val = sanitized[key]; - if (typeof val === 'string' && val.length > 10) { - sanitized[key] = val.substring(0, 4) + '****' + val.substring(val.length - 4); - } else { - sanitized[key] = '********'; - } - } - }); - } - - // 2. 净化 customName 中的 HTML/脚本 if (typeof sanitized.customName === 'string') { let name = sanitized.customName; + + // 拒绝包含危险协议 if (/(?:data|javascript|vbscript)\s*:/i.test(name)) { sanitized.customName = ''; return sanitized; } + + // 移除所有 HTML 标签(更安全的方式) name = name.replace(/<[^>]*>/g, ''); + + // 移除 HTML 事件处理器属性(onclick/onerror 等) name = name.replace(/\s+on\w+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]*)/gi, ''); + + // 移除潜在的 HTML 实体编码攻击 name = name.replace(/&[#\w]+;/g, ''); + sanitized.customName = name.trim(); } return sanitized; } -function sanitizeProviderPools(pools, maskSensitive = false) { +function sanitizeProviderPools(pools) { if (!pools || typeof pools !== 'object') return pools; const sanitized = {}; for (const [type, providers] of Object.entries(pools)) { sanitized[type] = Array.isArray(providers) - ? providers.map(p => sanitizeProviderData(p, maskSensitive)) + ? providers.map(sanitizeProviderData) : providers; } return sanitized; @@ -85,53 +70,34 @@ function withFileLock(fn) { return next; } /** - * 获取所有提供商的状态(包括支持的类型和号池组) + * 获取提供商池摘要 */ export async function handleGetProviders(req, res, currentConfig, providerPoolManager) { - if (!providerPoolManager) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: { message: 'Provider pool manager not initialized' } })); - return true; - } - - // 1. 获取支持的基础提供商类型 - const registeredProviders = getRegisteredProviders(); - let poolTypes = []; - - // 2. 从管理器获取当前所有池的状态 - const providerStatus = {}; - for (const [type, providers] of Object.entries(providerPoolManager.providerStatus)) { - providerStatus[type] = providers.map(p => ({ - ...p.config, - activeRequests: p.state?.activeCount || 0, - waitingRequests: p.state?.waitingCount || 0 - })); - } - - // 3. 补全号池配置文件中的所有组 + let providerPools = {}; const filePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'configs/provider_pools.json'; try { - if (existsSync(filePath)) { + if (providerPoolManager && providerPoolManager.providerPools) { + providerPools = providerPoolManager.providerPools; + } else if (filePath && existsSync(filePath)) { const poolsData = JSON.parse(readFileSync(filePath, 'utf-8')); - poolTypes = Object.keys(poolsData); - poolTypes.forEach(type => { - if (!providerStatus[type]) { - providerStatus[type] = []; - } - }); + providerPools = poolsData; } } catch (error) { - logger.warn('[UI API] Failed to supplement provider status:', error.message); + logger.warn('[UI API] Failed to load provider pools:', error.message); } - // 合并生成支持的类型列表 - const supportedProviders = [...new Set([...registeredProviders, ...poolTypes])]; - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - providers: sanitizeProviderPools(providerStatus, true), // 列表显示进行打码 - supportedProviders: supportedProviders - })); + res.end(JSON.stringify(sanitizeProviderPools(providerPools))); + return true; +} + +/** + * 获取支持的提供商类型(已注册适配器的) + */ +export async function handleGetSupportedProviders(req, res) { + const supportedProviders = getRegisteredProviders(); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(supportedProviders)); return true; } @@ -156,7 +122,7 @@ export async function handleGetProviderType(req, res, currentConfig, providerPoo res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ providerType, - providers: providers.map(p => sanitizeProviderData(p, false)), // 详情页(用于编辑)不打码 + providers: providers.map(sanitizeProviderData), totalCount: providers.length, healthyCount: providers.filter(p => p.isHealthy).length })); @@ -164,62 +130,10 @@ export async function handleGetProviderType(req, res, currentConfig, providerPoo } /** - * 获取支持的提供商类型(已注册适配器的,以及号池中已存在的自定义类型) + * 获取所有提供商的可用模型 */ -export async function handleGetSupportedProviders(req, res, currentConfig, providerPoolManager) { - const registeredProviders = getRegisteredProviders(); - let poolTypes = []; - - const filePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'configs/provider_pools.json'; - try { - if (providerPoolManager && providerPoolManager.providerPools) { - poolTypes = Object.keys(providerPoolManager.providerPools); - } else if (filePath && existsSync(filePath)) { - const poolsData = JSON.parse(readFileSync(filePath, 'utf-8')); - poolTypes = Object.keys(poolsData); - } - } catch (error) { - logger.warn('[UI API] Failed to load provider pools for supported types:', error.message); - } - - // 合并注册的提供商和号池中的类型 - const supportedProviders = [...new Set([...registeredProviders, ...poolTypes])]; - - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(supportedProviders)); - return true; -} - -/** - * 获取所有提供商的可用模型(支持动态配置组) - */ -export async function handleGetProviderModels(req, res, currentConfig, providerPoolManager) { - const registeredProviders = getRegisteredProviders(); - let poolTypes = []; - - // 获取所有存在的类型(基础 + 动态) - const filePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'configs/provider_pools.json'; - try { - if (providerPoolManager && providerPoolManager.providerPools) { - poolTypes = Object.keys(providerPoolManager.providerPools); - } else if (existsSync(filePath)) { - const poolsData = JSON.parse(readFileSync(filePath, 'utf-8')); - poolTypes = Object.keys(poolsData); - } - } catch (error) { - logger.warn('[UI API] Failed to load provider pools for models:', error.message); - } - - const allTypes = [...new Set([...registeredProviders, ...poolTypes])]; - const allModels = {}; - - allTypes.forEach(type => { - const models = getProviderModels(type); - if (models && models.length > 0) { - allModels[type] = models; - } - }); - +export async function handleGetProviderModels(req, res) { + const allModels = getAllProviderModels(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(allModels)); return true; diff --git a/src/utils/common.js b/src/utils/common.js index 3582277..c031ea4 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -961,7 +961,7 @@ export async function handleContentGenerationRequest(req, res, service, endpoint } // 为 forward provider 添加原始请求路径作为 endpoint - if (requestPath && getProtocolPrefix(toProvider) === MODEL_PROTOCOL_PREFIX.FORWARD) { + if (requestPath && toProvider === MODEL_PROVIDER.FORWARD_API) { logger.info(`[Forward API] Request path: ${requestPath}`); processedRequestBody.endpoint = requestPath; } diff --git a/src/utils/proxy-utils.js b/src/utils/proxy-utils.js index 8053c19..45bd9ec 100644 --- a/src/utils/proxy-utils.js +++ b/src/utils/proxy-utils.js @@ -54,7 +54,7 @@ export function parseProxyUrl(proxyUrl) { } /** - * 检查指定的提供商是否启用了代理(支持前缀匹配) + * 检查指定的提供商是否启用了代理 * @param {Object} config - 配置对象 * @param {string} providerType - 提供商类型 * @returns {boolean} 是否启用代理 @@ -69,13 +69,7 @@ export function isProxyEnabledForProvider(config, providerType) { return false; } - // 1. 尝试精确匹配 - if (enabledProviders.includes(providerType)) { - return true; - } - - // 2. 尝试前缀匹配 (例如 openai-custom-prod 继承 openai-custom 的配置) - return enabledProviders.some(p => providerType.startsWith(p + '-')); + return enabledProviders.includes(providerType); } /** @@ -118,7 +112,7 @@ export function configureAxiosProxy(axiosConfig, config, providerType) { } /** - * 检查指定的提供商是否启用了 TLS Sidecar(支持前缀匹配) + * 检查指定的提供商是否启用了 TLS Sidecar * @param {Object} config - 配置对象 * @param {string} providerType - 提供商类型 * @returns {boolean} 是否启用 TLS Sidecar @@ -133,13 +127,7 @@ export function isTLSSidecarEnabledForProvider(config, providerType) { return false; } - // 1. 尝试精确匹配 - if (enabledProviders.includes(providerType)) { - return true; - } - - // 2. 尝试前缀匹配 - return enabledProviders.some(p => providerType.startsWith(p + '-')); + return enabledProviders.includes(providerType); } /** diff --git a/static/app/app.js b/static/app/app.js index 4d38fb3..2704104 100644 --- a/static/app/app.js +++ b/static/app/app.js @@ -41,8 +41,7 @@ import { openProviderManager, showAuthModal, executeGenerateAuthUrl, - handleGenerateAuthUrl, - showAddProviderGroupModal + handleGenerateAuthUrl } from './provider-manager.js'; import { @@ -235,7 +234,6 @@ window.fileUploadHandler = fileUploadHandler; window.showAuthModal = showAuthModal; window.executeGenerateAuthUrl = executeGenerateAuthUrl; window.handleGenerateAuthUrl = handleGenerateAuthUrl; -window.showAddProviderGroupModal = showAddProviderGroupModal; // 配置管理相关全局函数 window.viewConfig = viewConfig; diff --git a/static/app/event-handlers.js b/static/app/event-handlers.js index 5377ecb..283bdcf 100644 --- a/static/app/event-handlers.js +++ b/static/app/event-handlers.js @@ -219,18 +219,6 @@ function initEventListeners() { performUpdateBtn.addEventListener('click', performUpdate); } - // 添加提供商组按钮 - const addProviderGroupBtn = document.getElementById('add-provider-group-btn'); - if (addProviderGroupBtn) { - addProviderGroupBtn.addEventListener('click', () => { - if (window.showAddProviderGroupModal) { - window.showAddProviderGroupModal(); - } else { - console.error('showAddProviderGroupModal function not found'); - } - }); - } - // 日志容器滚动 if (elements.logsContainer) { elements.logsContainer.addEventListener('scroll', () => { diff --git a/static/app/i18n.js b/static/app/i18n.js index f4ee713..13a06ee 100644 --- a/static/app/i18n.js +++ b/static/app/i18n.js @@ -452,12 +452,6 @@ const translations = { // Providers 'providers.title': '提供商池管理', - 'providers.addGroup': '新的分组', - 'providers.addGroup.title': '添加新分组', - 'providers.addGroup.success': '分组创建成功,请添加账号', - 'providers.addGroup.error': '创建失败', - 'providers.addGroup.suffix': '分组名称 (后缀)', - 'providers.addGroup.suffixPlaceholder': '例如: qwen, glm, minimax', 'providers.note': '如使用客户端默认授权配置需使用空节点', 'providers.activeConnections': '活动连接', 'providers.activeProviders': '活跃提供商', @@ -1310,13 +1304,6 @@ const translations = { // Providers 'providers.title': 'Provider Pool Management', - 'providers.addGroup': 'Add Group', - 'providers.addGroup.title': 'Add New Configuration Group', - 'providers.addGroup.baseType': 'Base Type', - 'providers.addGroup.suffix': 'Suffix Name', - 'providers.addGroup.suffixPlaceholder': 'e.g., qwen, glm, minimax', - 'providers.addGroup.success': 'Configuration group created, please add accounts', - 'providers.addGroup.error': 'Creation failed', 'providers.note': 'If using default client authorization config, use an empty node', 'providers.activeConnections': 'Active Connections', 'providers.activeProviders': 'Active Providers', diff --git a/static/app/modal.js b/static/app/modal.js index 60a1cd3..f906030 100644 --- a/static/app/modal.js +++ b/static/app/modal.js @@ -667,13 +667,8 @@ function renderProviderConfig(provider) { * @param {Object} provider - 提供商对象 * @returns {Array} 字段键数组 */ -/** - * 获取字段显示顺序 - * @param {Object} provider - 提供商对象 - * @returns {Array} 字段名数组 - */ function getFieldOrder(provider) { - const orderedFields = ['customName', 'checkModelName', 'checkHealth', 'concurrencyLimit', 'queueLimit']; + const orderedFields = ['customName', 'checkModelName', 'checkHealth']; // 需要排除的内部状态字段 const excludedFields = [ @@ -682,10 +677,23 @@ function getFieldOrder(provider) { 'notSupportedModels', 'refreshCount', 'needsRefresh', '_lastSelectionSeq' ]; - // 尝试从当前模态框上下文中获取提供商类型 - let providerType = currentProviderType; + // 从 getProviderTypeFields 获取字段顺序映射 + const fieldOrderMap = { + 'openai-custom': ['OPENAI_API_KEY', 'OPENAI_BASE_URL'], + 'openaiResponses-custom': ['OPENAI_API_KEY', 'OPENAI_BASE_URL'], + 'claude-custom': ['CLAUDE_API_KEY', 'CLAUDE_BASE_URL'], + 'gemini-cli-oauth': ['PROJECT_ID', 'GEMINI_OAUTH_CREDS_FILE_PATH', 'GEMINI_BASE_URL'], + 'claude-kiro-oauth': ['KIRO_OAUTH_CREDS_FILE_PATH', 'KIRO_BASE_URL', 'KIRO_REFRESH_URL', 'KIRO_REFRESH_IDC_URL'], + 'openai-qwen-oauth': ['QWEN_OAUTH_CREDS_FILE_PATH', 'QWEN_BASE_URL', 'QWEN_OAUTH_BASE_URL'], + 'gemini-antigravity': ['PROJECT_ID', 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH', 'ANTIGRAVITY_BASE_URL_DAILY', 'ANTIGRAVITY_BASE_URL_AUTOPUSH'], + 'openai-iflow': ['IFLOW_OAUTH_CREDS_FILE_PATH', 'IFLOW_BASE_URL'], + 'openai-codex-oauth': ['CODEX_OAUTH_CREDS_FILE_PATH', 'CODEX_EMAIL', 'CODEX_BASE_URL'], + 'grok-custom': ['GROK_COOKIE_TOKEN', 'GROK_CF_CLEARANCE', 'GROK_USER_AGENT', 'GROK_BASE_URL'], + 'forward-api': ['FORWARD_API_KEY', 'FORWARD_BASE_URL', 'FORWARD_HEADER_NAME', 'FORWARD_HEADER_VALUE_PREFIX'] + }; - // 如果没有上下文类型,尝试从对象字段推断(回退逻辑) + // 尝试从全局或当前模态框上下文中推断提供商类型 + let providerType = currentProviderType; if (!providerType) { if (provider.OPENAI_API_KEY && provider.OPENAI_BASE_URL) { providerType = 'openai-custom'; @@ -710,9 +718,8 @@ function getFieldOrder(provider) { } } - // 直接从 utils.js 获取该类型的预定义字段列表(支持前缀匹配) - const predefinedFields = providerType ? getProviderTypeFields(providerType) : []; - const predefinedOrder = predefinedFields.map(f => f.id); + // 获取该类型应该具有的所有字段(预定义顺序) + const predefinedOrder = providerType ? (fieldOrderMap[providerType] || []) : []; // 获取当前对象中存在且不在预定义列表中的其他字段 const otherFields = Object.keys(provider).filter(key => @@ -727,8 +734,12 @@ function getFieldOrder(provider) { // 只有在字段确实存在于 provider 中,或者它是该提供商类型的预定义字段时才显示 return allExpectedFields.filter(key => - Object.prototype.hasOwnProperty.call(provider, key) || predefinedOrder.includes(key) + provider.hasOwnProperty(key) || predefinedOrder.includes(key) ); + + // 如果无法识别提供商类型,按字母顺序排序 + otherFields.sort(); + return [...orderedFields, ...otherFields].filter(key => provider.hasOwnProperty(key)); } /** diff --git a/static/app/models-manager.js b/static/app/models-manager.js index b976b13..25a5099 100644 --- a/static/app/models-manager.js +++ b/static/app/models-manager.js @@ -199,23 +199,10 @@ function getProviderDisplayName(providerType) { 'openaiResponses-custom': 'OpenAI Responses Custom', 'openai-qwen-oauth': 'Qwen (OAuth)', 'openai-iflow': 'iFlow', - 'openai-codex-oauth': 'OpenAI Codex (OAuth)', - 'grok-custom': 'Grok Reverse' + 'openai-codex-oauth': 'OpenAI Codex (OAuth)' }; - if (displayNames[providerType]) { - return displayNames[providerType]; - } - - // 尝试前缀匹配 - for (const baseType in displayNames) { - if (providerType.startsWith(baseType + '-')) { - const suffix = providerType.substring(baseType.length + 1); - return `${displayNames[baseType]} (${suffix})`; - } - } - - return providerType; + return displayNames[providerType] || providerType; } /** @@ -233,22 +220,13 @@ function getProviderIcon(providerType) { } } - const iconMap = { - 'gemini': 'fas fa-gem', - 'claude': 'fas fa-robot', - 'openai': 'fas fa-brain', - 'qwen': 'fas fa-brain', - 'iflow': 'fas fa-brain', - 'forward': 'fas fa-share-square', - 'grok': 'fas fa-search' - }; - - for (const key in iconMap) { - if (providerType.includes(key)) { - return iconMap[key]; - } + if (providerType.includes('gemini')) { + return 'fas fa-gem'; + } else if (providerType.includes('claude')) { + return 'fas fa-robot'; + } else if (providerType.includes('openai') || providerType.includes('qwen') || providerType.includes('iflow')) { + return 'fas fa-brain'; } - return 'fas fa-server'; } diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index 6238720..1a9b12f 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -1,7 +1,7 @@ // 提供商管理功能模块 import { providerStats, updateProviderStats } from './constants.js'; -import { showToast, formatUptime, getProviderConfigs, getBaseProviderConfigs } from './utils.js'; +import { showToast, formatUptime, getProviderConfigs } from './utils.js'; import { fileUploadHandler } from './file-upload.js'; import { t, getCurrentLanguage } from './i18n.js'; import { renderRoutingExamples } from './routing-examples.js'; @@ -178,37 +178,35 @@ function updateTimeDisplay() { } /** - * 加载提供商数据 - * @param {boolean} forceRefreshSupported - 是否强制刷新支持的提供商列表 + * 加载提供商列表 */ -async function loadProviders(forceRefreshSupported = false) { +async function loadProviders() { try { - // 获取合并后的数据(包括 providers 和 supportedProviders) - const data = await window.apiClient.get('/providers'); - if (!data || !data.providers) return; + const providers = await window.apiClient.get('/providers'); - const { providers, supportedProviders } = data; - - // 检查支持列表是否发生了变化(或者是否尚未初始化) - const isChanged = !cachedSupportedProviders || - supportedProviders.length !== cachedSupportedProviders.length || - supportedProviders.some((p, i) => p !== cachedSupportedProviders[i]); - - // 如果强制刷新或是对象类型(可能是由事件触发),则也视为需要刷新 - const shouldForce = forceRefreshSupported === true || (typeof forceRefreshSupported === 'object'); - - if (isChanged || shouldForce) { - cachedSupportedProviders = supportedProviders; + // 动态更新其他模块的提供商信息,只需更新一次 + if (!isStaticProviderConfigsUpdated) { + cachedSupportedProviders = await window.apiClient.get('/providers/supported'); const providerConfigs = getProviderConfigs(cachedSupportedProviders); - // 动态更新各个页面的提供商信息 - updateModelsProviderConfigs(providerConfigs); - updateTutorialProviderConfigs(providerConfigs); - updateUsageProviderConfigs(providerConfigs); - updateConfigProviderConfigs(providerConfigs); + // 动态更新凭据文件管理的提供商类型筛选项 updateProviderFilterOptions(providerConfigs); + + // 动态更新仪表盘页面的路径路由调用示例 renderRoutingExamples(providerConfigs); + // 动态更新仪表盘页面的可用模型列表提供商信息 + updateModelsProviderConfigs(providerConfigs); + + // 动态更新配置教程页面的提供商信息 + updateTutorialProviderConfigs(providerConfigs); + + // 动态更新用量查询页面的提供商信息 + updateUsageProviderConfigs(providerConfigs); + + // 动态更新配置管理页面的提供商选择标签 + updateConfigProviderConfigs(providerConfigs); + isStaticProviderConfigsUpdated = true; } @@ -321,7 +319,6 @@ function renderProviders(providers, supportedProviders = []) { ${displayName}
- ${generateAddGroupButton(providerType)} ${generateAuthButton(providerType)}
@@ -362,60 +359,6 @@ function renderProviders(providers, supportedProviders = []) { container.appendChild(providerDiv); - // 为添加分组按钮添加事件监听 - const addGroupBtn = providerDiv.querySelector('.add-group-btn'); - if (addGroupBtn) { - addGroupBtn.addEventListener('click', (e) => { - e.stopPropagation(); - - // 使用自定义的主题风格 Prompt - showSimplePrompt( - t('providers.addGroup.title'), - t('providers.addGroup.suffixPlaceholder'), - async (suffix) => { - const cleanSuffix = suffix.toLowerCase().replace(/[^a-z0-9]/g, ''); - if (!cleanSuffix) { - showToast(t('common.warning'), '请输入有效的后缀(仅限字母和数字)', 'warning'); - return; - } - - const newProviderType = `${providerType}-${cleanSuffix}`; - - // 显示加载状态 - addGroupBtn.disabled = true; - const originalHtml = addGroupBtn.innerHTML; - addGroupBtn.innerHTML = ''; - - try { - const response = await window.apiClient.post('/providers', { - providerType: newProviderType, - providerConfig: { - customName: cleanSuffix.toUpperCase(), - isHealthy: true, - isDisabled: false, - usageCount: 0, - errorCount: 0 - } - }); - - if (response.success) { - showToast(t('common.success'), t('providers.addGroup.success'), 'success'); - await loadProviders(true); - setTimeout(() => openProviderManager(newProviderType), 500); - } else { - throw new Error(response.error?.message || 'Unknown error'); - } - } catch (error) { - console.error('Failed to add provider group:', error); - showToast(t('common.error'), t('providers.addGroup.error') + ': ' + error.message, 'error'); - addGroupBtn.disabled = false; - addGroupBtn.innerHTML = originalHtml; - } - } - ); - }); - } - // 为授权按钮添加事件监听 const authBtn = providerDiv.querySelector('.generate-auth-btn'); if (authBtn) { @@ -543,74 +486,6 @@ function generateAuthButton(providerType) { `; } -/** - * 显示一个极简的主题风格输入框 - * @param {string} title - 标题 - * @param {string} placeholder - 占位符 - * @param {function} callback - 确认回调 - */ -function showSimplePrompt(title, placeholder, callback) { - const overlay = document.createElement('div'); - overlay.className = 'modal-overlay'; - overlay.style.display = 'flex'; - overlay.style.zIndex = '3000'; - overlay.style.background = 'rgba(0, 0, 0, 0.2)'; - overlay.style.backdropFilter = 'blur(2px)'; - - overlay.innerHTML = ` - - `; - - document.body.appendChild(overlay); - - const input = overlay.querySelector('#simple-prompt-input'); - const submitBtn = overlay.querySelector('#simple-prompt-submit'); - - input.focus(); - - const finish = () => { - const val = input.value.trim(); - if (val) { - overlay.remove(); - callback(val); - } - }; - - submitBtn.onclick = finish; - input.onkeydown = (e) => { - if (e.key === 'Enter') finish(); - if (e.key === 'Escape') overlay.remove(); - }; - overlay.onclick = (e) => { - if (e.target === overlay) overlay.remove(); - }; -} - -/** - * 生成添加分组按钮HTML - * @param {string} providerType - 提供商类型 - * @returns {string} 按钮HTML - */ -function generateAddGroupButton(providerType) { - const allowedTypes = ['claude-custom', 'openai-custom', 'openaiResponses-custom']; - if (!allowedTypes.includes(providerType)) { - return ''; - } - - return ` - - `; -} - /** * 处理生成授权链接 * @param {string} providerType - 提供商类型 @@ -3246,148 +3121,16 @@ async function restartServiceAfterUpdate() { } } -/** - * 显示添加提供商组模态框 - * @param {string} defaultBaseType - 默认的基础类型 - */ -function showAddProviderGroupModal(defaultBaseType = null) { - const modal = document.createElement('div'); - modal.className = 'modal-overlay'; - modal.style.display = 'flex'; - modal.style.zIndex = '2000'; - - // 获取所有基础母版配置,并过滤掉当前已经存在的“自定义组” - // 确保下拉菜单只显示纯净的基础类型(如 openai-custom),而不显示已有的带后缀组 - const allBaseConfigs = getBaseProviderConfigs(); - const baseTypes = allBaseConfigs.filter(config => { - // 1. 必须在后端支持的列表中 - const isSupported = cachedSupportedProviders.includes(config.id); - - // 2. 限制只能添加特定类型的配置组 (Claude Custom, OpenAI Custom, OpenAI Responses) - const allowedTypes = ['claude-custom', 'openai-custom', 'openaiResponses-custom']; - const isAllowed = allowedTypes.includes(config.id); - - return isSupported && isAllowed; - }); - - let optionsHtml = baseTypes.map(type => { - const selected = (defaultBaseType && type.id === defaultBaseType) ? 'selected' : ''; - return ``; - }).join(''); - - const selectedConfig = allBaseConfigs.find(c => c.id === defaultBaseType); - const baseTypeSectionHtml = defaultBaseType ? ` -
- -
- - ${selectedConfig?.name || defaultBaseType} -
- -
- ` : ` -
- - -
- `; - - modal.innerHTML = ` - - `; - - document.body.appendChild(modal); - - const closeBtn = modal.querySelector('.modal-close'); - const cancelBtn = modal.querySelector('.modal-cancel'); - const submitBtn = modal.querySelector('.modal-submit'); - const suffixInput = modal.querySelector('#groupSuffix'); - const baseTypeSelect = modal.querySelector('#groupBaseType'); - - const closeModal = () => modal.remove(); - - [closeBtn, cancelBtn].forEach(btn => btn.addEventListener('click', closeModal)); - - submitBtn.addEventListener('click', async () => { - const baseType = baseTypeSelect.value; - const suffix = suffixInput.value.trim().toLowerCase().replace(/[^a-z0-9]/g, ''); - - if (!suffix) { - showToast(t('common.warning'), '请输入有效的后缀(仅限字母和数字)', 'warning'); - return; - } - - const newProviderType = `${baseType}-${suffix}`; - - submitBtn.disabled = true; - submitBtn.innerHTML = ''; - - try { - // 创建一个带后缀的新提供商组,并添加一个初始的空配置(或者让用户在随后的模态框中添加) - // 这里我们先创建一个临时的空配置,这样组就会在 dashboard 中显示出来 - const response = await window.apiClient.post('/providers', { - providerType: newProviderType, - providerConfig: { - customName: suffix.toUpperCase(), - isHealthy: true, - isDisabled: false, - usageCount: 0, - errorCount: 0 - } - }); - - if (response.success) { - showToast(t('common.success'), t('providers.addGroup.success'), 'success'); - closeModal(); - // 重新加载提供商列表,强制刷新支持的类型 - await loadProviders(true); - // 自动打开新创建的组的管理界面 - setTimeout(() => openProviderManager(newProviderType), 500); - } else { - throw new Error(response.error?.message || 'Unknown error'); - } - } catch (error) { - console.error('Failed to add provider group:', error); - showToast(t('common.error'), t('providers.addGroup.error') + ': ' + error.message, 'error'); - submitBtn.disabled = false; - submitBtn.innerHTML = ` ${t('common.confirm')}`; - } - }); -} - export { loadSystemInfo, updateTimeDisplay, loadProviders, + renderProviders, + updateProviderStatsDisplay, openProviderManager, showAuthModal, executeGenerateAuthUrl, handleGenerateAuthUrl, checkUpdate, - performUpdate, - showAddProviderGroupModal + performUpdate }; diff --git a/static/app/routing-examples.js b/static/app/routing-examples.js index 4caf762..ed71b3b 100644 --- a/static/app/routing-examples.js +++ b/static/app/routing-examples.js @@ -453,17 +453,8 @@ function renderRoutingExamples(providerConfigs) { let routeInfo = routes.find(r => r.provider === config.id); - // 如果没找到,则创建一个默认的,并尝试继承基础类型的徽章 + // 如果没找到,则创建一个默认的 if (!routeInfo) { - // 尝试查找基础类型的路由信息以获取徽章 - let baseRouteInfo = null; - for (const r of routes) { - if (config.id.startsWith(r.provider + '-')) { - baseRouteInfo = r; - break; - } - } - routeInfo = { provider: config.id, name: config.name, @@ -471,35 +462,14 @@ function renderRoutingExamples(providerConfigs) { openai: `/${config.id}/v1/chat/completions`, claude: `/${config.id}/v1/messages` }, - description: baseRouteInfo ? baseRouteInfo.description : t('dashboard.routing.oauth'), - badge: baseRouteInfo ? baseRouteInfo.badge : t('dashboard.routing.oauth'), - badgeClass: baseRouteInfo ? baseRouteInfo.badgeClass : 'oauth' + description: t('dashboard.routing.oauth'), + badge: t('dashboard.routing.oauth'), + badgeClass: 'oauth' }; } - // 确定图标:尝试精确匹配,然后尝试前缀匹配 - let icon = iconMap[config.id]; - if (!icon) { - for (const baseId in iconMap) { - if (config.id.startsWith(baseId + '-')) { - icon = iconMap[baseId]; - break; - } - } - } - icon = icon || 'fa-route'; - - // 确定默认模型:尝试精确匹配,然后尝试前缀匹配 - let defaultModel = modelMap[config.id]; - if (!defaultModel) { - for (const baseId in modelMap) { - if (config.id.startsWith(baseId + '-')) { - defaultModel = modelMap[baseId]; - break; - } - } - } - defaultModel = defaultModel || 'default-model'; + const icon = iconMap[config.id] || 'fa-route'; + const defaultModel = modelMap[config.id] || 'default-model'; const hostname = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' ? `http://${window.location.host}` : `${window.location.protocol}//${window.location.host}`; diff --git a/static/app/utils.js b/static/app/utils.js index d43e29e..f796c45 100644 --- a/static/app/utils.js +++ b/static/app/utils.js @@ -7,115 +7,83 @@ import { apiClient } from './auth.js'; * @param {string[]} supportedProviders - 已注册的提供商类型列表 * @returns {Object[]} 提供商配置对象数组 */ -/** - * 获取所有基础提供商配置(母版) - * @returns {Object[]} 基础提供商配置数组 - */ -function getBaseProviderConfigs() { +function getProviderConfigs(supportedProviders = []) { return [ { id: 'forward-api', name: 'NewAPI', - icon: 'fa-share-square' + icon: 'fa-share-square', + visible: supportedProviders.includes('forward-api') }, { id: 'gemini-cli-oauth', name: t('dashboard.routing.nodeName.gemini'), icon: 'fa-robot', - defaultPath: 'configs/gemini/' + defaultPath: 'configs/gemini/', + visible: supportedProviders.includes('gemini-cli-oauth') }, { id: 'gemini-antigravity', name: t('dashboard.routing.nodeName.antigravity'), icon: 'fa-rocket', - defaultPath: 'configs/antigravity/' + defaultPath: 'configs/antigravity/', + visible: supportedProviders.includes('gemini-antigravity') }, { id: 'claude-kiro-oauth', name: t('dashboard.routing.nodeName.kiro'), icon: 'fa-key', - defaultPath: 'configs/kiro/' + defaultPath: 'configs/kiro/', + visible: supportedProviders.includes('claude-kiro-oauth') }, { id: 'openai-codex-oauth', name: t('dashboard.routing.nodeName.codex'), icon: 'fa-code', - defaultPath: 'configs/codex/' + defaultPath: 'configs/codex/', + visible: supportedProviders.includes('openai-codex-oauth') }, { id: 'openai-qwen-oauth', name: t('dashboard.routing.nodeName.qwen'), icon: 'fa-cloud', - defaultPath: 'configs/qwen/' + defaultPath: 'configs/qwen/', + visible: supportedProviders.includes('openai-qwen-oauth') }, { id: 'openai-iflow', name: t('dashboard.routing.nodeName.iflow'), icon: 'fa-stream', - defaultPath: 'configs/iflow/' + defaultPath: 'configs/iflow/', + visible: supportedProviders.includes('openai-iflow') }, { id: 'grok-custom', name: t('dashboard.routing.nodeName.grok'), - icon: 'fa-user-secret' + icon: 'fa-user-secret', + visible: supportedProviders.includes('grok-custom') }, { id: 'openai-custom', name: t('dashboard.routing.nodeName.openai'), - icon: 'fa-microchip' + icon: 'fa-microchip', + visible: supportedProviders.includes('openai-custom') }, { id: 'claude-custom', name: t('dashboard.routing.nodeName.claude'), - icon: 'fa-brain' + icon: 'fa-brain', + visible: supportedProviders.includes('claude-custom') }, { id: 'openaiResponses-custom', name: 'OpenAI Responses', - icon: 'fa-reply-all' + icon: 'fa-reply-all', + visible: supportedProviders.includes('openaiResponses-custom') }, ]; } -/** - * 获取所有支持的提供商配置列表 - * @param {string[]} supportedProviders - 已注册的提供商类型列表 - * @returns {Object[]} 提供商配置对象数组 - */ -function getProviderConfigs(supportedProviders = []) { - const baseConfigs = getBaseProviderConfigs(); - - const result = []; - const usedIds = new Set(); - - // 1. 处理 supportedProviders 中匹配基础配置的类型 - baseConfigs.forEach(config => { - const isSupported = supportedProviders.includes(config.id); - result.push({ ...config, visible: isSupported }); - usedIds.add(config.id); - }); - - // 2. 处理带有后缀的自定义类型 (例如 openai-custom-test) - supportedProviders.forEach(providerId => { - if (usedIds.has(providerId)) return; - - // 查找匹配的前缀 - const baseConfig = baseConfigs.find(bc => providerId.startsWith(bc.id + '-')); - if (baseConfig) { - const suffix = providerId.substring(baseConfig.id.length + 1); - result.push({ - ...baseConfig, - id: providerId, - name: `${baseConfig.name} (${suffix})`, - visible: true - }); - usedIds.add(providerId); - } - }); - - return result; -} - /** * 格式化运行时间 * @param {number} seconds - 秒数 @@ -229,7 +197,6 @@ function getFieldLabel(key) { * @returns {Array} 字段配置数组 */ function getProviderTypeFields(providerType) { - // 基础配置字段定义 const fieldConfigs = { 'openai-custom': [ { @@ -453,19 +420,7 @@ function getProviderTypeFields(providerType) { ] }; - // 1. 尝试精确匹配 - if (fieldConfigs[providerType]) { - return fieldConfigs[providerType]; - } - - // 2. 尝试匹配前缀 (例如 openai-custom-test -> openai-custom) - for (const baseType in fieldConfigs) { - if (providerType.startsWith(baseType + '-')) { - return fieldConfigs[baseType]; - } - } - - return []; + return fieldConfigs[providerType] || []; } /** @@ -506,7 +461,6 @@ export { getFieldLabel, getProviderTypeFields, getProviderConfigs, - getBaseProviderConfigs, getProviderStats, apiRequest }; \ No newline at end of file diff --git a/static/components/section-providers.css b/static/components/section-providers.css index f8304de..c14907d 100644 --- a/static/components/section-providers.css +++ b/static/components/section-providers.css @@ -1138,28 +1138,6 @@ transform: translateY(-1px); } -/* 添加分组按钮样式 */ -.add-group-btn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.25rem 0.75rem; - background: #ecfdf5; /* 绿色系背景 */ - color: #065f46; /* 绿色系文字 */ - border: none; - border-radius: 9999px; - font-size: 0.75rem; - font-weight: 500; - cursor: pointer; - transition: var(--transition); -} - -.add-group-btn:hover { - background: #10b981; - color: white; - transform: translateY(-1px); -} - /* 授权模态框样式 */ .modal-overlay { position: fixed; diff --git a/static/components/section-providers.html b/static/components/section-providers.html index 69b520a..93b481a 100644 --- a/static/components/section-providers.html +++ b/static/components/section-providers.html @@ -38,9 +38,6 @@
-
- -