first commit
This commit is contained in:
513
AIEC-server/js/chat-manager.js
Normal file
513
AIEC-server/js/chat-manager.js
Normal file
@ -0,0 +1,513 @@
|
||||
/**
|
||||
* 对话管理器 - 负责管理聊天会话的保存、加载和持久化
|
||||
*/
|
||||
class ChatManager {
|
||||
constructor() {
|
||||
this.currentChatId = null;
|
||||
this.chats = new Map(); // 存储所有聊天会话
|
||||
this.storageKey = 'yundage_chat_history';
|
||||
this.currentUserKey = 'yundage_current_user';
|
||||
this.initialized = false;
|
||||
|
||||
// 监听认证状态变化事件
|
||||
window.addEventListener('authStatusChecked', () => {
|
||||
if (!this.initialized) {
|
||||
console.log('ChatManager 收到认证状态检查完成事件,开始初始化');
|
||||
this.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
});
|
||||
|
||||
// 备用方案:如果3秒内没有收到认证事件,强制初始化
|
||||
setTimeout(() => {
|
||||
if (!this.initialized) {
|
||||
console.log('ChatManager 超时初始化');
|
||||
this.init();
|
||||
this.initialized = true;
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
this.loadFromStorage();
|
||||
this.setupAutoSave();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地存储加载聊天记录
|
||||
*/
|
||||
loadFromStorage() {
|
||||
try {
|
||||
// 清空当前聊天记录
|
||||
this.chats.clear();
|
||||
this.currentChatId = null;
|
||||
|
||||
const currentUser = this.getCurrentUser();
|
||||
if (!currentUser) {
|
||||
console.log('没有登录用户,跳过加载');
|
||||
return;
|
||||
}
|
||||
|
||||
const storageKey = `${this.storageKey}_${currentUser.id}`;
|
||||
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
|
||||
|
||||
console.log(`[ChatManager] 开始加载聊天记录...`);
|
||||
console.log(`[ChatManager] 存储键: ${storageKey}`);
|
||||
console.log(`[ChatManager] 当前聊天键: ${currentChatKey}`);
|
||||
|
||||
const savedChats = localStorage.getItem(storageKey);
|
||||
const savedCurrentChatId = localStorage.getItem(currentChatKey);
|
||||
|
||||
console.log(`[ChatManager] localStorage中保存的当前聊天ID: ${savedCurrentChatId}`);
|
||||
|
||||
if (savedChats) {
|
||||
const chatsData = JSON.parse(savedChats);
|
||||
this.chats = new Map(chatsData.map(chat => [chat.id, chat]));
|
||||
console.log(`[ChatManager] 成功加载了 ${this.chats.size} 个聊天会话`);
|
||||
console.log(`[ChatManager] 聊天ID列表:`, Array.from(this.chats.keys()));
|
||||
|
||||
// 恢复当前聊天ID
|
||||
if (savedCurrentChatId && this.chats.has(savedCurrentChatId)) {
|
||||
this.currentChatId = savedCurrentChatId;
|
||||
console.log(`[ChatManager] ✅ 成功恢复当前聊天: ${savedCurrentChatId}`);
|
||||
} else if (savedCurrentChatId) {
|
||||
console.log(`[ChatManager] ⚠️ 保存的聊天ID ${savedCurrentChatId} 不在聊天列表中`);
|
||||
} else {
|
||||
console.log(`[ChatManager] 没有保存的当前聊天ID`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[ChatManager] 未找到保存的聊天记录`);
|
||||
}
|
||||
|
||||
console.log(`[ChatManager] 加载完成,当前聊天ID: ${this.currentChatId}`);
|
||||
} catch (error) {
|
||||
console.error('[ChatManager] 加载聊天记录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存到本地存储
|
||||
*/
|
||||
saveToStorage() {
|
||||
try {
|
||||
const currentUser = this.getCurrentUser();
|
||||
if (!currentUser) {
|
||||
console.log('没有登录用户,跳过保存');
|
||||
return;
|
||||
}
|
||||
|
||||
const storageKey = `${this.storageKey}_${currentUser.id}`;
|
||||
const chatsArray = Array.from(this.chats.values());
|
||||
console.log(`正在保存 ${chatsArray.length} 个聊天到 localStorage,键名: ${storageKey}`);
|
||||
localStorage.setItem(storageKey, JSON.stringify(chatsArray));
|
||||
console.log(`聊天记录已保存 (用户: ${currentUser.username}, ID: ${currentUser.id})`);
|
||||
} catch (error) {
|
||||
console.error('保存聊天记录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*/
|
||||
getCurrentUser() {
|
||||
const userStr = localStorage.getItem(this.currentUserKey);
|
||||
return userStr ? JSON.parse(userStr) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自动保存
|
||||
*/
|
||||
setupAutoSave() {
|
||||
// 每30秒自动保存一次
|
||||
setInterval(() => {
|
||||
if (this.currentChatId) {
|
||||
this.saveToStorage();
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
// 页面卸载时保存
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this.saveToStorage();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新的聊天会话
|
||||
* @param {string} firstMessage - 第一条消息,用于生成标题
|
||||
* @returns {string} 聊天ID
|
||||
*/
|
||||
createChat(firstMessage = '') {
|
||||
const chatId = `chat_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
const chat = {
|
||||
id: chatId,
|
||||
title: this.generateTitle(firstMessage),
|
||||
messages: [],
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
metadata: {
|
||||
messageCount: 0,
|
||||
lastActive: new Date().toISOString()
|
||||
}
|
||||
};
|
||||
|
||||
this.chats.set(chatId, chat);
|
||||
this.currentChatId = chatId;
|
||||
|
||||
// 保存当前聊天ID
|
||||
const currentUser = this.getCurrentUser();
|
||||
if (currentUser) {
|
||||
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
|
||||
localStorage.setItem(currentChatKey, chatId);
|
||||
}
|
||||
|
||||
this.saveToStorage();
|
||||
|
||||
return chatId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成聊天标题
|
||||
* @param {string} firstMessage - 第一条消息
|
||||
* @returns {string} 标题
|
||||
*/
|
||||
generateTitle(firstMessage) {
|
||||
if (!firstMessage) return '新对话';
|
||||
|
||||
// 移除多余的空格和换行
|
||||
const cleaned = firstMessage.trim().replace(/\s+/g, ' ');
|
||||
|
||||
// 如果消息太长,截取前30个字符
|
||||
if (cleaned.length > 30) {
|
||||
return cleaned.substring(0, 30) + '...';
|
||||
}
|
||||
|
||||
return cleaned || '新对话';
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加消息到当前聊天
|
||||
* @param {string} content - 消息内容
|
||||
* @param {string} type - 消息类型 ('user' 或 'ai')
|
||||
* @param {object} metadata - 额外的元数据
|
||||
*/
|
||||
addMessage(content, type, metadata = {}) {
|
||||
if (!this.currentChatId) {
|
||||
console.error('没有活动的聊天会话');
|
||||
return;
|
||||
}
|
||||
|
||||
const chat = this.chats.get(this.currentChatId);
|
||||
if (!chat) {
|
||||
console.error('聊天会话不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = {
|
||||
id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
content,
|
||||
contentType: metadata.contentType || 'markdown', // 默认使用markdown
|
||||
type,
|
||||
timestamp: new Date().toISOString(),
|
||||
status: type === 'user' ? 'sent' : 'complete',
|
||||
metadata: {
|
||||
deviceInfo: navigator.userAgent,
|
||||
...metadata
|
||||
}
|
||||
};
|
||||
|
||||
chat.messages.push(message);
|
||||
chat.updatedAt = new Date().toISOString();
|
||||
chat.metadata.messageCount = chat.messages.length;
|
||||
chat.metadata.lastActive = new Date().toISOString();
|
||||
|
||||
// 如果是第一条消息,更新标题
|
||||
if (chat.messages.length === 1 && type === 'user') {
|
||||
chat.title = this.generateTitle(content);
|
||||
}
|
||||
|
||||
this.chats.set(this.currentChatId, chat);
|
||||
|
||||
// 触发消息添加事件
|
||||
this.dispatchEvent('messageAdded', { chatId: this.currentChatId, message });
|
||||
|
||||
// 保存到本地存储
|
||||
this.saveToStorage();
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天会话
|
||||
* @param {string} chatId - 聊天ID
|
||||
* @returns {object|null} 聊天会话
|
||||
*/
|
||||
getChat(chatId) {
|
||||
return this.chats.get(chatId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前聊天会话
|
||||
* @returns {object|null} 当前聊天会话
|
||||
*/
|
||||
getCurrentChat() {
|
||||
return this.currentChatId ? this.getChat(this.currentChatId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换到指定聊天
|
||||
* @param {string} chatId - 聊天ID
|
||||
*/
|
||||
switchToChat(chatId) {
|
||||
if (this.chats.has(chatId)) {
|
||||
this.currentChatId = chatId;
|
||||
const chat = this.chats.get(chatId);
|
||||
chat.metadata.lastActive = new Date().toISOString();
|
||||
|
||||
// 保存当前聊天ID
|
||||
const currentUser = this.getCurrentUser();
|
||||
if (currentUser) {
|
||||
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
|
||||
localStorage.setItem(currentChatKey, chatId);
|
||||
console.log(`保存当前聊天ID: ${chatId}`);
|
||||
}
|
||||
|
||||
// 触发聊天切换事件
|
||||
this.dispatchEvent('chatSwitched', { chatId, chat });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除聊天会话
|
||||
* @param {string} chatId - 聊天ID
|
||||
*/
|
||||
deleteChat(chatId) {
|
||||
if (this.chats.has(chatId)) {
|
||||
this.chats.delete(chatId);
|
||||
|
||||
// 如果删除的是当前聊天,清空当前聊天ID
|
||||
if (this.currentChatId === chatId) {
|
||||
this.currentChatId = null;
|
||||
|
||||
// 清除保存的当前聊天ID
|
||||
const currentUser = this.getCurrentUser();
|
||||
if (currentUser) {
|
||||
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
|
||||
localStorage.removeItem(currentChatKey);
|
||||
}
|
||||
}
|
||||
|
||||
this.saveToStorage();
|
||||
|
||||
// 触发聊天删除事件
|
||||
this.dispatchEvent('chatDeleted', { chatId });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新聊天标题
|
||||
* @param {string} chatId - 聊天ID
|
||||
* @param {string} newTitle - 新标题
|
||||
*/
|
||||
updateChatTitle(chatId, newTitle) {
|
||||
const chat = this.chats.get(chatId);
|
||||
if (chat) {
|
||||
chat.title = newTitle;
|
||||
chat.updatedAt = new Date().toISOString();
|
||||
this.chats.set(chatId, chat);
|
||||
this.saveToStorage();
|
||||
|
||||
// 触发标题更新事件
|
||||
this.dispatchEvent('chatTitleUpdated', { chatId, title: newTitle });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有聊天列表
|
||||
* @param {object} options - 选项
|
||||
* @returns {array} 聊天列表
|
||||
*/
|
||||
getAllChats(options = {}) {
|
||||
const { sortBy = 'updatedAt', order = 'desc', limit = null } = options;
|
||||
|
||||
let chatsArray = Array.from(this.chats.values());
|
||||
|
||||
// 排序
|
||||
chatsArray.sort((a, b) => {
|
||||
const aValue = a[sortBy];
|
||||
const bValue = b[sortBy];
|
||||
|
||||
if (order === 'desc') {
|
||||
return bValue > aValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
// 限制数量
|
||||
if (limit) {
|
||||
chatsArray = chatsArray.slice(0, limit);
|
||||
}
|
||||
|
||||
return chatsArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索聊天记录
|
||||
* @param {string} query - 搜索关键词
|
||||
* @returns {array} 匹配的聊天列表
|
||||
*/
|
||||
searchChats(query) {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const results = [];
|
||||
|
||||
for (const chat of this.chats.values()) {
|
||||
// 搜索标题
|
||||
if (chat.title.toLowerCase().includes(lowerQuery)) {
|
||||
results.push({
|
||||
chat,
|
||||
matchType: 'title'
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// 搜索消息内容
|
||||
const matchedMessages = chat.messages.filter(msg =>
|
||||
msg.content.toLowerCase().includes(lowerQuery)
|
||||
);
|
||||
|
||||
if (matchedMessages.length > 0) {
|
||||
results.push({
|
||||
chat,
|
||||
matchType: 'content',
|
||||
matchedMessages
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有聊天记录
|
||||
*/
|
||||
clearAllChats() {
|
||||
this.chats.clear();
|
||||
this.currentChatId = null;
|
||||
this.saveToStorage();
|
||||
|
||||
// 触发清空事件
|
||||
this.dispatchEvent('allChatsCleared');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出聊天记录
|
||||
* @param {string} chatId - 聊天ID,如果不提供则导出所有
|
||||
* @returns {string} JSON格式的聊天记录
|
||||
*/
|
||||
exportChats(chatId = null) {
|
||||
if (chatId) {
|
||||
const chat = this.chats.get(chatId);
|
||||
return chat ? JSON.stringify(chat, null, 2) : null;
|
||||
} else {
|
||||
const allChats = Array.from(this.chats.values());
|
||||
return JSON.stringify(allChats, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入聊天记录
|
||||
* @param {string} jsonData - JSON格式的聊天记录
|
||||
*/
|
||||
importChats(jsonData) {
|
||||
try {
|
||||
const data = JSON.parse(jsonData);
|
||||
const chatsToImport = Array.isArray(data) ? data : [data];
|
||||
|
||||
for (const chat of chatsToImport) {
|
||||
if (chat.id && chat.messages) {
|
||||
this.chats.set(chat.id, chat);
|
||||
}
|
||||
}
|
||||
|
||||
this.saveToStorage();
|
||||
|
||||
// 触发导入事件
|
||||
this.dispatchEvent('chatsImported', { count: chatsToImport.length });
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('导入聊天记录失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天统计信息
|
||||
*/
|
||||
getStatistics() {
|
||||
const totalChats = this.chats.size;
|
||||
let totalMessages = 0;
|
||||
let userMessages = 0;
|
||||
let aiMessages = 0;
|
||||
|
||||
for (const chat of this.chats.values()) {
|
||||
totalMessages += chat.messages.length;
|
||||
userMessages += chat.messages.filter(m => m.type === 'user').length;
|
||||
aiMessages += chat.messages.filter(m => m.type === 'ai').length;
|
||||
}
|
||||
|
||||
return {
|
||||
totalChats,
|
||||
totalMessages,
|
||||
userMessages,
|
||||
aiMessages,
|
||||
averageMessagesPerChat: totalChats > 0 ? Math.round(totalMessages / totalChats) : 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发自定义事件
|
||||
* @param {string} eventName - 事件名称
|
||||
* @param {object} detail - 事件详情
|
||||
*/
|
||||
dispatchEvent(eventName, detail = {}) {
|
||||
const event = new CustomEvent(`chatManager:${eventName}`, { detail });
|
||||
window.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始新的聊天会话
|
||||
*/
|
||||
startNewChat() {
|
||||
this.currentChatId = null;
|
||||
|
||||
// 清除保存的当前聊天ID
|
||||
const currentUser = this.getCurrentUser();
|
||||
if (currentUser) {
|
||||
const currentChatKey = `${this.storageKey}_current_${currentUser.id}`;
|
||||
localStorage.removeItem(currentChatKey);
|
||||
}
|
||||
|
||||
this.dispatchEvent('newChatStarted');
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登录后重新加载聊天记录
|
||||
*/
|
||||
reloadForUser() {
|
||||
console.log('用户登录状态变化,重新加载聊天记录');
|
||||
this.loadFromStorage();
|
||||
this.dispatchEvent('userChanged');
|
||||
}
|
||||
}
|
||||
|
||||
// 创建全局实例
|
||||
window.chatManager = new ChatManager();
|
||||
Reference in New Issue
Block a user