llm-chat/backend/models/Message.js

227 lines
5.9 KiB
JavaScript
Raw Normal View History

const database = require('./database');
class Message {
constructor(data = {}) {
this.id = data.id;
this.conversation_id = data.conversation_id;
this.role = data.role; // 'user' 或 'assistant'
this.content = data.content;
this.timestamp = data.timestamp;
}
/**
* 创建新消息
*/
static async create(conversationId, role, content) {
try {
// 验证角色
if (!['user', 'assistant'].includes(role)) {
throw new Error('无效的消息角色');
}
const sql = `
INSERT INTO messages (conversation_id, role, content, timestamp)
VALUES (?, ?, ?, datetime('now'))
`;
const result = await database.run(sql, [conversationId, role, content]);
// 返回新创建的消息
return await Message.findById(result.id);
} catch (error) {
console.error('创建消息失败:', error);
throw error;
}
}
/**
* 根据ID查找消息
*/
static async findById(id) {
try {
const sql = 'SELECT * FROM messages WHERE id = ?';
const row = await database.get(sql, [id]);
return row ? new Message(row) : null;
} catch (error) {
console.error('查找消息失败:', error);
throw error;
}
}
/**
* 获取对话的所有消息按时间正序
*/
static async findByConversationId(conversationId, limit = 100, offset = 0) {
try {
const sql = `
SELECT * FROM messages
WHERE conversation_id = ?
ORDER BY timestamp ASC
LIMIT ? OFFSET ?
`;
const rows = await database.all(sql, [conversationId, limit, offset]);
return rows.map(row => new Message(row));
} catch (error) {
console.error('获取对话消息失败:', error);
throw error;
}
}
/**
* 获取对话的最近N条消息用于构建上下文
*/
static async getRecentMessages(conversationId, limit = 10) {
try {
const sql = `
SELECT * FROM messages
WHERE conversation_id = ?
ORDER BY timestamp DESC
LIMIT ?
`;
const rows = await database.all(sql, [conversationId, limit]);
// 返回正序排列的消息
return rows.reverse().map(row => new Message(row));
} catch (error) {
console.error('获取最近消息失败:', error);
throw error;
}
}
/**
* 获取对话的消息总数
*/
static async getMessageCount(conversationId) {
try {
const sql = 'SELECT COUNT(*) as count FROM messages WHERE conversation_id = ?';
const result = await database.get(sql, [conversationId]);
return result.count;
} catch (error) {
console.error('获取消息数量失败:', error);
throw error;
}
}
/**
* 删除指定消息
*/
static async delete(id) {
try {
const sql = 'DELETE FROM messages WHERE id = ?';
const result = await database.run(sql, [id]);
if (result.changes === 0) {
throw new Error('消息不存在');
}
return true;
} catch (error) {
console.error('删除消息失败:', error);
throw error;
}
}
/**
* 删除对话的所有消息
*/
static async deleteByConversationId(conversationId) {
try {
const sql = 'DELETE FROM messages WHERE conversation_id = ?';
const result = await database.run(sql, [conversationId]);
return result.changes;
} catch (error) {
console.error('删除对话消息失败:', error);
throw error;
}
}
/**
* 批量创建消息
*/
static async createBatch(messages) {
try {
await database.beginTransaction();
const results = [];
for (const messageData of messages) {
const { conversationId, role, content } = messageData;
const message = await Message.create(conversationId, role, content);
results.push(message);
}
await database.commit();
return results;
} catch (error) {
await database.rollback();
console.error('批量创建消息失败:', error);
throw error;
}
}
/**
* 搜索消息内容
*/
static async search(query, conversationId = null, limit = 50) {
try {
let sql = `
SELECT m.*, c.title as conversation_title
FROM messages m
JOIN conversations c ON m.conversation_id = c.id
WHERE m.content LIKE ?
`;
const params = [`%${query}%`];
if (conversationId) {
sql += ' AND m.conversation_id = ?';
params.push(conversationId);
}
sql += ' ORDER BY m.timestamp DESC LIMIT ?';
params.push(limit);
const rows = await database.all(sql, params);
return rows.map(row => ({
...new Message(row),
conversation_title: row.conversation_title
}));
} catch (error) {
console.error('搜索消息失败:', error);
throw error;
}
}
/**
* 获取用户消息和AI回复的对话历史用于LLM上下文
*/
static async getConversationHistory(conversationId, maxTokens = 4000) {
try {
// 简单估算每个字符约占1个token中文可能占更多
const messages = await Message.getRecentMessages(conversationId, 20);
const history = [];
let totalLength = 0;
// 从最新消息开始,控制总长度
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
const messageLength = message.content.length;
if (totalLength + messageLength > maxTokens && history.length > 0) {
break;
}
history.unshift({
role: message.role,
content: message.content
});
totalLength += messageLength;
}
return history;
} catch (error) {
console.error('获取对话历史失败:', error);
throw error;
}
}
}
module.exports = Message;