新增完整的Web UI管理控制台,包含以下主要功能: 1. 响应式设计的现代化界面 2. 实时监控系统状态和提供商统计 3. 配置管理功能,支持多种模型提供商 4. 文件上传和OAuth凭据管理 5. 路径路由调用示例和curl命令生成 6. 实时日志查看和事件流处理 7. 提供完整的UI文档说明 新增多个前端模块文件,包括导航、事件处理、文件上传等功能组件,并更新package.json添加multer依赖以支持文件上传功能。同时添加详细的UI_README.md文档说明所有功能特性和使用方法。
365 lines
No EOL
15 KiB
JavaScript
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 }; |