feat: Add AssistantConfigView for create/edit#51
feat: Add AssistantConfigView for create/edit#51stepandel merged 1 commit intofeat/assistant-modefrom
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 a full Assistant feature: service layer, IPC + preload API, types, React UI (assistants, chat, files), contexts, hooks, mode switching, streaming chat with citations, and related menu/shortcut integrations. Changes
Sequence Diagram(s)sequenceDiagram
participant Renderer as Renderer Process
participant Main as Main Process
participant AssistantService as AssistantService
participant SDK as Pinecone SDK
Renderer->>Main: assistant:chat:stream:start(profileId, assistantName, params)
Main->>Main: create streamId, AbortController
Main->>AssistantService: chatStream(assistantName, params, onChunk, signal)
AssistantService->>SDK: start streaming request (with signal)
SDK-->>AssistantService: stream chunks (message_start/content/citation/message_end/error)
AssistantService->>Main: invoke onChunk for each parsed chunk
Main->>Renderer: emit chat:stream:chunk(streamId, chunk)
Renderer->>Renderer: append/merge chunks, render content & citations
Renderer->>Main: assistant:chat:stream:cancel(streamId)
Main->>Main: abort controller -> SDK stream aborted
SDK-->>AssistantService: stream terminated
AssistantService->>Main: final chunk/error
Main->>Renderer: final chunk / finish signal
sequenceDiagram
participant UI as AssistantConfigView
participant Context as DraftAssistantContext
participant API as electronAPI.assistant
participant Queries as useAssistantsQuery
UI->>Context: startCreation()
UI->>Context: updateDraft(fields)
UI->>Context: saveDraft()
Context->>API: assistant.create(profileId, params)
API-->>Context: AssistantModel (created)
Context->>Queries: invalidate/list refresh
Context->>UI: clear draft, select new assistant
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes 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 |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
electron/pinecone-service.ts (1)
30-60:⚠️ Potential issue | 🟠 MajorReset cached AssistantService on reconnect/disconnect to avoid stale clients.
assistantServiceis cached and never cleared. After disconnect or reconnect with a different profile,getAssistantService()can keep using the old client. Clear it when the client changes.🔧 Suggested fix
async connect(profile: ConnectionProfile): Promise<void> { try { + this.assistantService = null this.client = new Pinecone({ apiKey: profile.apiKey, }) @@ } catch (error) { this.client = null this.embeddingService = null + this.assistantService = null this.profile = null throw error } } @@ disconnect(): void { this.client = null this.embeddingService = null + this.assistantService = null this.profile = null this.indexCache.clear() this.indexInfoCache.clear() }src/constants/keyboard-shortcuts.ts (1)
100-107:⚠️ Potential issue | 🟠 MajorShortcut collisions confirmed but incompletely scoped.
The collisions exist:
NEW_INDEXandNEW_ASSISTANTboth useCmdOrCtrl+Shift+N(lines 104, 239), andSAVE_ENTERandSEND_MESSAGEboth useCmdOrCtrl+Return(lines 209, 248).Handlers are registered globally on
windowin React components viauseKeyboardShortcut. The collision is managed implicitly through component-level conditions (draftIndex === null,hasDirtyChanges, etc.) rather than explicit mode-scoping. The Electron menu correctly usesregisterAccelerator: falseto avoid global registration (lines 278, 378, 390), delegating via IPC to the React components.However, this approach is fragile—if a developer forgets to add the necessary condition or if multiple components with conflicting shortcuts are active simultaneously, both handlers can fire. Consider adding explicit mode-scoping at the hook level or runtime validation to prevent accidental collisions.
src/components/layout/MainContent.tsx (1)
270-298:⚠️ Potential issue | 🟡 MinorRight panel shows "No vector selected" in assistant mode when no file is active.
When in assistant mode without an active file, the panel falls through to the else branch showing "No vector selected" (Line 295). This message is incorrect for assistant mode.
🐛 Proposed fix for correct placeholder text
) : ( <div className="flex items-center justify-center h-full" style={{ background: 'var(--panel-detail)', backdropFilter: 'blur(24px) saturate(1.5)', WebkitBackdropFilter: 'blur(24px) saturate(1.5)', boxShadow: 'var(--panel-detail-shadow)', }} > <div className="text-center text-muted-foreground"> - <p className="text-sm">No vector selected</p> + <p className="text-sm"> + {mode === 'assistant' ? 'No file selected' : 'No vector selected'} + </p> </div> </div> )}
🤖 Fix all issues with AI agents
In @.linear.toml:
- Line 4: The workspace name in .linear.toml is set to "chroma-explorer" while
package.json identifies the project as "pinecone-explorer"; update
.linear.toml's workspace value to match the project name or change package.json
if the intended project is "chroma-explorer" so both identifiers are consistent
(look for the workspace = "chroma-explorer" entry in .linear.toml and the "name"
field in package.json to make them match).
In `@electron/assistant-service.ts`:
- Around line 177-203: The chat implementation drops caller-specified
parameters; update the assistant.chat call in chat(assistantName: string,
params: ChatParams) to pass params.temperature and params.contextOptions through
(alongside existing model/filter), and mirror these additions in chatStream so
both methods forward temperature, contextOptions, and also ensure chatStream
accepts jsonResponse and includeHighlights the same way chat() does (reference
the assistant.chat invocation and the chatStream method to locate changes).
In `@electron/main.ts`:
- Around line 1006-1037: The background chatStream promise is missing error
handling so streaming failures are swallowed; attach a .catch handler to the
assistantService.chatStream(...) call that sends the error to the renderer (e.g.
event.sender.send('assistant:chat:error', streamId, errorMessage)) and still
cleans up activeChatStreams (keep the existing .finally() to delete streamId),
using the same streamId and abortController to correlate and allow the renderer
to abort/handle the failure; extract a safe message via error instanceof Error ?
error.message : String(error) before sending.
In `@src/components/assistants/AssistantConfigView.tsx`:
- Around line 33-36: The computed isNameValid is not used in canSubmit, allowing
submits despite name validation errors; update the canSubmit logic to include
isNameValid in both branches so submission requires the name to be valid (i.e.,
change canSubmit to depend on isEditing ? !isSubmitting && isNameValid :
!isSubmitting && isNameValid && draftAssistant.name.trim().length > 0),
referencing the existing symbols isNameValid, canSubmit, draftAssistant.name,
validationErrors.name, isEditing, and isSubmitting.
In `@src/components/chat/ChatMessage.tsx`:
- Around line 68-104: renderContentWithCitations has an off-by-one /
incorrect-index bug when multiple Citation objects share the same position
because it uses citations.indexOf(citation) after sorting; instead preserve each
citation's original index before sorting (e.g., attach an originalIndex field or
build an array of {citation, originalIndex} then sort into sortedCitations) and
use that originalIndex when setting the segment.citationIndex so segments
reference the correct citation even when positions are equal; update references
to sortedCitations and segment creation to use this preserved original index.
In `@src/components/chat/CitationPopover.tsx`:
- Around line 10-18: Remove the duplicated local interfaces CitationReference
and Citation from CitationPopover.tsx and instead import them from the shared
types file (electron/types.ts); update the top of the file to add an import for
{ CitationReference, Citation } from 'electron/types' (or the project’s exact
module alias) and ensure any usages inside the CitationPopover component/type
annotations continue to reference these imported types so there is a single
source of truth shared with ChatMessage.tsx.
In `@src/components/files/FilesPanel.tsx`:
- Around line 88-103: The effect can fire multiple times when fileDetail is
refetched; add a ref to track whether the current fileToDownload has already had
its download initiated (e.g., downloadedRef storing the file id) and only open
the signedUrl when fileDetail?.signedUrl && fileToDownload &&
downloadedRef.current !== fileToDownload.id; after opening, set
downloadedRef.current = fileToDownload.id and call setFileToDownload(null); also
reset downloadedRef.current when a new fileToDownload is selected (watch
fileToDownload in an effect or set it when starting a new download) so future
downloads are allowed; update code around useFileDetailQuery, the useEffect that
opens signedUrl, and setFileToDownload to use this guard.
In `@src/components/files/UploadFileDialog.tsx`:
- Line 26: The multimodal boolean state is tracked by useState and the checkbox
but never passed to uploadMutation.mutateAsync nor present on
UploadAssistantFileParams; either wire it through or remove it. To fix, update
the UploadAssistantFileParams type to include multimodal?: boolean and pass {
filePath, metadata, multimodal } from handleUpload into
uploadMutation.mutateAsync (references: multimodal state, handleUpload,
uploadMutation.mutateAsync, UploadAssistantFileParams, and the checkbox render),
or if the feature is not needed remove the multimodal state and checkbox to
eliminate dead code.
In `@src/context/DraftAssistantContext.tsx`:
- Around line 119-123: The current code uses useMemo to run a side effect
(calling setDraftAssistant with createDraftFromAssistant) which is incorrect;
replace the useMemo call with useEffect so the side effect runs reliably when
dependencies change: move the body that checks isEditing && editingAssistant &&
editingAssistantName and calls
setDraftAssistant(createDraftFromAssistant(editingAssistant)) into a useEffect
whose dependency array contains isEditing, editingAssistant, and
editingAssistantName; keep the same conditional logic and ensure no return value
is expected from this hook.
In `@src/context/ModeContext.tsx`:
- Around line 22-36: The effect in ModeContext's useEffect can update state from
an outdated async call; modify it to cancel stale responses by tracking the
currentProfile (e.g., capture currentProfile.id in a local const or use an
"active" flag) before calling window.electronAPI.profiles.getPreferredMode and
then, inside the .then/.catch handlers, check that the profile id still matches
or the effect is still active before calling setModeState or setIsInitialized;
alternatively return a cleanup function that flips the active flag to false so
in-flight promises are ignored; ensure checks reference
window.electronAPI.profiles.getPreferredMode, setModeState and setIsInitialized
so only the latest profile's result is applied.
In `@src/hooks/useChatStream.ts`:
- Around line 59-78: The subscription to stream chunks in sendMessage is
registered before calling chatStream.start, which can drop early chunks because
currentStreamIdRef.current is still null; to fix, call chatStream.start(...)
first, capture/assign the returned streamId into currentStreamIdRef.current
immediately, then register the IPC/subscription handler that checks streamId
against currentStreamIdRef.current (or adapt the handler to accept the provided
streamId), and only after subscription begin setting isStreaming and updating
messages; ensure the IPC implementation can buffer or that the handler tolerates
late registration as noted.
- Around line 4-10: The file declares ChatMessageWithMeta and uses types
ChatMessage, Citation, ChatUsage and ChatStreamChunk but does not import them,
causing TypeScript errors; fix by adding the appropriate type imports at the top
of src/hooks/useChatStream.ts (import ChatMessage, Citation, ChatUsage,
ChatStreamChunk from their module where the chat types live) so that
ChatMessageWithMeta and any usage of ChatStreamChunk resolve correctly.
🧹 Nitpick comments (15)
src/components/files/FileDetailPanel.tsx (1)
124-127: Mutation initialized with potentially empty strings.
useDeleteFileMutationis called withcurrentProfile?.id || ''andactiveAssistant || ''. While the handlers guard against nullactiveFile, the mutation is still created with potentially invalid parameters. This works because the mutation won't execute without a valid file selection, but consider adding a comment or documenting this assumption.src/context/AssistantSelectionContext.tsx (1)
14-16: TheuseCallbackwrapper is unnecessary here.
setActiveAssistantStatefromuseStateis already stable across renders. The wrapper adds no value but also causes no harm.♻️ Optional simplification
export function AssistantSelectionProvider({ children }: { children: ReactNode }) { const [activeAssistant, setActiveAssistantState] = useState<string | null>(null) - const setActiveAssistant = useCallback((assistantName: string | null) => { - setActiveAssistantState(assistantName) - }, []) - const value = useMemo<AssistantSelectionContextValue>( () => ({ activeAssistant, - setActiveAssistant, + setActiveAssistant: setActiveAssistantState, }), - [activeAssistant, setActiveAssistant] + [activeAssistant] )src/context/ModeContext.tsx (1)
4-4: Consider importingExplorerModefrom the shared types.
ExplorerModeis also defined inelectron/types.ts(line 55). Having duplicate type definitions can lead to drift. Consider importing from the shared location or re-exporting from a common types module.♻️ Proposed fix
import { createContext, useContext, useState, useEffect, useCallback, useRef, ReactNode } from 'react' import { usePinecone } from '../providers/PineconeProvider' +import type { ExplorerMode } from '../../electron/types' -export type ExplorerMode = 'index' | 'assistant' +export type { ExplorerMode }src/components/mode/ModeSwitcher.tsx (1)
11-14: Keyboard shortcuts show macOS-only modifier.The shortcuts display
⌘1and⌘2which is macOS-specific, but the actual handler inModeContextresponds to bothmetaKey(Cmd) andctrlKey. Consider showing the appropriate modifier based on platform.♻️ Proposed platform-aware shortcut display
+// Detect platform for shortcut display +const isMac = typeof navigator !== 'undefined' && /Mac|iPhone|iPad|iPod/i.test(navigator.platform) +const modKey = isMac ? '⌘' : 'Ctrl+' const modes: ModeOption[] = [ - { value: 'index', icon: Database, label: 'Index Explorer', shortcut: '⌘1' }, - { value: 'assistant', icon: Bot, label: 'Assistant Explorer', shortcut: '⌘2' }, + { value: 'index', icon: Database, label: 'Index Explorer', shortcut: `${modKey}1` }, + { value: 'assistant', icon: Bot, label: 'Assistant Explorer', shortcut: `${modKey}2` }, ]src/context/DraftAssistantContext.tsx (1)
150-157: Static analysis:forEachcallback return value is ignored.The
deleteoperator returns a boolean, butforEachignores return values. While functionally correct, this triggers the linter. A minor refactor silences the warning.♻️ Proposed fix to silence linter
if (updatedKeys.length > 0) { setValidationErrors(prev => { const next = { ...prev } - updatedKeys.forEach(key => delete next[key]) + updatedKeys.forEach(key => { delete next[key] }) return next }) }src/components/assistants/AssistantsPanel.tsx (1)
108-130: Consider resetting dialog state before async operation to prevent stale state on rapid interactions.The confirmation input validation and delete flow are correct. However, if the mutation fails after clearing
activeAssistant, the selection state becomes inconsistent. Consider moving the selection clearing toonSuccessof the mutation or after the dialog closes successfully.♻️ Suggested improvement
try { await deleteMutation.mutateAsync(assistantToDelete) - // If we deleted the active assistant, clear selection - if (activeAssistant === assistantToDelete) { - setActiveAssistant(null) - } setDeleteDialogOpen(false) setAssistantToDelete(null) setConfirmationInput('') setDeleteError(null) + // Clear selection after successful deletion and dialog cleanup + if (activeAssistant === assistantToDelete) { + setActiveAssistant(null) + } } catch (error) {src/components/files/FilesPanel.tsx (2)
153-156: Download handler does not provide user feedback while loading.When a download is initiated, the user has no visual indication that something is happening. Consider showing a loading state or toast while
isDownloadLoadingis true.
240-245: Duplicate upload button in header.There's a
NewButtonfor upload in the header (Line 240) and a full-widthUpload Filebutton below (Lines 274-283). This provides two ways to initiate the same action. Consider whether both are necessary or if one could be removed for cleaner UI.src/hooks/useChatStream.ts (1)
28-32: DEFAULT_MODELS is hardcoded and may become stale.The model list is hardcoded. If the available models change, this needs manual updates. Consider fetching available models from the API or making this configurable.
src/components/chat/ChatMessage.tsx (3)
8-16: Type definitions duplicate those inelectron/types.ts.
CitationReferenceandCitationinterfaces are defined locally but already exist inelectron/types.ts(Lines 536-552). Consider importing from the shared types to maintain consistency and avoid drift.♻️ Suggested refactor to use shared types
import { memo, Fragment } from 'react' import Markdown from 'react-markdown' import { Bot, User } from 'lucide-react' import { cn } from '@/lib/utils' import { useFileSelection } from '@/context/FileSelectionContext' import { CitationPopover } from './CitationPopover' +import type { Citation, CitationReference } from '../../../electron/types' -interface CitationReference { - file: { name: string; id: string } - pages?: number[] -} - -interface Citation { - position: number - references: CitationReference[] -} - export interface ChatMessageProps {
136-186: Markdowncomponentsprop object is recreated on every render.The custom components object passed to
<Markdown>is defined inline, causing new object references on each render. Whilereact-markdownmay handle this gracefully, extracting this to a stable reference (viauseMemoor module-level constant) could improve performance.
188-252: Duplicate Markdown component definitions.The components for
pre,code, andaare duplicated between the "no citations" path (Lines 137-176) and the "with citations" path (Lines 196-233). Consider extracting these to a shared constant or hook.♻️ Extract shared Markdown components
+// Shared Markdown component overrides +const markdownComponents = { + 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'>) => { + 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> + ), +} + +// For inline rendering (no extra paragraphs) +const inlineMarkdownComponents = { + ...markdownComponents, + p: ({ children }: { children: React.ReactNode }) => <span>{children}</span>, +}Then use
markdownComponentsandinlineMarkdownComponentsin the respective<Markdown>instances.src/components/chat/ChatView.tsx (2)
96-111: Consider using refs to avoid subscription churn.The effect depends on
messages.lengthandisStreaming, causing the IPC listeners to be unsubscribed and resubscribed on every message. While functionally correct, this creates unnecessary overhead. Consider using refs to access current values inside the callbacks.♻️ Suggested refactor using refs
+ const messagesLengthRef = useRef(messages.length) + const isStreamingRef = useRef(isStreaming) + messagesLengthRef.current = messages.length + isStreamingRef.current = 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 (messagesLengthRef.current > 0 && !isStreamingRef.current) { clearMessages() } }) return () => { unsubFocus() unsubClear() } - }, [messages.length, isStreaming, clearMessages]) + }, [clearMessages])
200-208: Using index as key is acceptable here but consider adding message IDs.For append-only chat messages, index keys work correctly. However, if messages ever gain unique IDs from the backend (e.g., for persistence or editing), switching to those IDs would be more robust.
src/components/chat/CitationPopover.tsx (1)
26-29: Minor redundancy: the pages check is duplicated.The condition
ref.pages && ref.pages.length > 0at line 63 duplicates the null/empty check insideformatPages. You could simplify by callingformatPagesunconditionally and checking if the result is truthy.♻️ Suggested simplification
- {ref.pages && ref.pages.length > 0 && ( - <div className="text-xs text-muted-foreground"> - {formatPages(ref.pages)} - </div> - )} + {formatPages(ref.pages) && ( + <div className="text-xs text-muted-foreground"> + {formatPages(ref.pages)} + </div> + )}Or store the result to avoid calling twice:
const pagesText = formatPages(ref.pages) // ... {pagesText && ( <div className="text-xs text-muted-foreground">{pagesText}</div> )}Also applies to: 63-66
electron/assistant-service.ts
Outdated
| async chat(assistantName: string, params: ChatParams): Promise<ChatResponse> { | ||
| const assistant = this.client.assistant(assistantName) | ||
| const response = await assistant.chat({ | ||
| messages: params.messages.map(m => ({ role: m.role, content: m.content })), | ||
| model: params.model, | ||
| filter: params.filter, | ||
| // @ts-expect-error - SDK types may not include all options | ||
| jsonResponse: params.jsonResponse, | ||
| includeHighlights: params.includeHighlights, | ||
| }) | ||
|
|
||
| return { | ||
| id: response.id || crypto.randomUUID(), | ||
| message: { | ||
| role: 'assistant' as const, | ||
| content: response.message?.content || '', | ||
| }, | ||
| citations: this.mapCitations(response.citations), | ||
| usage: response.usage ? { | ||
| promptTokens: response.usage.promptTokens || 0, | ||
| completionTokens: response.usage.completionTokens || 0, | ||
| totalTokens: response.usage.totalTokens || 0, | ||
| } : undefined, | ||
| model: response.model, | ||
| finishReason: response.finishReason, | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "assistant-service.ts" | head -20Repository: stepandel/pinecone-explorer
Length of output: 102
🏁 Script executed:
find . -type f -name "*.ts" | rg -l "ChatParams" | head -10Repository: stepandel/pinecone-explorer
Length of output: 53
🏁 Script executed:
git ls-files | grep -E "(assistant|chat)" | head -20Repository: stepandel/pinecone-explorer
Length of output: 305
🏁 Script executed:
wc -l ./electron/assistant-service.tsRepository: stepandel/pinecone-explorer
Length of output: 106
🏁 Script executed:
cat -n ./electron/assistant-service.tsRepository: stepandel/pinecone-explorer
Length of output: 11518
🏁 Script executed:
find . -type f -name "types.ts" -o -name "types.d.ts" | grep electronRepository: stepandel/pinecone-explorer
Length of output: 90
🏁 Script executed:
cat -n ./electron/types.tsRepository: stepandel/pinecone-explorer
Length of output: 19998
🌐 Web query:
Pinecone JavaScript SDK v6.1.3 assistant chat method temperature contextOptions parameters
💡 Result:
In the Pinecone Node/TypeScript SDK v6.x (including v6.1.3), the assistant.chat(...) call accepts a temperature number and a contextOptions object for controlling how much retrieved context is sent to the LLM.
assistant.chat({ ... }) (relevant fields)
temperature?: number— sampling temperature (randomness); ignored if the chosen model doesn’t support it. (sdk.pinecone.io)contextOptions?: { ... }— controls context snippets sent to the LLM. (sdk.pinecone.io)topK?: number— max number of context snippets (defaults to 16). (docs.pinecone.io)snippetSize?: number— max size (tokens) per snippet (defaults to 2048 tokens). (docs.pinecone.io)
Note on naming: in the SDK it’s typically camelCase (contextOptions, topK, snippetSize), while the underlying API uses snake_case (context_options, top_k, snippet_size). (docs.pinecone.io)
Example
const resp = await assistant.chat({
messages: [{ role: 'user', content: 'Who is the CFO of Netflix?' }],
temperature: 0.8,
contextOptions: { topK: 10, snippetSize: 2500 },
});(Temperature support was added in Node SDK v6.1.1; context-size controls in v6.1.0.) (docs.pinecone.io)
Citations:
- 1: https://sdk.pinecone.io/typescript/interfaces/ChatOptions.html
- 2: https://sdk.pinecone.io/typescript/interfaces/ChatOptions.html
- 3: https://docs.pinecone.io/guides/assistant/chat-with-assistant?utm_source=openai
- 4: https://docs.pinecone.io/guides/assistant/chat-with-assistant?utm_source=openai
- 5: https://docs.pinecone.io/guides/assistant/chat-with-assistant?utm_source=openai
- 6: https://docs.pinecone.io/release-notes/2025?utm_source=openai
Forward temperature and contextOptions to the SDK in both chat() and chatStream().
ChatParams exposes temperature and contextOptions, but they're not being passed to the assistant API in either method, causing caller configuration to be dropped. Pinecone SDK v6.1.3 supports both parameters for chat operations. Additionally, chatStream() is missing jsonResponse and includeHighlights that chat() already forwards.
🛠️ Suggested updates
const response = await assistant.chat({
messages: params.messages.map(m => ({ role: m.role, content: m.content })),
model: params.model,
filter: params.filter,
+ temperature: params.temperature,
+ contextOptions: params.contextOptions,
// `@ts-expect-error` - SDK types may not include all options
jsonResponse: params.jsonResponse,
includeHighlights: params.includeHighlights,
})
@@
const stream = await assistant.chat({
messages: params.messages.map(m => ({ role: m.role, content: m.content })),
model: params.model,
filter: params.filter,
+ temperature: params.temperature,
+ contextOptions: params.contextOptions,
+ // `@ts-expect-error` - SDK types may not include all options
+ jsonResponse: params.jsonResponse,
+ includeHighlights: params.includeHighlights,
stream: true,
})🤖 Prompt for AI Agents
In `@electron/assistant-service.ts` around lines 177 - 203, The chat
implementation drops caller-specified parameters; update the assistant.chat call
in chat(assistantName: string, params: ChatParams) to pass params.temperature
and params.contextOptions through (alongside existing model/filter), and mirror
these additions in chatStream so both methods forward temperature,
contextOptions, and also ensure chatStream accepts jsonResponse and
includeHighlights the same way chat() does (reference the assistant.chat
invocation and the chatStream method to locate changes).
| const isNameValid = !validationErrors.name | ||
| const canSubmit = isEditing | ||
| ? !isSubmitting | ||
| : !isSubmitting && draftAssistant.name.trim().length > 0 |
There was a problem hiding this comment.
isNameValid is computed but not used in canSubmit.
The isNameValid variable is defined but not incorporated into the canSubmit check. This allows form submission even when there are name validation errors (e.g., invalid characters, length violations).
🛠️ Proposed fix
const isNameValid = !validationErrors.name
const canSubmit = isEditing
? !isSubmitting
- : !isSubmitting && draftAssistant.name.trim().length > 0
+ : !isSubmitting && isNameValid && draftAssistant.name.trim().length > 0📝 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 isNameValid = !validationErrors.name | |
| const canSubmit = isEditing | |
| ? !isSubmitting | |
| : !isSubmitting && draftAssistant.name.trim().length > 0 | |
| const isNameValid = !validationErrors.name | |
| const canSubmit = isEditing | |
| ? !isSubmitting | |
| : !isSubmitting && isNameValid && draftAssistant.name.trim().length > 0 |
🤖 Prompt for AI Agents
In `@src/components/assistants/AssistantConfigView.tsx` around lines 33 - 36, The
computed isNameValid is not used in canSubmit, allowing submits despite name
validation errors; update the canSubmit logic to include isNameValid in both
branches so submission requires the name to be valid (i.e., change canSubmit to
depend on isEditing ? !isSubmitting && isNameValid : !isSubmitting &&
isNameValid && draftAssistant.name.trim().length > 0), referencing the existing
symbols isNameValid, canSubmit, draftAssistant.name, validationErrors.name,
isEditing, and isSubmitting.
| const [selectedFile, setSelectedFile] = useState<{ name: string; path: string; size: number } | null>(null) | ||
| const [metadataJson, setMetadataJson] = useState('') | ||
| const [metadataError, setMetadataError] = useState<string | null>(null) | ||
| const [multimodal, setMultimodal] = useState(false) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for multimodal references across the codebase
echo "=== Searching for 'multimodal' in hooks ==="
rg -n "multimodal" src/hooks/
echo -e "\n=== Searching for 'multimodal' in electron ==="
rg -n "multimodal" electron/
echo -e "\n=== Searching for 'multimodal' in API/queries ==="
rg -n "multimodal" src/ --type ts --type tsx
echo -e "\n=== Checking UploadFileDialog context ==="
rg -n "multimodal\|uploadMutation" src/components/files/UploadFileDialog.tsxRepository: stepandel/pinecone-explorer
Length of output: 301
🏁 Script executed:
#!/bin/bash
# Search for multimodal references across the codebase with correct syntax
echo "=== Searching for 'multimodal' in entire src directory ==="
rg "multimodal" src/ -n
echo -e "\n=== Searching for 'multimodal' in electron directory ==="
rg "multimodal" electron/ -n
echo -e "\n=== Checking UploadFileDialog file ==="
cat -n src/components/files/UploadFileDialog.tsx | grep -A 5 -B 5 "multimodal"
echo -e "\n=== Check if uploadMutation is defined and what parameters it accepts ==="
rg "uploadMutation" src/ -n -A 3 -B 1Repository: stepandel/pinecone-explorer
Length of output: 4610
🏁 Script executed:
#!/bin/bash
# Find the useUploadFileMutation hook definition
echo "=== Locating useUploadFileMutation hook ==="
fd "useUploadFileMutation\|useUploadFile" src/
echo -e "\n=== Searching for hook implementation ==="
rg "useUploadFileMutation\|function useUploadFileMutation\|const useUploadFileMutation" src/ -A 10Repository: stepandel/pinecone-explorer
Length of output: 157
🏁 Script executed:
#!/bin/bash
# Check the imports in UploadFileDialog to find where useUploadFileMutation comes from
echo "=== Checking imports in UploadFileDialog ==="
head -n 20 src/components/files/UploadFileDialog.tsx
echo -e "\n=== Search for useUploadFileMutation across entire src ==="
rg "useUploadFileMutation" src/ -n
echo -e "\n=== Search for uploadFile mutation in hooks ==="
find src/hooks -type f -name "*.ts" -o -name "*.js" | xargs cat | head -n 200Repository: stepandel/pinecone-explorer
Length of output: 7715
🏁 Script executed:
#!/bin/bash
# Check the electron API definition for assistant.files.upload to see if it supports multimodal
echo "=== Checking electron API types ==="
rg "assistant.*files.*upload" electron/ -A 5 -B 2
echo -e "\n=== Searching for multimodal in electron handlers ==="
rg "multimodal" electron/ -n
echo -e "\n=== Check types definition ==="
find electron -name "*.ts" | head -5 | xargs ls -la
</parameter>Repository: stepandel/pinecone-explorer
Length of output: 1724
🏁 Script executed:
#!/bin/bash
# Check the types definition for UploadAssistantFileParams
echo "=== Searching for UploadAssistantFileParams type definition ==="
rg "UploadAssistantFileParams\|interface.*Upload.*File" electron/ src/ -A 5
echo -e "\n=== Check electron/types.ts for file upload types ==="
fd "types.ts" electron/
echo -e "\n=== Check preload.ts for type imports ==="
head -n 30 electron/preload.tsRepository: stepandel/pinecone-explorer
Length of output: 921
🏁 Script executed:
#!/bin/bash
# Check electron/types.ts for file upload related types
echo "=== Checking electron/types.ts ==="
cat -n electron/types.ts | grep -A 10 -B 2 "UploadAssistantFileParams\|AssistantFile\|upload"
echo -e "\n=== Check full electron/types.ts ==="
wc -l electron/types.ts
head -n 50 electron/types.ts
</script>Repository: stepandel/pinecone-explorer
Length of output: 3695
🏁 Script executed:
#!/bin/bash
# Verify the complete UploadAssistantFileParams interface
echo "=== Complete UploadAssistantFileParams definition ==="
sed -n '485,490p' electron/types.ts
echo -e "\n=== All multimodal references in the codebase ==="
rg "multimodal" . --type-list | head -5
rg -i "multimodal" . -l 2>/dev/null
echo -e "\n=== Check if multimodal is ever used in handleUpload ==="
sed -n '155,182p' src/components/files/UploadFileDialog.tsxRepository: stepandel/pinecone-explorer
Length of output: 1699
The multimodal state is tracked but never used in the upload request.
The multimodal checkbox is rendered and its state is maintained (lines 318-320), but the value is never passed to uploadMutation.mutateAsync() in handleUpload (lines 173-176). The UploadAssistantFileParams interface only accepts filePath and optional metadata, with no multimodal parameter. Either this is an incomplete feature or dead code that should be removed.
🤖 Prompt for AI Agents
In `@src/components/files/UploadFileDialog.tsx` at line 26, The multimodal boolean
state is tracked by useState and the checkbox but never passed to
uploadMutation.mutateAsync nor present on UploadAssistantFileParams; either wire
it through or remove it. To fix, update the UploadAssistantFileParams type to
include multimodal?: boolean and pass { filePath, metadata, multimodal } from
handleUpload into uploadMutation.mutateAsync (references: multimodal state,
handleUpload, uploadMutation.mutateAsync, UploadAssistantFileParams, and the
checkbox render), or if the feature is not needed remove the multimodal state
and checkbox to eliminate dead code.
| useMemo(() => { | ||
| if (isEditing && editingAssistant && editingAssistantName) { | ||
| setDraftAssistant(createDraftFromAssistant(editingAssistant)) | ||
| } | ||
| }, [isEditing, editingAssistant, editingAssistantName]) |
There was a problem hiding this comment.
useMemo misused for side effects — should be useEffect.
useMemo is for computing derived values, not for performing side effects like setDraftAssistant. This can lead to unpredictable behavior since React may re-run or skip memoization at will.
🐛 Proposed fix using useEffect
// Update draft when editing assistant data loads
- useMemo(() => {
+ useEffect(() => {
if (isEditing && editingAssistant && editingAssistantName) {
setDraftAssistant(createDraftFromAssistant(editingAssistant))
}
}, [isEditing, editingAssistant, editingAssistantName])📝 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.
| useMemo(() => { | |
| if (isEditing && editingAssistant && editingAssistantName) { | |
| setDraftAssistant(createDraftFromAssistant(editingAssistant)) | |
| } | |
| }, [isEditing, editingAssistant, editingAssistantName]) | |
| useEffect(() => { | |
| if (isEditing && editingAssistant && editingAssistantName) { | |
| setDraftAssistant(createDraftFromAssistant(editingAssistant)) | |
| } | |
| }, [isEditing, editingAssistant, editingAssistantName]) |
🤖 Prompt for AI Agents
In `@src/context/DraftAssistantContext.tsx` around lines 119 - 123, The current
code uses useMemo to run a side effect (calling setDraftAssistant with
createDraftFromAssistant) which is incorrect; replace the useMemo call with
useEffect so the side effect runs reliably when dependencies change: move the
body that checks isEditing && editingAssistant && editingAssistantName and calls
setDraftAssistant(createDraftFromAssistant(editingAssistant)) into a useEffect
whose dependency array contains isEditing, editingAssistant, and
editingAssistantName; keep the same conditional logic and ensure no return value
is expected from this hook.
| const sendMessage = useCallback(async (content: string) => { | ||
| if (!currentProfile?.id || !assistantName || !content.trim()) return | ||
| if (isStreaming) return | ||
|
|
||
| setError(null) | ||
| setIsStreaming(true) | ||
|
|
||
| // Add user message | ||
| const userMessage: ChatMessageWithMeta = { | ||
| role: 'user', | ||
| content: content.trim(), | ||
| } | ||
| setMessages(prev => [...prev, userMessage]) | ||
|
|
||
| // Prepare messages for API (convert to ChatMessage format) | ||
| const apiMessages: ChatMessage[] = [ | ||
| ...messages.map(m => ({ role: m.role, content: m.content })), | ||
| { role: 'user' as const, content: content.trim() }, | ||
| ] | ||
|
|
There was a problem hiding this comment.
Race condition: subscribing before starting the stream may miss early chunks.
The subscription is set up (Line 89) before chatStream.start is called (Line 191). If the main process emits chunks immediately after start resolves, there's a window where currentStreamIdRef.current is still null, and chunks could be filtered out by the streamId !== currentStreamIdRef.current check at Line 91.
🔧 Proposed fix: Start stream first, then subscribe
try {
+ // Start the stream first to get the streamId
+ const streamId = await window.electronAPI.assistant.chatStream.start(
+ currentProfile.id,
+ assistantName,
+ {
+ messages: apiMessages,
+ model: currentModel,
+ }
+ )
+ currentStreamIdRef.current = streamId
+
// Subscribe to chunk events
unsubscribeRef.current = window.electronAPI.assistant.chatStream.onChunk(
- (streamId: string, chunk: ChatStreamChunk) => {
- if (streamId !== currentStreamIdRef.current) return
+ (chunkStreamId: string, chunk: ChatStreamChunk) => {
+ if (chunkStreamId !== currentStreamIdRef.current) return
// ... rest of handler
}
)
-
- // Start the stream
- const streamId = await window.electronAPI.assistant.chatStream.start(
- currentProfile.id,
- assistantName,
- {
- messages: apiMessages,
- model: currentModel,
- }
- )
- currentStreamIdRef.current = streamId
} catch (err) {Note: This requires the IPC to buffer chunks until the renderer subscribes, or the subscription to handle late registration. Verify the IPC implementation supports this.
🤖 Prompt for AI Agents
In `@src/hooks/useChatStream.ts` around lines 59 - 78, The subscription to stream
chunks in sendMessage is registered before calling chatStream.start, which can
drop early chunks because currentStreamIdRef.current is still null; to fix, call
chatStream.start(...) first, capture/assign the returned streamId into
currentStreamIdRef.current immediately, then register the IPC/subscription
handler that checks streamId against currentStreamIdRef.current (or adapt the
handler to accept the provided streamId), and only after subscription begin
setting isStreaming and updating messages; ensure the IPC implementation can
buffer or that the handler tolerates late registration as noted.
Code reviewFound 1 issue to address: File: Issue: Incorrect use of
Suggested fix: Replace useEffect(() => {
if (isEditing && editingAssistant && editingAssistantName) {
setDraftAssistant(createDraftFromAssistant(editingAssistant))
}
}, [isEditing, editingAssistant, editingAssistantName])Reference: pinecone-explorer/src/context/DraftAssistantContext.tsx Lines 117 to 124 in 0235e06 |
0235e06 to
27df902
Compare
- MainContent: Show 'No file selected' in assistant mode instead of 'No vector selected' - AssistantConfigView: Include isNameValid in canSubmit check - DraftAssistantContext: Replace useMemo with useEffect for side effect
- MainContent: Show 'No file selected' in assistant mode instead of 'No vector selected' - AssistantConfigView: Include isNameValid in canSubmit check - DraftAssistantContext: Replace useMemo with useEffect for side effect
c2aa254 to
229f54b
Compare
- 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
229f54b to
b164b5b
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 a full-width form view for creating new assistants or editing existing ones.
Changes
DraftAssistantContext.tsxfor form state managementAssistantConfigView.tsxwith:Files Changed
src/context/DraftAssistantContext.tsx(new)src/components/assistants/AssistantConfigView.tsx(new)src/components/layout/MainContent.tsxsrc/windows/ConnectionWindow.tsxTesting
pnpm test:build✅Closes PINE-39
Summary by CodeRabbit