376 lines
8.9 KiB
JavaScript
376 lines
8.9 KiB
JavaScript
const express = require('express');
|
|
const ChatService = require('../services/ChatService');
|
|
|
|
const router = express.Router();
|
|
const chatService = new ChatService();
|
|
|
|
/**
|
|
* 发送消息
|
|
* POST /api/chat/send
|
|
*/
|
|
router.post('/send', async (req, res) => {
|
|
try {
|
|
const { conversationId, message, options = {} } = req.body;
|
|
|
|
// 验证必需参数
|
|
if (!conversationId || isNaN(conversationId)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '无效的对话ID',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
if (!message || message.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '消息内容不能为空',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
// 限制消息长度
|
|
if (message.length > 10000) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '消息长度不能超过10000个字符',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
// 处理消息
|
|
const result = await chatService.processMessage(
|
|
parseInt(conversationId),
|
|
message.trim(),
|
|
options
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
message: '消息发送成功'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('发送消息API错误:', error);
|
|
|
|
// 根据错误类型返回不同的HTTP状态码
|
|
let statusCode = 500;
|
|
let errorMessage = '发送消息失败';
|
|
|
|
if (error.message === '对话不存在') {
|
|
statusCode = 404;
|
|
errorMessage = '对话未找到';
|
|
} else if (error.message.includes('AI服务配置错误')) {
|
|
statusCode = 503;
|
|
errorMessage = 'AI服务配置错误';
|
|
} else if (error.message.includes('请求过于频繁')) {
|
|
statusCode = 429;
|
|
errorMessage = '请求过于频繁';
|
|
} else if (error.message.includes('网络')) {
|
|
statusCode = 503;
|
|
errorMessage = '网络连接异常';
|
|
}
|
|
|
|
res.status(statusCode).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: errorMessage
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 发送流式消息
|
|
* POST /api/chat/stream
|
|
*/
|
|
router.post('/stream', async (req, res) => {
|
|
try {
|
|
const { conversationId, message, options = {} } = req.body;
|
|
|
|
// 验证必需参数
|
|
if (!conversationId || isNaN(conversationId)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '无效的对话ID',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
if (!message || message.trim().length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '消息内容不能为空',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
// 设置流式响应头
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/plain; charset=utf-8',
|
|
'Transfer-Encoding': 'chunked',
|
|
'Cache-Control': 'no-cache',
|
|
'Connection': 'keep-alive'
|
|
});
|
|
|
|
const streamResult = await chatService.processStreamMessage(
|
|
parseInt(conversationId),
|
|
message.trim(),
|
|
options
|
|
);
|
|
|
|
// 发送用户消息信息
|
|
res.write(JSON.stringify({
|
|
type: 'user_message',
|
|
data: streamResult.userMessage
|
|
}) + '\n');
|
|
|
|
let fullContent = '';
|
|
|
|
// 处理流式响应
|
|
streamResult.stream.on('data', (chunk) => {
|
|
const lines = chunk.toString().split('\n');
|
|
|
|
for (const line of lines) {
|
|
if (line.startsWith('data: ')) {
|
|
const data = line.slice(6);
|
|
|
|
if (data === '[DONE]') {
|
|
// 流式响应结束
|
|
res.write(JSON.stringify({
|
|
type: 'done',
|
|
data: { content: fullContent }
|
|
}) + '\n');
|
|
res.end();
|
|
|
|
// 保存完整的AI回复
|
|
chatService.completeStreamMessage(conversationId, fullContent)
|
|
.catch(error => {
|
|
console.error('保存流式消息失败:', error);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const parsed = JSON.parse(data);
|
|
const delta = parsed.choices?.[0]?.delta;
|
|
|
|
if (delta?.content) {
|
|
fullContent += delta.content;
|
|
|
|
// 发送增量内容
|
|
res.write(JSON.stringify({
|
|
type: 'content',
|
|
data: { delta: delta.content, content: fullContent }
|
|
}) + '\n');
|
|
}
|
|
|
|
} catch (parseError) {
|
|
console.error('解析流式数据失败:', parseError);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
streamResult.stream.on('error', (error) => {
|
|
console.error('流式响应错误:', error);
|
|
res.write(JSON.stringify({
|
|
type: 'error',
|
|
data: { error: error.message }
|
|
}) + '\n');
|
|
res.end();
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('流式消息API错误:', error);
|
|
|
|
if (!res.headersSent) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: '流式消息发送失败'
|
|
});
|
|
} else {
|
|
res.write(JSON.stringify({
|
|
type: 'error',
|
|
data: { error: error.message }
|
|
}) + '\n');
|
|
res.end();
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 重新生成回复
|
|
* POST /api/chat/regenerate
|
|
*/
|
|
router.post('/regenerate', async (req, res) => {
|
|
try {
|
|
const { conversationId, options = {} } = req.body;
|
|
|
|
if (!conversationId || isNaN(conversationId)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '无效的对话ID',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
const result = await chatService.regenerateResponse(
|
|
parseInt(conversationId),
|
|
options
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
message: '重新生成回复成功'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('重新生成回复API错误:', error);
|
|
|
|
let statusCode = 500;
|
|
let errorMessage = '重新生成回复失败';
|
|
|
|
if (error.message === '对话不存在') {
|
|
statusCode = 404;
|
|
errorMessage = '对话未找到';
|
|
} else if (error.message === '没有找到用户消息') {
|
|
statusCode = 400;
|
|
errorMessage = '没有可重新生成的消息';
|
|
}
|
|
|
|
res.status(statusCode).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: errorMessage
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 检查LLM服务状态
|
|
* GET /api/chat/status
|
|
*/
|
|
router.get('/status', async (req, res) => {
|
|
try {
|
|
const status = await chatService.checkLLMStatus();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: status,
|
|
message: 'LLM服务状态检查完成'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('检查LLM状态API错误:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: '检查LLM状态失败'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 获取聊天统计信息
|
|
* GET /api/chat/stats
|
|
*/
|
|
router.get('/stats', async (req, res) => {
|
|
try {
|
|
const stats = await chatService.getChatStats();
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
message: '获取聊天统计成功'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('获取聊天统计API错误:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: '获取聊天统计失败'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 清除缓存
|
|
* POST /api/chat/clear-cache
|
|
*/
|
|
router.post('/clear-cache', async (req, res) => {
|
|
try {
|
|
const { type = 'all' } = req.body;
|
|
|
|
const cacheManager = require('../utils/cache');
|
|
|
|
if (type === 'all') {
|
|
cacheManager.clearAll();
|
|
} else {
|
|
cacheManager.clear(type);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `${type}缓存清理成功`
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('清除缓存API错误:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: '清除缓存失败'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 获取对话上下文
|
|
* GET /api/chat/context/:conversationId
|
|
*/
|
|
router.get('/context/:conversationId', async (req, res) => {
|
|
try {
|
|
const { conversationId } = req.params;
|
|
const { maxTokens = 4000 } = req.query;
|
|
|
|
if (!conversationId || isNaN(conversationId)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: '无效的对话ID',
|
|
message: '参数错误'
|
|
});
|
|
}
|
|
|
|
const context = await chatService.getConversationContext(
|
|
parseInt(conversationId),
|
|
parseInt(maxTokens)
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
context,
|
|
messageCount: context.length,
|
|
estimatedTokens: context.reduce((sum, msg) =>
|
|
sum + (msg.content?.length || 0), 0
|
|
)
|
|
},
|
|
message: '获取对话上下文成功'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('获取对话上下文API错误:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message,
|
|
message: '获取对话上下文失败'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router; |