From a6096514dcf4b98596188354a4cfcfe81bd406ec Mon Sep 17 00:00:00 2001 From: "Atlas (Engineering Lead)" Date: Fri, 6 Feb 2026 00:16:27 +0000 Subject: [PATCH 1/2] feat(files): add right-click context menu for file actions PINE-56: Phase 6 feedback - Part 2 Add file context menu with Download and Delete actions: - Add showFileMenu/onFileAction to electron/preload.ts - IPC handler already existed in main.ts (lines 656-673) - Add onContextMenu handler to FilesPanel file buttons - Delete action shows confirmation dialog, then deletes file - Download action opens signed URL in browser - Add TypeScript types to electron.d.ts Shortcuts in Settings were already implemented (keyboard-shortcuts.ts has 'assistant' and 'chat' categories). --- electron/preload.ts | 8 +++++ src/components/files/FilesPanel.tsx | 48 ++++++++++++++++++++++++++++- src/types/electron.d.ts | 2 ++ 3 files changed, 57 insertions(+), 1 deletion(-) diff --git a/electron/preload.ts b/electron/preload.ts index a6951fa..24e08fb 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -219,6 +219,14 @@ contextBridge.exposeInMainWorld('electronAPI', { ipcRenderer.on('context-menu:assistant-action', handler) return () => ipcRenderer.removeListener('context-menu:assistant-action', handler) }, + showFileMenu: (assistantName: string, fileId: string, fileName: string): void => { + ipcRenderer.send('context-menu:show-file', assistantName, fileId, fileName) + }, + onFileAction: (callback: (action: { action: string; assistantName: string; fileId: string; fileName: string }) => void): (() => void) => { + const handler = (_event: any, data: { action: string; assistantName: string; fileId: string; fileName: string }) => callback(data) + ipcRenderer.on('context-menu:file-action', handler) + return () => ipcRenderer.removeListener('context-menu:file-action', handler) + }, }, profiles: { getAll: async (): Promise => { diff --git a/src/components/files/FilesPanel.tsx b/src/components/files/FilesPanel.tsx index e349a5c..31ef825 100644 --- a/src/components/files/FilesPanel.tsx +++ b/src/components/files/FilesPanel.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo, useCallback } from 'react' +import { useState, useMemo, useCallback, useEffect } from 'react' import { FileText, Upload, Check, AlertCircle, Loader2, Trash2 } from 'lucide-react' import { usePinecone } from '../../providers/PineconeProvider' import { useAssistantSelection } from '../../context/AssistantSelectionContext' @@ -119,6 +119,51 @@ export function FilesPanel({ className }: FilesPanelProps) { setActiveFile(fileId) }, [setActiveFile]) + // Handle right-click context menu on file items + const handleFileContextMenu = useCallback((e: React.MouseEvent, file: AssistantFile) => { + e.preventDefault() + if (!activeAssistant) return + window.electronAPI.contextMenu.showFileMenu(activeAssistant, file.id, file.name) + }, [activeAssistant]) + + // Listen for file context menu actions + useEffect(() => { + if (!currentProfile?.id || !activeAssistant) return + + const cleanup = window.electronAPI.contextMenu.onFileAction(async (data) => { + if (data.assistantName !== activeAssistant) return + + if (data.action === 'delete') { + // Confirm deletion + const confirmed = window.confirm(`Delete "${data.fileName}"?\n\nThis action cannot be undone.`) + if (!confirmed) return + + try { + await window.electronAPI.assistant.files.delete(currentProfile.id, activeAssistant, data.fileId) + // Clear selection if deleted file was selected + if (activeFile === data.fileId) { + setActiveFile(null) + } + refetch() + } catch (err) { + console.error('Failed to delete file:', err) + } + } else if (data.action === 'download') { + // Get file details to get signed URL + try { + const file = await window.electronAPI.assistant.files.describe(currentProfile.id, activeAssistant, data.fileId) + if (file.signedUrl) { + await window.electronAPI.shell.openExternal(file.signedUrl) + } + } catch (err) { + console.error('Failed to download file:', err) + } + } + }) + + return cleanup + }, [currentProfile?.id, activeAssistant, activeFile, setActiveFile, refetch]) + // If no assistant is selected, show prompt if (!activeAssistant) { return ( @@ -254,6 +299,7 @@ export function FilesPanel({ className }: FilesPanelProps) {