From 82e790af0027a79e93d925dad39362b135217ffc Mon Sep 17 00:00:00 2001 From: Edgar Date: Mon, 4 Aug 2025 19:03:00 +0200 Subject: [PATCH 1/3] feat: Add chat history deletion functionality - Add DeleteSessionModal component for confirmation dialog - Add delete button to each session item in SessionListView - Implement deleteSessionById function using Electron API - Add IPC handlers for session file deletion - Add window transparency handlers for PR3 compatibility - Update preload.ts with new API functions - Fix TypeScript errors and imports --- .../sessions/DeleteSessionModal.tsx | 77 +++++++++++++++++++ .../components/sessions/SessionListView.tsx | 74 +++++++++++++++++- ui/desktop/src/main.ts | 65 ++++++++++++++++ ui/desktop/src/preload.ts | 10 +++ ui/desktop/src/sessions.ts | 19 +++++ ui/desktop/src/utils/settings.ts | 1 + 6 files changed, 242 insertions(+), 4 deletions(-) create mode 100644 ui/desktop/src/components/sessions/DeleteSessionModal.tsx diff --git a/ui/desktop/src/components/sessions/DeleteSessionModal.tsx b/ui/desktop/src/components/sessions/DeleteSessionModal.tsx new file mode 100644 index 000000000000..936e03d1c7a0 --- /dev/null +++ b/ui/desktop/src/components/sessions/DeleteSessionModal.tsx @@ -0,0 +1,77 @@ +import { useState } from 'react'; +import { Button } from '../ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '../ui/dialog'; +import { Loader2, Trash2 } from 'lucide-react'; + +interface DeleteSessionModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => Promise; + sessionName: string; +} + +export function DeleteSessionModal({ + isOpen, + onClose, + onConfirm, + sessionName, +}: DeleteSessionModalProps) { + const [isDeleting, setIsDeleting] = useState(false); + + const handleConfirm = async () => { + setIsDeleting(true); + try { + await onConfirm(); + onClose(); + } catch (error) { + console.error('Error deleting session:', error); + } finally { + setIsDeleting(false); + } + }; + + return ( + + + + + + Delete Session + + + Are you sure you want to delete the session "{sessionName}"? This action cannot be undone. + + + + + + + + + ); +} \ No newline at end of file diff --git a/ui/desktop/src/components/sessions/SessionListView.tsx b/ui/desktop/src/components/sessions/SessionListView.tsx index 9b4e1f73fa71..2f86c49a931f 100644 --- a/ui/desktop/src/components/sessions/SessionListView.tsx +++ b/ui/desktop/src/components/sessions/SessionListView.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, useRef, useCallback, useMemo, startTransition } from 'react'; -import { MessageSquareText, Target, AlertCircle, Calendar, Folder } from 'lucide-react'; -import { fetchSessions, type Session } from '../../sessions'; +import { MessageSquareText, Target, AlertCircle, Calendar, Folder, Trash2 } from 'lucide-react'; +import { fetchSessions, deleteSessionById, type Session } from '../../sessions'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { ScrollArea } from '../ui/scroll-area'; @@ -11,6 +11,7 @@ import { SearchHighlighter } from '../../utils/searchHighlighter'; import { MainPanelLayout } from '../Layout/MainPanelLayout'; import { groupSessionsByDate, type DateGroup } from '../../utils/dateUtils'; import { Skeleton } from '../ui/skeleton'; +import { DeleteSessionModal } from './DeleteSessionModal'; // Debounce hook for search function useDebounce(value: T, delay: number): T { @@ -52,6 +53,10 @@ const SessionListView: React.FC = React.memo(({ onSelectSe currentIndex: number; } | null>(null); + // Delete modal state + const [deleteModalOpen, setDeleteModalOpen] = useState(false); + const [sessionToDelete, setSessionToDelete] = useState(null); + // Search state for debouncing const [searchTerm, setSearchTerm] = useState(''); const [caseSensitive, setCaseSensitive] = useState(false); @@ -184,12 +189,49 @@ const SessionListView: React.FC = React.memo(({ onSelectSe } }; + // Handle delete session + const handleDeleteSession = (session: Session) => { + setSessionToDelete(session); + setDeleteModalOpen(true); + }; + + const handleConfirmDelete = async () => { + if (!sessionToDelete) return; + + try { + await deleteSessionById(sessionToDelete.id); + + // Remove the session from local state + setSessions(prev => prev.filter(s => s.id !== sessionToDelete.id)); + setFilteredSessions(prev => prev.filter(s => s.id !== sessionToDelete.id)); + + setDeleteModalOpen(false); + setSessionToDelete(null); + } catch (error) { + console.error('Failed to delete session:', error); + // You could show a toast notification here + } + }; + // Render a session item const SessionItem = React.memo(function SessionItem({ session }: { session: Session }) { + const handleCardClick = (e: React.MouseEvent) => { + // Don't trigger session selection if clicking on delete button + if ((e.target as HTMLElement).closest('.delete-button')) { + return; + } + onSelectSession(session.id); + }; + + const handleDeleteClick = (e: React.MouseEvent) => { + e.stopPropagation(); + handleDeleteSession(session); + }; + return ( onSelectSession(session.id)} - className="session-item h-full py-3 px-4 hover:shadow-default cursor-pointer transition-all duration-150 flex flex-col justify-between" + onClick={handleCardClick} + className="session-item h-full py-3 px-4 hover:shadow-default cursor-pointer transition-all duration-150 flex flex-col justify-between relative group" >

{session.metadata.description || session.id}

@@ -216,6 +258,16 @@ const SessionListView: React.FC = React.memo(({ onSelectSe
)} + + {/* Delete button */} +
); @@ -326,6 +378,20 @@ const SessionListView: React.FC = React.memo(({ onSelectSe + {/* Delete Session Modal */} + {sessionToDelete && ( + { + setDeleteModalOpen(false); + setSessionToDelete(null); + }} + onConfirm={handleConfirmDelete} + sessionName={sessionToDelete.metadata.description || sessionToDelete.id} + sessionId={sessionToDelete.id} + /> + )} +
diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index 589acd2d4b64..55ecd2e118dc 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -1199,6 +1199,71 @@ ipcMain.handle('get-dock-icon-state', () => { } }); +// Window transparency handlers +ipcMain.handle('set-window-opacity', async (_event, opacity: number) => { + try { + // Validate opacity value (0.03 to 1.0, where 0.03 = 97% transparency) + const validOpacity = Math.max(0.03, Math.min(1.0, opacity)); + + console.log(`Setting window opacity to: ${validOpacity}`); + + // Get all windows and apply opacity + const windows = BrowserWindow.getAllWindows(); + for (const window of windows) { + window.setOpacity(validOpacity); + console.log(`Applied opacity ${validOpacity} to window: ${window.id}`); + } + + // Save opacity setting + const settings = loadSettings(); + settings.windowOpacity = validOpacity; + saveSettings(settings); + + console.log(`Saved opacity setting: ${validOpacity}`); + return true; + } catch (error) { + console.error('Error setting window opacity:', error); + return false; + } +}); + +ipcMain.handle('get-window-opacity', async () => { + try { + const settings = loadSettings(); + const opacity = settings.windowOpacity || 1.0; + console.log(`Retrieved window opacity: ${opacity}`); + return opacity; + } catch (error) { + console.error('Error getting window opacity:', error); + return 1.0; + } +}); + +// Session deletion handler +ipcMain.handle('delete-session-file', async (_event, sessionId: string) => { + try { + const fs = require('fs'); + const path = require('path'); + + // Get the sessions directory from the API base URL or use a default + const sessionsDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.goose', 'sessions'); + const sessionFile = path.join(sessionsDir, `${sessionId}.json`); + + // Check if file exists before deleting + if (fs.existsSync(sessionFile)) { + fs.unlinkSync(sessionFile); + console.log(`Deleted session file: ${sessionFile}`); + return true; + } else { + console.warn(`Session file not found: ${sessionFile}`); + return false; + } + } catch (error) { + console.error('Error deleting session file:', error); + return false; + } +}); + // Handle opening system notifications preferences ipcMain.handle('open-notifications-settings', async () => { try { diff --git a/ui/desktop/src/preload.ts b/ui/desktop/src/preload.ts index 7f73d2963625..74858a9f3796 100644 --- a/ui/desktop/src/preload.ts +++ b/ui/desktop/src/preload.ts @@ -112,6 +112,11 @@ type ElectronAPI = { closeWindow: () => void; hasAcceptedRecipeBefore: (recipeConfig: Recipe) => Promise; recordRecipeHash: (recipeConfig: Recipe) => Promise; + // Window transparency functions + setWindowOpacity: (opacity: number) => Promise; + getWindowOpacity: () => Promise; + // Session deletion function + deleteSessionFile: (sessionId: string) => Promise; }; type AppConfigAPI = { @@ -240,6 +245,11 @@ const electronAPI: ElectronAPI = { ipcRenderer.invoke('has-accepted-recipe-before', recipeConfig), recordRecipeHash: (recipeConfig: Recipe) => ipcRenderer.invoke('record-recipe-hash', recipeConfig), + // Window transparency functions + setWindowOpacity: (opacity: number) => ipcRenderer.invoke('set-window-opacity', opacity), + getWindowOpacity: () => ipcRenderer.invoke('get-window-opacity'), + // Session deletion function + deleteSessionFile: (sessionId: string) => ipcRenderer.invoke('delete-session-file', sessionId), }; const appConfigAPI: AppConfigAPI = { diff --git a/ui/desktop/src/sessions.ts b/ui/desktop/src/sessions.ts index 749f8bc73cf0..ead8cea01442 100644 --- a/ui/desktop/src/sessions.ts +++ b/ui/desktop/src/sessions.ts @@ -126,3 +126,22 @@ export async function fetchSessionDetails(sessionId: string): Promise { + try { + // Use the Electron API to delete the session file + const success = await window.electron.deleteSessionFile(sessionId); + + if (!success) { + throw new Error('Failed to delete session file'); + } + } catch (error) { + console.error(`Error deleting session ${sessionId}:`, error); + throw error; + } +} diff --git a/ui/desktop/src/utils/settings.ts b/ui/desktop/src/utils/settings.ts index 5fb1d64a41a3..1756f977ea6a 100644 --- a/ui/desktop/src/utils/settings.ts +++ b/ui/desktop/src/utils/settings.ts @@ -17,6 +17,7 @@ export interface Settings { schedulingEngine: SchedulingEngine; showQuitConfirmation: boolean; enableWakelock: boolean; + windowOpacity?: number; } // Constants From b0581f00e13b01896d8aa73462f4837d8e2729f6 Mon Sep 17 00:00:00 2001 From: Edgar Date: Mon, 4 Aug 2025 20:21:58 +0200 Subject: [PATCH 2/3] Update .gitignore to exclude desktop app binaries --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8c6129ab3b22..e05f85416a3d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,9 @@ target/ ./ui/desktop/node_modules ./ui/desktop/out +# Desktop app binaries (built at build time, not checked in) +Goose-Desktop-App-Fixed/ + # Generated Goose DLLs (built at build time, not checked in) ui/desktop/src/bin/goose_ffi.dll ui/desktop/src/bin/goose_llm.dll From 7ca12a728b199f70e2cca04b5a6ed9ffb74d7f8c Mon Sep 17 00:00:00 2001 From: Edgar Date: Mon, 4 Aug 2025 20:43:30 +0200 Subject: [PATCH 3/3] Fix TypeScript error: Remove unnecessary sessionId prop from DeleteSessionModal --- .../components/sessions/SessionListView.tsx | 11 +++--- ui/desktop/src/main.ts | 36 ++++++++++++------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/ui/desktop/src/components/sessions/SessionListView.tsx b/ui/desktop/src/components/sessions/SessionListView.tsx index 2f86c49a931f..acbd05135a89 100644 --- a/ui/desktop/src/components/sessions/SessionListView.tsx +++ b/ui/desktop/src/components/sessions/SessionListView.tsx @@ -200,11 +200,11 @@ const SessionListView: React.FC = React.memo(({ onSelectSe try { await deleteSessionById(sessionToDelete.id); - + // Remove the session from local state - setSessions(prev => prev.filter(s => s.id !== sessionToDelete.id)); - setFilteredSessions(prev => prev.filter(s => s.id !== sessionToDelete.id)); - + setSessions((prev) => prev.filter((s) => s.id !== sessionToDelete.id)); + setFilteredSessions((prev) => prev.filter((s) => s.id !== sessionToDelete.id)); + setDeleteModalOpen(false); setSessionToDelete(null); } catch (error) { @@ -258,7 +258,7 @@ const SessionListView: React.FC = React.memo(({ onSelectSe
)}
- + {/* Delete button */}