diff --git a/electron/preload.ts b/electron/preload.ts index 68e7d19..63dc8b7 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 }> => { @@ -469,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/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/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 8c1628d..1400d82 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 }> @@ -396,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