Files
5why-analyzer/server.js
donald 957003bc7c feat: Add multi-LLM provider support with DeepSeek integration
Major Features:
-  Multi-LLM provider support (DeepSeek, Ollama, OpenAI, Custom)
- 🤖 Admin panel LLM configuration management UI
- 🔄 Dynamic provider switching without restart
- 🧪 Built-in API connection testing
- 🔒 Secure API key management

Backend Changes:
- Add routes/llmConfig.js: Complete LLM config CRUD API
- Update routes/analyze.js: Use database LLM configuration
- Update server.js: Add LLM config routes
- Add scripts/add-deepseek-config.js: DeepSeek setup script

Frontend Changes:
- Update src/pages/AdminPage.jsx: Add LLM Config tab + modal
- Update src/services/api.js: Add LLM config API methods
- Provider presets for DeepSeek, Ollama, OpenAI
- Test connection feature in config modal

Configuration:
- Update .env.example: Add DeepSeek API configuration
- Update package.json: Add llm:add-deepseek script

Documentation:
- Add docs/LLM_CONFIGURATION_GUIDE.md: Complete guide
- Add DEEPSEEK_INTEGRATION.md: Integration summary
- Quick setup instructions for DeepSeek

API Endpoints:
- GET /api/llm-config: List all configurations
- GET /api/llm-config/active: Get active configuration
- POST /api/llm-config: Create configuration
- PUT /api/llm-config/🆔 Update configuration
- PUT /api/llm-config/:id/activate: Activate configuration
- DELETE /api/llm-config/🆔 Delete configuration
- POST /api/llm-config/test: Test API connection

Database:
- Uses existing llm_configs table
- Only one config active at a time
- Fallback to Ollama if no database config

Security:
- Admin-only access to LLM configuration
- API keys never returned in GET requests
- Audit logging for all config changes
- Cannot delete active configuration

DeepSeek Model:
- Model: deepseek-chat
- High-quality 5 Why analysis
- Excellent Chinese language support
- Cost-effective pricing

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 00:33:10 +08:00

219 lines
6.1 KiB
JavaScript

import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import session from 'express-session';
import rateLimit from 'express-rate-limit';
import dotenv from 'dotenv';
import { testConnection } from './config.js';
import { sessionConfig, securityConfig, serverConfig } from './config.js';
import { notFoundHandler, errorHandler } from './middleware/errorHandler.js';
// Routes
import authRoutes from './routes/auth.js';
import analyzeRoutes from './routes/analyze.js';
import adminRoutes from './routes/admin.js';
import llmConfigRoutes from './routes/llmConfig.js';
// 載入環境變數
dotenv.config();
const app = express();
const PORT = serverConfig.port;
// ============================================
// Middleware Setup
// ============================================
// Security Headers
app.use(helmet({
contentSecurityPolicy: false, // 暫時關閉 CSP 以便開發
}));
// CORS
app.use(cors({
origin: [`http://localhost:${serverConfig.clientPort}`, 'http://localhost:5173'],
credentials: true
}));
// Body Parser
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// Session
app.use(session({
secret: sessionConfig.secret,
resave: sessionConfig.resave,
saveUninitialized: sessionConfig.saveUninitialized,
cookie: sessionConfig.cookie,
name: '5why.sid'
}));
// Rate Limiting
const limiter = rateLimit({
windowMs: securityConfig.rateLimitWindowMs,
max: securityConfig.rateLimitMax,
message: {
success: false,
error: '請求過於頻繁,請稍後再試'
},
standardHeaders: true,
legacyHeaders: false
});
app.use('/api/', limiter);
// Request Logging (Development)
if (process.env.NODE_ENV === 'development') {
app.use((req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
next();
});
}
// ============================================
// Routes
// ============================================
// Health Check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
message: 'Server is running',
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'development'
});
});
// Database Health Check
app.get('/health/db', async (req, res) => {
try {
const isConnected = await testConnection();
res.json({
status: isConnected ? 'ok' : 'error',
database: isConnected ? 'connected' : 'disconnected'
});
} catch (error) {
res.status(500).json({
status: 'error',
database: 'error',
message: error.message
});
}
});
// API Routes
app.use('/api/auth', authRoutes);
app.use('/api/analyze', analyzeRoutes);
app.use('/api/admin', adminRoutes);
app.use('/api/llm-config', llmConfigRoutes);
// Root Endpoint
app.get('/', (req, res) => {
res.json({
message: '5 Why Root Cause Analyzer API',
version: '1.0.0',
endpoints: {
health: '/health',
auth: {
login: 'POST /api/auth/login',
logout: 'POST /api/auth/logout',
me: 'GET /api/auth/me'
},
analyze: {
create: 'POST /api/analyze',
history: 'GET /api/analyze/history',
detail: 'GET /api/analyze/:id',
translate: 'POST /api/analyze/translate'
},
admin: {
dashboard: 'GET /api/admin/dashboard',
users: 'GET /api/admin/users',
analyses: 'GET /api/admin/analyses',
auditLogs: 'GET /api/admin/audit-logs'
},
llmConfig: {
list: 'GET /api/llm-config',
active: 'GET /api/llm-config/active',
create: 'POST /api/llm-config',
update: 'PUT /api/llm-config/:id',
activate: 'PUT /api/llm-config/:id/activate',
delete: 'DELETE /api/llm-config/:id',
test: 'POST /api/llm-config/test'
}
}
});
});
// ============================================
// Error Handling
// ============================================
// 404 Handler
app.use(notFoundHandler);
// Global Error Handler
app.use(errorHandler);
// ============================================
// Server Startup
// ============================================
async function startServer() {
try {
console.log('\n╔════════════════════════════════════════════╗');
console.log('║ 5 Why Analyzer - Server Starting... ║');
console.log('╚════════════════════════════════════════════╝\n');
// Test database connection
console.log('📡 Testing database connection...');
const dbConnected = await testConnection();
if (!dbConnected) {
console.warn('⚠️ Warning: Database connection failed');
console.warn(' Server will start but database features will not work');
}
// Start server
app.listen(PORT, () => {
console.log('\n✅ Server is running!');
console.log(` URL: http://localhost:${PORT}`);
console.log(` Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(` Database: ${dbConnected ? 'Connected ✓' : 'Disconnected ✗'}`);
console.log(`\n📚 API Documentation: http://localhost:${PORT}/`);
console.log(`🔍 Health Check: http://localhost:${PORT}/health`);
console.log('\n💡 Press Ctrl+C to stop the server\n');
});
} catch (error) {
console.error('\n❌ Failed to start server:');
console.error(' Error:', error.message);
process.exit(1);
}
}
// Handle uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
process.exit(1);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('\n📴 SIGTERM received. Shutting down gracefully...');
process.exit(0);
});
process.on('SIGINT', () => {
console.log('\n📴 SIGINT received. Shutting down gracefully...');
process.exit(0);
});
// Start the server
startServer();