Conversation
* 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 - 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>
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughThis PR introduces a comprehensive Assistant mode feature to Pinecone Explorer, enabling users to create and manage AI assistants, upload knowledge files, and interact via streaming chat. The implementation includes a new AssistantService wrapping Pinecone SDK operations, extensive Electron IPC layer expansion for assistant CRUD and file management, dual-mode UI (Index vs Assistant), context-based state management, and complete E2E test coverage across assistant workflows. Changes
Sequence Diagram(s)sequenceDiagram
participant Renderer as Renderer Process
participant MainElectron as Main Process
participant PineconeService as PineconeService
participant AssistantService as AssistantService
participant PineconeSDK as Pinecone SDK
participant IPC as IPC Bridge
Renderer->>IPC: assistant:chat:stream:start(profileId, name, params)
IPC->>MainElectron: Handle stream:start
MainElectron->>PineconeService: getAssistantService()
PineconeService->>AssistantService: new AssistantService(pinecone)
MainElectron->>AssistantService: chatStream(name, params, onChunk, signal)
AssistantService->>PineconeSDK: client.assistants.chat.stream()
PineconeSDK-->>AssistantService: event: message_start
AssistantService->>MainElectron: onChunk({type: 'message_start', ...})
MainElectron->>Renderer: assistant:chat:stream:chunk
Renderer->>Renderer: Update message placeholder
PineconeSDK-->>AssistantService: event: content
AssistantService->>MainElectron: onChunk({type: 'content', delta})
MainElectron->>Renderer: assistant:chat:stream:chunk
Renderer->>Renderer: Append to message
PineconeSDK-->>AssistantService: event: citation
AssistantService->>MainElectron: onChunk({type: 'citation', ...})
MainElectron->>Renderer: assistant:chat:stream:chunk
PineconeSDK-->>AssistantService: event: message_end
AssistantService->>MainElectron: onChunk({type: 'message_end', usage, model})
MainElectron->>Renderer: assistant:chat:stream:chunk
Renderer->>Renderer: Finalize message
sequenceDiagram
participant User as User
participant UI as UI Layer
participant DraftContext as DraftAssistantContext
participant Mutations as useMutations
participant MainElectron as Main Process
participant PineconeSDK as Pinecone SDK
User->>UI: Click "New Assistant"
UI->>DraftContext: startCreating()
DraftContext->>DraftContext: Initialize draft (empty name, instructions, region)
User->>UI: Enter name, instructions, select region
UI->>DraftContext: updateDraft(field, value)
DraftContext->>DraftContext: Validate field (name required, lowercase)
UI->>UI: Show validation error/success
User->>UI: Click "Create"
UI->>Mutations: useCreateAssistantMutation.mutate(params)
Mutations->>MainElectron: assistant:create(profileId, params)
MainElectron->>PineconeSDK: client.assistants.create(...)
PineconeSDK-->>MainElectron: AssistantModel
MainElectron->>Mutations: Return {success: true, data: assistant}
Mutations->>DraftContext: setActiveAssistant(name)
Mutations->>DraftContext: invalidate queries (refresh list)
DraftContext->>DraftContext: Clear draft
UI->>UI: Switch to ChatView
sequenceDiagram
participant User as User
participant FilesPanel as FilesPanel
participant Dialog as dialog.showOpenDialog
participant Mutations as useUploadFileMutation
participant MainElectron as Main Process
participant PineconeSDK as Pinecone SDK
User->>FilesPanel: Click "Upload File"
FilesPanel->>Dialog: electronAPI.dialog.showOpenDialog({filters, properties})
Dialog-->>FilesPanel: {canceled: false, filePaths: [path]}
User->>Mutations: (Implicit via FilesPanel)
Mutations->>MainElectron: assistant:files:upload(profileId, assistantName, {filePath, metadata, multimodal})
MainElectron->>PineconeSDK: client.assistants.files.upload(...)
PineconeSDK-->>MainElectron: AssistantFile {id, name, status: 'Processing', percentDone: 0}
MainElectron->>Mutations: Return {success: true, data: file}
Mutations->>Mutations: Optimistically update cache
Mutations->>Mutations: Poll file status (refetch every 5s)
PineconeSDK-->>PineconeSDK: status transitions Processing → Available
Mutations->>FilesPanel: Update UI with new status
FilesPanel->>User: Display file with Ready status
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
* 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>
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
electron/pinecone-service.ts (1)
92-98:⚠️ Potential issue | 🟠 MajorMissing cleanup for
assistantServiceindisconnect().The
disconnect()method resetsembeddingServicetonullbut does not resetassistantService. This could cause stale references after reconnection, wheregetAssistantService()returns a service bound to the old Pinecone client.🐛 Proposed fix
disconnect(): void { this.client = null this.embeddingService = null + this.assistantService = null this.profile = null this.indexCache.clear() this.indexInfoCache.clear() }
🧹 Nitpick comments (2)
src/components/mode/ModeSwitcher.tsx (1)
11-14: Consider platform-aware keyboard shortcut display.The shortcuts use the macOS convention (
⌘1,⌘2), but Windows/Linux users would expectCtrl+1/Ctrl+2. This could cause confusion for non-macOS users.♻️ Suggested approach
You could detect the platform and display the appropriate modifier:
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 const modes: ModeOption[] = [ { value: 'index', icon: Database, label: 'Index Explorer', shortcut: isMac ? '⌘1' : 'Ctrl+1' }, { value: 'assistant', icon: Bot, label: 'Assistant Explorer', shortcut: isMac ? '⌘2' : 'Ctrl+2' }, ]Or move
modesinside the component and compute shortcuts dynamically.src/context/ModeContext.tsx (1)
18-33: Avoid stale mode updates when profile changes.If
getPreferredModeresolves after a profile switch/unmount, it can overwrite the newer mode or set state after unmount. Consider a simple cancellation guard.Suggested fix
useEffect(() => { if (!currentProfile) return + let cancelled = false + const profileId = currentProfile.id - window.electronAPI.profiles.getPreferredMode(currentProfile.id) + window.electronAPI.profiles.getPreferredMode(profileId) .then((savedMode) => { + if (cancelled) return if (savedMode) { setModeState(savedMode) } setIsInitialized(true) }) .catch((err) => { + if (cancelled) return console.warn('Failed to load saved mode, using default:', err) setIsInitialized(true) }) + return () => { + cancelled = true + } }, [currentProfile])
* 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>
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
electron/menu.ts (1)
276-383:⚠️ Potential issue | 🟠 MajorNew Index accelerator should also disable global registration to match New Assistant.
Both "New Index..." and "New Assistant..." use
CmdOrCtrl+Shift+N. The "New Assistant" menu item already hasregisterAccelerator: false(line 382) to prevent blocking the shortcut in assistant mode, but "New Index..." (line 278) does NOT have this flag. This creates an asymmetry: the New Index accelerator registers globally while New Assistant relies on the renderer's context-aware keyboard shortcut handler.Add
registerAccelerator: falseto the "New Index" menu item to match the pattern and ensure both shortcuts are handled consistently by the renderer-side handlers in IndexesPanel.tsx and AssistantsPanel.tsx.Suggested fix
{ label: 'New Index...', accelerator: 'CmdOrCtrl+Shift+N', click: () => { sendToFocusedWindow('menu:new-index') }, + registerAccelerator: false, },electron/preload.ts (1)
173-212:⚠️ Potential issue | 🟠 MajorExpose file context‑menu APIs used by FilesPanel.
FilesPanelcallswindow.electronAPI.contextMenu.showFileMenuandonFileAction, but these aren’t exposed here, so the renderer will hitundefined. Add the preload wrappers and wire matching IPC handlers inelectron/main.ts.Suggested preload additions
contextMenu: { showIndexMenu: (indexName: string): void => { ipcRenderer.send('context-menu:show-index', indexName) }, + showFileMenu: (assistantName: string, fileId: string, fileName: string): void => { + ipcRenderer.send('context-menu:show-file', assistantName, fileId, fileName) + }, showIndexPanelMenu: (): void => { ipcRenderer.send('context-menu:show-index-panel') }, onAction: (callback: (action: { action: string; indexName?: string }) => void): (() => void) => { const handler = (_event: any, data: { action: string; indexName?: string }) => callback(data) ipcRenderer.on('context-menu:action', handler) return () => ipcRenderer.removeListener('context-menu:action', handler) }, + onFileAction: ( + callback: (action: { action: string; fileId?: string; fileName?: string }) => void + ): (() => void) => { + const handler = (_event: any, data: { action: string; fileId?: string; fileName?: string }) => callback(data) + ipcRenderer.on('context-menu:file-action', handler) + return () => ipcRenderer.removeListener('context-menu:file-action', handler) + },
🤖 Fix all issues with AI agents
In `@src/components/assistants/AssistantsPanel.tsx`:
- Around line 27-56: The status mapping functions getStatusColor and
getStatusTooltip need to treat the AssistantStatus value "InitializationFailed"
as a failure: update both functions' switch statements to add a case
'InitializationFailed' that returns the same failure styling/class as 'Failed'
(e.g., 'bg-red-500') in getStatusColor and the same failure tooltip string
(e.g., 'Failed' or 'Initialization failed') in getStatusTooltip so
initialization failures are rendered and labeled as failures instead of falling
through to the default.
In `@src/components/chat/ChatMessage.tsx`:
- Around line 44-60: The <sup> element in CitationSuperscript is not
keyboard-focusable so keyboard users cannot open the CitationPopover; update the
CitationSuperscript component (the CitationSuperscript function that renders
CitationPopover) to use an accessible trigger: replace the <sup> with a semantic
<button> (or add role="button", tabIndex={0} and key handlers for Enter/Space)
and ensure it forwards onViewFile to CitationPopover; keep the same visual
classes, aria-label (e.g., `aria-label="Open citation ${index+1}"`), and
keyboard activation so the popover can be opened by keyboard and screen readers.
In `@src/components/files/FilesPanel.tsx`:
- Around line 96-103: The effect that opens the download URL (useEffect watching
fileDetail and fileToDownload) calls
window.electronAPI.shell.openExternal(fileDetail.signedUrl) but doesn’t handle
the returned Promise; wrap the call in a try/catch or use .then/.catch so
rejections are handled and only call setFileToDownload(null) after the
openExternal Promise resolves (or in finally if you want to clear on failure
too); update the effect to await or chain the Promise and log or surface errors
rather than letting unhandled rejections occur, referencing useEffect,
fileDetail, fileToDownload, window.electronAPI.shell.openExternal, and
setFileToDownload to locate the change.
In `@src/components/files/UploadFileDialog.tsx`:
- Line 26: The multimodal boolean state (multimodal / setMultimodal) is
collected by the UI but never forwarded to the upload logic; update handleUpload
to include the multimodal flag when calling uploadMutation (or remove the
checkbox if not supported). Specifically, extend the upload payload passed to
uploadMutation to include multimodal, and add an optional multimodal?: boolean
to the UploadAssistantFileParams type in electron/types.ts so the shape matches;
keep the existing checkbox and state as-is and pass multimodal through to
uploadMutation (or delete the checkbox and state if you decide not to support
multimodal yet).
In `@src/constants/keyboard-shortcuts.ts`:
- Around line 236-242: The NEW_ASSISTANT keyboard shortcut conflicts with
NEW_INDEX because both objects (NEW_ASSISTANT and NEW_INDEX) use the same
accelerator 'CmdOrCtrl+Shift+N'; update NEW_ASSISTANT's accelerator value to a
non-conflicting combination (for example 'CmdOrCtrl+Shift+A') so each shortcut
is unique, and ensure the corresponding keys/stub label (keys: '⌘⇧N') is updated
to match if needed (e.g., to '⌘⇧A') to keep displayed hint consistent.
In `@src/hooks/useChatStream.ts`:
- Around line 219-233: The clearMessages logic currently cancels the active
stream and clears state but isn't triggered when the assistant changes, so
messages and streams can leak between assistants; update useChatStream to call
the existing clearMessages (or inline its cancellation/cleanup steps that use
currentStreamIdRef, unsubscribeRef, setMessages, setIsStreaming, setError)
whenever assistantName changes by adding a useEffect that depends on
assistantName and invokes clearMessages, and ensure clearMessages is memoized
(or included in the effect deps) so the cleanup runs reliably when assistantName
updates.
🧹 Nitpick comments (4)
src/components/files/UploadFileDialog.tsx (1)
140-143: Consider adding a type definition for Electron's File path property.The
anycast is necessary because Electron extends the standardFileinterface with apathproperty. For better type safety, consider defining this interface extension.♻️ Proposed type definition
Add to your type definitions (e.g., in
src/types/electron.d.ts):interface ElectronFile extends File { path: string }Then update the usage:
- const filePath = (file as any).path + const filePath = (file as ElectronFile).pathsrc/types/electron.d.ts (1)
39-88: Consider consolidating duplicated type definitions.The Assistant-related types (
AssistantModel,AssistantFile, etc.) are defined in bothelectron/types.tsand here as global declarations. While this pattern is common in Electron apps, you could reduce maintenance burden by exporting types from a shared location and re-exporting them where needed.src/components/files/FileDetailPanel.tsx (1)
60-87: Optional: clear CopyButton timeout on unmount.This avoids a possible state update after unmount if the panel changes quickly.
♻️ Suggested change
-import { useState, useCallback } from 'react' +import { useState, useCallback, useEffect, useRef } from 'react'function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false) + const timeoutRef = useRef<number | null>(null) const handleCopy = useCallback(async () => { try { await navigator.clipboard.writeText(text) setCopied(true) - setTimeout(() => setCopied(false), 2000) + if (timeoutRef.current) { + window.clearTimeout(timeoutRef.current) + } + timeoutRef.current = window.setTimeout(() => setCopied(false), 2000) } catch (err) { console.error('Failed to copy:', err) } }, [text]) + + useEffect(() => { + return () => { + if (timeoutRef.current) { + window.clearTimeout(timeoutRef.current) + } + } + }, [])src/components/chat/ChatView.tsx (1)
199-208: Prefer stable message keys to avoid remount churn.Using array indices as keys can cause unnecessary remounts when messages are appended/removed. If you have an id (or can derive a stable key), prefer that.
Suggested fix
- {messages.map((message, idx) => ( - <ChatMessage - key={idx} + {messages.map((message, idx) => ( + <ChatMessage + key={message.id ?? `${message.role}-${idx}`} role={message.role} content={message.content} citations={message.citations} isStreaming={message.isStreaming} /> ))}
* 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>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/files/FileDetailPanel.tsx`:
- Around line 68-71: The handleDownload callback currently calls
window.electronAPI.shell.openExternal(file.signedUrl) without validating the
URL; change handleDownload to first safely parse file?.signedUrl (using the URL
constructor inside a try/catch) and only call openExternal when parsing succeeds
and url.protocol === 'https:'. If parsing fails or the protocol is not 'https:',
do not call window.electronAPI.shell.openExternal(file.signedUrl) and instead
surface or log an error (e.g., show a UI error/toast or console.error) so
malformed or dangerous schemes (file:, javascript:, search-ms:, etc.) are never
handed to the OS handler.
🧹 Nitpick comments (1)
src/components/files/FileDetailPanel.tsx (1)
203-211: Disable delete while server-side status is already “Deleting.”
Whenfile.statusisDeleting, the button can still trigger another delete request. Disable it to avoid duplicate deletes or noisy errors.♻️ Suggested tweak
- <Button + <Button onClick={handleDelete} size="sm" variant="destructive" - disabled={deleteMutation.isPending} + disabled={deleteMutation.isPending || file.status === 'Deleting'} className="w-full h-7 text-[11px] gap-1.5" >
- 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
- 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>
Co-authored-by: Scout (Lead Tester) <scout@openclaw.ai>
- 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>
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@electron/menu.ts`:
- Around line 373-427: The assistant menu events are not exposed to the
renderer; update electron/preload.ts to add an electronAPI.menu object that
exposes listener registration functions (e.g., onNewAssistant, onSendMessage,
onFocusChatInput, onClearConversation, onEditAssistant, onDeleteAssistant) which
internally register ipcRenderer.on for the corresponding channels
('menu:new-assistant', 'menu:send-message', 'menu:focus-chat-input',
'menu:clear-conversation', 'menu:edit-assistant', 'menu:delete-assistant') and
call the provided renderer callback; ensure each exposed function follows the
existing pattern used elsewhere in preload.ts (using
contextBridge.exposeInMainWorld/electronAPI and safe callback handling) so the
renderer can subscribe to these menu events.
In `@src/components/assistants/AssistantsPanel.tsx`:
- Around line 111-133: The delete handler can fire multiple times while a
deletion is in-flight; add a pending guard in handleConfirmDelete to return
early when a deletion is already pending (e.g., check deleteMutation.isLoading
or a local isDeleting ref/state) and ensure you set the guard before awaiting
deleteMutation.mutateAsync and clear it in finally; reference
handleConfirmDelete, deleteMutation, assistantToDelete and activeAssistant when
adding the early-return and the finally cleanup so duplicate delete calls are
prevented.
In `@src/context/DraftAssistantContext.tsx`:
- Around line 118-123: The effect that hydrates the draft on edit load
(useEffect watching isEditing, editingAssistant, editingAssistantName)
overwrites any user input when the fetched editingAssistant arrives; change it
to only call setDraftAssistant(createDraftFromAssistant(editingAssistant)) if
the current draft is still pristine (e.g., draftAssistant is null/undefined or
equals the initial empty draft state) so user-typed changes aren't clobbered—use
the existing draft state variable (draftAssistant) to gate hydration and keep
the same dependencies (isEditing, editingAssistant, editingAssistantName).
- Around line 147-156: In updateDraft, the forEach callback used to remove
validation errors returns the value of the delete expression (violating the
linter); change the forEach to use a block-bodied arrow function so the delete
is executed as a statement (e.g., updatedKeys.forEach(key => { delete next[key]
})) inside the setValidationErrors updater, keeping the existing variables
setDraftAssistant, setValidationErrors, updatedKeys and next unchanged.
In `@src/context/ModeContext.tsx`:
- Around line 19-41: When currentProfile changes the effect should reset the
initialization state so the UI doesn't briefly show the previous profile's mode:
inside the useEffect that watches currentProfile (the block calling
window.electronAPI.profiles.getPreferredMode(profileId)), call
setIsInitialized(false) immediately after the early return guard (or at the top
of the effect) before starting the async fetch; keep the cancelled flag and the
existing .then/.catch handlers but ensure you set setIsInitialized(true) only
after the fetch resolves or fails and avoid setting mode when cancelled (use
setModeState(savedMode) as now).
🧹 Nitpick comments (1)
src/components/chat/ChatMessage.tsx (1)
136-188: Consider extracting shared Markdown components configuration.The Markdown
componentsconfiguration forpre,code, andais duplicated between the two render paths (lines 139-179 and 197-236). The only difference is that the citations path overridespto render asspan.Extracting a shared config object would reduce duplication and make future styling updates easier to maintain.
♻️ Suggested refactor
+const sharedMarkdownComponents = { + pre: ({ children, ...props }: React.ComponentPropsWithoutRef<'pre'>) => ( + <pre + className="bg-muted/50 rounded-md p-3 overflow-x-auto text-xs" + {...props} + > + {children} + </pre> + ), + code: ({ className, children, ...props }: React.ComponentPropsWithoutRef<'code'> & { className?: string }) => { + const isInline = !className + if (isInline) { + return ( + <code className="bg-muted/50 rounded px-1 py-0.5 text-xs font-mono" {...props}> + {children} + </code> + ) + } + return ( + <code className={cn('text-xs font-mono', className)} {...props}> + {children} + </code> + ) + }, + a: ({ children, ...props }: React.ComponentPropsWithoutRef<'a'>) => ( + <a + className="text-primary hover:underline" + target="_blank" + rel="noopener noreferrer" + {...props} + > + {children} + </a> + ), +} // Then use in both paths: - components={{ - // Style code blocks - pre: ({ children, ...props }) => ( ... ), - code: ({ className, children, ...props }) => { ... }, - a: ({ children, ...props }) => ( ... ), - }} + components={sharedMarkdownComponents} // And for citations path: + components={{ + ...sharedMarkdownComponents, + p: ({ children }) => <span>{children}</span>, + }}Also applies to: 191-254
| // Update draft when editing assistant data loads | ||
| useEffect(() => { | ||
| if (isEditing && editingAssistant && editingAssistantName) { | ||
| setDraftAssistant(createDraftFromAssistant(editingAssistant)) | ||
| } | ||
| }, [isEditing, editingAssistant, editingAssistantName]) |
There was a problem hiding this comment.
Avoid overwriting user edits when edit data finishes loading.
The effect resets the draft whenever editingAssistant arrives (Line 119). If a user starts typing before the fetch resolves, their input can be clobbered. Consider only hydrating when the draft is still pristine.
🔧 Suggested fix
useEffect(() => {
if (isEditing && editingAssistant && editingAssistantName) {
- setDraftAssistant(createDraftFromAssistant(editingAssistant))
+ setDraftAssistant((prev) => {
+ if (prev && (prev.instructions || prev.metadata)) return prev
+ if (prev && prev.name !== editingAssistantName) return prev
+ return createDraftFromAssistant(editingAssistant)
+ })
}
}, [isEditing, editingAssistant, editingAssistantName])🤖 Prompt for AI Agents
In `@src/context/DraftAssistantContext.tsx` around lines 118 - 123, The effect
that hydrates the draft on edit load (useEffect watching isEditing,
editingAssistant, editingAssistantName) overwrites any user input when the
fetched editingAssistant arrives; change it to only call
setDraftAssistant(createDraftFromAssistant(editingAssistant)) if the current
draft is still pristine (e.g., draftAssistant is null/undefined or equals the
initial empty draft state) so user-typed changes aren't clobbered—use the
existing draft state variable (draftAssistant) to gate hydration and keep the
same dependencies (isEditing, editingAssistant, editingAssistantName).
| const updateDraft = useCallback((updates: Partial<DraftAssistant>) => { | ||
| setDraftAssistant(prev => prev ? { ...prev, ...updates } : prev) | ||
| // Clear validation errors for updated fields | ||
| const updatedKeys = Object.keys(updates) | ||
| if (updatedKeys.length > 0) { | ||
| setValidationErrors(prev => { | ||
| const next = { ...prev } | ||
| updatedKeys.forEach(key => delete next[key]) | ||
| return next | ||
| }) |
There was a problem hiding this comment.
Biome error: avoid returning a value from forEach callback.
delete next[key] returns a boolean, which violates the linter rule (Line 154). Wrap the body in a block to keep it void.
🔧 Suggested fix
- updatedKeys.forEach(key => delete next[key])
+ updatedKeys.forEach((key) => {
+ delete next[key]
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const updateDraft = useCallback((updates: Partial<DraftAssistant>) => { | |
| setDraftAssistant(prev => prev ? { ...prev, ...updates } : prev) | |
| // Clear validation errors for updated fields | |
| const updatedKeys = Object.keys(updates) | |
| if (updatedKeys.length > 0) { | |
| setValidationErrors(prev => { | |
| const next = { ...prev } | |
| updatedKeys.forEach(key => delete next[key]) | |
| return next | |
| }) | |
| const updateDraft = useCallback((updates: Partial<DraftAssistant>) => { | |
| setDraftAssistant(prev => prev ? { ...prev, ...updates } : prev) | |
| // Clear validation errors for updated fields | |
| const updatedKeys = Object.keys(updates) | |
| if (updatedKeys.length > 0) { | |
| setValidationErrors(prev => { | |
| const next = { ...prev } | |
| updatedKeys.forEach((key) => { | |
| delete next[key] | |
| }) | |
| return next | |
| }) |
🧰 Tools
🪛 Biome (2.3.13)
[error] 154-154: This callback passed to forEach() iterable method should not return a value.
Either remove this return or remove the returned value.
(lint/suspicious/useIterableCallbackReturn)
🤖 Prompt for AI Agents
In `@src/context/DraftAssistantContext.tsx` around lines 147 - 156, In
updateDraft, the forEach callback used to remove validation errors returns the
value of the delete expression (violating the linter); change the forEach to use
a block-bodied arrow function so the delete is executed as a statement (e.g.,
updatedKeys.forEach(key => { delete next[key] })) inside the setValidationErrors
updater, keeping the existing variables setDraftAssistant, setValidationErrors,
updatedKeys and next unchanged.
- 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>
- 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>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@electron/assistant-service.ts`:
- Around line 126-134: The uploadFile method in assistant-service.ts is passing
an unsupported multimodal option to the Pinecone SDK (see async uploadFile),
which the v6.1.3/6.1.4 SDK ignores; remove params.multimodal from the call and
stop using the type assertion so you only pass { path: params.filePath,
metadata: params.metadata } to assistant.uploadFile in the uploadFile function
(or, if you need multimodal now, replace this call with a direct REST upload
using the Pinecone Assistant upload endpoint including X-Pinecone-Api-Version:
2025-10 and ?multimodal=true). Ensure mapFileModel(response) still receives the
SDK/REST response shape expected by the rest of the code.
🧹 Nitpick comments (5)
src/components/files/UploadFileDialog.tsx (1)
104-112: File size displays as "Unknown size" when using the file picker.The file picker sets
size: 0(line 111) sincedialog.showOpenDialogdoesn't return file size. Consider using Electron'sfsmodule via preload to fetch the actual size, or document this limitation more explicitly in the UI.src/components/chat/ChatMessage.tsx (1)
140-180: Consider extracting shared Markdown components to reduce duplication.The Markdown component configurations for
pre,code, andaare nearly identical between the two rendering paths. Extract these into a sharedmarkdownComponentsobject or a reusable wrapper component.♻️ Example refactor
const markdownComponents = { pre: ({ children, ...props }: ComponentPropsWithoutRef<'pre'>) => ( <pre className="bg-muted/50 rounded-md p-3 overflow-x-auto text-xs" {...props}> {children} </pre> ), code: ({ className, children, ...props }: ComponentPropsWithoutRef<'code'>) => { const isInline = !className if (isInline) { return ( <code className="bg-muted/50 rounded px-1 py-0.5 text-xs font-mono" {...props}> {children} </code> ) } return ( <code className={cn('text-xs font-mono', className)} {...props}> {children} </code> ) }, a: ({ children, ...props }: ComponentPropsWithoutRef<'a'>) => ( <a className="text-primary hover:underline" target="_blank" rel="noopener noreferrer" {...props}> {children} </a> ), }Also applies to: 199-237
src/components/chat/ChatView.tsx (3)
57-62: Consider optimizing auto-scroll during streaming.The effect runs on every
messagesarray change, which during streaming could be every token update. Usingbehavior: 'smooth'with high-frequency updates may cause visual jitter.Consider scrolling immediately (without smooth) when streaming, or debouncing the scroll:
♻️ Potential optimization
// Auto-scroll to bottom on new messages useEffect(() => { if (messagesEndRef.current) { - messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) + // Use instant scroll during streaming to avoid jitter + const behavior = isStreaming ? 'instant' : 'smooth' + messagesEndRef.current.scrollIntoView({ behavior }) } - }, [messages]) + }, [messages, isStreaming])
97-111: Avoid re-subscribing IPC handlers on every message.Including
messages.lengthin the dependency array causes the effect to re-subscribe on every new message. Use a ref for the current state or move the condition inside stable callbacks.♻️ Suggested optimization using refs
+ const messagesRef = useRef(messages) + const isStreamingRef = useRef(isStreaming) + + useEffect(() => { + messagesRef.current = messages + isStreamingRef.current = isStreaming + }, [messages, isStreaming]) // Listen for menu IPC events useEffect(() => { const unsubFocus = window.electronAPI.menu.onFocusChatInput(() => { textareaRef.current?.focus() }) const unsubClear = window.electronAPI.menu.onClearConversation(() => { - if (messages.length > 0 && !isStreaming) { + if (messagesRef.current.length > 0 && !isStreamingRef.current) { clearMessages() } }) return () => { unsubFocus() unsubClear() } - }, [messages.length, isStreaming, clearMessages]) + }, [clearMessages])
137-142: IPC send listener re-subscribes on every input change.Since
handleSubmitdepends oninputValue, this effect re-subscribes on every keystroke. Consider using a ref pattern similar to the suggestion above.
| async uploadFile(assistantName: string, params: UploadAssistantFileParams): Promise<AssistantFile> { | ||
| const assistant = this.client.assistant(assistantName) | ||
| const response = await assistant.uploadFile({ | ||
| path: params.filePath, | ||
| metadata: params.metadata, | ||
| multimodal: params.multimodal, | ||
| } as Parameters<typeof assistant.uploadFile>[0]) | ||
| return this.mapFileModel(response) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
git ls-files | grep -E "(package\.json|tsconfig|electron)" | head -20Repository: stepandel/pinecone-explorer
Length of output: 564
🏁 Script executed:
find . -name "package.json" -type f | head -5Repository: stepandel/pinecone-explorer
Length of output: 85
🏁 Script executed:
fd "assistant-service" --type fRepository: stepandel/pinecone-explorer
Length of output: 100
🏁 Script executed:
cat package.json | grep -A 5 -B 5 "pinecone"Repository: stepandel/pinecone-explorer
Length of output: 937
🏁 Script executed:
head -50 electron/assistant-service.tsRepository: stepandel/pinecone-explorer
Length of output: 1317
🏁 Script executed:
rg "UploadAssistantFileParams" --type tsRepository: stepandel/pinecone-explorer
Length of output: 729
🏁 Script executed:
rg "export interface UploadAssistantFileParams" -A 15 electron/types.tsRepository: stepandel/pinecone-explorer
Length of output: 364
🏁 Script executed:
cat -n electron/types.ts | head -100Repository: stepandel/pinecone-explorer
Length of output: 3743
🏁 Script executed:
cat -n electron/assistant-service.ts | sed -n '120,150p'Repository: stepandel/pinecone-explorer
Length of output: 1179
🏁 Script executed:
rg "uploadFile" electron/assistant-service.ts -B 2 -A 8Repository: stepandel/pinecone-explorer
Length of output: 720
🌐 Web query:
Pinecone SDK @pinecone-database/pinecone v6.1.3 uploadFile multimodal parameter
💡 Result:
In @pinecone-database/pinecone v6.1.3, assistant.uploadFile(...) does not expose a multimodal option—UploadFileOptions only includes:
path: stringmetadata?: Record<string, string>[1]
And the v6.1.3 release notes mention only a Assistant.listFiles() filter bugfix (no uploadFile change) [2].
If you need multimodal PDF processing, that capability is controlled by the Assistant “upload file” API via the multimodal query parameter (true|false, default false, PDFs only) [3][4]—so with v6.1.3 you must call the HTTP endpoint directly (or upgrade to a newer SDK version that adds first-class support, if/when available).
Sources:
[1] TypeScript SDK UploadFileOptions (v6.1.1 docs; same API surface for v6.1.x) https://sdk.pinecone.io/typescript/interfaces/UploadFileOptions.html
[2] Pinecone release notes: Node.js SDK v6.1.3 https://docs.pinecone.io/release-notes/2025
[3] Assistant Upload File API (latest): multimodal query param https://docs.pinecone.io/reference/api/latest/assistant/upload_file
[4] Multimodal assistants guide (PDFs; multimodal=true) https://docs.pinecone.io/guides/assistant/multimodal
🌐 Web query:
@pinecone-database/pinecone uploadFile multimodal parameter support latest version
💡 Result:
As of @pinecone-database/pinecone latest = 6.1.4 (published Jan 22, 2026), the Node/TypeScript SDK’s assistant.uploadFile(...) does not expose a multimodal option—UploadFileOptions only includes path and optional metadata. [1][2]
Pinecone’s multimodal file processing for Assistants is available via the API 2025-10 upload_file endpoint using the multimodal=true query parameter (PDFs only), but SDKs targeting API 2025-10 “will be available soon” (so the Node SDK pinned to 2025-04 doesn’t yet surface it). [3][4]
Workarounds:
- Call the REST endpoint with
X-Pinecone-Api-Version: 2025-10and?multimodal=true. [3] - Or use an SDK that already supports a
multimodalflag for file upload (documented for Python). [5]
Sources: [1] Yarn package page, [2] TS SDK UploadFileOptions, [3] API 2025-10 upload_file reference, [4] Pinecone SDKs versioning page, [5] Pinecone multimodal assistants guide.
The multimodal parameter is not supported by the Pinecone SDK v6.1.3 in use.
The type assertion masks an actual incompatibility: UploadFileOptions in SDK v6.1.3 (and latest v6.1.4) only accepts path and metadata—the multimodal parameter will be silently ignored at runtime.
Pinecone's API supports multimodal PDF processing via the multimodal query parameter in API version 2025-10, but the Node SDK is currently pinned to 2025-04 and doesn't yet expose this option.
Workarounds:
- Remove the
multimodalparameter until the SDK adds support, or - Call the Pinecone Assistant upload file REST endpoint directly with
X-Pinecone-Api-Version: 2025-10header and?multimodal=truequery parameter.
🤖 Prompt for AI Agents
In `@electron/assistant-service.ts` around lines 126 - 134, The uploadFile method
in assistant-service.ts is passing an unsupported multimodal option to the
Pinecone SDK (see async uploadFile), which the v6.1.3/6.1.4 SDK ignores; remove
params.multimodal from the call and stop using the type assertion so you only
pass { path: params.filePath, metadata: params.metadata } to
assistant.uploadFile in the uploadFile function (or, if you need multimodal now,
replace this call with a direct REST upload using the Pinecone Assistant upload
endpoint including X-Pinecone-Api-Version: 2025-10 and ?multimodal=true). Ensure
mapFileModel(response) still receives the SDK/REST response shape expected by
the rest of the code.
- 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
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/assistants/AssistantsPanel.tsx`:
- Around line 87-95: The right-click handler handleAssistantContextMenu calls
window.electronAPI.contextMenu.showAssistantMenu (and elsewhere code uses
contextMenu.onAssistantAction) but those APIs may not exist on the preload
surface; either add showAssistantMenu and onAssistantAction to the preload
exports in electron/preload.ts and declare them in src/types/electron.d.ts, or
defensively guard the calls in AssistantsPanel.tsx by checking
window.electronAPI?.contextMenu?.showAssistantMenu (and similarly for
onAssistantAction) before invoking and failing silently or logging a warning;
update both usages (the handleAssistantContextMenu and the other
assistant-context handlers around the later assistant action code) so they use
the same guarded access or the new declared preload methods.
In `@src/types/electron.d.ts`:
- Around line 85-88: The UploadAssistantFileParams interface in
src/types/electron.d.ts is missing the multimodal property and is out of sync
with the electron/types.ts definition; update UploadAssistantFileParams to
include multimodal?: boolean (or the exact type used in electron/types.ts) and
any related docs/comments so renderer callers can pass that option and the types
remain consistent with the electron/types.ts declaration.
* 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>
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>
…#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>
… 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>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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(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>
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>
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>
Summary by CodeRabbit
New Features
Tests