feat(认证流程): 添加浏览器自动打开功能并改进授权流程
添加自动打开浏览器功能到Gemini和Antigravity认证流程,当自动打开失败时显示备用提示信息 在提供商管理界面添加重置健康状态功能 调整CSS布局增加元素间距
This commit is contained in:
parent
cb76d85ecd
commit
7420c88a6f
5 changed files with 159 additions and 7 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1357,6 +1357,7 @@ input:checked + .toggle-slider:before {
|
|||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.provider-actions {
|
||||
|
|
|
|||
Loading…
Reference in a new issue