feat: Add AssistantsPanel component#39
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the
📝 WalkthroughWalkthroughAdds Pinecone Assistant support end-to-end: service layer, IPC + preload API, types, persisted profile mode, React contexts, UI components, React Query hooks, workspace/config updates, and wiring in the connection window. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Renderer (React UI)
participant Preload as Preload (electronAPI)
participant Main as Main (IPC handlers)
participant PineSvc as PineconeService / AssistantService
participant SDK as Pinecone SDK
UI->>Preload: assistant.create(profileId, params)
Preload->>Main: ipcInvoke "assistant:create" (profileId, params)
Main->>PineSvc: getAssistantService(profileId)
PineSvc->>SDK: Assistant.create(params)
SDK-->>PineSvc: assistant data
PineSvc-->>Main: mapped AssistantModel
Main-->>Preload: { success: true, data: AssistantModel }
Preload-->>UI: Promise resolves with AssistantModel
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@electron/assistant-service.ts`:
- Around line 68-86: The AssistantStatus union type is missing
'InitializationFailed' which the Pinecone SDK can return; update the
AssistantStatus type definition to include 'InitializationFailed' so the status
is accurately typed (this will remove the need to rely on the unsafe cast in
mapAssistantModel). Locate the AssistantStatus type and add the literal
'InitializationFailed' to the union, then ensure usages like mapAssistantModel
(where model.status is cast as AssistantModel['status']) and helper functions
getStatusColor/getStatusTooltip accept the new status without changes.
In `@electron/pinecone-service.ts`:
- Around line 49-60: The cached assistantService instance causes stale Pinecone
client use after reconnects; update the connect and disconnect flows and the
connect error path to reset assistantService to null so a new AssistantService
is constructed with the fresh client; specifically, clear this.assistantService
= null in the methods that establish/tear down the connection (e.g., in
connect(), disconnect(), and the catch/error handling inside connect()) and
ensure getAssistantService() still lazily constructs a new
AssistantService(this.client) when assistantService is null.
In `@electron/types.ts`:
- Around line 401-439: Update the AssistantStatus type to include the missing
'InitializationFailed' variant so the union now covers API responses; locate the
AssistantStatus declaration and add 'InitializationFailed' to the union
('Initializing' | 'Ready' | 'Failed' | 'Terminating' | 'InitializationFailed')
so code that maps assistant states (e.g., anywhere using AssistantStatus or
AssistantModel.status) will handle the SDK v6.1.3 response correctly.
In `@src/components/mode/ModeSwitcher.tsx`:
- Around line 20-47: The component currently implements a tab pattern but has no
tabpanel or keyboard tab behaviors; update ModeSwitcher so the container uses
role="group" (replace role="tablist") and each option button uses
aria-pressed={isActive} (remove role="tab" and aria-selected) while keeping
onClick={() => setMode(value)} and the existing data-testid; ensure the
span/text and Icon rendering remain unchanged and keep keyboard activation via
Enter/Space by relying on native button behavior in the buttons rendered from
modes, referencing the modes mapping, mode state and setMode function.
🧹 Nitpick comments (8)
src/components/assistants/AssistantsPanel.tsx (2)
64-82: Consider removing console.log statements before merging.The
console.logstatements on lines 71 and 81 are useful for development but should be removed or wrapped in a debug flag before production. Even with the TODO comments indicating future implementation, leaving debug logs can clutter the console.🔧 Proposed fix
const handleAssistantContextMenu = useCallback( (e: React.MouseEvent, assistantName: string) => { e.preventDefault() e.stopPropagation() // TODO: Implement context menu for assistants when needed // window.electronAPI.contextMenu.showAssistantMenu(assistantName) - console.log('Context menu for assistant:', assistantName) }, [] ) // Handle right-click on panel background const handlePanelContextMenu = useCallback((e: React.MouseEvent) => { e.preventDefault() // TODO: Implement panel context menu for assistants when needed // window.electronAPI.contextMenu.showAssistantPanelMenu() - console.log('Panel context menu') }, [])
84-88: SimplifyhandleCreateNewcallback.The
useCallbackwrapper here adds unnecessary overhead since it only conditionally callsonCreateNew. You can passonCreateNewdirectly to the button'sonClickor simplify to avoid the extra wrapper.♻️ Proposed simplification
- const handleCreateNew = useCallback(() => { - if (onCreateNew) { - onCreateNew() - } - }, [onCreateNew]) + const handleCreateNew = onCreateNewThen in JSX:
<NewButton - onClick={handleCreateNew} + onClick={onCreateNew} + disabled={!onCreateNew} label="Assistant"Alternatively, keep it simple if the button should always be visible:
<NewButton - onClick={handleCreateNew} + onClick={onCreateNew ?? (() => {})} label="Assistant"electron/main.ts (2)
819-832: Consider using imported types for consistency.The parameter types for
assistant:createandassistant:updateare defined inline rather than importingCreateAssistantParamsandUpdateAssistantParamsfrom./types. While functionally equivalent, using the imported types would ensure consistency and make refactoring easier.♻️ Proposed change for assistant:create
Add to imports at top of file:
import { // ... existing imports CreateAssistantParams, UpdateAssistantParams, } from './types'Then update the handler:
-ipcMain.handle('assistant:create', async (_event, profileId: string, params: { name: string; instructions?: string; metadata?: Record<string, string>; region?: 'us' | 'eu' }) => { +ipcMain.handle('assistant:create', async (_event, profileId: string, params: CreateAssistantParams) => {
849-862: Same inline type concern forassistant:update.♻️ Proposed change
-ipcMain.handle('assistant:update', async (_event, profileId: string, name: string, params: { instructions?: string; metadata?: Record<string, string> }) => { +ipcMain.handle('assistant:update', async (_event, profileId: string, name: string, params: UpdateAssistantParams) => {src/context/ModeContext.tsx (2)
36-52: Consider includingsetModein the dependency array.The
eslint-disable-line react-hooks/exhaustive-depssuppresses a valid warning. WhilesetModeis stable due touseCallback, the current pattern creates a closure over the initial (undefined) version ofsetModebefore it's defined. This works becausesetModeis hoisted as a function declaration viauseCallback, but it's fragile.A safer approach is to include
setModein the dependencies, which the linter expects:♻️ Proposed fix
useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Check for Cmd/Ctrl + 1 or 2 if (e.metaKey || e.ctrlKey) { if (e.key === '1') { e.preventDefault() setMode('index') } else if (e.key === '2') { e.preventDefault() setMode('assistant') } } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) - }, []) // eslint-disable-line react-hooks/exhaustive-deps + }, [setMode])Since
setModeis wrapped inuseCallback, it will remain stable and won't cause unnecessary effect re-runs.
4-4: TypeExplorerModeis duplicated.The
ExplorerModetype is also defined inelectron/types.ts(line 55). Consider importing it from there or from a shared types location to maintain a single source of truth.src/hooks/useAssistantQueries.ts (1)
126-133: Inconsistent error logging in delete mutation.The
onErrorcallback doesn't log the error to console unlikeuseCreateAssistantMutationanduseUpdateAssistantMutation. Consider adding consistent error logging for debugging purposes.🔧 Proposed fix for consistency
- onError: (_err, _assistantName, context) => { + onError: (error, _assistantName, context) => { + console.error('Failed to delete assistant:', error) // Roll back to the previous value on error if (context?.previousAssistants) {src/types/electron.d.ts (1)
39-64: Correct the file path reference toelectron/types.ts(notsrc/electron/types.ts) and consider consolidating duplicate type definitions.Type duplication exists between
src/types/electron.d.ts(lines 39-64) andelectron/types.ts(lines 56, 408, 413-438). The types are identical:ExplorerMode,AssistantStatus,AssistantModel,CreateAssistantParams, andUpdateAssistantParamsappear in both locations.While the pattern of global ambient types for the renderer and module exports for the electron layer is intentional, maintaining two identical copies risks sync issues. Consider consolidating by either having the global declarations reference the canonical definitions or documenting the intentional duplication with a reference to the source.
| private mapAssistantModel(model: { | ||
| name: string | ||
| status: string | ||
| instructions?: string | null | ||
| metadata?: object | null | ||
| host?: string | ||
| createdAt?: Date | ||
| updatedAt?: Date | ||
| }): AssistantModel { | ||
| return { | ||
| name: model.name, | ||
| status: model.status as AssistantModel['status'], | ||
| instructions: model.instructions ?? undefined, | ||
| metadata: model.metadata as Record<string, string> | undefined, | ||
| host: model.host, | ||
| createdAt: model.createdAt?.toISOString(), | ||
| updatedAt: model.updatedAt?.toISOString(), | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the AssistantModel type and AssistantStatus enum
find . -type f -name "*.ts" -o -name "*.tsx" | head -20Repository: stepandel/pinecone-explorer
Length of output: 647
🏁 Script executed:
# Search for AssistantModel type definition
rg "type AssistantModel|interface AssistantModel" -A 20Repository: stepandel/pinecone-explorer
Length of output: 1806
🏁 Script executed:
# Search for AssistantStatus enum or type definition
rg "enum AssistantStatus|type AssistantStatus" -A 15Repository: stepandel/pinecone-explorer
Length of output: 1491
🏁 Script executed:
# Find the getStatusColor function in AssistantsPanel.tsx
rg "getStatusColor" -B 2 -A 15Repository: stepandel/pinecone-explorer
Length of output: 2960
🌐 Web query:
Pinecone Assistant API status values
💡 Result:
Pinecone Assistant API status can be one of these values:
InitializingReadyFailedTerminatingInitializationFailed[1][2]
Sources: Pinecone Assistant API reference for “Create an assistant” and “Check assistant status”. [1][2]
🏁 Script executed:
# Find the getStatusTooltip function
rg "getStatusTooltip" -B 2 -A 15Repository: stepandel/pinecone-explorer
Length of output: 3619
Add 'InitializationFailed' to AssistantStatus type definition.
The Pinecone SDK can return InitializationFailed as a valid status, but AssistantStatus only defines 'Initializing' | 'Ready' | 'Failed' | 'Terminating'. The type assertion model.status as AssistantModel['status'] bypasses this gap, allowing the missing status to propagate untyped. While getStatusColor and getStatusTooltip have default cases that prevent crashes, the type system should accurately reflect all possible SDK values. Update AssistantStatus to include 'InitializationFailed'.
🤖 Prompt for AI Agents
In `@electron/assistant-service.ts` around lines 68 - 86, The AssistantStatus
union type is missing 'InitializationFailed' which the Pinecone SDK can return;
update the AssistantStatus type definition to include 'InitializationFailed' so
the status is accurately typed (this will remove the need to rely on the unsafe
cast in mapAssistantModel). Locate the AssistantStatus type and add the literal
'InitializationFailed' to the union, then ensure usages like mapAssistantModel
(where model.status is cast as AssistantModel['status']) and helper functions
getStatusColor/getStatusTooltip accept the new status without changes.
| <div | ||
| className="flex items-center bg-black/[0.04] dark:bg-white/[0.06] rounded-md p-0.5" | ||
| role="tablist" | ||
| aria-label="Explorer mode" | ||
| data-testid="mode-switcher" | ||
| > | ||
| {modes.map(({ value, icon: Icon, label, shortcut }) => { | ||
| const isActive = mode === value | ||
| return ( | ||
| <button | ||
| key={value} | ||
| role="tab" | ||
| aria-selected={isActive} | ||
| onClick={() => setMode(value)} | ||
| className={` | ||
| flex items-center gap-1.5 px-2 py-1 rounded text-[11px] font-medium transition-all | ||
| ${isActive | ||
| ? 'bg-white dark:bg-white/[0.12] text-foreground shadow-sm' | ||
| : 'text-foreground/50 hover:text-foreground/70' | ||
| } | ||
| `} | ||
| title={`${label} (${shortcut})`} | ||
| data-testid={`mode-${value}`} | ||
| > | ||
| <Icon className="h-3.5 w-3.5" /> | ||
| <span className="hidden sm:inline">{value === 'index' ? 'Index' : 'Assistant'}</span> | ||
| </button> | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
WAI-ARIA authoring practices tab pattern requirements keyboard behavior aria-controls
💡 Result:
WAI-ARIA APG Tabs pattern: required roles/states/properties (including aria-controls)
From the WAI-ARIA Authoring Practices Guide (APG) Tabs Pattern:
- Container: element with
role="tablist"(and it must be labeled viaaria-labelledbyoraria-label). [1] - Tabs: each tab has
role="tab"and is contained in thetablist. [1] - Panels: each content panel has
role="tabpanel". [1] - Required relationship:
- Each
tabhasaria-controls="ID_OF_ASSOCIATED_TABPANEL". (This is the APG’s required tab→panel association.) [1] - Each
tabpanelhasaria-labelledby="ID_OF_ASSOCIATED_TAB"(panel name comes from its tab). [1]
- Each
- Selection state:
- The active tab has
aria-selected="true", all othersaria-selected="false". [1]
- The active tab has
- Orientation (if vertical): set
aria-orientation="vertical"on thetablist(default is horizontal). [1]
Common APG implementation detail (shown in the manual example): only the selected tab is in the page Tab order (roving tabindex), with non-selected tabs typically set to tabindex="-1". [2]
Keyboard behavior (APG)
Tab / Shift+Tab
- When focus enters the tablist via
Tab, focus goes to the active tab. [1] - Pressing
Tabagain moves focus out of the tablist to the next item in the page tab sequence—typically thetabpanel, unless the first meaningful element inside the panel is focusable. [1] - If the
tabpanelitself needs to be reachable (e.g., no focusable content inside), APG recommends giving ittabindex="0". [1]
Arrow keys (move focus among tabs)
- Horizontal tablist:
Left Arrow: focus previous tab (wrap first→last). [1]Right Arrow: focus next tab (wrap last→first). [1]
- Vertical tablist (
aria-orientation="vertical"):Up Arrow/Down Arrowperform the same function as Left/Right respectively. [1]
Home / End
Home: move focus to first tab. [2]End: move focus to last tab. [2]
Activation model
- Manual activation:
EnterorSpaceactivates the focused tab (shows its panel). [2] - Automatic activation: moving focus with arrow keys also activates the newly focused tab (and
Home/Endactivate too). [3] - APG notes automatic activation is recommended only when panels display without noticeable latency (e.g., preloaded). [1]
Sources: APG Tabs Pattern [1], APG manual tabs example (keyboard + roving tabindex + aria-controls) [2], APG automatic tabs example [3].
🏁 Script executed:
find . -name "ModeSwitcher.tsx" -type fRepository: stepandel/pinecone-explorer
Length of output: 109
🏁 Script executed:
cat -n ./src/components/mode/ModeSwitcher.tsxRepository: stepandel/pinecone-explorer
Length of output: 2001
🏁 Script executed:
rg "tabpanel" --type tsx --type ts -iRepository: stepandel/pinecone-explorer
Length of output: 98
🏁 Script executed:
rg "aria-controls" --type tsx --type tsRepository: stepandel/pinecone-explorer
Length of output: 98
🏁 Script executed:
# Check what the ModeContext contains and how mode switching works
find . -name "ModeContext*" -type fRepository: stepandel/pinecone-explorer
Length of output: 100
🏁 Script executed:
rg "aria-controls|tabpanel" --glob="*.tsx" --glob="*.ts"Repository: stepandel/pinecone-explorer
Length of output: 53
🏁 Script executed:
cat -n ./src/context/ModeContext.tsxRepository: stepandel/pinecone-explorer
Length of output: 3069
🏁 Script executed:
# Search for keyboard event handlers related to mode switching
rg "onKeyDown|onKeyPress|ArrowLeft|ArrowRight" --glob="*.tsx" --glob="*.ts"Repository: stepandel/pinecone-explorer
Length of output: 782
Missing ARIA controls and tab keyboard handlers—use a toggle pattern instead.
This component advertises role="tablist" and role="tab" but lacks the required APG tab pattern elements: aria-controls attributes, associated tabpanel elements, and keyboard handlers (arrow keys, Home/End). Since this is a simple 2-option mode switcher without tab panels, switch to role="group" with aria-pressed on the buttons instead.
🧭 Suggested toggle-button ARIA adjustment
- <div
- className="flex items-center bg-black/[0.04] dark:bg-white/[0.06] rounded-md p-0.5"
- role="tablist"
- aria-label="Explorer mode"
- data-testid="mode-switcher"
- >
+ <div
+ className="flex items-center bg-black/[0.04] dark:bg-white/[0.06] rounded-md p-0.5"
+ role="group"
+ aria-label="Explorer mode"
+ data-testid="mode-switcher"
+ >
{modes.map(({ value, icon: Icon, label, shortcut }) => {
const isActive = mode === value
return (
<button
key={value}
- role="tab"
- aria-selected={isActive}
+ type="button"
+ aria-pressed={isActive}
onClick={() => setMode(value)}🤖 Prompt for AI Agents
In `@src/components/mode/ModeSwitcher.tsx` around lines 20 - 47, The component
currently implements a tab pattern but has no tabpanel or keyboard tab
behaviors; update ModeSwitcher so the container uses role="group" (replace
role="tablist") and each option button uses aria-pressed={isActive} (remove
role="tab" and aria-selected) while keeping onClick={() => setMode(value)} and
the existing data-testid; ensure the span/text and Icon rendering remain
unchanged and keep keyboard activation via Enter/Space by relying on native
button behavior in the buttons rendered from modes, referencing the modes
mapping, mode state and setMode function.
Code ReviewFound 1 issue that needs to be addressed. File: Issue: Missing Provider in Component Tree The Evidence:
Suggested Fix: Add import { AssistantSelectionProvider } from '../context/AssistantSelectionContext'
// In the component tree:
<ModeProvider>
<AssistantSelectionProvider>
<ClipboardProvider>
...
</ClipboardProvider>
</AssistantSelectionProvider>
</ModeProvider> |
- Add useAssistantQueries.ts with React Query hooks for assistant CRUD - Add AssistantSelectionContext.tsx for managing selected assistant - Add AssistantsPanel.tsx mirroring IndexesPanel pattern - Status indicators: Ready (green), Initializing (yellow), Failed (red) - Loading, error, and empty states handled Closes PINE-38
Add file operations scoped to a specific assistant: Types (electron/types.ts): - AssistantFileStatus enum: Processing, Available, Deleting, ProcessingFailed - AssistantFile interface with id, name, status, percentDone, metadata, signedUrl, errorMessage - ListAssistantFilesFilter for filtering files - UploadAssistantFileParams for file upload with metadata AssistantService (electron/assistant-service.ts): - listFiles(assistantName, filter?) - List files for an assistant - describeFile(assistantName, fileId) - Get file details with signed URL - uploadFile(assistantName, params) - Upload file from disk path - deleteFile(assistantName, fileId) - Delete a file IPC handlers (electron/main.ts): - assistant:files:list - assistant:files:describe - assistant:files:upload - assistant:files:delete Preload bindings (electron/preload.ts): - assistant.files.list() - assistant.files.describe() - assistant.files.upload() - assistant.files.delete() TypeScript declarations (src/types/electron.d.ts): - Added all file types and API methods
- Add useFilesQuery hook with dynamic polling (5s while processing) - Add FileSelectionContext for tracking selected file - Add FilesPanel component with status indicators and upload button - Add dialog:showOpenDialog IPC handler for native file picker - Wire up providers in ConnectionWindow Closes PINE-41
PINE-42 - Add useUploadFileMutation hook to useAssistantQueries.ts - Create UploadFileDialog component with: - Drag-and-drop file zone - File picker button using native dialog - Selected file preview with name and size - Optional metadata JSON editor with validation - Multimodal checkbox (enabled only for PDF files) - Upload progress indicator - Error handling with inline message - Dialog closes on successful upload - Wire up FilesPanel upload button to open UploadFileDialog
- Create FileDetailPanel.tsx showing file metadata when selected - Add ID field with copy-to-clipboard button - Display status with color indicator (Available/Processing/Failed/Deleting) - Show processing progress bar when file is processing - Show error message section for failed files - Display timestamps (created/updated) with formatted dates - Show custom metadata as JSON - Add Download button that opens signedUrl via shell.openExternal - Add Delete button with confirmation dialog - Add useDeleteFileMutation and useFileDetailQuery hooks to useAssistantQueries.ts - Export FileDetailPanel from files/index.ts Note: File size display not implemented as AssistantFile type from Pinecone API does not include a size field.
- Add ChatMessage, ChatParams, ChatResponse, ChatStreamChunk types - Add chat() and chatStream() methods to AssistantService - Add IPC handlers for assistant:chat, assistant:chat:stream:start/cancel - Add preload bindings with onChunk event listener for streaming - Track active streams with AbortController for cancellation Closes PINE-44
- Create src/components/chat/ChatView.tsx - Layout with scrollable message list and fixed input area - Message display with user/assistant avatars and styling - Model dropdown selector (gpt-4o, claude-3-5-sonnet, gemini-2.0-flash) - Clear conversation button - Auto-scroll to bottom on new messages - Submit on Enter, Shift+Enter for newline - Send button disabled while streaming - Stop generation button during streaming - Citation display for assistant messages - Empty state with helpful instructions - Create src/hooks/useChatStream.ts - Manages streaming state and message accumulation - Handles chunk events (message_start, content, citation, message_end, error) - Returns: messages, isStreaming, sendMessage, clearMessages, cancelStream - Properly cleans up subscriptions on unmount - Update src/components/layout/MainContent.tsx - Import ModeContext and AssistantSelectionContext - Render ChatView when mode === 'assistant' and an assistant is selected - Show empty state message when in assistant mode without selection - Keep existing VectorsView for mode === 'index'
- Add ChatMessage component with markdown support via react-markdown - User messages right-aligned with blue/primary background - Assistant messages left-aligned with muted background - Typing indicator (animated dots) when streaming with empty content - Live cursor animation during content streaming - Citation numbers as clickable superscripts inline with text - Styled code blocks and inline code - Update ChatView to use new ChatMessage component
- Create CitationPopover component using Radix Popover - Shows file name and page numbers for each reference - View File button navigates using FileSelectionContext - Update ChatMessage to wrap citation superscripts with popover - Popover closes on outside click (Radix default behavior) Closes PINE-47
- Conditionally render AssistantsPanel/IndexesPanel based on mode - Conditionally render FilesPanel/NamespacesPanel based on mode - Conditionally render FileDetailPanel/VectorDetailPanel based on mode - Preserve panel resize handles and widths - Selection state isolated between modes via separate contexts Closes PINE-48
- Add IPC handlers in main.ts for context-menu:show-assistant and context-menu:show-file - Add preload bindings for showAssistantMenu, onAssistantAction, showFileMenu, onFileAction - Update AssistantsPanel.tsx with: - Native context menu on right-click with Edit and Delete options - Delete confirmation dialog with name verification - Hook integration with useDeleteAssistantMutation - Update FilesPanel.tsx with: - Native context menu on right-click with Download and Delete options - Delete confirmation dialog - Download via signedUrl fetch and shell.openExternal - Hook integration with useDeleteFileMutation and useFileDetailQuery - Update TypeScript declarations in electron.d.ts Acceptance criteria: - Right-click assistant shows Edit/Delete menu ✓ - Right-click file shows Download/Delete menu ✓ - Delete actions show confirmation dialog ✓ - Menu actions trigger correct operations ✓
- Update keyboard shortcuts constants with new assistant/chat categories: - INDEX_MODE (Cmd+1): Switch to Index mode - ASSISTANT_MODE (Cmd+2): Switch to Assistant mode - NEW_ASSISTANT (Cmd+Shift+N): Create new assistant - SEND_MESSAGE (Cmd+Enter): Send chat message - FOCUS_CHAT_INPUT (Cmd+K): Focus chat input - CLEAR_CONVERSATION (Cmd+Shift+Backspace): Clear conversation - Update electron menu.ts: - Replace panel toggle items with Index Mode/Assistant Mode in View menu - Add Assistant menu with New Assistant, Chat submenu, Edit/Delete items - Chat submenu includes Send, Focus Input, Clear Conversation - Add IPC bindings in preload.ts for new menu events: - Mode switching: onSwitchToIndexMode, onSwitchToAssistantMode - Assistant: onNewAssistant, onEditAssistant, onDeleteAssistant - Chat: onSendMessage, onFocusChatInput, onClearConversation - Update ModeContext.tsx to listen for menu IPC events - Update ChatView.tsx with keyboard shortcut handlers: - Cmd+K focuses chat input - Cmd+Shift+Backspace clears conversation - Cmd+Enter sends message (via menu) - Update AssistantsPanel.tsx: - Cmd+Shift+N creates new assistant - Handle menu events for edit/delete assistant - Update TypeScript types in electron.d.ts - Clean up obsolete toggle panel handlers from useMenuHandlers.ts
f846d60 to
032f954
Compare
* feat: Add ModeContext and ModeSwitcher UI (#37) * feat: Add ModeContext and ModeSwitcher UI - Add ModeContext for managing index/assistant mode state - Add ModeSwitcher segmented control with Database/Bot icons - Persist mode preference per connection profile - Add keyboard shortcuts Cmd+1 (Index) and Cmd+2 (Assistant) Closes PINE-36 * fix: address review comments on PINE-36 - Update .linear.toml workspace from chroma-explorer to pinecone-explorer - Make setPreferredMode throw on missing profile instead of silently no-op - Change ModeSwitcher from tablist/tab to radiogroup/radio for accessibility * fix: add packages field to pnpm-workspace.yaml for CI * fix: add aria-label for accessible name on ModeSwitcher buttons --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * feat: Add AssistantService with CRUD operations (#38) * feat: Add AssistantService with CRUD operations - Add AssistantService class wrapping Pinecone SDK assistant methods - Add IPC handlers for assistant:list/create/describe/update/delete - Add preload bindings for window.electronAPI.assistant - Add TypeScript types for AssistantModel, CreateAssistantParams, UpdateAssistantParams - Wire up service to PineconeService with getAssistantService() method Closes PINE-37 * fix: address review comments on PINE-37 - Normalize metadata null to undefined in mapAssistantModel - Fix stale setMode closure in keyboard shortcut handler (moved setMode before useEffect, added to deps) --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * feat: Add AssistantsPanel component (#39) * feat: Add AssistantsPanel component - Add useAssistantQueries.ts with React Query hooks for assistant CRUD - Add AssistantSelectionContext.tsx for managing selected assistant - Add AssistantsPanel.tsx mirroring IndexesPanel pattern - Status indicators: Ready (green), Initializing (yellow), Failed (red) - Loading, error, and empty states handled Closes PINE-38 * feat(assistant): add file management IPC handlers (PINE-40) Add file operations scoped to a specific assistant: Types (electron/types.ts): - AssistantFileStatus enum: Processing, Available, Deleting, ProcessingFailed - AssistantFile interface with id, name, status, percentDone, metadata, signedUrl, errorMessage - ListAssistantFilesFilter for filtering files - UploadAssistantFileParams for file upload with metadata AssistantService (electron/assistant-service.ts): - listFiles(assistantName, filter?) - List files for an assistant - describeFile(assistantName, fileId) - Get file details with signed URL - uploadFile(assistantName, params) - Upload file from disk path - deleteFile(assistantName, fileId) - Delete a file IPC handlers (electron/main.ts): - assistant:files:list - assistant:files:describe - assistant:files:upload - assistant:files:delete Preload bindings (electron/preload.ts): - assistant.files.list() - assistant.files.describe() - assistant.files.upload() - assistant.files.delete() TypeScript declarations (src/types/electron.d.ts): - Added all file types and API methods * feat: Add FilesPanel component - Add useFilesQuery hook with dynamic polling (5s while processing) - Add FileSelectionContext for tracking selected file - Add FilesPanel component with status indicators and upload button - Add dialog:showOpenDialog IPC handler for native file picker - Wire up providers in ConnectionWindow Closes PINE-41 * feat(files): add UploadFileDialog component with drag-and-drop support PINE-42 - Add useUploadFileMutation hook to useAssistantQueries.ts - Create UploadFileDialog component with: - Drag-and-drop file zone - File picker button using native dialog - Selected file preview with name and size - Optional metadata JSON editor with validation - Multimodal checkbox (enabled only for PDF files) - Upload progress indicator - Error handling with inline message - Dialog closes on successful upload - Wire up FilesPanel upload button to open UploadFileDialog * feat(PINE-43): Create FileDetailPanel component - Create FileDetailPanel.tsx showing file metadata when selected - Add ID field with copy-to-clipboard button - Display status with color indicator (Available/Processing/Failed/Deleting) - Show processing progress bar when file is processing - Show error message section for failed files - Display timestamps (created/updated) with formatted dates - Show custom metadata as JSON - Add Download button that opens signedUrl via shell.openExternal - Add Delete button with confirmation dialog - Add useDeleteFileMutation and useFileDetailQuery hooks to useAssistantQueries.ts - Export FileDetailPanel from files/index.ts Note: File size display not implemented as AssistantFile type from Pinecone API does not include a size field. * feat: Add chat IPC handlers with streaming support - Add ChatMessage, ChatParams, ChatResponse, ChatStreamChunk types - Add chat() and chatStream() methods to AssistantService - Add IPC handlers for assistant:chat, assistant:chat:stream:start/cancel - Add preload bindings with onChunk event listener for streaming - Track active streams with AbortController for cancellation Closes PINE-44 * feat(PINE-45): Create ChatView main component - Create src/components/chat/ChatView.tsx - Layout with scrollable message list and fixed input area - Message display with user/assistant avatars and styling - Model dropdown selector (gpt-4o, claude-3-5-sonnet, gemini-2.0-flash) - Clear conversation button - Auto-scroll to bottom on new messages - Submit on Enter, Shift+Enter for newline - Send button disabled while streaming - Stop generation button during streaming - Citation display for assistant messages - Empty state with helpful instructions - Create src/hooks/useChatStream.ts - Manages streaming state and message accumulation - Handles chunk events (message_start, content, citation, message_end, error) - Returns: messages, isStreaming, sendMessage, clearMessages, cancelStream - Properly cleans up subscriptions on unmount - Update src/components/layout/MainContent.tsx - Import ModeContext and AssistantSelectionContext - Render ChatView when mode === 'assistant' and an assistant is selected - Show empty state message when in assistant mode without selection - Keep existing VectorsView for mode === 'index' * feat(PINE-46): Create ChatMessage component with streaming - Add ChatMessage component with markdown support via react-markdown - User messages right-aligned with blue/primary background - Assistant messages left-aligned with muted background - Typing indicator (animated dots) when streaming with empty content - Live cursor animation during content streaming - Citation numbers as clickable superscripts inline with text - Styled code blocks and inline code - Update ChatView to use new ChatMessage component * feat(chat): add CitationPopover component for interactive citations - Create CitationPopover component using Radix Popover - Shows file name and page numbers for each reference - View File button navigates using FileSelectionContext - Update ChatMessage to wrap citation superscripts with popover - Popover closes on outside click (Radix default behavior) Closes PINE-47 * feat: Wire up Assistant mode in MainContent - Conditionally render AssistantsPanel/IndexesPanel based on mode - Conditionally render FilesPanel/NamespacesPanel based on mode - Conditionally render FileDetailPanel/VectorDetailPanel based on mode - Preserve panel resize handles and widths - Selection state isolated between modes via separate contexts Closes PINE-48 * feat(PINE-49): Add context menus for Assistants and Files - Add IPC handlers in main.ts for context-menu:show-assistant and context-menu:show-file - Add preload bindings for showAssistantMenu, onAssistantAction, showFileMenu, onFileAction - Update AssistantsPanel.tsx with: - Native context menu on right-click with Edit and Delete options - Delete confirmation dialog with name verification - Hook integration with useDeleteAssistantMutation - Update FilesPanel.tsx with: - Native context menu on right-click with Download and Delete options - Delete confirmation dialog - Download via signedUrl fetch and shell.openExternal - Hook integration with useDeleteFileMutation and useFileDetailQuery - Update TypeScript declarations in electron.d.ts Acceptance criteria: - Right-click assistant shows Edit/Delete menu ✓ - Right-click file shows Download/Delete menu ✓ - Delete actions show confirmation dialog ✓ - Menu actions trigger correct operations ✓ * feat(PINE-50): Add keyboard shortcuts for Assistant mode - Update keyboard shortcuts constants with new assistant/chat categories: - INDEX_MODE (Cmd+1): Switch to Index mode - ASSISTANT_MODE (Cmd+2): Switch to Assistant mode - NEW_ASSISTANT (Cmd+Shift+N): Create new assistant - SEND_MESSAGE (Cmd+Enter): Send chat message - FOCUS_CHAT_INPUT (Cmd+K): Focus chat input - CLEAR_CONVERSATION (Cmd+Shift+Backspace): Clear conversation - Update electron menu.ts: - Replace panel toggle items with Index Mode/Assistant Mode in View menu - Add Assistant menu with New Assistant, Chat submenu, Edit/Delete items - Chat submenu includes Send, Focus Input, Clear Conversation - Add IPC bindings in preload.ts for new menu events: - Mode switching: onSwitchToIndexMode, onSwitchToAssistantMode - Assistant: onNewAssistant, onEditAssistant, onDeleteAssistant - Chat: onSendMessage, onFocusChatInput, onClearConversation - Update ModeContext.tsx to listen for menu IPC events - Update ChatView.tsx with keyboard shortcut handlers: - Cmd+K focuses chat input - Cmd+Shift+Backspace clears conversation - Cmd+Enter sends message (via menu) - Update AssistantsPanel.tsx: - Cmd+Shift+N creates new assistant - Handle menu events for edit/delete assistant - Update TypeScript types in electron.d.ts - Clean up obsolete toggle panel handlers from useMenuHandlers.ts --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> * feat: Add file management IPC handlers (#40) * feat(assistant): add file management IPC handlers (PINE-40) Add file operations scoped to a specific assistant: Types (electron/types.ts): - AssistantFileStatus enum: Processing, Available, Deleting, ProcessingFailed - AssistantFile interface with id, name, status, percentDone, metadata, signedUrl, errorMessage - ListAssistantFilesFilter for filtering files - UploadAssistantFileParams for file upload with metadata AssistantService (electron/assistant-service.ts): - listFiles(assistantName, filter?) - List files for an assistant - describeFile(assistantName, fileId) - Get file details with signed URL - uploadFile(assistantName, params) - Upload file from disk path - deleteFile(assistantName, fileId) - Delete a file IPC handlers (electron/main.ts): - assistant:files:list - assistant:files:describe - assistant:files:upload - assistant:files:delete Preload bindings (electron/preload.ts): - assistant.files.list() - assistant.files.describe() - assistant.files.upload() - assistant.files.delete() TypeScript declarations (src/types/electron.d.ts): - Added all file types and API methods * fix: address review comments - pnpm workspace, linear config, AssistantStatus type * fix: address review comments on PINE-40 - AssistantsPanel: Convert assistant row div to button for keyboard accessibility - AssistantsPanel: Add aria-pressed attribute for active state - ModeContext: Fix stale setMode closure by adding to useEffect dependencies - ModeContext: Reorder setMode definition before keyboard effect * fix: remove unused ExplorerMode type from electron/types.ts The ExplorerMode type was defined in electron/types.ts but never imported or used. The only active definition is in src/context/ModeContext.tsx. This change removes the duplicate definition and updates ConnectionProfile.preferredMode to use an inline union type. Co-authored-by: Stepan Arsentjev <stepandel@users.noreply.github.com> --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Stepan Arsentjev <stepandel@users.noreply.github.com> * feat: Add FilesPanel component (#41) * feat: Add FilesPanel component - Add useFilesQuery hook with dynamic polling (5s while processing) - Add FileSelectionContext for tracking selected file - Add FilesPanel component with status indicators and upload button - Add dialog:showOpenDialog IPC handler for native file picker - Wire up providers in ConnectionWindow Closes PINE-41 * fix: address review comments - pnpm workspace, linear config, AssistantStatus type * fix: clear assistantService on disconnect, remove files from later PRs --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * fix build: add stub FileDetailPanel * fix: Address PR review comments - Add registerAccelerator: false to New Index menu item for consistency - Add cancellation guard in ModeContext to prevent stale mode updates - Handle 'InitializationFailed' status in AssistantsPanel - Make citation superscripts keyboard accessible (button with aria-label) - Validate URL protocol before calling openExternal for security - Disable delete button when file status is 'Deleting' - Clear messages when switching assistants in useChatStream * feat: Add AssistantConfigView for create/edit (#51) - Add AssistantConfigView component for creating/editing assistants - Add DraftAssistantContext for managing draft assistant state - Wire up AssistantConfigView in MainContent - Add DraftAssistantProvider to ConnectionWindow Closes PINE-39 Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * fix: address review comments on PINE-42 (#42) Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * fix: address review comments on PINE-44 (#45) - Remove @ts-expect-error, add temperature/contextOptions to chat params - Add isDestroyed check and error handling for streaming in main.ts - Clear assistantService on disconnect in pinecone-service.ts - Make assistant rows keyboard-accessible (use button element) - Add IME composition check (isComposing) to prevent accidental submit - Use stable message keys (message.id) instead of array index - Reset conversation state when assistantName changes - Remove empty assistant placeholder when canceling stream - Pass multimodal flag through file upload flow Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * fix: address review comments on PINE-39 (#47) - Fix .linear.toml workspace (chroma-explorer → pinecone-explorer) - Add packages field to pnpm-workspace.yaml for CI - Add InitializationFailed to AssistantStatus type - Reset assistantService on connect/disconnect Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * fix: address review comments on PINE-49 (#49) - Pass temperature and contextOptions to chat() and chatStream() methods - Add multimodal param to upload file flow (types, hook, service) - Guard handleConfirmDelete against null currentProfile - Use stable message.id key instead of array index in ChatView - Fix download race condition by verifying fileDetail matches fileToDownload - Keep file detail cache consistent during delete operations - Handle early stream chunks before stream ID is set Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> * fix: Address additional PR review comments (round 2) - Add assistant and chat menu event handlers to preload.ts - Add types for new menu handlers in electron.d.ts - Add pending guard to prevent duplicate delete calls in AssistantsPanel - Reset initialization state when profile changes in ModeContext - Extract shared Markdown components to reduce duplication in ChatMessage * fix buid errors * feat: Add E2E test suite for Assistant feature (#53) * feat(e2e): add data-testid attributes to assistant components Add comprehensive data-testid attributes for E2E testing: Mode: - mode-switcher, mode-index, mode-assistant Assistants: - assistants-panel, assistant-item, assistant-status - new-assistant-button, assistant-config-view - assistant-name-input, assistant-instructions-input - assistant-save-button, assistant-cancel-button Files: - files-panel, files-empty-state, file-item - upload-file-button, file-detail-panel - file-detail-empty-state, file-download-button - file-delete-button, upload-file-dialog - browse-files-button, upload-submit-button Chat: - chat-view, chat-message-list, chat-input - chat-send-button, chat-stop-button - chat-clear-button, chat-model-selector - chat-message-user, chat-message-assistant Citations: - citation-superscript, citation-popover - citation-reference, citation-file-name - citation-view-file-button Part of PINE-51 * fix(e2e): Address PR review comments - 13 actionable items Fixes from CodeRabbit review: 1. assistant-citations.spec.ts: - Citation test now explicitly skips with message when no citations - Multiple citations test uses test.skip() when citationCount <= 1 2. assistant-crud.spec.ts: - Added assertion for errorMessage visibility in validation test - Context menu edit test now explicitly skipped (native menu limitation) 3. assistant-file-detail.spec.ts: - File selection test explicitly skips when fileCount === 0 - Delete test now asserts file disappears and count decreases 4. assistant-integration.spec.ts: - Network failure test now uses page.route() to simulate failures 5. assistant-mode.spec.ts: - Cross-platform keyboard shortcuts (Meta on macOS, Control on others) - Mode persistence test asserts switcher visibility (no silent skip) 6. assistant-upload.spec.ts: - API upload test: skip until real file fixture available - Metadata input test: skip until file dialog mocking available - Processing status test: explicit skip when no files - Progress test: skip until upload flow is wired --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> * fix(assistant): wire chatStream API through preload (#54) PINE-55: Fix 'Cannot read properties of undefined (reading onChunk)' Root cause: useChatStream.ts expected assistant.chatStream.* APIs but electron/preload.ts never exposed them. IPC handlers existed in main.ts but weren't wired through the preload bridge. Changes: - Add Chat types to electron/types.ts (ChatMessage, Citation, CitationReference, ChatUsage, ChatParams, ChatResponse, ChatStreamChunk) - Add chatStream namespace to assistant API in preload.ts with start/cancel/onChunk methods - Add corresponding TypeScript types to src/types/electron.d.ts The chatStream API now properly exposes: - start(profileId, assistantName, params) -> streamId - cancel(streamId) -> void - onChunk(callback) -> cleanup function Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> * fix: Phase 6 feedback - dialog API and mode labels (PINE-53, PINE-54) (#55) * fix(assistant): wire chatStream API through preload PINE-55: Fix 'Cannot read properties of undefined (reading onChunk)' Root cause: useChatStream.ts expected assistant.chatStream.* APIs but electron/preload.ts never exposed them. IPC handlers existed in main.ts but weren't wired through the preload bridge. Changes: - Add Chat types to electron/types.ts (ChatMessage, Citation, CitationReference, ChatUsage, ChatParams, ChatResponse, ChatStreamChunk) - Add chatStream namespace to assistant API in preload.ts with start/cancel/onChunk methods - Add corresponding TypeScript types to src/types/electron.d.ts The chatStream API now properly exposes: - start(profileId, assistantName, params) -> streamId - cancel(streamId) -> void - onChunk(callback) -> cleanup function * fix: Phase 6 feedback - dialog API and mode labels PINE-54: Wire dialog API through preload - Add dialog.showOpenDialog to electron/preload.ts - Add corresponding TypeScript types PINE-53: Update mode labels to 'Database' instead of 'Index' - Change 'Index Explorer' → 'Database Explorer' in tooltip - Change 'Index' → 'Database' in button text --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> * fix uploader * fix(assistant): use SDK chatStream method instead of chat with stream flag The Pinecone SDK has a dedicated `assistant.chatStream()` method for streaming. Passing `stream: true` to `assistant.chat()` is invalid and causes an API error. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(assistant): update supported models list to match Pinecone Assistant Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style(chat): compact macOS-native chat UI Remove avatars and role labels for an Apple Messages-style layout. Tighten spacing, use pill-shaped bubbles, circular send button, and smaller typography to match the app's TopBar and sidebar density. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(files): add right-click context menu for file actions (#56) 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). Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> * feat(analytics): add tracking for Assistant events (PINE-57) (#57) * 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). * feat(analytics): add tracking for Assistant events PINE-57: Phase 7 - Add analytics Add track() calls to Assistant IPC handlers: - assistant_created (with region) - assistant_deleted - file_uploaded (with multimodal flag) - file_deleted - chat_message_sent (with model, messageCount) - chat_stream_started (with model) Follows existing analytics pattern from index operations. --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> * fix(files): poll for Deleting status to update UI after file removal The files query only polled while files had 'Processing' status, so files stuck in 'Deleting' status never refreshed until a manual page reload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(assistant): production readiness fixes from audit Address 10 issues found during production readiness audit: 1. Fix IPC wiring for menu-driven mode switching (onIndexMode/onAssistantMode) 2. Add URL protocol validation in shell:openExternal (block non-http(s)) 3. Add confirmation dialog before file deletion in FileDetailPanel 4. Surface file operation errors to users in FilesPanel (upload/delete/download) 5. Resolve keyboard shortcut collision (NEW_ASSISTANT → CmdOrCtrl+Shift+A) 6. Add synchronous ref guard to prevent double-submit in useChatStream 7. Abort active chat streams when window is destroyed 8. Add vitest framework with 17 unit tests for AssistantService and matchesShortcut 9. Add file path validation (exists check) before upload in main process 10. Remove unused dependencies (sharp, dotenv, bufferutil, utf-8-validate) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * clean up --------- Co-authored-by: Atlas (Engineering Lead) <atlas@openclaw.ai> Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Stepan Arsentjev <stepandel@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Create the left sidebar panel that lists all assistants for the connected account, mirroring the existing IndexesPanel pattern.
Changes
useAssistantQueries.tswith React Query hooks for assistant CRUD operationsAssistantSelectionContext.tsxfor managing selected assistant stateAssistantsPanel.tsxcomponent with:Files Added
src/hooks/useAssistantQueries.tssrc/context/AssistantSelectionContext.tsxsrc/components/assistants/AssistantsPanel.tsxTesting
pnpm test:build✅Closes PINE-38
Summary by CodeRabbit