// 消息路由 + 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;