import express from 'express'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { saveRecord, listRecords, getRecord, updateRecord, deleteRecord, getWord, listWords, upsertWordData, getRoot, } from './db.js'; import { queryWords, queryWordsStream } from './llm.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const app = express(); app.use(express.json({ limit: '2mb' })); app.use(express.static(path.join(__dirname, 'public'))); // 发起查询 app.post('/api/query', async (req, res) => { const sentence = (req.body.sentence || '').trim(); const words = Array.isArray(req.body.words) ? req.body.words.map((w) => String(w).trim()).filter(Boolean) : []; if (!sentence) return res.status(400).json({ error: '请提供英语句子' }); if (words.length === 0) return res.status(400).json({ error: '请提供至少一个要查询的单词' }); if (words.length > 5) return res.status(400).json({ error: '一次最多查询 5 个单词' }); try { const { raw, parsed } = await queryWords(sentence, words); const record = saveRecord({ sentence, words, result: parsed, raw_result: raw, }); res.json(record); } catch (err) { console.error('查询失败:', err.message); res.status(502).json({ error: `查询失败:${err.message}` }); } }); // 流式查询:SSE 增量推送 LLM 原始输出,完成后推送已保存的记录 app.post('/api/query/stream', async (req, res) => { const sentence = (req.body.sentence || '').trim(); const words = Array.isArray(req.body.words) ? req.body.words.map((w) => String(w).trim()).filter(Boolean) : []; // 校验失败直接返回普通 JSON 错误 if (!sentence) return res.status(400).json({ error: '请提供英语句子' }); if (words.length === 0) return res.status(400).json({ error: '请提供至少一个要查询的单词' }); if (words.length > 5) return res.status(400).json({ error: '一次最多查询 5 个单词' }); res.set({ 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache', Connection: 'keep-alive', 'X-Accel-Buffering': 'no', }); res.flushHeaders(); const send = (obj) => res.write(`data: ${JSON.stringify(obj)}\n\n`); try { // 查询已在词库中的单词:把已有释义带给 LLM,用于同义项合并 const existingWords = words .map((w) => getWord(w)) .filter(Boolean) .map((r) => ({ word: r.word, senses: r.senses.map((s) => s.sense), roots: r.roots })); const { raw, parsed } = await queryWordsStream(sentence, words, (delta) => { send({ type: 'delta', text: delta }); }, existingWords); // 按单词维度并入词库(同义项合并、原句计入该单词记录) for (const w of parsed.words || []) { try { upsertWordData(w, sentence, parsed.sentence_translation || ''); } catch (e) { console.error('词库更新失败:', e.message); } } const record = saveRecord({ sentence, words, result: parsed, raw_result: raw, }); send({ type: 'done', record }); } catch (err) { console.error('流式查询失败:', err.message); send({ type: 'error', message: `查询失败:${err.message}` }); } finally { res.end(); } }); // 词库列表 app.get('/api/words', (req, res) => { const { page, size } = pagerParams(req); res.json(listWords(page, size, req.query.q || '')); }); // 单词详情(含全部释义与例句) app.get('/api/words/:word', (req, res) => { const record = getWord(req.params.word.toLowerCase()); if (!record) return res.status(404).json({ error: '词库中不存在该单词' }); res.json(record); }); // 词根详情(该词根下的所有学过的词,按含义变体并列) app.get('/api/roots/:root', (req, res) => { const record = getRoot(req.params.root.toLowerCase()); if (!record) return res.status(404).json({ error: '词根库中不存在' }); res.json(record); }); // 历史列表 app.get('/api/history', (req, res) => { const { page, size } = pagerParams(req); res.json(listRecords(page, size, req.query.q || '')); }); // 历史详情 app.get('/api/history/:id', (req, res) => { const record = getRecord(req.params.id); if (!record) return res.status(404).json({ error: '记录不存在' }); res.json(record); }); // 编辑保存 app.put('/api/history/:id', (req, res) => { const { sentence, words, result } = req.body; if (!sentence || !Array.isArray(words) || !result) { return res.status(400).json({ error: '字段不完整' }); } const record = updateRecord(req.params.id, { sentence, words, result }); if (!record) return res.status(404).json({ error: '记录不存在' }); res.json(record); }); // 删除 app.delete('/api/history/:id', (req, res) => { const ok = deleteRecord(req.params.id); if (!ok) return res.status(404).json({ error: '记录不存在' }); res.json({ ok: true }); }); // 分页参数解析(默认第 1 页、每页 10 条,上限 50) function pagerParams(req) { const page = Math.max(1, parseInt(req.query.page) || 1); const size = Math.min(50, Math.max(1, parseInt(req.query.size) || 10)); return { page, size }; } const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`✅ 服务已启动: http://localhost:${PORT}`); });