From 7420c88a6f6197cc1baba27ab3e89eff5362e4a2 Mon Sep 17 00:00:00 2001 From: hex2077 Date: Mon, 1 Dec 2025 12:44:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=AE=A4=E8=AF=81=E6=B5=81=E7=A8=8B):=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8F=E8=A7=88=E5=99=A8=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=89=93=E5=BC=80=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E6=8E=88=E6=9D=83=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加自动打开浏览器功能到Gemini和Antigravity认证流程,当自动打开失败时显示备用提示信息 在提供商管理界面添加重置健康状态功能 调整CSS布局增加元素间距 --- src/gemini/antigravity-core.js | 25 ++++++++++-- src/gemini/gemini-core.js | 25 ++++++++++-- src/ui-manager.js | 74 ++++++++++++++++++++++++++++++++++ static/app/modal.js | 41 ++++++++++++++++++- static/app/styles.css | 1 + 5 files changed, 159 insertions(+), 7 deletions(-) diff --git a/src/gemini/antigravity-core.js b/src/gemini/antigravity-core.js index de84e6a..93f317e 100644 --- a/src/gemini/antigravity-core.js +++ b/src/gemini/antigravity-core.js @@ -5,6 +5,7 @@ import * as path from 'path'; import * as os from 'os'; import * as readline from 'readline'; import { v4 as uuidv4 } from 'uuid'; +import open from 'open'; import { API_ACTIONS, formatExpiryTime } from '../common.js'; import { getProviderModels } from '../provider-models.js'; @@ -293,14 +294,32 @@ export class AntigravityApiService { const redirectUri = `http://${host}:${AUTH_REDIRECT_PORT}`; this.authClient.redirectUri = redirectUri; - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { const authUrl = this.authClient.generateAuthUrl({ access_type: 'offline', scope: ['https://www.googleapis.com/auth/cloud-platform'] }); - console.log('\n[Antigravity Auth] Please open this URL in your browser to authenticate:'); - console.log(authUrl, '\n'); + console.log('\n[Antigravity Auth] 正在自动打开浏览器进行授权...'); + console.log('[Antigravity Auth] 授权链接:', authUrl, '\n'); + + // 自动打开浏览器 + const showFallbackMessage = () => { + console.log('[Antigravity Auth] 无法自动打开浏览器,请手动复制上面的链接到浏览器中打开'); + }; + + if (this.config) { + try { + const childProcess = await open(authUrl); + if (childProcess) { + childProcess.on('error', () => showFallbackMessage()); + } + } catch (_err) { + showFallbackMessage(); + } + } else { + showFallbackMessage(); + } const server = http.createServer(async (req, res) => { try { diff --git a/src/gemini/gemini-core.js b/src/gemini/gemini-core.js index 1a0acd8..da1dc80 100644 --- a/src/gemini/gemini-core.js +++ b/src/gemini/gemini-core.js @@ -4,6 +4,7 @@ import { promises as fs } from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as readline from 'readline'; +import open from 'open'; import { API_ACTIONS, formatExpiryTime } from '../common.js'; import { getProviderModels } from '../provider-models.js'; @@ -257,10 +258,28 @@ export class GeminiApiService { } const redirectUri = `http://${host}:${AUTH_REDIRECT_PORT}`; this.authClient.redirectUri = redirectUri; - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { const authUrl = this.authClient.generateAuthUrl({ access_type: 'offline', scope: ['https://www.googleapis.com/auth/cloud-platform'] }); - console.log('\n[Gemini Auth] Please open this URL in your browser to authenticate:'); - console.log(authUrl, '\n'); + console.log('\n[Gemini Auth] 正在自动打开浏览器进行授权...'); + + // 自动打开浏览器 + const showFallbackMessage = () => { + console.log('[Gemini Auth] 无法自动打开浏览器,请手动复制上面的链接到浏览器中打开'); + }; + + if (this.config) { + try { + const childProcess = await open(authUrl); + if (childProcess) { + childProcess.on('error', () => showFallbackMessage()); + } + } catch (_err) { + showFallbackMessage(); + } + } else { + showFallbackMessage(); + } + console.log('[Gemini Auth] 授权链接:', authUrl, '\n'); const server = http.createServer(async (req, res) => { try { const url = new URL(req.url, redirectUri); diff --git a/src/ui-manager.js b/src/ui-manager.js index 44a7536..e680593 100644 --- a/src/ui-manager.js +++ b/src/ui-manager.js @@ -1017,6 +1017,80 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo } } + // Reset all providers health status for a specific provider type + const resetHealthMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/reset-health$/); + if (method === 'POST' && resetHealthMatch) { + const providerType = decodeURIComponent(resetHealthMatch[1]); + + try { + const filePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'provider_pools.json'; + let providerPools = {}; + + // Load existing pools + if (existsSync(filePath)) { + try { + const fileContent = readFileSync(filePath, 'utf8'); + providerPools = JSON.parse(fileContent); + } catch (readError) { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: 'Provider pools file not found' } })); + return true; + } + } + + // Reset health status for all providers of this type + const providers = providerPools[providerType] || []; + + if (providers.length === 0) { + res.writeHead(404, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: 'No providers found for this type' } })); + return true; + } + + let resetCount = 0; + providers.forEach(provider => { + if (!provider.isHealthy) { + provider.isHealthy = true; + provider.errorCount = 0; + provider.lastErrorTime = null; + resetCount++; + } + }); + + // Save to file + writeFileSync(filePath, JSON.stringify(providerPools, null, 2), 'utf8'); + console.log(`[UI API] Reset health status for ${resetCount} providers in ${providerType}`); + + // Update provider pool manager if available + if (providerPoolManager) { + providerPoolManager.providerPools = providerPools; + providerPoolManager.initializeProviderStatus(); + } + + // 广播更新事件 + broadcastEvent('config_update', { + action: 'reset_health', + filePath: filePath, + providerType, + resetCount, + timestamp: new Date().toISOString() + }); + + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ + success: true, + message: `成功重置 ${resetCount} 个节点的健康状态`, + resetCount, + totalCount: providers.length + })); + return true; + } catch (error) { + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: { message: error.message } })); + return true; + } + } + // Generate OAuth authorization URL for providers const generateAuthUrlMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/generate-auth-url$/); if (method === 'POST' && generateAuthUrlMatch) { diff --git a/static/app/modal.js b/static/app/modal.js index 8984238..b07b1d7 100644 --- a/static/app/modal.js +++ b/static/app/modal.js @@ -46,6 +46,9 @@ function showProviderManagerModal(data) { + @@ -996,6 +999,40 @@ async function toggleProviderStatus(uuid, event) { } } +/** + * 重置所有提供商的健康状态 + * @param {string} providerType - 提供商类型 + */ +async function resetAllProvidersHealth(providerType) { + if (!confirm(`确定要将 ${providerType} 的所有节点重置为健康状态吗?\n\n这将清除所有节点的错误计数和错误时间。`)) { + return; + } + + try { + showToast('正在重置健康状态...', 'info'); + + const response = await window.apiClient.post( + `/providers/${encodeURIComponent(providerType)}/reset-health`, + {} + ); + + if (response.success) { + showToast(`成功重置 ${response.resetCount} 个节点的健康状态`, 'success'); + + // 重新加载配置 + await window.apiClient.post('/reload-config'); + + // 刷新提供商配置显示 + await refreshProviderConfig(providerType); + } else { + showToast('重置健康状态失败', 'error'); + } + } catch (error) { + console.error('重置健康状态失败:', error); + showToast(`重置健康状态失败: ${error.message}`, 'error'); + } +} + /** * 渲染不支持的模型选择器(不调用API,直接使用传入的模型列表) * @param {string} uuid - 提供商UUID @@ -1045,6 +1082,7 @@ export { showAddProviderForm, addProvider, toggleProviderStatus, + resetAllProvidersHealth, loadModelsForProviderType, renderNotSupportedModelsSelector }; @@ -1058,4 +1096,5 @@ window.saveProvider = saveProvider; window.deleteProvider = deleteProvider; window.showAddProviderForm = showAddProviderForm; window.addProvider = addProvider; -window.toggleProviderStatus = toggleProviderStatus; \ No newline at end of file +window.toggleProviderStatus = toggleProviderStatus; +window.resetAllProvidersHealth = resetAllProvidersHealth; \ No newline at end of file diff --git a/static/app/styles.css b/static/app/styles.css index a8c9c56..0b08775 100644 --- a/static/app/styles.css +++ b/static/app/styles.css @@ -1357,6 +1357,7 @@ input:checked + .toggle-slider:before { margin-left: auto; display: flex; align-items: center; + gap: 0.75rem; } .provider-actions {