forked from Gitlink/gitlink_help_center
feat: add Express API server with chat endpoint and static proxy
This commit is contained in:
parent
11c79472ce
commit
cba879b7f3
|
|
@ -0,0 +1,101 @@
|
|||
// api/server.js
|
||||
const express = require('express');
|
||||
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||
const { search } = require('./search/indexer');
|
||||
const { streamChat } = require('./llm/deepseek');
|
||||
|
||||
const API_PORT = parseInt(process.env.API_PORT || '3000', 10);
|
||||
const SERVE_PORT = parseInt(process.env.SERVE_PORT || '3001', 10);
|
||||
const MAX_HISTORY = 10;
|
||||
|
||||
const SYSTEM_PROMPT = `你是 GitLink 帮助中心的 AI 助手。请基于提供的文档内容回答用户的问题。
|
||||
|
||||
要求:
|
||||
1. 回答要简洁明了,直接回答用户的问题
|
||||
2. 如果提供的文档中没有相关内容,请诚实说明
|
||||
3. 回答时引用具体的操作步骤和页面位置`;
|
||||
|
||||
function buildMessages(query, history, searchResults) {
|
||||
const messages = [{ role: 'system', content: SYSTEM_PROMPT }];
|
||||
|
||||
if (searchResults.length > 0) {
|
||||
const context = searchResults.map((doc, i) =>
|
||||
`[文档${i + 1}] 标题: ${doc.title}\n路径: ${doc.breadcrumb}\nURL: ${doc.url}\n内容:\n${doc.text}`
|
||||
).join('\n\n---\n\n');
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: `以下是相关的帮助中心文档内容:\n\n${context}`,
|
||||
});
|
||||
}
|
||||
|
||||
const recentHistory = history.slice(-MAX_HISTORY);
|
||||
for (const msg of recentHistory) {
|
||||
messages.push({ role: msg.role, content: msg.content });
|
||||
}
|
||||
|
||||
messages.push({ role: 'user', content: query });
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok' });
|
||||
});
|
||||
|
||||
// Chat endpoint (SSE streaming)
|
||||
app.post('/api/chat', async (req, res) => {
|
||||
const { message, history = [] } = req.body;
|
||||
|
||||
if (!message || typeof message !== 'string') {
|
||||
return res.status(400).json({ error: 'message is required' });
|
||||
}
|
||||
|
||||
let searchResults = [];
|
||||
try {
|
||||
searchResults = search(message, 3);
|
||||
} catch (err) {
|
||||
console.error('Search error:', err.message);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
res.setHeader('X-Accel-Buffering', 'no');
|
||||
|
||||
if (searchResults.length > 0) {
|
||||
const links = searchResults.map(doc => ({
|
||||
title: doc.title,
|
||||
path: encodeURI(doc.url),
|
||||
breadcrumb: doc.breadcrumb,
|
||||
}));
|
||||
res.write(`data: ${JSON.stringify({ type: 'sources', links })}\n\n`);
|
||||
}
|
||||
|
||||
try {
|
||||
const messages = buildMessages(message, history, searchResults);
|
||||
for await (const chunk of streamChat(messages)) {
|
||||
res.write(`data: ${JSON.stringify({ type: 'content', text: chunk })}\n\n`);
|
||||
}
|
||||
res.write(`data: ${JSON.stringify({ type: 'done' })}\n\n`);
|
||||
} catch (err) {
|
||||
console.error('LLM error:', err.message);
|
||||
res.write(`data: ${JSON.stringify({ type: 'error', message: 'AI 服务暂时不可用,请稍后重试' })}\n\n`);
|
||||
}
|
||||
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Proxy all other requests to Docusaurus serve
|
||||
app.use('/', createProxyMiddleware({
|
||||
target: `http://localhost:${SERVE_PORT}`,
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
}));
|
||||
|
||||
app.listen(API_PORT, () => {
|
||||
console.log(`API server listening on port ${API_PORT}, proxying to Docusaurus on port ${SERVE_PORT}`);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,7 +11,9 @@
|
|||
"clear": "docusaurus clear",
|
||||
"serve": "docusaurus serve",
|
||||
"write-translations": "docusaurus write-translations",
|
||||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
"write-heading-ids": "docusaurus write-heading-ids",
|
||||
"build:api": "node scripts/generate-doc-index.js",
|
||||
"serve:api": "node api/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "3.9.2",
|
||||
|
|
@ -21,6 +23,9 @@
|
|||
"@node-rs/jieba": "^1.7.0",
|
||||
"clsx": "1.2.1",
|
||||
"docusaurus-plugin-image-zoom": "1.0.1",
|
||||
"express": "^5.2.1",
|
||||
"http-proxy-middleware": "^4.0.0",
|
||||
"pm2": "^7.0.1",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"prismjs": "^1.30.0",
|
||||
"react": "18.2.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue