From 830eed22763f6bca6fb37564da77beebd6729ba1 Mon Sep 17 00:00:00 2001 From: "Atlas (Engineering Lead)" Date: Thu, 5 Feb 2026 22:56:05 +0000 Subject: [PATCH 1/2] 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 --- electron/preload.ts | 27 +++++++++++++ electron/types.ts | 85 +++++++++++++++++++++++++++++++++++++++++ src/types/electron.d.ts | 43 +++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/electron/preload.ts b/electron/preload.ts index 68e7d19..f1ca61b 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -29,6 +29,8 @@ import { AssistantFile, ListAssistantFilesFilter, UploadAssistantFileParams, + ChatStreamChunk, + ChatMessage, } from './types' console.log('Preload script is running!') @@ -387,6 +389,31 @@ contextBridge.exposeInMainWorld('electronAPI', { } }, }, + // Chat streaming operations + chatStream: { + start: async ( + profileId: string, + assistantName: string, + params: { messages: ChatMessage[]; model?: string; filter?: Record } + ): Promise => { + const result = await ipcRenderer.invoke('assistant:chat:stream:start', profileId, assistantName, params) + if (!result.success) { + throw new Error(result.error) + } + return result.data.streamId + }, + cancel: async (streamId: string): Promise => { + const result = await ipcRenderer.invoke('assistant:chat:stream:cancel', streamId) + if (!result.success) { + throw new Error(result.error) + } + }, + onChunk: (callback: (streamId: string, chunk: ChatStreamChunk) => void): (() => void) => { + const handler = (_event: Electron.IpcRendererEvent, streamId: string, chunk: ChatStreamChunk) => callback(streamId, chunk) + ipcRenderer.on('assistant:chat:chunk', handler) + return () => ipcRenderer.removeListener('assistant:chat:chunk', handler) + }, + }, }, window: { createConnection: async (profile: ConnectionProfile): Promise<{ windowId: string }> => { diff --git a/electron/types.ts b/electron/types.ts index 801a26d..82879c7 100644 --- a/electron/types.ts +++ b/electron/types.ts @@ -486,3 +486,88 @@ export interface UploadAssistantFileParams { multimodal?: boolean } +// ============================================================================ +// Assistant Chat Types +// ============================================================================ + +/** + * Chat message structure + */ +export interface ChatMessage { + role: 'user' | 'assistant' + content: string +} + +/** + * A single reference within a citation + */ +export interface CitationReference { + file: { + name: string + id: string + status?: string + signedUrl?: string | null + } + pages?: number[] +} + +/** + * Citation reference in assistant responses + */ +export interface Citation { + position: number + references: CitationReference[] +} + +/** + * Token usage statistics for chat + */ +export interface ChatUsage { + promptTokens: number + completionTokens: number + totalTokens: number +} + +/** + * Context options for chat requests + */ +export interface ChatContextOptions { + topK?: number + snippetSize?: number +} + +/** + * Parameters for chat requests + */ +export interface ChatParams { + messages: ChatMessage[] + model?: string + filter?: Record + jsonResponse?: boolean + includeHighlights?: boolean + temperature?: number + contextOptions?: ChatContextOptions +} + +/** + * Response from non-streaming chat + */ +export interface ChatResponse { + id: string + message: ChatMessage + citations?: Citation[] + usage?: ChatUsage + finishReason?: string + model?: string +} + +/** + * Stream chunk types for streaming chat responses + */ +export type ChatStreamChunk = + | { type: 'message_start'; id: string; model: string; role: 'assistant' } + | { type: 'content'; content: string } + | { type: 'citation'; citation: Citation | undefined } + | { type: 'message_end'; usage?: ChatUsage; finishReason?: string } + | { type: 'error'; error: string } + diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 8c1628d..b2bb0f5 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -87,6 +87,40 @@ declare global { metadata?: Record } + // Chat types for assistant streaming + interface ChatMessage { + role: 'user' | 'assistant' + content: string + } + + interface CitationReference { + file: { + name: string + id: string + status?: string + signedUrl?: string | null + } + pages?: number[] + } + + interface Citation { + position: number + references: CitationReference[] + } + + interface ChatUsage { + promptTokens: number + completionTokens: number + totalTokens: number + } + + type ChatStreamChunk = + | { type: 'message_start'; id: string; model: string; role: 'assistant' } + | { type: 'content'; content: string } + | { type: 'citation'; citation: Citation | undefined } + | { type: 'message_end'; usage?: ChatUsage; finishReason?: string } + | { type: 'error'; error: string } + interface ConnectionProfile { id: string name: string @@ -377,6 +411,15 @@ declare global { upload: (profileId: string, assistantName: string, params: UploadAssistantFileParams) => Promise delete: (profileId: string, assistantName: string, fileId: string) => Promise } + chatStream: { + start: (profileId: string, assistantName: string, params: { + messages: Array<{ role: 'user' | 'assistant'; content: string }>; + model?: string; + filter?: Record; + }) => Promise + cancel: (streamId: string) => Promise + onChunk: (callback: (streamId: string, chunk: ChatStreamChunk) => void) => () => void + } } window: { createConnection: (profile: ConnectionProfile) => Promise<{ windowId: string }> From 44064671e7d847d608570ee302fd41be085da537 Mon Sep 17 00:00:00 2001 From: "Atlas (Engineering Lead)" Date: Thu, 5 Feb 2026 23:15:38 +0000 Subject: [PATCH 2/2] fix: Phase 6 feedback - dialog API and mode labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- electron/preload.ts | 8 ++++++++ src/components/mode/ModeSwitcher.tsx | 4 ++-- src/types/electron.d.ts | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/electron/preload.ts b/electron/preload.ts index f1ca61b..63dc8b7 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -496,6 +496,14 @@ contextBridge.exposeInMainWorld('electronAPI', { } }, }, + dialog: { + showOpenDialog: async (options: { + properties?: Array<'openFile' | 'openDirectory' | 'multiSelections'> + filters?: Array<{ name: string; extensions: string[] }> + }): Promise<{ canceled: boolean; filePaths: string[] }> => { + return await ipcRenderer.invoke('dialog:showOpenDialog', options) + }, + }, updater: { checkForUpdates: async (): Promise => { const result = await ipcRenderer.invoke('updater:check') diff --git a/src/components/mode/ModeSwitcher.tsx b/src/components/mode/ModeSwitcher.tsx index ae1ebe0..647211b 100644 --- a/src/components/mode/ModeSwitcher.tsx +++ b/src/components/mode/ModeSwitcher.tsx @@ -9,7 +9,7 @@ interface ModeOption { } const modes: ModeOption[] = [ - { value: 'index', icon: Database, label: 'Index Explorer', shortcut: '⌘1' }, + { value: 'index', icon: Database, label: 'Database Explorer', shortcut: '⌘1' }, { value: 'assistant', icon: Bot, label: 'Assistant Explorer', shortcut: '⌘2' }, ] @@ -43,7 +43,7 @@ export function ModeSwitcher() { data-testid={`mode-${value}`} > - {value === 'index' ? 'Index' : 'Assistant'} + {value === 'index' ? 'Database' : 'Assistant'} ) })} diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index b2bb0f5..1400d82 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -439,6 +439,12 @@ declare global { shell: { openExternal: (url: string) => Promise } + dialog: { + showOpenDialog: (options: { + properties?: Array<'openFile' | 'openDirectory' | 'multiSelections'> + filters?: Array<{ name: string; extensions: string[] }> + }) => Promise<{ canceled: boolean; filePaths: string[] }> + } updater: { checkForUpdates: () => Promise downloadUpdate: () => Promise