AIClient-2-API/src/services/ui-manager.js
hex2077 25bcb5a232 feat(oauth): 新增 Codex Token 批量导入功能并优化 OAuth 服务器关闭逻辑
- 添加 Codex Token 批量导入功能,支持 SSE 实时进度显示
- 统一各 OAuth 服务器关闭逻辑,添加超时机制和错误处理
- 更新 Codex 支持的模型列表,添加 gpt-5.4 模型
- 优化 Codex API 版本管理,修复模型回退逻辑
- 添加前端批量导入界面及多语言支持
2026-03-06 12:46:12 +08:00

344 lines
No EOL
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { existsSync, readFileSync } from 'fs';
import path from 'path';
// Import UI modules
import * as auth from '../ui-modules/auth.js';
import * as configApi from '../ui-modules/config-api.js';
import * as providerApi from '../ui-modules/provider-api.js';
import * as usageApi from '../ui-modules/usage-api.js';
import * as pluginApi from '../ui-modules/plugin-api.js';
import * as uploadConfigApi from '../ui-modules/upload-config-api.js';
import * as systemApi from '../ui-modules/system-api.js';
import * as updateApi from '../ui-modules/update-api.js';
import * as oauthApi from '../ui-modules/oauth-api.js';
import * as eventBroadcast from '../ui-modules/event-broadcast.js';
// Re-export from event-broadcast module
export { broadcastEvent, initializeUIManagement, handleUploadOAuthCredentials, upload } from '../ui-modules/event-broadcast.js';
/**
* Serve static files for the UI
* @param {string} path - The request path
* @param {http.ServerResponse} res - The HTTP response object
*/
export async function serveStaticFiles(pathParam, res) {
const filePath = path.join(process.cwd(), 'static', pathParam === '/' || pathParam === '/index.html' ? 'index.html' : pathParam.replace('/static/', ''));
if (existsSync(filePath)) {
const ext = path.extname(filePath);
const contentType = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.ico': 'image/x-icon'
}[ext] || 'text/plain';
res.writeHead(200, { 'Content-Type': contentType });
res.end(readFileSync(filePath));
return true;
}
return false;
}
/**
* Handle UI management API requests
* @param {string} method - The HTTP method
* @param {string} path - The request path
* @param {http.IncomingMessage} req - The HTTP request object
* @param {http.ServerResponse} res - The HTTP response object
* @param {Object} currentConfig - The current configuration object
* @param {Object} providerPoolManager - The provider pool manager instance
* @returns {Promise<boolean>} - True if the request was handled by UI API
*/
export async function handleUIApiRequests(method, pathParam, req, res, currentConfig, providerPoolManager) {
// 处理登录接口
if (method === 'POST' && pathParam === '/api/login') {
return await auth.handleLoginRequest(req, res);
}
// 健康检查接口用于前端token验证
if (method === 'GET' && pathParam === '/api/health') {
return await systemApi.handleHealthCheck(req, res);
}
// Handle UI management API requests (需要token验证除了登录接口、健康检查和Events接口)
if (pathParam.startsWith('/api/') && pathParam !== '/api/login' && pathParam !== '/api/health' && pathParam !== '/api/events' && pathParam !== '/api/grok/assets') {
// 检查token验证
const isAuth = await auth.checkAuth(req);
if (!isAuth) {
res.writeHead(401, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
});
res.end(JSON.stringify({
error: {
message: 'Unauthorized access, please login first',
code: 'UNAUTHORIZED'
}
}));
return true;
}
}
// 文件上传API
if (method === 'POST' && pathParam === '/api/upload-oauth-credentials') {
return await eventBroadcast.handleUploadOAuthCredentials(req, res);
}
// Update admin password
if (method === 'POST' && pathParam === '/api/admin-password') {
return await configApi.handleUpdateAdminPassword(req, res);
}
// Get configuration
if (method === 'GET' && pathParam === '/api/config') {
return await configApi.handleGetConfig(req, res, currentConfig);
}
// Update configuration
if (method === 'POST' && pathParam === '/api/config') {
return await configApi.handleUpdateConfig(req, res, currentConfig);
}
// Get system information
if (method === 'GET' && pathParam === '/api/system') {
return await systemApi.handleGetSystem(req, res);
}
// Download today's log file
if (method === 'GET' && pathParam === '/api/system/download-log') {
return await systemApi.handleDownloadTodayLog(req, res);
}
// Clear today's log file
if (method === 'POST' && pathParam === '/api/system/clear-log') {
return await systemApi.handleClearTodayLog(req, res);
}
// Get provider pools summary
if (method === 'GET' && pathParam === '/api/providers') {
return await providerApi.handleGetProviders(req, res, currentConfig, providerPoolManager);
}
// Get supported provider types based on registered adapters
if (method === 'GET' && pathParam === '/api/providers/supported') {
return await providerApi.handleGetSupportedProviders(req, res);
}
// Get specific provider type details
const providerTypeMatch = pathParam.match(/^\/api\/providers\/([^\/]+)$/);
if (method === 'GET' && providerTypeMatch) {
const providerType = decodeURIComponent(providerTypeMatch[1]);
return await providerApi.handleGetProviderType(req, res, currentConfig, providerPoolManager, providerType);
}
// Get available models for all providers or specific provider type
if (method === 'GET' && pathParam === '/api/provider-models') {
return await providerApi.handleGetProviderModels(req, res);
}
// Get available models for a specific provider type
const providerModelsMatch = pathParam.match(/^\/api\/provider-models\/([^\/]+)$/);
if (method === 'GET' && providerModelsMatch) {
const providerType = decodeURIComponent(providerModelsMatch[1]);
return await providerApi.handleGetProviderTypeModels(req, res, providerType);
}
// Add new provider configuration
if (method === 'POST' && pathParam === '/api/providers') {
return await providerApi.handleAddProvider(req, res, currentConfig, providerPoolManager);
}
// Reset all providers health status for a specific provider type
// NOTE: This must be before the generic /{providerType}/{uuid} route to avoid matching 'reset-health' as UUID
const resetHealthMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/reset-health$/);
if (method === 'POST' && resetHealthMatch) {
const providerType = decodeURIComponent(resetHealthMatch[1]);
return await providerApi.handleResetProviderHealth(req, res, currentConfig, providerPoolManager, providerType);
}
// Perform health check for all providers of a specific type
// NOTE: This must be before the generic /{providerType}/{uuid} route to avoid matching 'health-check' as UUID
const healthCheckMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/health-check$/);
if (method === 'POST' && healthCheckMatch) {
const providerType = decodeURIComponent(healthCheckMatch[1]);
return await providerApi.handleHealthCheck(req, res, currentConfig, providerPoolManager, providerType);
}
// Delete all unhealthy providers for a specific type
// NOTE: This must be before the generic /{providerType}/{uuid} route to avoid matching 'delete-unhealthy' as UUID
const deleteUnhealthyMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/delete-unhealthy$/);
if (method === 'DELETE' && deleteUnhealthyMatch) {
const providerType = decodeURIComponent(deleteUnhealthyMatch[1]);
return await providerApi.handleDeleteUnhealthyProviders(req, res, currentConfig, providerPoolManager, providerType);
}
// Refresh UUIDs for all unhealthy providers of a specific type
// NOTE: This must be before the generic /{providerType}/{uuid} route to avoid matching 'refresh-unhealthy-uuids' as UUID
const refreshUnhealthyUuidsMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/refresh-unhealthy-uuids$/);
if (method === 'POST' && refreshUnhealthyUuidsMatch) {
const providerType = decodeURIComponent(refreshUnhealthyUuidsMatch[1]);
return await providerApi.handleRefreshUnhealthyUuids(req, res, currentConfig, providerPoolManager, providerType);
}
// Disable/Enable specific provider configuration
const disableEnableProviderMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/([^\/]+)\/(disable|enable)$/);
if (disableEnableProviderMatch) {
const providerType = decodeURIComponent(disableEnableProviderMatch[1]);
const providerUuid = disableEnableProviderMatch[2];
const action = disableEnableProviderMatch[3];
return await providerApi.handleDisableEnableProvider(req, res, currentConfig, providerPoolManager, providerType, providerUuid, action);
}
// Refresh UUID for specific provider configuration
const refreshUuidMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/([^\/]+)\/refresh-uuid$/);
if (method === 'POST' && refreshUuidMatch) {
const providerType = decodeURIComponent(refreshUuidMatch[1]);
const providerUuid = refreshUuidMatch[2];
return await providerApi.handleRefreshProviderUuid(req, res, currentConfig, providerPoolManager, providerType, providerUuid);
}
// Update specific provider configuration
// NOTE: This generic route must be after all specific routes like /reset-health, /health-check, /delete-unhealthy
const updateProviderMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/([^\/]+)$/);
if (method === 'PUT' && updateProviderMatch) {
const providerType = decodeURIComponent(updateProviderMatch[1]);
const providerUuid = updateProviderMatch[2];
return await providerApi.handleUpdateProvider(req, res, currentConfig, providerPoolManager, providerType, providerUuid);
}
// Delete specific provider configuration
if (method === 'DELETE' && updateProviderMatch) {
const providerType = decodeURIComponent(updateProviderMatch[1]);
const providerUuid = updateProviderMatch[2];
return await providerApi.handleDeleteProvider(req, res, currentConfig, providerPoolManager, providerType, providerUuid);
}
// Generate OAuth authorization URL for providers
const generateAuthUrlMatch = pathParam.match(/^\/api\/providers\/([^\/]+)\/generate-auth-url$/);
if (method === 'POST' && generateAuthUrlMatch) {
const providerType = decodeURIComponent(generateAuthUrlMatch[1]);
return await oauthApi.handleGenerateAuthUrl(req, res, currentConfig, providerType);
}
// Handle manual OAuth callback
if (method === 'POST' && pathParam === '/api/oauth/manual-callback') {
return await oauthApi.handleManualOAuthCallback(req, res);
}
// Server-Sent Events for real-time updates
if (method === 'GET' && pathParam === '/api/events') {
return await eventBroadcast.handleEvents(req, res);
}
// Get upload configuration files list
if (method === 'GET' && pathParam === '/api/upload-configs') {
return await uploadConfigApi.handleGetUploadConfigs(req, res, currentConfig, providerPoolManager);
}
// View specific configuration file
const viewConfigMatch = pathParam.match(/^\/api\/upload-configs\/view\/(.+)$/);
if (method === 'GET' && viewConfigMatch) {
const filePath = decodeURIComponent(viewConfigMatch[1]);
return await uploadConfigApi.handleViewConfigFile(req, res, filePath);
}
// Delete specific configuration file
const deleteConfigMatch = pathParam.match(/^\/api\/upload-configs\/delete\/(.+)$/);
if (method === 'DELETE' && deleteConfigMatch) {
const filePath = decodeURIComponent(deleteConfigMatch[1]);
return await uploadConfigApi.handleDeleteConfigFile(req, res, filePath);
}
// Download all configs as zip
if (method === 'GET' && pathParam === '/api/upload-configs/download-all') {
return await uploadConfigApi.handleDownloadAllConfigs(req, res);
}
// Delete all unbound config files
if (method === 'DELETE' && pathParam === '/api/upload-configs/delete-unbound') {
return await uploadConfigApi.handleDeleteUnboundConfigs(req, res, currentConfig, providerPoolManager);
}
// Quick link config to corresponding provider based on directory
if (method === 'POST' && pathParam === '/api/quick-link-provider') {
return await providerApi.handleQuickLinkProvider(req, res, currentConfig, providerPoolManager);
}
// Get usage limits for all providers
if (method === 'GET' && pathParam === '/api/usage') {
return await usageApi.handleGetUsage(req, res, currentConfig, providerPoolManager);
}
// Get supported providers for usage query
if (method === 'GET' && pathParam === '/api/usage/supported-providers') {
return await usageApi.handleGetSupportedProviders(req, res);
}
// Get usage limits for a specific provider type
const usageProviderMatch = pathParam.match(/^\/api\/usage\/([^\/]+)$/);
if (method === 'GET' && usageProviderMatch) {
const providerType = decodeURIComponent(usageProviderMatch[1]);
return await usageApi.handleGetProviderUsage(req, res, currentConfig, providerPoolManager, providerType);
}
// Check for updates - compare local VERSION with latest git tag
if (method === 'GET' && pathParam === '/api/check-update') {
return await updateApi.handleCheckUpdate(req, res);
}
// Perform update - git fetch and checkout to latest tag
if (method === 'POST' && pathParam === '/api/update') {
return await updateApi.handlePerformUpdate(req, res);
}
// Reload configuration files
if (method === 'POST' && pathParam === '/api/reload-config') {
return await configApi.handleReloadConfig(req, res, providerPoolManager);
}
// Restart service (worker process)
if (method === 'POST' && pathParam === '/api/restart-service') {
return await systemApi.handleRestartService(req, res);
}
// Get service mode information
if (method === 'GET' && pathParam === '/api/service-mode') {
return await systemApi.handleGetServiceMode(req, res);
}
// Batch import Kiro refresh tokens with SSE (real-time progress)
if (method === 'POST' && pathParam === '/api/kiro/batch-import-tokens') {
return await oauthApi.handleBatchImportKiroTokens(req, res);
}
if (method === 'POST' && pathParam === '/api/gemini/batch-import-tokens') {
return await oauthApi.handleBatchImportGeminiTokens(req, res);
}
if (method === 'POST' && pathParam === '/api/codex/batch-import-tokens') {
return await oauthApi.handleBatchImportCodexTokens(req, res);
}
// Import AWS SSO credentials for Kiro
if (method === 'POST' && pathParam === '/api/kiro/import-aws-credentials') {
return await oauthApi.handleImportAwsCredentials(req, res);
}
// Get plugins list
if (method === 'GET' && pathParam === '/api/plugins') {
return await pluginApi.handleGetPlugins(req, res);
}
// Toggle plugin status
const togglePluginMatch = pathParam.match(/^\/api\/plugins\/(.+)\/toggle$/);
if (method === 'POST' && togglePluginMatch) {
const pluginName = decodeURIComponent(togglePluginMatch[1]);
return await pluginApi.handleTogglePlugin(req, res, pluginName);
}
return false;
}