140 lines
3.9 KiB
JavaScript
140 lines
3.9 KiB
JavaScript
|
|
// 对话上下文管理器 · conversation-manager.js · v1.0
|
|||
|
|
// HoloLake · M-DINGTALK Phase 8
|
|||
|
|
// DEV-004 之之 × 秋秋
|
|||
|
|
// 功能:对话历史存储 · 上下文窗口管理 · 多用户隔离
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
var fs = require('fs');
|
|||
|
|
var path = require('path');
|
|||
|
|
|
|||
|
|
// 对话存储目录
|
|||
|
|
var CONVERSATION_DIR = path.join(__dirname, 'conversations');
|
|||
|
|
if (!fs.existsSync(CONVERSATION_DIR)) {
|
|||
|
|
fs.mkdirSync(CONVERSATION_DIR, { recursive: true });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 配置
|
|||
|
|
var MAX_HISTORY = 20; // 每个用户最多保留20轮对话
|
|||
|
|
var MAX_CONTEXT_TOKENS = 3000; // 上下文窗口大小(估算字符数)
|
|||
|
|
var EXPIRE_HOURS = 24; // 对话24小时后过期
|
|||
|
|
|
|||
|
|
// ===== 获取用户对话文件路径 =====
|
|||
|
|
function getConversationPath(userId) {
|
|||
|
|
var safeId = (userId || 'anonymous').replace(/[^a-zA-Z0-9_-]/g, '_');
|
|||
|
|
return path.join(CONVERSATION_DIR, safeId + '.json');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 加载对话历史 =====
|
|||
|
|
function loadHistory(userId) {
|
|||
|
|
var filePath = getConversationPath(userId);
|
|||
|
|
try {
|
|||
|
|
if (!fs.existsSync(filePath)) return [];
|
|||
|
|
var data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|||
|
|
// 过滤过期消息
|
|||
|
|
var now = Date.now();
|
|||
|
|
var expireMs = EXPIRE_HOURS * 60 * 60 * 1000;
|
|||
|
|
var valid = (data.messages || []).filter(function(msg) {
|
|||
|
|
return (now - msg.timestamp) < expireMs;
|
|||
|
|
});
|
|||
|
|
return valid;
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('[ConvMgr] 加载历史失败:', err.message);
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 保存对话历史 =====
|
|||
|
|
function saveHistory(userId, messages) {
|
|||
|
|
var filePath = getConversationPath(userId);
|
|||
|
|
try {
|
|||
|
|
// 只保留最近N轮
|
|||
|
|
var trimmed = messages.slice(-MAX_HISTORY * 2);
|
|||
|
|
fs.writeFileSync(filePath, JSON.stringify({
|
|||
|
|
userid: userId,
|
|||
|
|
updatedAt: new Date().toISOString(),
|
|||
|
|
messages: trimmed
|
|||
|
|
}, null, 2), 'utf8');
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('[ConvMgr] 保存历史失败:', err.message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ====== 添加消息 ======
|
|||
|
|
function addMessage(userId, role, content) {
|
|||
|
|
var messages = loadHistory(userId);
|
|||
|
|
messages.push({
|
|||
|
|
role: role,
|
|||
|
|
content: content,
|
|||
|
|
timestamp: Date.now()
|
|||
|
|
});
|
|||
|
|
saveHistory(userId, messages);
|
|||
|
|
return messages;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ====== 构建上下文窗口(给LLM用) ======
|
|||
|
|
function buildContext(userId, currentMessage) {
|
|||
|
|
var history = loadHistory(userId);
|
|||
|
|
var contextMessages = [];
|
|||
|
|
var totalChars = 0;
|
|||
|
|
|
|||
|
|
// 从最新的开始往回取,直到超过上下文窗口
|
|||
|
|
for (var i = history.length - 1; i >= 0; i--) {
|
|||
|
|
var msg = history[i];
|
|||
|
|
var msgChars = (msg.content || '').length;
|
|||
|
|
if (totalChars + msgChars > MAX_CONTEXT_TOKENS) break;
|
|||
|
|
contextMessages.unshift({
|
|||
|
|
role: msg.role,
|
|||
|
|
content: msg.content
|
|||
|
|
});
|
|||
|
|
totalChars += msgChars;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加当前消息
|
|||
|
|
contextMessages.push({
|
|||
|
|
role: 'user',
|
|||
|
|
content: currentMessage
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return contextMessages;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 清除用户对话历史 =====
|
|||
|
|
function clearHistory(userId) {
|
|||
|
|
var filePath = getConversationPath(userId);
|
|||
|
|
try {
|
|||
|
|
if (fs.existsSync(filePath)) {
|
|||
|
|
fs.unlinkSync(filePath);
|
|||
|
|
}
|
|||
|
|
return true;
|
|||
|
|
} catch (err) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ===== 获取活跃对话统计 =====
|
|||
|
|
function getStats() {
|
|||
|
|
try {
|
|||
|
|
var files = fs.readdirSync(CONVERSATION_DIR).filter(function(f) {
|
|||
|
|
return f.endsWith('.json');
|
|||
|
|
});
|
|||
|
|
return {
|
|||
|
|
activeUsers: files.length,
|
|||
|
|
conversationDir: CONVERSATION_DIR
|
|||
|
|
};
|
|||
|
|
} catch (err) {
|
|||
|
|
return {
|
|||
|
|
activeUsers: 0,
|
|||
|
|
conversationDir: CONVERSATION_DIR
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = {
|
|||
|
|
loadHistory: loadHistory,
|
|||
|
|
saveHistory: saveHistory,
|
|||
|
|
addMessage: addMessage,
|
|||
|
|
buildContext: buildContext,
|
|||
|
|
clearHistory: clearHistory,
|
|||
|
|
getStats: getStats
|
|||
|
|
};
|