132 lines
6.3 KiB
JavaScript
132 lines
6.3 KiB
JavaScript
// 消息路由 + 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 { TOOLS } = require('../ai/tools');
|
|
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];
|
|
var allIgnore = responses.every(function(r) { return r.action === 'ignore'; });
|
|
responses.forEach(function(r) {
|
|
if (r.action !== 'ignore') handleAIResult(r, roomId, username, text || '', attachments || []);
|
|
});
|
|
if (allIgnore) broadcastToRoom(roomId, { type: 'clear_temp' });
|
|
}
|
|
} 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 systemPrompt = buildSystemPrompt(username, isAdmin);
|
|
var messages = [
|
|
{ role: 'system', content: systemPrompt }
|
|
];
|
|
historyMessages.forEach(function(m) { messages.push(m); });
|
|
messages.push({ role: 'system', content: '当前服务器时间:' + new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }) });
|
|
const message = await callDeepSeek(messages, username, 0.1, true, TOOLS);
|
|
|
|
// 1) 工具调用路径:模型调用了 create_purchase / query_purchases / reply_chat / ignore
|
|
if (message && Array.isArray(message.tool_calls) && message.tool_calls.length) {
|
|
message.tool_calls.forEach(function(tc, i) {
|
|
const fn = tc.function || {};
|
|
console.log('🤖 AI 工具调用[' + i + ']: ' + (fn.name || '?') + ' args=' + (fn.arguments || '').substring(0, 300));
|
|
});
|
|
const results = [];
|
|
message.tool_calls.forEach(function(tc) {
|
|
const fn = tc.function || {};
|
|
const name = fn.name || '';
|
|
let args = {};
|
|
try { args = JSON.parse(fn.arguments || '{}'); } catch(e) { console.warn('⚠️ 工具参数解析失败:', name, (fn.arguments || '').substring(0, 100)); args = {}; }
|
|
if (name === 'create_purchase') {
|
|
// 工具参数已是结构化对象,补上 action 字段以复用现有 handleAIResult 的 purchase 分支
|
|
results.push(Object.assign({ action: 'purchase' }, args));
|
|
} else if (name === 'query_purchases') {
|
|
// 复用现有 query 分支(本地查库 + 二次汇总),不依赖模型返回结果
|
|
results.push({ action: 'query', keywords: args.keywords || '' });
|
|
} else if (name === 'reply_chat') {
|
|
results.push({ action: 'chat', reply: args.reply || '好的。' });
|
|
} else {
|
|
// ignore 或未知工具 → 忽略
|
|
console.warn('⚠️ 未识别的工具名:', name);
|
|
results.push({ action: 'ignore' });
|
|
}
|
|
});
|
|
return results;
|
|
}
|
|
|
|
// 2) 纯文本/JSON 路径:query / delete / clear_all 等按旧逻辑解析
|
|
const content = (message && typeof message === 'object') ? (message.content || '') : (message || '');
|
|
console.log('🤖 AI 返回:', content.substring(0, 200));
|
|
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;
|