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
|
||
};
|