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 {