claude-switcher
Version:
Cross-platform CLI tool for switching between different Claude AI model configurations. Supports automatic backup, rollback, and multi-platform configuration management for Claude API integrations.
853 lines (852 loc) ⢠35.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UIServer = void 0;
const express_1 = __importDefault(require("express"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const chalk_1 = __importDefault(require("chalk"));
function getPackageVersion() {
try {
const packageJsonPath = path_1.default.join(__dirname, '..', '..', 'package.json');
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
return packageJson.version;
}
catch (error) {
return '1.0.4';
}
}
const ErrorHandler_1 = require("../utils/ErrorHandler");
const OutputFormatter_1 = require("../utils/OutputFormatter");
const ConnectionTester_1 = require("./utils/ConnectionTester");
class UIServer {
constructor(configManager, options) {
this.server = null;
this.io = null;
this.fileWatcher = null;
this.app = (0, express_1.default)();
this.configManager = configManager;
this.options = options;
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
this.app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
}
else {
next();
}
});
this.app.use(express_1.default.json({ limit: '10mb' }));
this.app.use(express_1.default.urlencoded({ extended: true, limit: '10mb' }));
if (process.env.NODE_ENV !== 'production') {
this.app.use((req, _res, next) => {
console.log(chalk_1.default.gray(`${new Date().toISOString()} ${req.method} ${req.path}`));
next();
});
}
this.app.use(this.errorHandler.bind(this));
const staticPath = path_1.default.join(__dirname, '../../dist/ui-frontend');
this.app.use(express_1.default.static(staticPath));
}
setupRoutes() {
this.app.get('/api/health', this.handleHealthCheck.bind(this));
this.app.get('/api/configs', this.handleGetConfigs.bind(this));
this.app.get('/api/configs/:name', this.handleGetConfig.bind(this));
this.app.post('/api/configs', this.handleCreateConfig.bind(this));
this.app.put('/api/configs/:name', this.handleUpdateConfig.bind(this));
this.app.delete('/api/configs/:name', this.handleDeleteConfig.bind(this));
this.app.post('/api/configs/:name/activate', this.handleActivateConfig.bind(this));
this.app.post('/api/configs/:name/test', this.handleTestConfig.bind(this));
this.app.get('/api/backups', this.handleGetBackups.bind(this));
this.app.post('/api/backups', this.handleCreateBackup.bind(this));
this.app.post('/api/backups/:id/restore', this.handleRestoreBackup.bind(this));
this.app.delete('/api/backups/:id', this.handleDeleteBackup.bind(this));
this.app.get('/api/providers', this.handleGetProviders.bind(this));
this.app.post('/api/providers/:name/models', this.handleGetProviderModels.bind(this));
this.app.get('/api/providers/:name/docs', this.handleGetProviderDocs.bind(this));
this.app.get('/api/providers/cache/status', this.handleGetCacheStatus.bind(this));
this.app.delete('/api/providers/cache/:name?', this.handleClearCache.bind(this));
this.app.get('/api/system/status', this.handleGetSystemStatus.bind(this));
this.app.get('/api/system/verify', this.handleVerifySystem.bind(this));
this.app.post('/api/system/sync', this.handleSyncSystem.bind(this));
this.app.get('*', (_req, res) => {
const indexPath = path_1.default.join(__dirname, '../../dist/ui-frontend/index.html');
res.sendFile(indexPath, (err) => {
if (err) {
res.status(404).send('UI not built. Please run the build process first.');
}
});
});
}
async handleHealthCheck(_req, res) {
res.json({
status: 'ok',
timestamp: new Date().toISOString(),
version: getPackageVersion()
});
}
async handleGetConfigs(_req, res) {
try {
const configs = await this.configManager.getAvailableConfigs();
const activeConfig = await this.configManager.getCurrentConfig();
const enhancedConfigs = configs.map(config => ({
...config,
provider: this.extractProvider(config.fileName),
description: `${config.displayName} configuration`,
createdAt: new Date(),
updatedAt: new Date(),
tags: [this.extractProvider(config.fileName)],
isValid: true
}));
res.json({
configs: enhancedConfigs,
activeConfig: activeConfig ? configs.find(c => c.isActive)?.name : null,
total: configs.length
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get configurations', error);
}
}
async handleGetConfig(req, res) {
try {
const { name } = req.params;
const existingConfigs = await this.configManager.getAvailableConfigs();
const configInfo = existingConfigs.find(c => c.name === name);
if (!configInfo) {
return this.sendError(res, 404, `Configuration '${name}' not found`);
}
const configPath = path_1.default.join(this.configManager.getClaudeDir(), `settings_${name}.json`);
const configData = await fs_1.default.promises.readFile(configPath, 'utf-8');
const fullConfig = JSON.parse(configData);
const enhancedConfig = {
name: configInfo.name,
fileName: configInfo.fileName,
displayName: configInfo.displayName,
model: configInfo.model,
isActive: configInfo.isActive,
filePath: configInfo.filePath,
apiBaseUrl: fullConfig.env?.ANTHROPIC_BASE_URL || '',
primaryModel: fullConfig.env?.ANTHROPIC_MODEL || '',
fastModel: fullConfig.env?.ANTHROPIC_SMALL_FAST_MODEL || fullConfig.env?.ANTHROPIC_MODEL || '',
provider: this.extractProvider(configInfo.fileName),
description: `${configInfo.displayName} configuration`,
createdAt: new Date(),
updatedAt: new Date(),
tags: [this.extractProvider(configInfo.fileName)],
isValid: true,
apiKey: fullConfig.env?.ANTHROPIC_AUTH_TOKEN ? this.maskApiKey(fullConfig.env.ANTHROPIC_AUTH_TOKEN) : ''
};
res.json({
config: enhancedConfig
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get configuration', error);
}
}
async handleCreateConfig(req, res) {
try {
const { name, config } = req.body;
if (!name || !config) {
return this.sendError(res, 400, 'Missing required fields: name and config');
}
const validation = this.configManager.validateConfigIntegrity(config);
const formatValidation = ConnectionTester_1.ConnectionTester.validateConfigFormat(config);
if (!validation.isValid || !formatValidation.isValid) {
const allErrors = [...validation.errors, ...formatValidation.errors];
return this.sendError(res, 400, 'Invalid configuration format', {
errors: allErrors,
warnings: formatValidation.warnings
});
}
const existingConfigs = await this.configManager.getAvailableConfigs();
if (existingConfigs.some(c => c.name === name)) {
return this.sendError(res, 409, `Configuration '${name}' already exists`);
}
const configPath = path_1.default.join(this.configManager.getClaudeDir(), `settings_${name}.json`);
await fs_1.default.promises.writeFile(configPath, JSON.stringify(config, null, 2));
const newConfigs = await this.configManager.getAvailableConfigs();
const createdConfig = newConfigs.find(c => c.name === name);
if (createdConfig) {
this.broadcastConfigChange(createdConfig);
res.status(201).json({
message: 'Configuration created successfully',
config: createdConfig
});
}
else {
this.sendError(res, 500, 'Failed to create configuration');
}
}
catch (error) {
this.sendError(res, 500, 'Failed to create configuration', error);
}
}
async handleUpdateConfig(req, res) {
try {
const { name } = req.params;
const { config } = req.body;
if (!config) {
return this.sendError(res, 400, 'Missing required field: config');
}
const validation = this.configManager.validateConfigIntegrity(config);
const formatValidation = ConnectionTester_1.ConnectionTester.validateConfigFormat(config);
if (!validation.isValid || !formatValidation.isValid) {
const allErrors = [...validation.errors, ...formatValidation.errors];
return this.sendError(res, 400, 'Invalid configuration format', {
errors: allErrors,
warnings: formatValidation.warnings
});
}
const existingConfigs = await this.configManager.getAvailableConfigs();
const existingConfig = existingConfigs.find(c => c.name === name);
if (!existingConfig) {
return this.sendError(res, 404, `Configuration '${name}' not found`);
}
if (existingConfig.isActive) {
await this.configManager.createBackup();
}
const configPath = path_1.default.join(this.configManager.getClaudeDir(), `settings_${name}.json`);
await fs_1.default.promises.writeFile(configPath, JSON.stringify(config, null, 2));
const updatedConfigs = await this.configManager.getAvailableConfigs();
const updatedConfig = updatedConfigs.find(c => c.name === name);
if (updatedConfig) {
this.broadcastConfigChange(updatedConfig);
res.json({
message: 'Configuration updated successfully',
config: updatedConfig
});
}
else {
this.sendError(res, 500, 'Failed to update configuration');
}
}
catch (error) {
this.sendError(res, 500, 'Failed to update configuration', error);
}
}
async handleDeleteConfig(req, res) {
try {
const { name } = req.params;
const existingConfigs = await this.configManager.getAvailableConfigs();
const configToDelete = existingConfigs.find(c => c.name === name);
if (!configToDelete) {
return this.sendError(res, 404, `Configuration '${name}' not found`);
}
if (configToDelete.isActive) {
return this.sendError(res, 400, 'Cannot delete active configuration. Please switch to another configuration first.');
}
const configPath = path_1.default.join(this.configManager.getClaudeDir(), `settings_${name}.json`);
await fs_1.default.promises.unlink(configPath);
this.io?.emit('config:deleted', { name });
res.json({
message: 'Configuration deleted successfully',
name
});
}
catch (error) {
this.sendError(res, 500, 'Failed to delete configuration', error);
}
}
async handleActivateConfig(req, res) {
try {
const { name } = req.params;
const switchedConfig = await this.configManager.switchToConfig(name);
this.io?.emit('config:activated', name);
res.json({
message: 'Configuration activated successfully',
config: switchedConfig
});
}
catch (error) {
this.sendError(res, 500, 'Failed to activate configuration', error);
}
}
async handleTestConfig(req, res) {
try {
const { name } = req.params;
const { config: testConfig } = req.body;
let configToTest;
if (testConfig) {
configToTest = testConfig;
}
else {
const existingConfigs = await this.configManager.getAvailableConfigs();
const existingConfig = existingConfigs.find(c => c.name === name);
if (!existingConfig) {
return this.sendError(res, 404, `Configuration '${name}' not found`);
}
const configPath = path_1.default.join(this.configManager.getClaudeDir(), `settings_${name}.json`);
const configData = await fs_1.default.promises.readFile(configPath, 'utf-8');
configToTest = JSON.parse(configData);
}
const testResult = await this.performConnectionTest(configToTest);
res.json({
message: 'Connection test completed',
result: testResult
});
}
catch (error) {
this.sendError(res, 500, 'Failed to test configuration', error);
}
}
async handleGetBackups(_req, res) {
try {
const backups = await this.configManager.getBackupList();
const backupInfo = await Promise.all(backups.map(async (backup) => {
const backupPath = path_1.default.join(this.configManager.getClaudeDir(), backup);
const stats = await fs_1.default.promises.stat(backupPath);
return {
id: backup,
fileName: backup,
originalConfig: this.extractOriginalConfigFromBackup(backup),
createdAt: stats.birthtime,
size: stats.size,
description: `Backup created on ${stats.birthtime.toLocaleString()}`
};
}));
res.json({
backups: backupInfo,
total: backups.length
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get backups', error);
}
}
async handleCreateBackup(req, res) {
try {
const { description } = req.body;
const backupPath = await this.configManager.createBackup();
const backupFileName = path_1.default.basename(backupPath);
this.io?.emit('backup:created', {
id: backupFileName,
fileName: backupFileName,
createdAt: new Date(),
description: description || `Backup created on ${new Date().toLocaleString()}`
});
res.status(201).json({
message: 'Backup created successfully',
backup: {
id: backupFileName,
fileName: backupFileName,
path: backupPath
}
});
}
catch (error) {
this.sendError(res, 500, 'Failed to create backup', error);
}
}
async handleRestoreBackup(req, res) {
try {
const { id } = req.params;
const restoredConfig = await this.configManager.restoreFromBackup(id);
res.json({
message: 'Backup restored successfully',
config: restoredConfig
});
}
catch (error) {
this.sendError(res, 500, 'Failed to restore backup', error);
}
}
async handleDeleteBackup(req, res) {
try {
const { id } = req.params;
const backupPath = path_1.default.join(this.configManager.getClaudeDir(), id);
await fs_1.default.promises.unlink(backupPath);
res.json({
message: 'Backup deleted successfully',
id
});
}
catch (error) {
this.sendError(res, 500, 'Failed to delete backup', error);
}
}
async handleGetProviders(_req, res) {
try {
const providers = this.getProviderTemplates();
res.json({
providers,
total: providers.length
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get providers', error);
}
}
async handleGetProviderModels(req, res) {
try {
const { name } = req.params;
const { refresh = 'false' } = req.query;
const { config } = req.body;
const { ModelService } = await Promise.resolve().then(() => __importStar(require('./utils/ModelService')));
const forceRefresh = refresh === 'true';
const result = await ModelService.getModels(name, config, forceRefresh);
res.json(result);
}
catch (error) {
this.sendError(res, 500, 'Failed to get provider models', error);
}
}
async handleGetProviderDocs(req, res) {
try {
const { name } = req.params;
const docs = this.getProviderDocumentation(name);
res.json({
provider: name,
documentation: docs
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get provider documentation', error);
}
}
async handleGetCacheStatus(_req, res) {
try {
const { ModelService } = await Promise.resolve().then(() => __importStar(require('./utils/ModelService')));
const cacheStatus = ModelService.getCacheStatus();
res.json({
cache: cacheStatus,
timestamp: new Date().toISOString()
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get cache status', error);
}
}
async handleClearCache(req, res) {
try {
const { name } = req.params;
const { ModelService } = await Promise.resolve().then(() => __importStar(require('./utils/ModelService')));
ModelService.clearCache(name);
res.json({
message: name ? `Cache cleared for ${name}` : 'All cache cleared',
provider: name || 'all',
timestamp: new Date().toISOString()
});
}
catch (error) {
this.sendError(res, 500, 'Failed to clear cache', error);
}
}
async handleGetSystemStatus(_req, res) {
try {
const activeConfig = await this.configManager.getCurrentConfig();
const configDir = this.configManager.getClaudeDir();
const settingsFile = this.configManager.getSettingsFile();
res.json({
activeConfig,
configDirectory: configDir,
settingsFile,
timestamp: new Date().toISOString(),
isAccessible: await this.configManager.isClaudeDirAccessible()
});
}
catch (error) {
this.sendError(res, 500, 'Failed to get system status', error);
}
}
async handleVerifySystem(_req, res) {
try {
const currentConfig = await this.configManager.getCurrentConfig();
if (!currentConfig) {
return res.json({
isConsistent: false,
message: 'No active configuration found',
issues: ['No settings.json file found']
});
}
const validation = this.configManager.validateConfigIntegrity(currentConfig);
res.json({
isConsistent: validation.isValid,
message: validation.isValid ? 'Environment is consistent' : 'Environment has issues',
issues: validation.errors,
config: currentConfig
});
}
catch (error) {
this.sendError(res, 500, 'Failed to verify system', error);
}
}
async handleSyncSystem(_req, res) {
try {
res.json({
message: 'System synchronized successfully',
timestamp: new Date().toISOString()
});
}
catch (error) {
this.sendError(res, 500, 'Failed to sync system', error);
}
}
async start() {
return new Promise((resolve, reject) => {
try {
this.server = this.app.listen(this.options.port, this.options.host, () => {
const url = `http://${this.options.host}:${this.options.port}`;
this.setupWebSocket();
this.setupFileWatcher();
console.log(OutputFormatter_1.OutputFormatter.createBox([
'š Claude Switcher UI Server Started',
'',
`š Server URL: ${chalk_1.default.blue.underline(url)}`,
`š Host: ${this.options.host}`,
`š Port: ${this.options.port}`,
'',
'š” Commands:',
' ⢠Press Ctrl+C to stop the server',
' ⢠Visit the URL above to access the UI',
'',
'š Configuration Directory:',
` ${this.configManager.getClaudeDir()}`,
'',
'š Real-time Features:',
' ⢠WebSocket connections enabled',
' ⢠File system monitoring active'
], {
title: 'UI Server',
style: 'rounded',
color: chalk_1.default.green
}));
if (this.options.open) {
this.openBrowser(url);
}
resolve();
});
this.server.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
console.error(OutputFormatter_1.OutputFormatter.createBox([
'ā Port Already in Use',
'',
`Port ${this.options.port} is already being used by another process.`,
'',
'š” Solutions:',
` ⢠Try a different port: cs ui --port ${this.options.port + 1}`,
' ⢠Stop the other process using this port',
' ⢠Use netstat or lsof to find the conflicting process'
], {
title: 'Server Error',
style: 'double',
color: chalk_1.default.red
}));
}
else {
ErrorHandler_1.ErrorHandler.displayError(error, {
operation: 'Starting UI server',
severity: ErrorHandler_1.ErrorSeverity.HIGH
});
}
reject(error);
});
this.setupGracefulShutdown();
}
catch (error) {
reject(error);
}
});
}
async stop() {
return new Promise((resolve) => {
if (this.fileWatcher) {
this.fileWatcher.close();
this.fileWatcher = null;
}
if (this.io) {
this.io.close();
this.io = null;
}
if (this.server) {
this.server.close(() => {
console.log(chalk_1.default.yellow('\nš UI Server stopped gracefully'));
resolve();
});
}
else {
resolve();
}
});
}
async openBrowser(url) {
try {
const open = await Promise.resolve().then(() => __importStar(require('open')));
await open.default(url);
console.log(chalk_1.default.green(`š Opening browser at ${url}`));
}
catch (error) {
console.log(chalk_1.default.yellow(`ā ļø Could not auto-open browser. Please visit: ${url}`));
console.log(chalk_1.default.gray(` Reason: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
setupGracefulShutdown() {
const shutdown = async (signal) => {
console.log(chalk_1.default.yellow(`\n\nš Received ${signal}, shutting down UI server...`));
await this.stop();
process.exit(0);
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
}
setupWebSocket() {
if (!this.server)
return;
try {
const { Server: SocketIOServer } = require('socket.io');
this.io = new SocketIOServer(this.server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
this.io?.on('connection', (socket) => {
console.log(chalk_1.default.gray(`WebSocket client connected: ${socket.id}`));
socket.on('config:watch', () => {
socket.join('config-watchers');
});
socket.on('config:unwatch', () => {
socket.leave('config-watchers');
});
socket.on('system:subscribe', () => {
socket.join('system-watchers');
});
socket.on('disconnect', () => {
console.log(chalk_1.default.gray(`WebSocket client disconnected: ${socket.id}`));
});
});
console.log(chalk_1.default.green('ā
WebSocket server initialized'));
}
catch {
console.log(chalk_1.default.yellow('ā ļø WebSocket not available (socket.io not installed)'));
console.log(chalk_1.default.gray(' Real-time features will be disabled'));
}
}
setupFileWatcher() {
try {
const configDir = this.configManager.getClaudeDir();
this.fileWatcher = fs_1.default.watch(configDir, { recursive: false }, (eventType, filename) => {
if (filename && filename.match(/^settings.*\.json$/)) {
console.log(chalk_1.default.gray(`File changed: ${filename}`));
setTimeout(() => {
this.handleFileChange(filename, eventType);
}, 100);
}
});
console.log(chalk_1.default.green('ā
File system watcher initialized'));
}
catch (error) {
console.log(chalk_1.default.yellow('ā ļø File system watcher not available'));
console.log(chalk_1.default.gray(` Reason: ${error instanceof Error ? error.message : 'Unknown error'}`));
}
}
async handleFileChange(filename, eventType) {
try {
if (eventType === 'change' || eventType === 'rename') {
const configs = await this.configManager.getAvailableConfigs();
if (this.io) {
this.io.to('config-watchers').emit('config:changed', {
type: eventType,
filename,
configs
});
}
}
}
catch (error) {
console.error('Error handling file change:', error);
}
}
sendError(res, status, message, details) {
const error = {
error: message,
message,
details
};
if (details instanceof Error) {
error.message = details.message;
error.details = details.stack;
}
return res.status(status).json(error);
}
errorHandler(error, _req, res, next) {
console.error('API Error:', error);
if (res.headersSent) {
return next(error);
}
this.sendError(res, 500, 'Internal server error', error);
}
broadcastConfigChange(config) {
if (this.io) {
this.io.to('config-watchers').emit('config:changed', config);
}
}
extractProvider(fileName) {
const match = fileName.match(/^settings_(.+)\.json$/);
if (match) {
const name = match[1];
const providerMap = {
'deepseek': 'DeepSeek',
'qwen': 'Qwen',
'glm': 'GLM',
'kimi': 'Kimi',
'claude': 'Claude',
'openai': 'OpenAI',
'ollama': 'Ollama'
};
return providerMap[name.toLowerCase()] || name;
}
return 'Unknown';
}
extractOriginalConfigFromBackup(backupFileName) {
const parts = backupFileName.split('.backup.');
return parts[0] || 'settings.json';
}
async performConnectionTest(config) {
try {
const result = await ConnectionTester_1.ConnectionTester.testConnection(config, {
timeout: 15000,
retries: 2,
validateModels: true
});
return {
success: result.success,
latency: result.latency || undefined,
error: result.error || undefined,
modelInfo: result.modelInfo || undefined,
timestamp: result.timestamp
};
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Connection test failed',
timestamp: new Date()
};
}
}
getProviderTemplates() {
return [
{
id: 'deepseek',
name: 'DeepSeek',
description: 'DeepSeek AI models',
baseUrl: 'https://api.deepseek.com',
authType: 'bearer',
defaultModels: {
primary: 'deepseek-chat',
fast: 'deepseek-chat'
},
documentationUrl: 'https://platform.deepseek.com/api-docs',
signupUrl: 'https://platform.deepseek.com',
icon: 'š§ ',
category: 'AI Provider',
isPopular: true
},
{
id: 'qwen',
name: 'Qwen',
description: 'Alibaba Qwen models',
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
authType: 'bearer',
defaultModels: {
primary: 'qwen-turbo',
fast: 'qwen-turbo'
},
documentationUrl: 'https://help.aliyun.com/zh/dashscope',
signupUrl: 'https://dashscope.console.aliyun.com',
icon: 'š',
category: 'AI Provider',
isPopular: true
},
{
id: 'openai',
name: 'OpenAI',
description: 'OpenAI GPT models',
baseUrl: 'https://api.openai.com/v1',
authType: 'bearer',
defaultModels: {
primary: 'gpt-4',
fast: 'gpt-3.5-turbo'
},
documentationUrl: 'https://platform.openai.com/docs',
signupUrl: 'https://platform.openai.com',
icon: 'š¤',
category: 'AI Provider',
isPopular: true
}
];
}
getProviderDocumentation(providerName) {
const docMap = {
'deepseek': {
apiDocs: 'https://platform.deepseek.com/api-docs',
quickStart: 'https://platform.deepseek.com/docs/quick-start',
pricing: 'https://platform.deepseek.com/pricing'
},
'qwen': {
apiDocs: 'https://help.aliyun.com/zh/dashscope',
quickStart: 'https://help.aliyun.com/zh/dashscope/developer-reference/quick-start',
pricing: 'https://dashscope.console.aliyun.com/billing'
},
'openai': {
apiDocs: 'https://platform.openai.com/docs',
quickStart: 'https://platform.openai.com/docs/quickstart',
pricing: 'https://openai.com/pricing'
}
};
return docMap[providerName.toLowerCase()] || {};
}
maskApiKey(apiKey) {
if (!apiKey || apiKey.length < 8) {
return '***';
}
const start = apiKey.substring(0, 3);
const end = apiKey.substring(apiKey.length - 4);
const middle = '*'.repeat(Math.min(apiKey.length - 7, 20));
return `${start}${middle}${end}`;
}
}
exports.UIServer = UIServer;