diff --git a/config.json.example b/config.json.example index 8d484ef..4418014 100644 --- a/config.json.example +++ b/config.json.example @@ -23,5 +23,15 @@ "CRON_NEAR_MINUTES": 1, "CRON_REFRESH_TOKEN": false, "PROVIDER_POOLS_FILE_PATH": "provider_pools.json", - "MAX_ERROR_COUNT": 3 -} \ No newline at end of file + "MAX_ERROR_COUNT": 3, + "QWEN_BASE_URL": "https://portal.qwen.ai/v1", + "QWEN_OAUTH_BASE_URL": "https://chat.qwen.ai", + "GEMINI_BASE_URL": "https://cloudcode-pa.googleapis.com", + "ANTIGRAVITY_BASE_URL_DAILY": "https://daily-cloudcode-pa.sandbox.googleapis.com", + "ANTIGRAVITY_BASE_URL_AUTOPUSH": "https://autopush-cloudcode-pa.sandbox.googleapis.com", + "KIRO_REFRESH_URL": "https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken", + "KIRO_REFRESH_IDC_URL": "https://oidc.{{region}}.amazonaws.com/token", + "KIRO_BASE_URL": "https://codewhisperer.{{region}}.amazonaws.com/generateAssistantResponse", + "KIRO_AMAZON_Q_URL": "https://codewhisperer.{{region}}.amazonaws.com/SendMessageStreaming", + "KIRO_USAGE_LIMITS_URL": "https://q.{{region}}.amazonaws.com/getUsageLimits" + } \ No newline at end of file diff --git a/src/claude/claude-kiro.js b/src/claude/claude-kiro.js index 1813685..c539dca 100644 --- a/src/claude/claude-kiro.js +++ b/src/claude/claude-kiro.js @@ -451,10 +451,10 @@ async initializeAuth(forceRefresh = false) { this.region = 'us-east-1'; // Set default region } - this.refreshUrl = KIRO_CONSTANTS.REFRESH_URL.replace("{{region}}", this.region); - this.refreshIDCUrl = KIRO_CONSTANTS.REFRESH_IDC_URL.replace("{{region}}", this.region); - this.baseUrl = KIRO_CONSTANTS.BASE_URL.replace("{{region}}", this.region); - this.amazonQUrl = KIRO_CONSTANTS.AMAZON_Q_URL.replace("{{region}}", this.region); + this.refreshUrl = (this.config.KIRO_REFRESH_URL || KIRO_CONSTANTS.REFRESH_URL).replace("{{region}}", this.region); + this.refreshIDCUrl = (this.config.KIRO_REFRESH_IDC_URL || KIRO_CONSTANTS.REFRESH_IDC_URL).replace("{{region}}", this.region); + this.baseUrl = (this.config.KIRO_BASE_URL || KIRO_CONSTANTS.BASE_URL).replace("{{region}}", this.region); + this.amazonQUrl = (KIRO_CONSTANTS.AMAZON_Q_URL).replace("{{region}}", this.region); } catch (error) { console.warn(`[Kiro Auth] Error during credential loading: ${error.message}`); } diff --git a/src/config-manager.js b/src/config-manager.js index 8374cb4..91ad177 100644 --- a/src/config-manager.js +++ b/src/config-manager.js @@ -77,6 +77,15 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP KIRO_OAUTH_CREDS_FILE_PATH: null, QWEN_OAUTH_CREDS_FILE_PATH: null, PROJECT_ID: null, + // Provider URLs + QWEN_BASE_URL: null, + QWEN_OAUTH_BASE_URL: null, + GEMINI_BASE_URL: null, + ANTIGRAVITY_BASE_URL_DAILY: null, + ANTIGRAVITY_BASE_URL_AUTOPUSH: null, + KIRO_REFRESH_URL: null, + KIRO_REFRESH_IDC_URL: null, + KIRO_BASE_URL: null, SYSTEM_PROMPT_FILE_PATH: INPUT_SYSTEM_PROMPT_FILE, // Default value SYSTEM_PROMPT_MODE: 'append', PROMPT_LOG_BASE_NAME: "prompt_log", @@ -155,6 +164,48 @@ export async function initializeConfig(args = process.argv.slice(2), configFileP console.warn(`[Config Warning] --claude-base-url flag requires a value.`); } } + // Provider URL arguments + else if (args[i] === '--qwen-base-url') { + if (i + 1 < args.length) { + currentConfig.QWEN_BASE_URL = args[i + 1]; + i++; + } + } else if (args[i] === '--qwen-oauth-base-url') { + if (i + 1 < args.length) { + currentConfig.QWEN_OAUTH_BASE_URL = args[i + 1]; + i++; + } + } else if (args[i] === '--gemini-base-url') { + if (i + 1 < args.length) { + currentConfig.GEMINI_BASE_URL = args[i + 1]; + i++; + } + } else if (args[i] === '--antigravity-base-url-daily') { + if (i + 1 < args.length) { + currentConfig.ANTIGRAVITY_BASE_URL_DAILY = args[i + 1]; + i++; + } + } else if (args[i] === '--antigravity-base-url-autopush') { + if (i + 1 < args.length) { + currentConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH = args[i + 1]; + i++; + } + } else if (args[i] === '--kiro-refresh-url') { + if (i + 1 < args.length) { + currentConfig.KIRO_REFRESH_URL = args[i + 1]; + i++; + } + } else if (args[i] === '--kiro-refresh-idc-url') { + if (i + 1 < args.length) { + currentConfig.KIRO_REFRESH_IDC_URL = args[i + 1]; + i++; + } + } else if (args[i] === '--kiro-base-url') { + if (i + 1 < args.length) { + currentConfig.KIRO_BASE_URL = args[i + 1]; + i++; + } + } // Gemini-specific arguments else if (args[i] === '--gemini-oauth-creds-base64') { if (i + 1 < args.length) { diff --git a/src/gemini/antigravity-core.js b/src/gemini/antigravity-core.js index 112028e..2ec805d 100644 --- a/src/gemini/antigravity-core.js +++ b/src/gemini/antigravity-core.js @@ -29,8 +29,8 @@ const httpsAgent = new https.Agent({ const AUTH_REDIRECT_PORT = 8086; const CREDENTIALS_DIR = '.antigravity'; const CREDENTIALS_FILE = 'oauth_creds.json'; -const ANTIGRAVITY_BASE_URL_DAILY = 'https://daily-cloudcode-pa.sandbox.googleapis.com'; -const ANTIGRAVITY_BASE_URL_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com'; +const DEFAULT_ANTIGRAVITY_BASE_URL_DAILY = 'https://daily-cloudcode-pa.sandbox.googleapis.com'; +const DEFAULT_ANTIGRAVITY_BASE_URL_AUTOPUSH = 'https://autopush-cloudcode-pa.sandbox.googleapis.com'; const ANTIGRAVITY_API_VERSION = 'v1internal'; const OAUTH_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'; const OAUTH_CLIENT_SECRET = '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'; @@ -240,14 +240,18 @@ export class AntigravityApiService { this.config = config; this.host = config.HOST; this.oauthCredsFilePath = config.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH; - this.baseURL = ANTIGRAVITY_BASE_URL_DAILY; // 使用通用 GEMINI_BASE_URL 配置 + this.baseURL = DEFAULT_ANTIGRAVITY_BASE_URL_DAILY; // 使用通用 GEMINI_BASE_URL 配置 this.userAgent = DEFAULT_USER_AGENT; // 支持通用 USER_AGENT 配置 this.projectId = config.PROJECT_ID; + // Initialize instance-specific endpoints + this.baseUrlDaily = config.ANTIGRAVITY_BASE_URL_DAILY || DEFAULT_ANTIGRAVITY_BASE_URL_DAILY; + this.baseUrlAutopush = config.ANTIGRAVITY_BASE_URL_AUTOPUSH || DEFAULT_ANTIGRAVITY_BASE_URL_AUTOPUSH; + // 多环境降级顺序 this.baseURLs = this.baseURL ? [this.baseURL] : [ - ANTIGRAVITY_BASE_URL_DAILY, - ANTIGRAVITY_BASE_URL_AUTOPUSH + this.baseUrlDaily, + this.baseUrlAutopush // ANTIGRAVITY_BASE_URL_PROD // 生产环境已注释 ]; } diff --git a/src/gemini/gemini-core.js b/src/gemini/gemini-core.js index 1778e18..6ca6954 100644 --- a/src/gemini/gemini-core.js +++ b/src/gemini/gemini-core.js @@ -28,8 +28,8 @@ const httpsAgent = new https.Agent({ const AUTH_REDIRECT_PORT = 8085; const CREDENTIALS_DIR = '.gemini'; const CREDENTIALS_FILE = 'oauth_creds.json'; -const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com'; -const CODE_ASSIST_API_VERSION = 'v1internal'; +const DEFAULT_CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com'; +const DEFAULT_CODE_ASSIST_API_VERSION = 'v1internal'; const OAUTH_CLIENT_ID = '681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com'; const OAUTH_CLIENT_SECRET = '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'; const GEMINI_MODELS = getProviderModels('gemini-cli-oauth'); @@ -209,6 +209,9 @@ export class GeminiApiService { this.oauthCredsBase64 = config.GEMINI_OAUTH_CREDS_BASE64; this.oauthCredsFilePath = config.GEMINI_OAUTH_CREDS_FILE_PATH; this.projectId = config.PROJECT_ID; + + this.codeAssistEndpoint = config.GEMINI_BASE_URL || DEFAULT_CODE_ASSIST_ENDPOINT; + this.apiVersion = DEFAULT_CODE_ASSIST_API_VERSION; } async initialize() { @@ -409,7 +412,7 @@ export class GeminiApiService { try { const requestOptions = { - url: `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:${method}`, + url: `${this.codeAssistEndpoint}/${this.apiVersion}:${method}`, method: "POST", headers: { "Content-Type": "application/json" }, responseType: "json", @@ -453,7 +456,7 @@ export class GeminiApiService { try { const requestOptions = { - url: `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:${method}`, + url: `${this.codeAssistEndpoint}/${this.apiVersion}:${method}`, method: "POST", params: { alt: "sse" }, headers: { "Content-Type": "application/json" }, @@ -606,7 +609,7 @@ export class GeminiApiService { // 调用 retrieveUserQuota 接口获取用户配额信息 try { - const quotaURL = `${CODE_ASSIST_ENDPOINT}/${CODE_ASSIST_API_VERSION}:retrieveUserQuota`; + const quotaURL = `${this.codeAssistEndpoint}/${this.apiVersion}:retrieveUserQuota`; const requestBody = { project: `projects/${this.projectId}` }; diff --git a/src/openai/qwen-core.js b/src/openai/qwen-core.js index 7f344c6..e2beea7 100644 --- a/src/openai/qwen-core.js +++ b/src/openai/qwen-core.js @@ -30,16 +30,12 @@ const DEFAULT_LOCK_CONFIG = { attemptInterval: 200, }; -const QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai'; -const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/device/code`; -const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token`; +const DEFAULT_QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai'; +const DEFAULT_QWEN_BASE_URL = 'https://portal.qwen.ai/v1'; const QWEN_OAUTH_CLIENT_ID = 'f0304373b74a44d2b584a3fb70ca9e56'; const QWEN_OAUTH_SCOPE = 'openid profile email model.completion'; const QWEN_OAUTH_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code'; -// const DEFAULT_QWEN_BASE_URL = 'https://dashscope.aliyuncs.com/compatible-mode/v1'; -const DEFAULT_QWEN_BASE_URL = 'https://portal.qwen.ai/v1'; - export const QwenOAuth2Event = { AuthUri: 'auth-uri', AuthProgress: 'auth-progress', @@ -181,6 +177,13 @@ export class QwenApiService { this.currentAxiosInstance = null; this.tokenManagerOptions = { credentialFilePath: this._getQwenCachedCredentialPath() }; this.useSystemProxy = config?.USE_SYSTEM_PROXY_QWEN ?? false; + + // Initialize instance-specific endpoints + this.baseUrl = config.QWEN_BASE_URL || DEFAULT_QWEN_BASE_URL; + const oauthBaseUrl = config.QWEN_OAUTH_BASE_URL || DEFAULT_QWEN_OAUTH_BASE_URL; + this.oauthDeviceCodeEndpoint = `${oauthBaseUrl}/api/v1/oauth2/device/code`; + this.oauthTokenEndpoint = `${oauthBaseUrl}/api/v1/oauth2/token`; + console.log(`[Qwen] System proxy ${this.useSystemProxy ? 'enabled' : 'disabled'}`); this.qwenClient = new QwenOAuth2Client(config, this.useSystemProxy); } @@ -205,7 +208,7 @@ export class QwenApiService { }); const axiosConfig = { - baseURL: DEFAULT_QWEN_BASE_URL, + baseURL: this.baseUrl, httpAgent, httpsAgent, headers: { @@ -386,7 +389,7 @@ export class QwenApiService { } getCurrentEndpoint(resourceUrl) { - const baseEndpoint = resourceUrl || DEFAULT_QWEN_BASE_URL; + const baseEndpoint = resourceUrl || this.baseUrl; const suffix = '/v1'; const normalizedUrl = baseEndpoint.startsWith('http') ? @@ -887,7 +890,8 @@ class QwenOAuth2Client { client_id: QWEN_OAUTH_CLIENT_ID, }; try { - const response = await commonFetch(QWEN_OAUTH_TOKEN_ENDPOINT, { + const endpoint = this.oauthTokenEndpoint; + const response = await commonFetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' }, body: objectToUrlEncoded(bodyData), @@ -917,7 +921,8 @@ class QwenOAuth2Client { code_challenge_method: options.code_challenge_method, }; try { - const response = await commonFetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, { + const endpoint = this.oauthDeviceCodeEndpoint; + const response = await commonFetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' }, body: objectToUrlEncoded(bodyData), @@ -936,7 +941,8 @@ class QwenOAuth2Client { code_verifier: options.code_verifier, }; try { - const response = await commonFetch(QWEN_OAUTH_TOKEN_ENDPOINT, { + const endpoint = this.oauthTokenEndpoint; + const response = await commonFetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' }, body: objectToUrlEncoded(bodyData), diff --git a/src/provider-utils.js b/src/provider-utils.js index 380c207..e6edb51 100644 --- a/src/provider-utils.js +++ b/src/provider-utils.js @@ -19,7 +19,8 @@ export const PROVIDER_MAPPINGS = [ credPathKey: 'KIRO_OAUTH_CREDS_FILE_PATH', defaultCheckModel: 'claude-haiku-4-5', displayName: 'Claude Kiro OAuth', - needsProjectId: false + needsProjectId: false, + urlKeys: ['KIRO_BASE_URL', 'KIRO_REFRESH_URL', 'KIRO_REFRESH_IDC_URL'] }, { // Gemini CLI OAuth 配置 @@ -29,7 +30,8 @@ export const PROVIDER_MAPPINGS = [ credPathKey: 'GEMINI_OAUTH_CREDS_FILE_PATH', defaultCheckModel: 'gemini-2.5-flash', displayName: 'Gemini CLI OAuth', - needsProjectId: true + needsProjectId: true, + urlKeys: ['GEMINI_BASE_URL'] }, { // Qwen OAuth 配置 @@ -39,7 +41,8 @@ export const PROVIDER_MAPPINGS = [ credPathKey: 'QWEN_OAUTH_CREDS_FILE_PATH', defaultCheckModel: 'qwen3-coder-plus', displayName: 'Qwen OAuth', - needsProjectId: false + needsProjectId: false, + urlKeys: ['QWEN_BASE_URL', 'QWEN_OAUTH_BASE_URL'] }, { // Antigravity OAuth 配置 @@ -49,7 +52,8 @@ export const PROVIDER_MAPPINGS = [ credPathKey: 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH', defaultCheckModel: 'gemini-2.5-computer-use-preview-10-2025', displayName: 'Gemini Antigravity', - needsProjectId: true + needsProjectId: true, + urlKeys: ['ANTIGRAVITY_BASE_URL_DAILY', 'ANTIGRAVITY_BASE_URL_AUTOPUSH'] } ]; @@ -282,10 +286,11 @@ export async function isValidOAuthCredentials(filePath) { * @param {string} options.credPath - 凭据文件路径 * @param {string} options.defaultCheckModel - 默认检测模型 * @param {boolean} options.needsProjectId - 是否需要 PROJECT_ID + * @param {Array} options.urlKeys - 可选的 URL 配置项键名列表 * @returns {Object} 新的提供商配置对象 */ export function createProviderConfig(options) { - const { credPathKey, credPath, defaultCheckModel, needsProjectId } = options; + const { credPathKey, credPath, defaultCheckModel, needsProjectId, urlKeys } = options; const newProvider = { [credPathKey]: credPath, @@ -307,6 +312,13 @@ export function createProviderConfig(options) { if (needsProjectId) { newProvider.PROJECT_ID = ''; } + + // 初始化可选的 URL 配置项 + if (urlKeys && Array.isArray(urlKeys)) { + urlKeys.forEach(key => { + newProvider[key] = ''; + }); + } return newProvider; } diff --git a/src/ui-manager.js b/src/ui-manager.js index 50fcd33..3701e28 100644 --- a/src/ui-manager.js +++ b/src/ui-manager.js @@ -628,6 +628,16 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo if (newConfig.KIRO_OAUTH_CREDS_BASE64 !== undefined) currentConfig.KIRO_OAUTH_CREDS_BASE64 = newConfig.KIRO_OAUTH_CREDS_BASE64; if (newConfig.KIRO_OAUTH_CREDS_FILE_PATH !== undefined) currentConfig.KIRO_OAUTH_CREDS_FILE_PATH = newConfig.KIRO_OAUTH_CREDS_FILE_PATH; if (newConfig.QWEN_OAUTH_CREDS_FILE_PATH !== undefined) currentConfig.QWEN_OAUTH_CREDS_FILE_PATH = newConfig.QWEN_OAUTH_CREDS_FILE_PATH; + + // New Provider URLs + if (newConfig.QWEN_BASE_URL !== undefined) currentConfig.QWEN_BASE_URL = newConfig.QWEN_BASE_URL; + if (newConfig.QWEN_OAUTH_BASE_URL !== undefined) currentConfig.QWEN_OAUTH_BASE_URL = newConfig.QWEN_OAUTH_BASE_URL; + if (newConfig.GEMINI_BASE_URL !== undefined) currentConfig.GEMINI_BASE_URL = newConfig.GEMINI_BASE_URL; + if (newConfig.ANTIGRAVITY_BASE_URL_DAILY !== undefined) currentConfig.ANTIGRAVITY_BASE_URL_DAILY = newConfig.ANTIGRAVITY_BASE_URL_DAILY; + if (newConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH !== undefined) currentConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH = newConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH; + if (newConfig.KIRO_REFRESH_URL !== undefined) currentConfig.KIRO_REFRESH_URL = newConfig.KIRO_REFRESH_URL; + if (newConfig.KIRO_REFRESH_IDC_URL !== undefined) currentConfig.KIRO_REFRESH_IDC_URL = newConfig.KIRO_REFRESH_IDC_URL; + if (newConfig.KIRO_BASE_URL !== undefined) currentConfig.KIRO_BASE_URL = newConfig.KIRO_BASE_URL; if (newConfig.SYSTEM_PROMPT_FILE_PATH !== undefined) currentConfig.SYSTEM_PROMPT_FILE_PATH = newConfig.SYSTEM_PROMPT_FILE_PATH; if (newConfig.SYSTEM_PROMPT_MODE !== undefined) currentConfig.SYSTEM_PROMPT_MODE = newConfig.SYSTEM_PROMPT_MODE; if (newConfig.PROMPT_LOG_BASE_NAME !== undefined) currentConfig.PROMPT_LOG_BASE_NAME = newConfig.PROMPT_LOG_BASE_NAME; @@ -680,6 +690,17 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo KIRO_OAUTH_CREDS_BASE64: currentConfig.KIRO_OAUTH_CREDS_BASE64, KIRO_OAUTH_CREDS_FILE_PATH: currentConfig.KIRO_OAUTH_CREDS_FILE_PATH, QWEN_OAUTH_CREDS_FILE_PATH: currentConfig.QWEN_OAUTH_CREDS_FILE_PATH, + // Provider URLs + QWEN_BASE_URL: currentConfig.QWEN_BASE_URL, + QWEN_OAUTH_BASE_URL: currentConfig.QWEN_OAUTH_BASE_URL, + GEMINI_BASE_URL: currentConfig.GEMINI_BASE_URL, + ANTIGRAVITY_BASE_URL_DAILY: currentConfig.ANTIGRAVITY_BASE_URL_DAILY, + ANTIGRAVITY_BASE_URL_AUTOPUSH: currentConfig.ANTIGRAVITY_BASE_URL_AUTOPUSH, + KIRO_REFRESH_URL: currentConfig.KIRO_REFRESH_URL, + KIRO_REFRESH_IDC_URL: currentConfig.KIRO_REFRESH_IDC_URL, + KIRO_BASE_URL: currentConfig.KIRO_BASE_URL, + KIRO_AMAZON_Q_URL: currentConfig.KIRO_AMAZON_Q_URL, + KIRO_USAGE_LIMITS_URL: currentConfig.KIRO_USAGE_LIMITS_URL, SYSTEM_PROMPT_FILE_PATH: currentConfig.SYSTEM_PROMPT_FILE_PATH, SYSTEM_PROMPT_MODE: currentConfig.SYSTEM_PROMPT_MODE, PROMPT_LOG_BASE_NAME: currentConfig.PROMPT_LOG_BASE_NAME, diff --git a/static/app/config-manager.js b/static/app/config-manager.js index 27a9fd2..0e13cc9 100644 --- a/static/app/config-manager.js +++ b/static/app/config-manager.js @@ -32,6 +32,12 @@ async function loadConfiguration() { if (projectIdEl) projectIdEl.value = data.PROJECT_ID || ''; if (geminiOauthCredsBase64El) geminiOauthCredsBase64El.value = data.GEMINI_OAUTH_CREDS_BASE64 || ''; if (geminiOauthCredsFilePathEl) geminiOauthCredsFilePathEl.value = data.GEMINI_OAUTH_CREDS_FILE_PATH || ''; + const geminiBaseUrlEl = document.getElementById('geminiBaseUrl'); + if (geminiBaseUrlEl) geminiBaseUrlEl.value = data.GEMINI_BASE_URL || ''; + const antigravityBaseUrlDailyEl = document.getElementById('antigravityBaseUrlDaily'); + if (antigravityBaseUrlDailyEl) antigravityBaseUrlDailyEl.value = data.ANTIGRAVITY_BASE_URL_DAILY || ''; + const antigravityBaseUrlAutopushEl = document.getElementById('antigravityBaseUrlAutopush'); + if (antigravityBaseUrlAutopushEl) antigravityBaseUrlAutopushEl.value = data.ANTIGRAVITY_BASE_URL_AUTOPUSH || ''; // OpenAI Custom const openaiApiKeyEl = document.getElementById('openaiApiKey'); @@ -53,10 +59,20 @@ async function loadConfiguration() { if (kiroOauthCredsBase64El) kiroOauthCredsBase64El.value = data.KIRO_OAUTH_CREDS_BASE64 || ''; if (kiroOauthCredsFilePathEl) kiroOauthCredsFilePathEl.value = data.KIRO_OAUTH_CREDS_FILE_PATH || ''; + const kiroBaseUrlEl = document.getElementById('kiroBaseUrl'); + if (kiroBaseUrlEl) kiroBaseUrlEl.value = data.KIRO_BASE_URL || ''; + const kiroRefreshUrlEl = document.getElementById('kiroRefreshUrl'); + if (kiroRefreshUrlEl) kiroRefreshUrlEl.value = data.KIRO_REFRESH_URL || ''; + const kiroRefreshIdcUrlEl = document.getElementById('kiroRefreshIdcUrl'); + if (kiroRefreshIdcUrlEl) kiroRefreshIdcUrlEl.value = data.KIRO_REFRESH_IDC_URL || ''; // Qwen OAuth const qwenOauthCredsFilePathEl = document.getElementById('qwenOauthCredsFilePath'); if (qwenOauthCredsFilePathEl) qwenOauthCredsFilePathEl.value = data.QWEN_OAUTH_CREDS_FILE_PATH || ''; + const qwenBaseUrlEl = document.getElementById('qwenBaseUrl'); + if (qwenBaseUrlEl) qwenBaseUrlEl.value = data.QWEN_BASE_URL || ''; + const qwenOauthBaseUrlEl = document.getElementById('qwenOauthBaseUrl'); + if (qwenOauthBaseUrlEl) qwenOauthBaseUrlEl.value = data.QWEN_OAUTH_BASE_URL || ''; // OpenAI Responses const openaiResponsesApiKeyEl = document.getElementById('openaiResponsesApiKey'); @@ -150,6 +166,13 @@ async function saveConfiguration() { config.GEMINI_OAUTH_CREDS_BASE64 = null; config.GEMINI_OAUTH_CREDS_FILE_PATH = document.getElementById('geminiOauthCredsFilePath')?.value || ''; } + config.GEMINI_BASE_URL = document.getElementById('geminiBaseUrl')?.value || null; + break; + + case 'gemini-antigravity': + config.ANTIGRAVITY_BASE_URL_DAILY = document.getElementById('antigravityBaseUrlDaily')?.value || null; + config.ANTIGRAVITY_BASE_URL_AUTOPUSH = document.getElementById('antigravityBaseUrlAutopush')?.value || null; + config.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH = document.getElementById('antigravityOauthCredsFilePath')?.value || ''; break; case 'openai-custom': @@ -171,10 +194,15 @@ async function saveConfiguration() { config.KIRO_OAUTH_CREDS_BASE64 = null; config.KIRO_OAUTH_CREDS_FILE_PATH = document.getElementById('kiroOauthCredsFilePath')?.value || ''; } + config.KIRO_BASE_URL = document.getElementById('kiroBaseUrl')?.value || null; + config.KIRO_REFRESH_URL = document.getElementById('kiroRefreshUrl')?.value || null; + config.KIRO_REFRESH_IDC_URL = document.getElementById('kiroRefreshIdcUrl')?.value || null; break; case 'openai-qwen-oauth': config.QWEN_OAUTH_CREDS_FILE_PATH = document.getElementById('qwenOauthCredsFilePath')?.value || ''; + config.QWEN_BASE_URL = document.getElementById('qwenBaseUrl')?.value || null; + config.QWEN_OAUTH_BASE_URL = document.getElementById('qwenOauthBaseUrl')?.value || null; break; case 'openaiResponses-custom': diff --git a/static/app/modal.js b/static/app/modal.js index f5ef42e..905b9d6 100644 --- a/static/app/modal.js +++ b/static/app/modal.js @@ -435,7 +435,10 @@ function renderProviderList(providers) { * @returns {string} HTML字符串 */ function renderProviderConfig(provider) { - // 获取字段映射,确保顺序一致 + // 获取该提供商类型的所有字段定义(从 utils.js) + const fieldConfigs = getProviderTypeFields(currentProviderType); + + // 获取字段显示顺序 const fieldOrder = getFieldOrder(provider); // 先渲染基础配置字段(customName、checkModelName 和 checkHealth) @@ -447,6 +450,10 @@ function renderProviderConfig(provider) { const value = provider[fieldKey]; const displayValue = value || ''; + // 查找字段定义以获取 placeholder + const fieldDef = fieldConfigs.find(f => f.id === fieldKey) || fieldConfigs.find(f => f.id.toUpperCase() === fieldKey.toUpperCase()) || {}; + const placeholder = fieldDef.placeholder || (fieldKey === 'customName' ? '节点自定义名称' : (fieldKey === 'checkModelName' ? '例如: gpt-3.5-turbo' : '')); + // 如果是 customName 字段,使用普通文本输入框 if (fieldKey === 'customName') { html += ` @@ -457,7 +464,7 @@ function renderProviderConfig(provider) { readonly data-config-key="${fieldKey}" data-config-value="${value || ''}" - placeholder="节点自定义名称"> + placeholder="${placeholder}"> `; } else if (fieldKey === 'checkHealth') { @@ -485,7 +492,8 @@ function renderProviderConfig(provider) { value="${displayValue}" readonly data-config-key="${fieldKey}" - data-config-value="${value || ''}"> + data-config-value="${value || ''}" + placeholder="${placeholder}"> `; } @@ -504,6 +512,7 @@ function renderProviderConfig(provider) { const field1IsPassword = field1Key.toLowerCase().includes('key') || field1Key.toLowerCase().includes('password'); const field1IsOAuthFilePath = field1Key.includes('OAUTH_CREDS_FILE_PATH'); const field1DisplayValue = field1IsPassword && field1Value ? '••••••••' : (field1Value || ''); + const field1Def = fieldConfigs.find(f => f.id === field1Key) || fieldConfigs.find(f => f.id.toUpperCase() === field1Key.toUpperCase()) || {}; if (field1IsPassword) { html += ` @@ -514,8 +523,9 @@ function renderProviderConfig(provider) { value="${field1DisplayValue}" readonly data-config-key="${field1Key}" - data-config-value="${field1Value || ''}"> - @@ -532,8 +542,9 @@ function renderProviderConfig(provider) { value="${field1Value || ''}" readonly data-config-key="${field1Key}" - data-config-value="${field1Value || ''}"> - @@ -547,7 +558,8 @@ function renderProviderConfig(provider) { value="${field1DisplayValue}" readonly data-config-key="${field1Key}" - data-config-value="${field1Value || ''}"> + data-config-value="${field1Value || ''}" + placeholder="${field1Def.placeholder || ''}"> `; } @@ -560,6 +572,7 @@ function renderProviderConfig(provider) { const field2IsPassword = field2Key.toLowerCase().includes('key') || field2Key.toLowerCase().includes('password'); const field2IsOAuthFilePath = field2Key.includes('OAUTH_CREDS_FILE_PATH'); const field2DisplayValue = field2IsPassword && field2Value ? '••••••••' : (field2Value || ''); + const field2Def = fieldConfigs.find(f => f.id === field2Key) || fieldConfigs.find(f => f.id.toUpperCase() === field2Key.toUpperCase()) || {}; if (field2IsPassword) { html += ` @@ -570,7 +583,8 @@ function renderProviderConfig(provider) { value="${field2DisplayValue}" readonly data-config-key="${field2Key}" - data-config-value="${field2Value || ''}"> + data-config-value="${field2Value || ''}" + placeholder="${field2Def.placeholder || ''}"> @@ -588,7 +602,8 @@ function renderProviderConfig(provider) { value="${field2Value || ''}" readonly data-config-key="${field2Key}" - data-config-value="${field2Value || ''}"> + data-config-value="${field2Value || ''}" + placeholder="${field2Def.placeholder || ''}"> @@ -603,7 +618,8 @@ function renderProviderConfig(provider) { value="${field2DisplayValue}" readonly data-config-key="${field2Key}" - data-config-value="${field2Value || ''}"> + data-config-value="${field2Value || ''}" + placeholder="${field2Def.placeholder || ''}"> `; } @@ -652,54 +668,48 @@ function getFieldOrder(provider) { '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'], - 'claude-kiro-oauth': ['KIRO_OAUTH_CREDS_FILE_PATH'], - 'openai-qwen-oauth': ['QWEN_OAUTH_CREDS_FILE_PATH'], - 'gemini-antigravity': ['PROJECT_ID', 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH'] + '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'], + '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'] }; - // 获取所有其他配置项 + // 尝试从全局或当前模态框上下文中推断提供商类型 + let providerType = currentProviderType; + if (!providerType) { + if (provider.OPENAI_API_KEY && provider.OPENAI_BASE_URL) { + providerType = 'openai-custom'; + } else if (provider.CLAUDE_API_KEY && provider.CLAUDE_BASE_URL) { + providerType = 'claude-custom'; + } else if (provider.GEMINI_OAUTH_CREDS_FILE_PATH) { + providerType = 'gemini-cli-oauth'; + } else if (provider.KIRO_OAUTH_CREDS_FILE_PATH) { + providerType = 'claude-kiro-oauth'; + } else if (provider.QWEN_OAUTH_CREDS_FILE_PATH) { + providerType = 'openai-qwen-oauth'; + } else if (provider.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH) { + providerType = 'gemini-antigravity'; + } + } + + // 获取该类型应该具有的所有字段(预定义顺序) + const predefinedOrder = providerType ? (fieldOrderMap[providerType] || []) : []; + + // 获取当前对象中存在且不在预定义列表中的其他字段 const otherFields = Object.keys(provider).filter(key => - !excludedFields.includes(key) && !orderedFields.includes(key) + !excludedFields.includes(key) && + !orderedFields.includes(key) && + !predefinedOrder.includes(key) ); + otherFields.sort(); + + // 合并所有要显示的字段 + const allExpectedFields = [...orderedFields, ...predefinedOrder, ...otherFields]; - // 尝试从 provider 中推断提供商类型 - let providerType = null; - if (provider.OPENAI_API_KEY && provider.OPENAI_BASE_URL) { - providerType = 'openai-custom'; // 或 openaiResponses-custom,顺序相同 - } else if (provider.CLAUDE_API_KEY && provider.CLAUDE_BASE_URL) { - providerType = 'claude-custom'; - } else if (provider.GEMINI_OAUTH_CREDS_FILE_PATH) { - providerType = 'gemini-cli-oauth'; - } else if (provider.KIRO_OAUTH_CREDS_FILE_PATH) { - providerType = 'claude-kiro-oauth'; - } else if (provider.QWEN_OAUTH_CREDS_FILE_PATH) { - providerType = 'openai-qwen-oauth'; - } else if (provider.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH) { - providerType = 'gemini-antigravity'; - } - - // 如果能识别提供商类型,使用预定义的顺序 - if (providerType && fieldOrderMap[providerType]) { - const predefinedOrder = fieldOrderMap[providerType]; - const orderedOtherFields = []; - const remainingFields = [...otherFields]; - - // 先按预定义顺序添加字段 - predefinedOrder.forEach(fieldKey => { - const index = remainingFields.indexOf(fieldKey); - if (index !== -1) { - orderedOtherFields.push(fieldKey); - remainingFields.splice(index, 1); - } - }); - - // 剩余字段按字母顺序添加 - remainingFields.sort(); - orderedOtherFields.push(...remainingFields); - - return [...orderedFields, ...orderedOtherFields].filter(key => provider.hasOwnProperty(key)); - } + // 只有在字段确实存在于 provider 中,或者它是该提供商类型的预定义字段时才显示 + return allExpectedFields.filter(key => + provider.hasOwnProperty(key) || predefinedOrder.includes(key) + ); // 如果无法识别提供商类型,按字母顺序排序 otherFields.sort(); @@ -1071,16 +1081,21 @@ function showAddProviderForm(providerType) { function addDynamicConfigFields(form, providerType) { const configFields = form.querySelector('#dynamicConfigFields'); - // 获取该提供商类型的字段配置 - const providerFields = getProviderTypeFields(providerType); + // 获取该提供商类型的字段配置(已经在 utils.js 中包含了 URL 字段) + const allFields = getProviderTypeFields(providerType); + + // 过滤掉已经在 form-grid 中硬编码显示的三个基础字段,避免重复 + const baseFields = ['customName', 'checkModelName', 'checkHealth']; + const filteredFields = allFields.filter(f => !baseFields.some(bf => f.id.toLowerCase().includes(bf.toLowerCase()))); + let fields = ''; - if (providerFields.length > 0) { + if (filteredFields.length > 0) { // 分组显示,每行两个字段 - for (let i = 0; i < providerFields.length; i += 2) { + for (let i = 0; i < filteredFields.length; i += 2) { fields += '
'; - const field1 = providerFields[i]; + const field1 = filteredFields[i]; // 检查是否为密码类型字段 const isPassword1 = field1.type === 'password'; // 检查是否为OAuth凭据文件路径字段 @@ -1122,7 +1137,7 @@ function addDynamicConfigFields(form, providerType) { `; } - const field2 = providerFields[i + 1]; + const field2 = filteredFields[i + 1]; if (field2) { // 检查是否为密码类型字段 const isPassword2 = field2.type === 'password'; @@ -1215,35 +1230,14 @@ async function addProvider(providerType) { checkHealth }; - // 根据提供商类型收集配置 - switch (providerType) { - case 'openai-custom': - providerConfig.OPENAI_API_KEY = document.getElementById('newOpenaiApiKey')?.value || ''; - providerConfig.OPENAI_BASE_URL = document.getElementById('newOpenaiBaseUrl')?.value || ''; - break; - case 'openaiResponses-custom': - providerConfig.OPENAI_API_KEY = document.getElementById('newOpenaiApiKey')?.value || ''; - providerConfig.OPENAI_BASE_URL = document.getElementById('newOpenaiBaseUrl')?.value || ''; - break; - case 'claude-custom': - providerConfig.CLAUDE_API_KEY = document.getElementById('newClaudeApiKey')?.value || ''; - providerConfig.CLAUDE_BASE_URL = document.getElementById('newClaudeBaseUrl')?.value || ''; - break; - case 'gemini-cli-oauth': - providerConfig.PROJECT_ID = document.getElementById('newProjectId')?.value || ''; - providerConfig.GEMINI_OAUTH_CREDS_FILE_PATH = document.getElementById('newGeminiOauthCredsFilePath')?.value || ''; - break; - case 'claude-kiro-oauth': - providerConfig.KIRO_OAUTH_CREDS_FILE_PATH = document.getElementById('newKiroOauthCredsFilePath')?.value || ''; - break; - case 'openai-qwen-oauth': - providerConfig.QWEN_OAUTH_CREDS_FILE_PATH = document.getElementById('newQwenOauthCredsFilePath')?.value || ''; - break; - case 'gemini-antigravity': - providerConfig.PROJECT_ID = document.getElementById('newProjectId')?.value || ''; - providerConfig.ANTIGRAVITY_OAUTH_CREDS_FILE_PATH = document.getElementById('newAntigravityOauthCredsFilePath')?.value || ''; - break; - } + // 根据提供商类型动态收集配置字段(自动匹配 utils.js 中的定义) + const allFields = getProviderTypeFields(providerType); + allFields.forEach(field => { + const element = document.getElementById(`new${field.id}`); + if (element) { + providerConfig[field.id] = element.value || ''; + } + }); try { await window.apiClient.post('/providers', { diff --git a/static/app/provider-manager.js b/static/app/provider-manager.js index ec8f400..3eb0075 100644 --- a/static/app/provider-manager.js +++ b/static/app/provider-manager.js @@ -488,8 +488,104 @@ function showAuthModal(authUrl, authInfo) { // 在浏览器中打开按钮 const openBtn = modal.querySelector('.open-auth-btn'); openBtn.addEventListener('click', () => { - window.open(authUrl, '_blank'); - showToast('已在新标签页中打开授权页面', 'success'); + // 使用子窗口打开,以便监听 URL 变化 + const width = 600; + const height = 700; + const left = (window.screen.width - width) / 2; + const top = (window.screen.height - height) / 2; + + const authWindow = window.open( + authUrl, + 'OAuthAuthWindow', + `width=${width},height=${height},left=${left},top=${top},status=no,resizable=yes,scrollbars=yes` + ); + + if (authWindow) { + showToast('已打开授权窗口,请在窗口中完成操作', 'info'); + + // 添加手动输入回调 URL 的 UI + const urlSection = modal.querySelector('.auth-url-section'); + const manualInputHtml = ` +
+

自动监听受阻?

+

如果授权窗口重定向后显示“无法访问”,请将该窗口地址栏的 完整 URL 粘贴到下方:

+
+ + +
+
+ `; + urlSection.insertAdjacentHTML('afterend', manualInputHtml); + + const manualInput = modal.querySelector('.manual-callback-input'); + const applyBtn = modal.querySelector('.apply-callback-btn'); + + // 处理回调 URL 的核心逻辑 + const processCallback = (urlStr) => { + try { + // 尝试清理 URL(有些用户可能会复制多余的文字) + const cleanUrlStr = urlStr.trim().match(/https?:\/\/[^\s]+/)?.[0] || urlStr.trim(); + const url = new URL(cleanUrlStr); + + if (url.searchParams.has('code') || url.searchParams.has('token')) { + clearInterval(pollTimer); + // 构造本地可处理的 URL,只修改 hostname,保持原始 URL 的端口号不变 + const localUrl = new URL(url.href); + localUrl.hostname = window.location.hostname; + localUrl.protocol = window.location.protocol; + + showToast('正在完成授权...', 'info'); + + // 优先在子窗口中跳转(如果没关) + if (authWindow && !authWindow.closed) { + authWindow.location.href = localUrl.href; + } else { + // 备选方案:通过隐藏 iframe 或者是 fetch + const img = new Image(); + img.src = localUrl.href; + } + + setTimeout(() => { + showToast('授权完成!', 'success'); + if (authWindow && !authWindow.closed) authWindow.close(); + modal.remove(); + // 刷新列表以显示新状态 + loadProviders(); + }, 2500); + } else { + showToast('该 URL 似乎不包含有效的授权代码', 'warning'); + } + } catch (err) { + console.error('处理回调失败:', err); + showToast('无效的 URL 格式', 'error'); + } + }; + + applyBtn.addEventListener('click', () => { + processCallback(manualInput.value); + }); + + // 启动定时器轮询子窗口 URL + const pollTimer = setInterval(() => { + try { + if (authWindow.closed) { + clearInterval(pollTimer); + return; + } + // 如果能读到说明回到了同域 + const currentUrl = authWindow.location.href; + if (currentUrl && (currentUrl.includes('code=') || currentUrl.includes('token='))) { + processCallback(currentUrl); + } + } catch (e) { + // 跨域受限是正常的 + } + }, 1000); + } else { + showToast('授权窗口被浏览器拦截,请允许弹出窗口', 'error'); + } }); // 点击遮罩层关闭 diff --git a/static/app/styles.css b/static/app/styles.css index cecb67c..0cb36c0 100644 --- a/static/app/styles.css +++ b/static/app/styles.css @@ -309,11 +309,12 @@ body { color: var(--text-primary); } -.form-group label .optional-mark { +.optional-tag, .form-group label .optional-mark { font-size: 0.75rem; color: var(--text-secondary); font-weight: 400; margin-left: 0.25rem; + opacity: 0.8; } .form-control { diff --git a/static/app/utils.js b/static/app/utils.js index 5bb0c2f..e7846f0 100644 --- a/static/app/utils.js +++ b/static/app/utils.js @@ -65,7 +65,14 @@ function getFieldLabel(key) { 'GEMINI_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径', 'KIRO_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径', 'QWEN_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径', - 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径' + 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH': 'OAuth凭据文件路径', + 'GEMINI_BASE_URL': 'Gemini Base URL', + 'KIRO_BASE_URL': 'Base URL', + 'KIRO_REFRESH_URL': 'Refresh URL', + 'QWEN_BASE_URL': 'Qwen Base URL', + 'QWEN_OAUTH_BASE_URL': 'OAuth Base URL', + 'ANTIGRAVITY_BASE_URL_DAILY': 'Daily Base URL', + 'ANTIGRAVITY_BASE_URL_AUTOPUSH': 'Autopush Base URL' }; return labelMap[key] || key; @@ -80,88 +87,136 @@ function getProviderTypeFields(providerType) { const fieldConfigs = { 'openai-custom': [ { - id: 'OpenaiApiKey', + id: 'OPENAI_API_KEY', label: 'OpenAI API Key', type: 'password', placeholder: 'sk-...' }, { - id: 'OpenaiBaseUrl', + id: 'OPENAI_BASE_URL', label: 'OpenAI Base URL', type: 'text', - value: 'https://api.openai.com/v1' + placeholder: 'https://api.openai.com/v1' } ], 'openaiResponses-custom': [ { - id: 'OpenaiApiKey', + id: 'OPENAI_API_KEY', label: 'OpenAI API Key', type: 'password', placeholder: 'sk-...' }, { - id: 'OpenaiBaseUrl', + id: 'OPENAI_BASE_URL', label: 'OpenAI Base URL', type: 'text', - value: 'https://api.openai.com/v1' + placeholder: 'https://api.openai.com/v1' } ], 'claude-custom': [ { - id: 'ClaudeApiKey', + id: 'CLAUDE_API_KEY', label: 'Claude API Key', type: 'password', placeholder: 'sk-ant-...' }, { - id: 'ClaudeBaseUrl', + id: 'CLAUDE_BASE_URL', label: 'Claude Base URL', type: 'text', - value: 'https://api.anthropic.com' + placeholder: 'https://api.anthropic.com' } ], 'gemini-cli-oauth': [ { - id: 'ProjectId', + id: 'PROJECT_ID', label: '项目ID', type: 'text', placeholder: 'Google Cloud项目ID' }, { - id: 'GeminiOauthCredsFilePath', + id: 'GEMINI_OAUTH_CREDS_FILE_PATH', label: 'OAuth凭据文件路径', type: 'text', placeholder: '例如: ~/.gemini/oauth_creds.json' + }, + { + id: 'GEMINI_BASE_URL', + label: 'Gemini Base URL (选填)', + type: 'text', + placeholder: 'https://cloudcode-pa.googleapis.com' } ], 'claude-kiro-oauth': [ { - id: 'KiroOauthCredsFilePath', + id: 'KIRO_OAUTH_CREDS_FILE_PATH', label: 'OAuth凭据文件路径', type: 'text', placeholder: '例如: ~/.aws/sso/cache/kiro-auth-token.json' + }, + { + id: 'KIRO_BASE_URL', + label: 'Base URL (选填)', + type: 'text', + placeholder: 'https://codewhisperer.{{region}}.amazonaws.com/generateAssistantResponse' + }, + { + id: 'KIRO_REFRESH_URL', + label: 'Refresh URL (选填)', + type: 'text', + placeholder: 'https://prod.{{region}}.auth.desktop.kiro.dev/refreshToken' + }, + { + id: 'KIRO_REFRESH_IDC_URL', + label: 'Refresh IDC URL (选填)', + type: 'text', + placeholder: 'https://oidc.{{region}}.amazonaws.com/token' } ], 'openai-qwen-oauth': [ { - id: 'QwenOauthCredsFilePath', + id: 'QWEN_OAUTH_CREDS_FILE_PATH', label: 'OAuth凭据文件路径', type: 'text', placeholder: '例如: ~/.qwen/oauth_creds.json' + }, + { + id: 'QWEN_BASE_URL', + label: 'Qwen Base URL (选填)', + type: 'text', + placeholder: 'https://portal.qwen.ai/v1' + }, + { + id: 'QWEN_OAUTH_BASE_URL', + label: 'OAuth Base URL (选填)', + type: 'text', + placeholder: 'https://chat.qwen.ai' } ], 'gemini-antigravity': [ { - id: 'ProjectId', + id: 'PROJECT_ID', label: '项目ID (选填)', type: 'text', placeholder: 'Google Cloud项目ID (留空自动发现)' }, { - id: 'AntigravityOauthCredsFilePath', + id: 'ANTIGRAVITY_OAUTH_CREDS_FILE_PATH', label: 'OAuth凭据文件路径', type: 'text', placeholder: '例如: ~/.antigravity/oauth_creds.json' + }, + { + id: 'ANTIGRAVITY_BASE_URL_DAILY', + label: 'Daily Base URL (选填)', + type: 'text', + placeholder: 'https://daily-cloudcode-pa.sandbox.googleapis.com' + }, + { + id: 'ANTIGRAVITY_BASE_URL_AUTOPUSH', + label: 'Autopush Base URL (选填)', + type: 'text', + placeholder: 'https://autopush-cloudcode-pa.sandbox.googleapis.com' } ] }; diff --git a/static/index.html b/static/index.html index 71760d0..ae89b19 100644 --- a/static/index.html +++ b/static/index.html @@ -496,7 +496,11 @@
- + + +
+
+
@@ -529,6 +533,14 @@