import express from 'express' import { createServer as createViteServer } from 'vite' import fs from 'fs' import path from 'path' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const CONFIG_DIR = path.join(__dirname, '.config') const CONFIG_FILE = path.join(CONFIG_DIR, 'ai-config.json') // Ensure config directory exists if (!fs.existsSync(CONFIG_DIR)) { fs.mkdirSync(CONFIG_DIR, { recursive: true }) } // Load config from file function loadConfig() { try { if (fs.existsSync(CONFIG_FILE)) { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')) } } catch (e) { console.error('Failed to load config:', e) } return { provider: 'aliyun', apiKey: '', model: 'qwen3.6-plus', baseUrl: 'https://coding.dashscope.aliyuncs.com/v1', temperature: 0.7, maxTokens: 8000 } } // Save config to file function saveConfig(config) { try { fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8') return true } catch (e) { console.error('Failed to save config:', e) return false } } const app = express() app.use(express.json()) // API: Get config app.get('/api/config', (req, res) => { const config = loadConfig() // Don't expose full API key in GET request res.json({ ...config, apiKey: config.apiKey ? config.apiKey.slice(0, 8) + '********' : '' }) }) // API: Check if API key is configured app.get('/api/config/check', (req, res) => { const config = loadConfig() res.json({ configured: !!config.apiKey }) }) // API: Update config app.post('/api/config', (req, res) => { const currentConfig = loadConfig() const updates = req.body // If updating API key, use the new value; otherwise keep existing const newConfig = { ...currentConfig, ...updates, apiKey: updates.apiKey || currentConfig.apiKey } if (saveConfig(newConfig)) { res.json({ success: true }) } else { res.status(500).json({ success: false, message: 'Failed to save config' }) } }) // API: Test connection app.post('/api/config/test', async (req, res) => { const { apiKey, model, baseUrl } = req.body const config = loadConfig() const testApiKey = apiKey || config.apiKey const testModel = model || config.model const testBaseUrl = baseUrl || config.baseUrl if (!testApiKey) { return res.json({ success: false, message: '请先输入 API Key' }) } try { const response = await fetch(`${testBaseUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${testApiKey}` }, body: JSON.stringify({ model: testModel, messages: [{ role: 'user', content: '你好,请回复OK' }], max_tokens: 10 }) }) const data = await response.json() if (response.ok && data.choices) { res.json({ success: true, message: `连接成功!模型: ${data.model || testModel}` }) } else { res.json({ success: false, message: data.error?.message || '连接失败' }) } } catch (error) { res.json({ success: false, message: `网络错误: ${error.message}` }) } }) // API: Proxy AI chat request (keeps API key on server) app.post('/api/chat', async (req, res) => { const config = loadConfig() if (!config.apiKey) { return res.status(401).json({ error: { message: '请先配置 AI API Key(访问 /settings)' } }) } // Handle client disconnect let clientDisconnected = false req.on('close', () => { clientDisconnected = true }) try { const response = await fetch(`${config.baseUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.apiKey}` }, body: JSON.stringify({ ...req.body, model: req.body.model || config.model, temperature: req.body.temperature ?? config.temperature, max_tokens: req.body.max_tokens || config.maxTokens, response_format: { type: 'json_object' } }) }) if (!response.ok) { const error = await response.json().catch(() => ({})) return res.status(response.status).json(error) } // Stream the response to client res.setHeader('Content-Type', 'text/event-stream') res.setHeader('Cache-Control', 'no-cache') res.setHeader('Connection', 'keep-alive') res.setHeader('X-Accel-Buffering', 'no') const reader = response.body.getReader() const decoder = new TextDecoder() while (true) { if (clientDisconnected) { reader.cancel() return } const { done, value } = await reader.read() if (done) break const chunk = decoder.decode(value, { stream: true }) try { res.write(chunk) } catch (e) { // Client disconnected, stop writing reader.cancel() return } } res.end() } catch (error) { if (!clientDisconnected) { res.status(500).json({ error: { message: `请求失败: ${error.message}` } }) } } }) async function start() { const isDev = process.env.NODE_ENV !== 'production' if (isDev) { // Development: use Vite middleware const vite = await createViteServer({ server: { middlewareMode: true }, appType: 'spa' }) app.use(vite.middlewares) const port = 5173 app.listen(port, () => { console.log(`Server running at http://localhost:${port}`) console.log(`API Key stored in: ${CONFIG_FILE}`) }) } else { // Production: serve static files app.use(express.static(path.join(__dirname, 'dist'))) const port = process.env.PORT || 3000 app.listen(port, () => { console.log(`Server running on port ${port}`) }) } } start()