Skip to main content
Glama
ChatHistoryManager.ts9.86 kB
import { ChatSession, ChatMessage } from '../types'; export interface ChatHistoryOptions { maxSessions?: number; maxMessagesPerSession?: number; storageKey?: string; autoSave?: boolean; } export class ChatHistoryManager { private sessions = new Map<string, ChatSession>(); private currentSessionId: string | null = null; private options: Required<ChatHistoryOptions>; constructor(options: ChatHistoryOptions = {}) { this.options = { maxSessions: options.maxSessions ?? 50, maxMessagesPerSession: options.maxMessagesPerSession ?? 1000, storageKey: options.storageKey ?? 'office-ai-chat-history', autoSave: options.autoSave ?? true }; this.loadFromStorage(); } /** * 创建新的聊天会话 */ createSession(title?: string): ChatSession { const session: ChatSession = { id: this.generateId(), title: title || `对话 ${new Date().toLocaleString('zh-CN')}`, messages: [], createdAt: Date.now(), updatedAt: Date.now() }; this.sessions.set(session.id, session); this.currentSessionId = session.id; // 限制会话数量 this.enforceSessionLimit(); if (this.options.autoSave) { this.saveToStorage(); } return session; } /** * 获取当前会话 */ getCurrentSession(): ChatSession | null { if (!this.currentSessionId) { return null; } return this.sessions.get(this.currentSessionId) || null; } /** * 切换到指定会话 */ switchToSession(sessionId: string): ChatSession | null { const session = this.sessions.get(sessionId); if (session) { this.currentSessionId = sessionId; return session; } return null; } /** * 添加消息到当前会话 */ addMessage(message: ChatMessage): void { let session = this.getCurrentSession(); if (!session) { session = this.createSession(); } session.messages.push(message); session.updatedAt = Date.now(); // 限制消息数量 this.enforceMessageLimit(session); // 自动更新会话标题 this.updateSessionTitle(session); if (this.options.autoSave) { this.saveToStorage(); } } /** * 更新消息 */ updateMessage(messageId: string, updates: Partial<ChatMessage>): boolean { const session = this.getCurrentSession(); if (!session) return false; const messageIndex = session.messages.findIndex(m => m.id === messageId); if (messageIndex === -1) return false; session.messages[messageIndex] = { ...session.messages[messageIndex], ...updates }; session.updatedAt = Date.now(); if (this.options.autoSave) { this.saveToStorage(); } return true; } /** * 删除消息 */ deleteMessage(messageId: string): boolean { const session = this.getCurrentSession(); if (!session) return false; const messageIndex = session.messages.findIndex(m => m.id === messageId); if (messageIndex === -1) return false; session.messages.splice(messageIndex, 1); session.updatedAt = Date.now(); if (this.options.autoSave) { this.saveToStorage(); } return true; } /** * 获取所有会话 */ getAllSessions(): ChatSession[] { return Array.from(this.sessions.values()) .sort((a, b) => b.updatedAt - a.updatedAt); } /** * 获取会话 */ getSession(sessionId: string): ChatSession | null { return this.sessions.get(sessionId) || null; } /** * 删除会话 */ deleteSession(sessionId: string): boolean { const deleted = this.sessions.delete(sessionId); if (deleted && this.currentSessionId === sessionId) { // 切换到最近的会话 const sessions = this.getAllSessions(); this.currentSessionId = sessions.length > 0 ? sessions[0].id : null; } if (deleted && this.options.autoSave) { this.saveToStorage(); } return deleted; } /** * 更新会话标题 */ updateSessionTitle(sessionOrId: ChatSession | string, title?: string): boolean { const session = typeof sessionOrId === 'string' ? this.sessions.get(sessionOrId) : sessionOrId; if (!session) return false; if (title) { session.title = title; } else { // 自动生成标题 const firstUserMessage = session.messages.find(m => m.type === 'user'); if (firstUserMessage) { session.title = this.truncateText(firstUserMessage.content, 30); } } session.updatedAt = Date.now(); if (this.options.autoSave) { this.saveToStorage(); } return true; } /** * 清空所有历史 */ clearAllHistory(): void { this.sessions.clear(); this.currentSessionId = null; if (this.options.autoSave) { this.saveToStorage(); } } /** * 搜索消息 */ searchMessages(query: string, sessionId?: string): Array<{ session: ChatSession; message: ChatMessage; matchIndex: number; }> { const results: Array<{ session: ChatSession; message: ChatMessage; matchIndex: number; }> = []; const sessionsToSearch = sessionId ? [this.sessions.get(sessionId)].filter(Boolean) as ChatSession[] : Array.from(this.sessions.values()); const queryLower = query.toLowerCase(); for (const session of sessionsToSearch) { for (const message of session.messages) { const contentLower = message.content.toLowerCase(); const matchIndex = contentLower.indexOf(queryLower); if (matchIndex !== -1) { results.push({ session, message, matchIndex }); } } } return results.sort((a, b) => b.message.timestamp - a.message.timestamp); } /** * 导出历史数据 */ exportHistory(): string { const data = { sessions: Array.from(this.sessions.values()), currentSessionId: this.currentSessionId, exportedAt: Date.now() }; return JSON.stringify(data, null, 2); } /** * 导入历史数据 */ importHistory(jsonData: string): boolean { try { const data = JSON.parse(jsonData); if (!data.sessions || !Array.isArray(data.sessions)) { throw new Error('Invalid data format'); } this.sessions.clear(); for (const session of data.sessions) { if (this.isValidSession(session)) { this.sessions.set(session.id, session); } } this.currentSessionId = data.currentSessionId || null; if (this.options.autoSave) { this.saveToStorage(); } return true; } catch (error) { console.error('导入历史数据失败:', error); return false; } } /** * 获取统计信息 */ getStats(): { totalSessions: number; totalMessages: number; oldestSession: number | null; newestSession: number | null; averageMessagesPerSession: number; } { const sessions = Array.from(this.sessions.values()); const totalMessages = sessions.reduce((sum, s) => sum + s.messages.length, 0); return { totalSessions: sessions.length, totalMessages, oldestSession: sessions.length > 0 ? Math.min(...sessions.map(s => s.createdAt)) : null, newestSession: sessions.length > 0 ? Math.max(...sessions.map(s => s.updatedAt)) : null, averageMessagesPerSession: sessions.length > 0 ? Math.round(totalMessages / sessions.length) : 0 }; } /** * 保存到本地存储 */ private saveToStorage(): void { try { const data = { sessions: Array.from(this.sessions.entries()), currentSessionId: this.currentSessionId, savedAt: Date.now() }; localStorage.setItem(this.options.storageKey, JSON.stringify(data)); } catch (error) { console.error('保存聊天历史失败:', error); } } /** * 从本地存储加载 */ private loadFromStorage(): void { try { const data = localStorage.getItem(this.options.storageKey); if (!data) return; const parsed = JSON.parse(data); if (parsed.sessions && Array.isArray(parsed.sessions)) { this.sessions = new Map(parsed.sessions); this.currentSessionId = parsed.currentSessionId || null; } } catch (error) { console.error('加载聊天历史失败:', error); } } /** * 限制会话数量 */ private enforceSessionLimit(): void { const sessions = this.getAllSessions(); if (sessions.length > this.options.maxSessions) { const sessionsToDelete = sessions.slice(this.options.maxSessions); for (const session of sessionsToDelete) { this.sessions.delete(session.id); } } } /** * 限制消息数量 */ private enforceMessageLimit(session: ChatSession): void { if (session.messages.length > this.options.maxMessagesPerSession) { const messagesToRemove = session.messages.length - this.options.maxMessagesPerSession; session.messages.splice(0, messagesToRemove); } } /** * 验证会话数据 */ private isValidSession(session: any): session is ChatSession { return session && typeof session.id === 'string' && typeof session.title === 'string' && Array.isArray(session.messages) && typeof session.createdAt === 'number' && typeof session.updatedAt === 'number'; } /** * 截断文本 */ private truncateText(text: string, maxLength: number): string { if (text.length <= maxLength) return text; return text.substring(0, maxLength - 3) + '...'; } /** * 生成唯一ID */ private generateId(): string { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } export default ChatHistoryManager;

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/walkingzzzy/office-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server