-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
auto-claude: 225-bulk-delete-and-archive-chat-history #1829
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 14 commits
f1305d5
2382865
9379470
48b14c8
b00fb64
af22f05
f78d672
aef3fa0
2913d99
835b47e
5f8747d
f8a9890
dc8df1c
4e053fd
4f23ecd
fc484f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,10 +40,10 @@ export class SessionManager { | |
| /** | ||
| * List all sessions for a project | ||
| */ | ||
| listSessions(projectPath: string): InsightsSessionSummary[] { | ||
| listSessions(projectPath: string, includeArchived = false): InsightsSessionSummary[] { | ||
| // Migrate old format if needed | ||
| this.storage.migrateOldSession(projectPath); | ||
| return this.storage.listSessions(projectPath); | ||
| return this.storage.listSessions(projectPath, includeArchived); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -105,6 +105,80 @@ export class SessionManager { | |
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Archive a session | ||
| */ | ||
| archiveSession(projectId: string, projectPath: string, sessionId: string): boolean { | ||
| const success = this.storage.archiveSession(projectPath, sessionId); | ||
| if (!success) return false; | ||
|
|
||
| // If this was the current session, auto-switch | ||
| const currentSession = this.sessions.get(projectId); | ||
| if (currentSession?.id === sessionId) { | ||
| this.sessions.delete(projectId); | ||
|
|
||
| const remaining = this.listSessions(projectPath); | ||
| if (remaining.length > 0) { | ||
| this.switchSession(projectId, projectPath, remaining[0].id); | ||
| } else { | ||
| this.storage.clearCurrentSessionId(projectPath); | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** | ||
| * Unarchive a session | ||
| */ | ||
| unarchiveSession(projectPath: string, sessionId: string): boolean { | ||
| return this.storage.unarchiveSession(projectPath, sessionId); | ||
| } | ||
|
|
||
| /** | ||
| * Delete multiple sessions | ||
| */ | ||
| deleteSessions(projectId: string, projectPath: string, sessionIds: string[]): { deletedIds: string[]; failedIds: string[] } { | ||
| const result = this.storage.deleteSessions(projectPath, sessionIds); | ||
|
|
||
| // Check if current cached session was among deleted | ||
| const currentSession = this.sessions.get(projectId); | ||
| if (currentSession && result.deletedIds.includes(currentSession.id)) { | ||
| this.sessions.delete(projectId); | ||
|
|
||
| const remaining = this.listSessions(projectPath); | ||
| if (remaining.length > 0) { | ||
| this.switchSession(projectId, projectPath, remaining[0].id); | ||
| } else { | ||
| this.storage.clearCurrentSessionId(projectPath); | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /** | ||
| * Archive multiple sessions | ||
| */ | ||
| archiveSessions(projectId: string, projectPath: string, sessionIds: string[]): { archivedIds: string[]; failedIds: string[] } { | ||
| const result = this.storage.archiveSessions(projectPath, sessionIds); | ||
|
|
||
| // Check if current cached session was among archived | ||
| const currentSession = this.sessions.get(projectId); | ||
| if (currentSession && result.archivedIds.includes(currentSession.id)) { | ||
| this.sessions.delete(projectId); | ||
|
|
||
| const remaining = this.listSessions(projectPath); | ||
| if (remaining.length > 0) { | ||
| this.switchSession(projectId, projectPath, remaining[0].id); | ||
| } else { | ||
| this.storage.clearCurrentSessionId(projectPath); | ||
| } | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
Comment on lines
+109
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Duplicated "auto-switch on current session removal" logic across four methods. The pattern of clearing cache, listing remaining sessions, and switching (or clearing the current session pointer) is repeated in ♻️ Suggested helper extraction+ /**
+ * If the current cached session was removed/archived, switch to next available or clear.
+ */
+ private autoSwitchIfNeeded(projectId: string, projectPath: string, removedSessionId: string): void {
+ const currentSession = this.sessions.get(projectId);
+ if (currentSession?.id !== removedSessionId) return;
+
+ this.sessions.delete(projectId);
+ const remaining = this.listSessions(projectPath);
+ if (remaining.length > 0) {
+ this.switchSession(projectId, projectPath, remaining[0].id);
+ } else {
+ this.storage.clearCurrentSessionId(projectPath);
+ }
+ }Then each method simply calls 🤖 Prompt for AI Agents |
||
|
|
||
| /** | ||
| * Rename a session | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,74 @@ export class SessionStorage { | |
| writeFileSync(sessionPath, JSON.stringify(session, null, 2), 'utf-8'); | ||
| } | ||
|
|
||
| /** | ||
| * Archive a session | ||
| */ | ||
| archiveSession(projectPath: string, sessionId: string): boolean { | ||
| const session = this.loadSessionById(projectPath, sessionId); | ||
| if (!session) return false; | ||
|
|
||
| try { | ||
| session.archivedAt = new Date(); | ||
| this.saveSession(projectPath, session); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
|
||
| } | ||
|
|
||
| /** | ||
| * Unarchive a session | ||
| */ | ||
| unarchiveSession(projectPath: string, sessionId: string): boolean { | ||
| const session = this.loadSessionById(projectPath, sessionId); | ||
| if (!session) return false; | ||
|
|
||
| try { | ||
| delete session.archivedAt; | ||
| this.saveSession(projectPath, session); | ||
| return true; | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Delete multiple sessions | ||
| */ | ||
| deleteSessions(projectPath: string, sessionIds: string[]): { deletedIds: string[]; failedIds: string[] } { | ||
| const deletedIds: string[] = []; | ||
| const failedIds: string[] = []; | ||
|
|
||
| for (const sessionId of sessionIds) { | ||
| if (this.deleteSession(projectPath, sessionId)) { | ||
| deletedIds.push(sessionId); | ||
| } else { | ||
| failedIds.push(sessionId); | ||
| } | ||
| } | ||
|
|
||
| return { deletedIds, failedIds }; | ||
| } | ||
|
|
||
| /** | ||
| * Archive multiple sessions | ||
| */ | ||
| archiveSessions(projectPath: string, sessionIds: string[]): { archivedIds: string[]; failedIds: string[] } { | ||
| const archivedIds: string[] = []; | ||
| const failedIds: string[] = []; | ||
|
|
||
| for (const sessionId of sessionIds) { | ||
| if (this.archiveSession(projectPath, sessionId)) { | ||
| archivedIds.push(sessionId); | ||
| } else { | ||
| failedIds.push(sessionId); | ||
| } | ||
| } | ||
|
|
||
| return { archivedIds, failedIds }; | ||
| } | ||
|
|
||
| /** | ||
| * Delete a session from disk | ||
| */ | ||
|
|
@@ -82,7 +150,7 @@ export class SessionStorage { | |
| /** | ||
| * List all sessions for a project | ||
| */ | ||
| listSessions(projectPath: string): InsightsSessionSummary[] { | ||
| listSessions(projectPath: string, includeArchived = false): InsightsSessionSummary[] { | ||
| const sessionsDir = this.paths.getSessionsDir(projectPath); | ||
| if (!existsSync(sessionsDir)) return []; | ||
|
|
||
|
|
@@ -104,13 +172,20 @@ export class SessionStorage { | |
| : 'Untitled Conversation'; | ||
| } | ||
|
|
||
| // Skip archived sessions unless explicitly included | ||
| if (!includeArchived && session.archivedAt) { | ||
| continue; | ||
| } | ||
|
|
||
| sessions.push({ | ||
| id: session.id, | ||
| projectId: session.projectId, | ||
| title: title || 'New Conversation', | ||
| messageCount: session.messages.length, | ||
| modelConfig: session.modelConfig, | ||
| createdAt: new Date(session.createdAt), | ||
| updatedAt: new Date(session.updatedAt) | ||
| updatedAt: new Date(session.updatedAt), | ||
| ...(session.archivedAt ? { archivedAt: new Date(session.archivedAt) } : {}) | ||
| }); | ||
| } catch { | ||
| // Skip invalid session files | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -249,17 +249,79 @@ export function registerInsightsHandlers(getMainWindow: () => BrowserWindow | nu | |
| // List all sessions for a project | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.INSIGHTS_LIST_SESSIONS, | ||
| async (_, projectId: string): Promise<IPCResult<InsightsSessionSummary[]>> => { | ||
| async (_, projectId: string, includeArchived?: boolean): Promise<IPCResult<InsightsSessionSummary[]>> => { | ||
| const project = projectStore.getProject(projectId); | ||
| if (!project) { | ||
| return { success: false, error: "Project not found" }; | ||
| } | ||
|
|
||
| const sessions = insightsService.listSessions(project.path); | ||
| const sessions = insightsService.listSessions(project.path, includeArchived ?? false); | ||
| return { success: true, data: sessions }; | ||
| } | ||
| ); | ||
|
|
||
| // Delete multiple sessions | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.INSIGHTS_DELETE_SESSIONS, | ||
| async (_, projectId: string, sessionIds: string[]): Promise<IPCResult<{ deletedIds: string[]; failedIds: string[] }>> => { | ||
| const project = projectStore.getProject(projectId); | ||
| if (!project) { | ||
| return { success: false, error: "Project not found" }; | ||
| } | ||
|
|
||
| const result = insightsService.deleteSessions(projectId, project.path, sessionIds); | ||
| return { success: true, data: result }; | ||
| } | ||
| ); | ||
|
|
||
| // Archive a session | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.INSIGHTS_ARCHIVE_SESSION, | ||
| async (_, projectId: string, sessionId: string): Promise<IPCResult> => { | ||
| const project = projectStore.getProject(projectId); | ||
| if (!project) { | ||
| return { success: false, error: "Project not found" }; | ||
| } | ||
|
|
||
| const success = insightsService.archiveSession(projectId, project.path, sessionId); | ||
| if (success) { | ||
| return { success: true }; | ||
| } | ||
| return { success: false, error: "Failed to archive session" }; | ||
| } | ||
| ); | ||
|
|
||
| // Archive multiple sessions | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.INSIGHTS_ARCHIVE_SESSIONS, | ||
| async (_, projectId: string, sessionIds: string[]): Promise<IPCResult<{ archivedIds: string[]; failedIds: string[] }>> => { | ||
| const project = projectStore.getProject(projectId); | ||
| if (!project) { | ||
| return { success: false, error: "Project not found" }; | ||
| } | ||
|
|
||
| const result = insightsService.archiveSessions(projectId, project.path, sessionIds); | ||
| return { success: true, data: result }; | ||
| } | ||
| ); | ||
|
|
||
| // Unarchive a session | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.INSIGHTS_UNARCHIVE_SESSION, | ||
| async (_, projectId: string, sessionId: string): Promise<IPCResult> => { | ||
| const project = projectStore.getProject(projectId); | ||
| if (!project) { | ||
| return { success: false, error: "Project not found" }; | ||
| } | ||
|
|
||
| const success = insightsService.unarchiveSession(project.path, sessionId); | ||
| if (success) { | ||
| return { success: true }; | ||
| } | ||
| return { success: false, error: "Failed to unarchive session" }; | ||
| } | ||
| ); | ||
|
Comment on lines
+263
to
+323
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial New bulk and archive IPC handlers follow existing patterns well. The new handlers are consistent with the established project-existence-check-then-delegate pattern. One minor observation: 🤖 Prompt for AI Agents |
||
|
|
||
| // Create a new session | ||
| ipcMain.handle( | ||
| IPC_CHANNELS.INSIGHTS_NEW_SESSION, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic for handling the auto-switching of the current session if it's archived or deleted is duplicated across
archiveSession,deleteSessions, andarchiveSessions. To improve maintainability and adhere to the DRY principle, consider extracting this logic into a private helper method. For example:private handleCurrentSessionRemoval(projectId: string, projectPath: string): void.