513 lines
16 KiB
JavaScript
513 lines
16 KiB
JavaScript
/**
|
||
* 对话管理器 - 负责管理聊天会话的保存、加载和持久化
|
||
*/
|
||
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(); |