AIClient-2-API/src/config-manager.js
hex2077 80d4e16840 feat: 添加可视化Web UI管理控制台及相关功能模块
新增完整的Web UI管理控制台,包含以下主要功能:
1. 响应式设计的现代化界面
2. 实时监控系统状态和提供商统计
3. 配置管理功能,支持多种模型提供商
4. 文件上传和OAuth凭据管理
5. 路径路由调用示例和curl命令生成
6. 实时日志查看和事件流处理
7. 提供完整的UI文档说明

新增多个前端模块文件,包括导航、事件处理、文件上传等功能组件,并更新package.json添加multer依赖以支持文件上传功能。同时添加详细的UI_README.md文档说明所有功能特性和使用方法。
2025-11-11 18:53:39 +08:00

365 lines
No EOL
15 KiB
JavaScript

import * as fs from 'fs';
import { promises as pfs } from 'fs';
import { INPUT_SYSTEM_PROMPT_FILE, MODEL_PROVIDER } from './common.js';
export let CONFIG = {}; // Make CONFIG exportable
export let PROMPT_LOG_FILENAME = ''; // Make PROMPT_LOG_FILENAME exportable
const ALL_MODEL_PROVIDERS = Object.values(MODEL_PROVIDER);
function normalizeConfiguredProviders(config) {
const fallbackProvider = MODEL_PROVIDER.GEMINI_CLI;
const dedupedProviders = [];
const addProvider = (value) => {
if (typeof value !== 'string') {
return;
}
const trimmed = value.trim();
if (!trimmed) {
return;
}
const matched = ALL_MODEL_PROVIDERS.find((provider) => provider.toLowerCase() === trimmed.toLowerCase());
if (!matched) {
console.warn(`[Config Warning] Unknown model provider '${trimmed}'. This entry will be ignored.`);
return;
}
if (!dedupedProviders.includes(matched)) {
dedupedProviders.push(matched);
}
};
const rawValue = config.MODEL_PROVIDER;
if (Array.isArray(rawValue)) {
rawValue.forEach((entry) => addProvider(typeof entry === 'string' ? entry : String(entry)));
} else if (typeof rawValue === 'string') {
rawValue.split(',').forEach(addProvider);
} else if (rawValue != null) {
addProvider(String(rawValue));
}
if (dedupedProviders.length === 0) {
dedupedProviders.push(fallbackProvider);
}
config.DEFAULT_MODEL_PROVIDERS = dedupedProviders;
config.MODEL_PROVIDER = dedupedProviders[0];
}
/**
* Initializes the server configuration from config.json and command-line arguments.
* @param {string[]} args - Command-line arguments.
* @param {string} [configFilePath='config.json'] - Path to the configuration file.
* @returns {Object} The initialized configuration object.
*/
export async function initializeConfig(args = process.argv.slice(2), configFilePath = 'config.json') {
let currentConfig = {};
try {
const configData = fs.readFileSync(configFilePath, 'utf8');
currentConfig = JSON.parse(configData);
console.log('[Config] Loaded configuration from config.json');
} catch (error) {
console.error('[Config Error] Failed to load config.json:', error.message);
// Fallback to default values if config.json is not found or invalid
currentConfig = {
REQUIRED_API_KEY: "123456",
SERVER_PORT: 3000,
HOST: 'localhost',
MODEL_PROVIDER: MODEL_PROVIDER.GEMINI_CLI,
OPENAI_API_KEY: null,
OPENAI_BASE_URL: null,
CLAUDE_API_KEY: null,
CLAUDE_BASE_URL: null,
GEMINI_OAUTH_CREDS_BASE64: null,
GEMINI_OAUTH_CREDS_FILE_PATH: null,
KIRO_OAUTH_CREDS_BASE64: null,
KIRO_OAUTH_CREDS_FILE_PATH: null,
QWEN_OAUTH_CREDS_FILE_PATH: null,
PROJECT_ID: null,
SYSTEM_PROMPT_FILE_PATH: INPUT_SYSTEM_PROMPT_FILE, // Default value
SYSTEM_PROMPT_MODE: 'overwrite',
PROMPT_LOG_BASE_NAME: "prompt_log",
PROMPT_LOG_MODE: "none",
REQUEST_MAX_RETRIES: 3,
REQUEST_BASE_DELAY: 1000,
CRON_NEAR_MINUTES: 15,
CRON_REFRESH_TOKEN: true,
PROVIDER_POOLS_FILE_PATH: null // 新增号池配置文件路径
};
console.log('[Config] Using default configuration.');
}
// Parse command-line arguments
for (let i = 0; i < args.length; i++) {
if (args[i] === '--api-key') {
if (i + 1 < args.length) {
currentConfig.REQUIRED_API_KEY = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --api-key flag requires a value.`);
}
} else if (args[i] === '--log-prompts') {
if (i + 1 < args.length) {
const mode = args[i + 1];
if (mode === 'console' || mode === 'file') {
currentConfig.PROMPT_LOG_MODE = mode;
} else {
console.warn(`[Config Warning] Invalid mode for --log-prompts. Expected 'console' or 'file'. Prompt logging is disabled.`);
}
i++;
} else {
console.warn(`[Config Warning] --log-prompts flag requires a value.`);
}
} else if (args[i] === '--port') {
if (i + 1 < args.length) {
currentConfig.SERVER_PORT = parseInt(args[i + 1], 10);
i++;
} else {
console.warn(`[Config Warning] --port flag requires a value.`);
}
} else if (args[i] === '--model-provider') {
if (i + 1 < args.length) {
currentConfig.MODEL_PROVIDER = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --model-provider flag requires a value.`);
}
} else if (args[i] === '--openai-api-key') {
if (i + 1 < args.length) {
currentConfig.OPENAI_API_KEY = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --openai-api-key flag requires a value.`);
}
} else if (args[i] === '--openai-base-url') {
if (i + 1 < args.length) {
currentConfig.OPENAI_BASE_URL = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --openai-base-url flag requires a value.`);
}
} else if (args[i] === '--claude-api-key') {
if (i + 1 < args.length) {
currentConfig.CLAUDE_API_KEY = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --claude-api-key flag requires a value.`);
}
} else if (args[i] === '--claude-base-url') {
if (i + 1 < args.length) {
currentConfig.CLAUDE_BASE_URL = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --claude-base-url flag requires a value.`);
}
}
// Gemini-specific arguments
else if (args[i] === '--gemini-oauth-creds-base64') {
if (i + 1 < args.length) {
currentConfig.GEMINI_OAUTH_CREDS_BASE64 = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --gemini-oauth-creds-base64 flag requires a value.`);
}
} else if (args[i] === '--gemini-oauth-creds-file') {
if (i + 1 < args.length) {
currentConfig.GEMINI_OAUTH_CREDS_FILE_PATH = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --gemini-oauth-creds-file flag requires a value.`);
}
} else if (args[i] === '--project-id') {
if (i + 1 < args.length) {
currentConfig.PROJECT_ID = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --project-id flag requires a value.`);
}
} else if (args[i] === '--system-prompt-file') {
if (i + 1 < args.length) {
currentConfig.SYSTEM_PROMPT_FILE_PATH = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --system-prompt-file flag requires a value.`);
}
} else if (args[i] === '--system-prompt-mode') {
if (i + 1 < args.length) {
const mode = args[i + 1];
if (mode === 'overwrite' || mode === 'append') {
currentConfig.SYSTEM_PROMPT_MODE = mode;
} else {
console.warn(`[Config Warning] Invalid mode for --system-prompt-mode. Expected 'overwrite' or 'append'. Using default 'overwrite'.`);
}
i++;
} else {
console.warn(`[Config Warning] --system-prompt-mode flag requires a value.`);
}
} else if (args[i] === '--host') {
if (i + 1 < args.length) {
currentConfig.HOST = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --host flag requires a value.`);
}
} else if (args[i] === '--prompt-log-base-name') {
if (i + 1 < args.length) {
currentConfig.PROMPT_LOG_BASE_NAME = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --prompt-log-base-name flag requires a value.`);
}
} else if (args[i] === '--kiro-oauth-creds-base64') {
if (i + 1 < args.length) {
currentConfig.KIRO_OAUTH_CREDS_BASE64 = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --kiro-oauth-creds-base64 flag requires a value.`);
}
} else if (args[i] === '--kiro-oauth-creds-file') {
if (i + 1 < args.length) {
currentConfig.KIRO_OAUTH_CREDS_FILE_PATH = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --kiro-oauth-creds-file flag requires a value.`);
}
} else if (args[i] === '--qwen-oauth-creds-file') {
if (i + 1 < args.length) {
currentConfig.QWEN_OAUTH_CREDS_FILE_PATH = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --qwen-oauth-creds-file flag requires a value.`);
}
} else if (args[i] === '--cron-near-minutes') {
if (i + 1 < args.length) {
currentConfig.CRON_NEAR_MINUTES = parseInt(args[i + 1], 10);
i++;
} else {
console.warn(`[Config Warning] --cron-near-minutes flag requires a value.`);
}
} else if (args[i] === '--cron-refresh-token') {
if (i + 1 < args.length) {
currentConfig.CRON_REFRESH_TOKEN = args[i + 1].toLowerCase() === 'true';
i++;
} else {
console.warn(`[Config Warning] --cron-refresh-token flag requires a value.`);
}
} else if (args[i] === '--provider-pools-file') {
if (i + 1 < args.length) {
currentConfig.PROVIDER_POOLS_FILE_PATH = args[i + 1];
i++;
} else {
console.warn(`[Config Warning] --provider-pools-file flag requires a value.`);
}
}
}
normalizeConfiguredProviders(currentConfig);
if (!currentConfig.SYSTEM_PROMPT_FILE_PATH) {
currentConfig.SYSTEM_PROMPT_FILE_PATH = INPUT_SYSTEM_PROMPT_FILE;
}
currentConfig.SYSTEM_PROMPT_CONTENT = await getSystemPromptFileContent(currentConfig.SYSTEM_PROMPT_FILE_PATH);
// 加载号池配置
if (currentConfig.PROVIDER_POOLS_FILE_PATH) {
try {
const poolsData = await pfs.readFile(currentConfig.PROVIDER_POOLS_FILE_PATH, 'utf8');
currentConfig.providerPools = JSON.parse(poolsData);
console.log(`[Config] Loaded provider pools from ${currentConfig.PROVIDER_POOLS_FILE_PATH}`);
} catch (error) {
console.error(`[Config Error] Failed to load provider pools from ${currentConfig.PROVIDER_POOLS_FILE_PATH}: ${error.message}`);
currentConfig.providerPools = {};
}
} else {
currentConfig.providerPools = {};
}
// Set PROMPT_LOG_FILENAME based on the determined config
if (currentConfig.PROMPT_LOG_MODE === 'file') {
const now = new Date();
const pad = (num) => String(num).padStart(2, '0');
const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
PROMPT_LOG_FILENAME = `${currentConfig.PROMPT_LOG_BASE_NAME}-${timestamp}.log`;
} else {
PROMPT_LOG_FILENAME = ''; // Clear if not logging to file
}
// Assign to the exported CONFIG
Object.assign(CONFIG, currentConfig);
return CONFIG;
}
/**
* Gets system prompt content from the specified file path.
* @param {string} filePath - Path to the system prompt file.
* @returns {Promise<string|null>} File content, or null if the file does not exist, is empty, or an error occurs.
*/
export async function getSystemPromptFileContent(filePath) {
try {
await pfs.access(filePath, pfs.constants.F_OK);
} catch (error) {
if (error.code === 'ENOENT') {
console.warn(`[System Prompt] Specified system prompt file not found: ${filePath}`);
} else {
console.error(`[System Prompt] Error accessing system prompt file ${filePath}: ${error.message}`);
}
return null;
}
try {
const content = await pfs.readFile(filePath, 'utf8');
if (!content.trim()) {
return null;
}
console.log(`[System Prompt] Loaded system prompt from ${filePath}`);
return content;
} catch (error) {
console.error(`[System Prompt] Error reading system prompt file ${filePath}: ${error.message}`);
return null;
}
}
/**
* Logs provider-specific configuration details
* @param {string} provider - The model provider
* @param {Object} config - The configuration object
*/
export function logProviderSpecificDetails(provider, config) {
switch (provider) {
case MODEL_PROVIDER.OPENAI_CUSTOM:
console.log(` [openai-custom] API Key: ${config.OPENAI_API_KEY ? '******' : 'Not Set'}`);
console.log(` [openai-custom] Base URL: ${config.OPENAI_BASE_URL || 'Default'}`);
break;
case MODEL_PROVIDER.CLAUDE_CUSTOM:
console.log(` [claude-custom] API Key: ${config.CLAUDE_API_KEY ? '******' : 'Not Set'}`);
console.log(` [claude-custom] Base URL: ${config.CLAUDE_BASE_URL || 'Default'}`);
break;
case MODEL_PROVIDER.GEMINI_CLI:
if (config.GEMINI_OAUTH_CREDS_FILE_PATH) {
console.log(` [gemini-cli-oauth] OAuth Creds File Path: ${config.GEMINI_OAUTH_CREDS_FILE_PATH}`);
} else if (config.GEMINI_OAUTH_CREDS_BASE64) {
console.log(` [gemini-cli-oauth] OAuth Creds Source: Provided via Base64 string`);
} else {
console.log(` [gemini-cli-oauth] OAuth Creds: Default discovery`);
}
// console.log(` [gemini-cli-oauth] Project ID: ${config.PROJECT_ID || 'Auto-discovered'}`);
break;
case MODEL_PROVIDER.KIRO_API:
if (config.KIRO_OAUTH_CREDS_FILE_PATH) {
console.log(` [claude-kiro-oauth] OAuth Creds File Path: ${config.KIRO_OAUTH_CREDS_FILE_PATH}`);
} else if (config.KIRO_OAUTH_CREDS_BASE64) {
console.log(` [claude-kiro-oauth] OAuth Creds Source: Provided via Base64 string`);
} else {
console.log(` [claude-kiro-oauth] OAuth Creds: Default`);
}
break;
case MODEL_PROVIDER.QWEN_API:
console.log(` [openai-qwen-oauth] OAuth Creds File Path: ${config.QWEN_OAUTH_CREDS_FILE_PATH || 'Default'}`);
break;
default:
console.log(` [${provider}] Provider initialized.`);
}
}
export { ALL_MODEL_PROVIDERS };