feat(认证流程): 添加浏览器自动打开功能并改进授权流程

添加自动打开浏览器功能到Gemini和Antigravity认证流程,当自动打开失败时显示备用提示信息
在提供商管理界面添加重置健康状态功能
调整CSS布局增加元素间距
This commit is contained in:
hex2077 2025-12-01 12:44:11 +08:00
parent cb76d85ecd
commit 7420c88a6f
5 changed files with 159 additions and 7 deletions

View file

@ -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 {

View file

@ -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);

View file

@ -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) {

View file

@ -46,6 +46,9 @@ function showProviderManagerModal(data) {
<button class="btn btn-success" onclick="window.showAddProviderForm('${providerType}')">
<i class="fas fa-plus"></i>
</button>
<button class="btn btn-warning" onclick="window.resetAllProvidersHealth('${providerType}')" title="将所有节点的健康状态重置为健康">
<i class="fas fa-heartbeat"></i>
</button>
</div>
</div>
@ -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;
window.toggleProviderStatus = toggleProviderStatus;
window.resetAllProvidersHealth = resetAllProvidersHealth;

View file

@ -1357,6 +1357,7 @@ input:checked + .toggle-slider:before {
margin-left: auto;
display: flex;
align-items: center;
gap: 0.75rem;
}
.provider-actions {