import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; import type { SessionData, SessionSummary } from './types.js'; /** * 获取默认存储目录 * 遵循 XDG 规范:~/.local/share/ai-assist/ */ function getDefaultStorageDir(): string { const xdgDataHome = process.env.XDG_DATA_HOME; if (xdgDataHome) { return path.join(xdgDataHome, 'ai-assist'); } return path.join(os.homedir(), '.local', 'share', 'ai-assist'); } /** * 会话存储类 * 负责会话数据的读写操作 */ export class SessionStorage { private storageDir: string; private sessionsDir: string; private currentSessionFile: string; constructor(storageDir?: string) { this.storageDir = storageDir || getDefaultStorageDir(); this.sessionsDir = path.join(this.storageDir, 'sessions'); this.currentSessionFile = path.join(this.storageDir, 'current-session.json'); } /** * 确保存储目录存在 */ async ensureDir(): Promise { await fs.mkdir(this.sessionsDir, { recursive: true }); } /** * 生成会话 ID */ generateSessionId(): string { const now = new Date(); const timestamp = now.toISOString().slice(0, 10); // YYYY-MM-DD const random = Math.random().toString(36).substring(2, 8); return `${timestamp}_${random}`; } /** * 保存当前会话 */ async saveCurrentSession(session: SessionData): Promise { await this.ensureDir(); session.updatedAt = new Date().toISOString(); await fs.writeFile( this.currentSessionFile, JSON.stringify(session, null, 2), 'utf-8' ); } /** * 加载当前会话 */ async loadCurrentSession(): Promise { try { const content = await fs.readFile(this.currentSessionFile, 'utf-8'); return JSON.parse(content) as SessionData; } catch { return null; } } /** * 归档当前会话到历史 */ async archiveCurrentSession(): Promise { const current = await this.loadCurrentSession(); if (!current || current.messages.length === 0) { return; } await this.ensureDir(); const archivePath = path.join(this.sessionsDir, `${current.id}.json`); await fs.writeFile(archivePath, JSON.stringify(current, null, 2), 'utf-8'); } /** * 删除当前会话文件 */ async clearCurrentSession(): Promise { try { await fs.unlink(this.currentSessionFile); } catch { // 文件不存在,忽略 } } /** * 列出历史会话 */ async listSessions(): Promise { await this.ensureDir(); const files = await fs.readdir(this.sessionsDir); const summaries: SessionSummary[] = []; for (const file of files) { if (!file.endsWith('.json')) continue; try { const filePath = path.join(this.sessionsDir, file); const content = await fs.readFile(filePath, 'utf-8'); const session = JSON.parse(content) as SessionData; summaries.push({ id: session.id, title: session.title || this.generateTitle(session), workdir: session.workdir, messageCount: session.messages.length, createdAt: session.createdAt, updatedAt: session.updatedAt, }); } catch { // 跳过无法解析的文件 } } // 按更新时间降序排列 return summaries.sort( (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ); } /** * 加载指定会话 */ async loadSession(sessionId: string): Promise { try { const filePath = path.join(this.sessionsDir, `${sessionId}.json`); const content = await fs.readFile(filePath, 'utf-8'); return JSON.parse(content) as SessionData; } catch { return null; } } /** * 保存指定会话(用于子会话) */ async saveSession(session: SessionData): Promise { await this.ensureDir(); session.updatedAt = new Date().toISOString(); const filePath = path.join(this.sessionsDir, `${session.id}.json`); await fs.writeFile(filePath, JSON.stringify(session, null, 2), 'utf-8'); } /** * 删除指定会话 */ async deleteSession(sessionId: string): Promise { try { const filePath = path.join(this.sessionsDir, `${sessionId}.json`); await fs.unlink(filePath); return true; } catch { return false; } } /** * 清理旧会话(保留最近 N 个) */ async cleanupOldSessions(keepCount: number = 50): Promise { const sessions = await this.listSessions(); if (sessions.length <= keepCount) { return 0; } const toDelete = sessions.slice(keepCount); let deletedCount = 0; for (const session of toDelete) { if (await this.deleteSession(session.id)) { deletedCount++; } } return deletedCount; } /** * 从会话生成标题 */ private generateTitle(session: SessionData): string { // 从第一条用户消息生成标题 const firstUserMessage = session.messages.find((m) => m.role === 'user'); if (firstUserMessage && typeof firstUserMessage.content === 'string') { const content = firstUserMessage.content; // 取前 50 个字符 return content.length > 50 ? content.substring(0, 50) + '...' : content; } return `会话 ${session.id}`; } /** * 获取存储目录路径 */ getStorageDir(): string { return this.storageDir; } } // 导出默认实例 export const sessionStorage = new SessionStorage();