Files
ai-xiaocai/server/routes/messages.js
2026-07-29 09:48:44 +08:00

112 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 消息路由 + AI 分析
const express = require('express');
const { authMiddleware } = require('../middleware/auth');
const { broadcastToRoomExcludeSelf, broadcastToRoom } = require('../ws');
const { buildSystemPrompt } = require('../ai/prompt');
const { validate, normalize } = require('../ai/validator');
const { callDeepSeek } = require('../ai/client');
const { getHistory, addToHistory, hasPendingAction, handleAIResult, executePendingAction } = require('../handlers');
const db = require('../db');
const { timestamp } = require('../utils');
const router = express.Router();
router.get('/:roomId/messages', authMiddleware, (req, res) => {
const msgs = db.prepare('SELECT * FROM messages WHERE room_id = ? ORDER BY id ASC').all(req.params.roomId);
res.json(msgs);
});
router.post('/:roomId/messages', authMiddleware, async (req, res) => {
const { text, attachments } = req.body;
console.log('📨 收到消息, text:', (text||'').substring(0,30), 'attachments:', JSON.stringify(attachments));
const roomId = req.params.roomId;
const username = req.user.username;
const now = timestamp();
const result = db.prepare('INSERT INTO messages (room_id, user, text, attachments, timestamp) VALUES (?,?,?,?,?)').run(
roomId, username, text || '', attachments ? JSON.stringify(attachments) : null, now
);
const msg = { id: result.lastInsertRowid, room_id: roomId, user: username, text: text || '', attachments: attachments || [], timestamp: now };
const lastMsg = db.prepare('SELECT text, timestamp FROM messages WHERE room_id = ? ORDER BY id DESC LIMIT 1').get(roomId);
broadcastToRoomExcludeSelf(roomId, username, {
type: 'new_message', message: msg,
room_preview: { room_id: roomId, last_message: lastMsg ? lastMsg.text : '', last_time: lastMsg ? lastMsg.timestamp : '' }
});
addToHistory(roomId, 'user', `${username}: ${text || '图片'}`);
res.json(msg);
if (text && text.trim() === '确认' && hasPendingAction(roomId, username)) {
executePendingAction(roomId, username);
return;
}
try {
const history = getHistory(roomId);
const historyMessages = history.map(e => ({ role: e.role, content: e.content }));
const aiResponse = await analyzeWithDeepSeek(text || '', historyMessages, username, req.user.isAdmin, roomId);
if (aiResponse) {
var responses = Array.isArray(aiResponse) ? aiResponse : [aiResponse];
responses.forEach(function(r) {
if (r.action !== 'ignore') handleAIResult(r, roomId, username, text || '', attachments || []);
});
}
} catch (e) {
console.error('AI 分析失败:', e.message);
var errText = '❌ ' + (e.message || 'AI 处理失败,请稍后重试');
var errResult = db.prepare('INSERT INTO messages (room_id, user, text, timestamp) VALUES (?,?,?,?)').run(roomId, '小财', errText, timestamp());
var errMsg = { id: errResult.lastInsertRowid, room_id: roomId, user: '小财', text: errText, attachments: [], timestamp: timestamp() };
broadcastToRoom(roomId, { type: 'new_message', message: errMsg });
}
});
async function analyzeWithDeepSeek(text, historyMessages, username, isAdmin, roomId) {
// 采购清单(单独消息)
var purchases = db.prepare("SELECT item, quantity, amount, status, created_at FROM purchases WHERE room_id = ? ORDER BY created_at DESC LIMIT 10").all(roomId);
var purchaseMsg = purchases.length
? '📋 当前房间采购清单最近10条\n' + purchases.map(function(p) { return '- ' + p.item + ' | ×' + p.quantity + ' | ¥' + p.amount + ' | ' + p.status + ' | ' + p.created_at; }).join('\n')
: '';
// 系统提示词(稳定,不含时间)
var systemPrompt = buildSystemPrompt(username, isAdmin);
var messages = [
{ role: 'system', content: systemPrompt }
];
// 全量历史,每 30 条一轮前缀稳定期,轮内全部命中
historyMessages.forEach(function(m) { messages.push(m); });
// 采购清单、时间放最后,变动不影响历史命中
if (purchaseMsg) messages.push({ role: 'system', content: purchaseMsg });
messages.push({ role: 'system', content: '当前服务器时间:' + new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }) });
// 当前消息已在 historyMessages 中,不再重复 push
const content = await callDeepSeek(messages, username);
console.log('🤖 AI 返回:', content.substring(0, 200));
// 打印消息结构,方便排查缓存
console.log('📋 消息结构:', messages.map(function(m, i) {
var preview = m.content.substring(0, 60).split('\n').join(' ');
return '[' + i + '] ' + m.role + ' (' + m.content.length + '字): ' + preview;
}).join('\n'));
try {
var cleaned = content.replace(/```json|```/g, '').trim();
if (!cleaned) { console.warn('⚠️ AI 返回空内容'); return { action: 'ignore' }; }
var parsed = JSON.parse(cleaned);
// 支持批量AI 可能返回数组
if (Array.isArray(parsed)) {
parsed.forEach(function(item, i) {
normalize(item);
var v = validate(item);
if (!v.valid) console.warn('⚠️ AI 返回[' + i + ']校验失败:', v.error);
});
} else {
normalize(parsed);
var v = validate(parsed);
if (!v.valid) console.warn('⚠️ AI 返回校验失败:', v.error);
}
return parsed;
} catch (e) {
// AI 返回非 JSON如纯文本回复图片消息→ 降级为 chat
console.warn('⚠️ AI 返回非 JSON降级为 chat:', content.substring(0, 50));
return { action: 'chat', reply: content };
}
}
module.exports = router;