diff --git a/app.js b/app.js index 4026252..c0d4cd4 100644 --- a/app.js +++ b/app.js @@ -2526,7 +2526,8 @@ class CLIProxyManager {

${i18n.t('ai_providers.gemini_add_modal_title')}

- + +

${i18n.t('ai_providers.gemini_add_modal_key_hint')}

@@ -2543,35 +2544,90 @@ class CLIProxyManager { // 添加Gemini密钥 async addGeminiKey() { - const newKey = document.getElementById('new-gemini-key').value.trim(); + const keyInput = document.getElementById('new-gemini-key'); const baseUrlInput = document.getElementById('new-gemini-url'); + if (!keyInput) { + return; + } + + const keys = keyInput.value + .split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0); const baseUrl = baseUrlInput ? baseUrlInput.value.trim() : ''; - if (!newKey) { - this.showNotification(i18n.t('notification.please_enter') + ' ' + i18n.t('notification.gemini_api_key'), 'error'); + if (keys.length === 0) { + this.showNotification(i18n.t('notification.gemini_multi_input_required'), 'error'); return; } try { const data = await this.makeRequest('/gemini-api-key'); - const currentKeys = data['gemini-api-key'] || []; - const newConfig = { 'api-key': newKey }; - if (baseUrl) { - newConfig['base-url'] = baseUrl; - } - currentKeys.push(newConfig); + let currentKeys = Array.isArray(data['gemini-api-key']) ? data['gemini-api-key'] : []; + const existingKeys = new Set(currentKeys.map(item => item && item['api-key']).filter(Boolean)); + const batchSeen = new Set(); - await this.makeRequest('/gemini-api-key', { - method: 'PUT', - body: JSON.stringify(currentKeys) - }); + let successCount = 0; + let skippedCount = 0; + let failedCount = 0; + + for (const apiKey of keys) { + if (!apiKey) { + continue; + } + + if (batchSeen.has(apiKey)) { + skippedCount++; + continue; + } + batchSeen.add(apiKey); + + if (existingKeys.has(apiKey)) { + skippedCount++; + continue; + } + + const newConfig = { 'api-key': apiKey }; + if (baseUrl) { + newConfig['base-url'] = baseUrl; + } else { + delete newConfig['base-url']; + } + + const nextKeys = [...currentKeys, newConfig]; + + try { + await this.makeRequest('/gemini-api-key', { + method: 'PUT', + body: JSON.stringify(nextKeys) + }); + currentKeys = nextKeys; + existingKeys.add(apiKey); + successCount++; + } catch (error) { + failedCount++; + console.error('Gemini key add failed:', error); + } + } this.clearCache(); // 清除缓存 this.closeModal(); this.loadGeminiKeys(); - this.showNotification(i18n.t('notification.gemini_key_added'), 'success'); + + if (successCount === 1 && skippedCount === 0 && failedCount === 0) { + this.showNotification(i18n.t('notification.gemini_key_added'), 'success'); + return; + } + + const summaryTemplate = i18n.t('notification.gemini_multi_summary'); + const summary = summaryTemplate + .replace('{success}', successCount) + .replace('{skipped}', skippedCount) + .replace('{failed}', failedCount); + const status = failedCount > 0 ? 'warning' : (successCount > 0 ? 'success' : 'info'); + this.showNotification(summary, status); } catch (error) { - this.showNotification(`${i18n.t('notification.add_failed')}: ${error.message}`, 'error'); + this.showNotification(`${i18n.t('notification.gemini_multi_failed')}: ${error.message}`, 'error'); } } diff --git a/i18n.js b/i18n.js index 5716a32..be29314 100644 --- a/i18n.js +++ b/i18n.js @@ -131,8 +131,9 @@ const i18n = { 'ai_providers.gemini_empty_desc': '点击上方按钮添加第一个密钥', 'ai_providers.gemini_item_title': 'Gemini密钥', 'ai_providers.gemini_add_modal_title': '添加Gemini API密钥', - 'ai_providers.gemini_add_modal_key_label': 'API密钥:', - 'ai_providers.gemini_add_modal_key_placeholder': '请输入Gemini API密钥', + 'ai_providers.gemini_add_modal_key_label': 'API密钥列表:', + 'ai_providers.gemini_add_modal_key_placeholder': '请输入Gemini API密钥(每行一个)', + 'ai_providers.gemini_add_modal_key_hint': '可一次粘贴多个密钥,每行一个。', 'ai_providers.gemini_add_modal_url_label': 'Base URL (可选):', 'ai_providers.gemini_add_modal_url_placeholder': '例如: https://generativelanguage.googleapis.com', 'ai_providers.gemini_edit_modal_title': '编辑Gemini API密钥', @@ -403,6 +404,9 @@ const i18n = { 'notification.gemini_key_added': 'Gemini密钥添加成功', 'notification.gemini_key_updated': 'Gemini密钥更新成功', 'notification.gemini_key_deleted': 'Gemini密钥删除成功', + 'notification.gemini_multi_input_required': '请先输入至少一个Gemini密钥', + 'notification.gemini_multi_failed': 'Gemini密钥批量添加失败', + 'notification.gemini_multi_summary': 'Gemini批量添加完成:成功 {success},跳过 {skipped},失败 {failed}', 'notification.codex_config_added': 'Codex配置添加成功', 'notification.codex_config_updated': 'Codex配置更新成功', 'notification.codex_config_deleted': 'Codex配置删除成功', @@ -580,8 +584,9 @@ const i18n = { 'ai_providers.gemini_empty_desc': 'Click the button above to add the first key', 'ai_providers.gemini_item_title': 'Gemini Key', 'ai_providers.gemini_add_modal_title': 'Add Gemini API Key', - 'ai_providers.gemini_add_modal_key_label': 'API Key:', - 'ai_providers.gemini_add_modal_key_placeholder': 'Please enter Gemini API key', + 'ai_providers.gemini_add_modal_key_label': 'API Keys:', + 'ai_providers.gemini_add_modal_key_placeholder': 'Enter Gemini API keys (one per line)', + 'ai_providers.gemini_add_modal_key_hint': 'You can paste multiple keys, one per line.', 'ai_providers.gemini_add_modal_url_label': 'Base URL (optional):', 'ai_providers.gemini_add_modal_url_placeholder': 'e.g. https://generativelanguage.googleapis.com', 'ai_providers.gemini_edit_modal_title': 'Edit Gemini API Key', @@ -851,6 +856,9 @@ const i18n = { 'notification.gemini_key_added': 'Gemini key added successfully', 'notification.gemini_key_updated': 'Gemini key updated successfully', 'notification.gemini_key_deleted': 'Gemini key deleted successfully', + 'notification.gemini_multi_input_required': 'Please enter at least one Gemini key', + 'notification.gemini_multi_failed': 'Gemini bulk add failed', + 'notification.gemini_multi_summary': 'Gemini bulk add finished: {success} added, {skipped} skipped, {failed} failed', 'notification.codex_config_added': 'Codex configuration added successfully', 'notification.codex_config_updated': 'Codex configuration updated successfully', 'notification.codex_config_deleted': 'Codex configuration deleted successfully',