Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions electron/assistant-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Pinecone } from '@pinecone-database/pinecone'
import { AssistantModel, CreateAssistantParams, UpdateAssistantParams } from './types'

/**
* Service layer for Pinecone Assistant API operations.
* Wraps the Pinecone SDK's Assistant methods and provides a clean interface
* for CRUD operations on assistants.
*/
export class AssistantService {
private client: Pinecone

constructor(client: Pinecone) {
this.client = client
}

/**
* List all assistants for the current Pinecone API key
*/
async listAssistants(): Promise<AssistantModel[]> {
const response = await this.client.listAssistants()
return (response.assistants || []).map(this.mapAssistantModel)
}

/**
* Create a new assistant
*/
async createAssistant(params: CreateAssistantParams): Promise<AssistantModel> {
const response = await this.client.createAssistant({
name: params.name,
instructions: params.instructions,
metadata: params.metadata,
region: params.region,
})
return this.mapAssistantModel(response)
}

/**
* Get details of a specific assistant by name
*/
async describeAssistant(name: string): Promise<AssistantModel> {
const response = await this.client.describeAssistant(name)
return this.mapAssistantModel(response)
}

/**
* Update an existing assistant
*/
async updateAssistant(name: string, params: UpdateAssistantParams): Promise<AssistantModel> {
const response = await this.client.updateAssistant(name, {
instructions: params.instructions,
metadata: params.metadata,
})
// updateAssistant returns a different response type, reconstruct AssistantModel
// by fetching the updated assistant
return this.describeAssistant(name)
}

/**
* Delete an assistant by name
*/
async deleteAssistant(name: string): Promise<void> {
await this.client.deleteAssistant(name)
}

/**
* Map SDK AssistantModel to our internal type
*/
private mapAssistantModel(model: {
name: string
status: string
instructions?: string | null
metadata?: object | null
host?: string
createdAt?: Date
updatedAt?: Date
}): AssistantModel {
return {
name: model.name,
status: model.status as AssistantModel['status'],
instructions: model.instructions ?? undefined,
metadata: (model.metadata ?? undefined) as Record<string, string> | undefined,
host: model.host,
createdAt: model.createdAt?.toISOString(),
updatedAt: model.updatedAt?.toISOString(),
}
}
}
79 changes: 79 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,85 @@ ipcMain.handle('profiles:setPreferredMode', async (_event, profileId: string, mo
}
})

// ============================================================================
// Assistant IPC Handlers
// ============================================================================

ipcMain.handle('assistant:list', async (_event, profileId: string) => {
try {
const service = pineconeConnectionPool.getConnection(profileId)
if (!service) {
return { success: false, error: 'Not connected to Pinecone' }
}
const assistantService = service.getAssistantService()
const assistants = await assistantService.listAssistants()
return { success: true, data: assistants }
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to list assistants'
return { success: false, error: message }
}
})

ipcMain.handle('assistant:create', async (_event, profileId: string, params: { name: string; instructions?: string; metadata?: Record<string, string>; region?: 'us' | 'eu' }) => {
try {
const service = pineconeConnectionPool.getConnection(profileId)
if (!service) {
return { success: false, error: 'Not connected to Pinecone' }
}
const assistantService = service.getAssistantService()
const assistant = await assistantService.createAssistant(params)
return { success: true, data: assistant }
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create assistant'
return { success: false, error: message }
}
})

ipcMain.handle('assistant:describe', async (_event, profileId: string, name: string) => {
try {
const service = pineconeConnectionPool.getConnection(profileId)
if (!service) {
return { success: false, error: 'Not connected to Pinecone' }
}
const assistantService = service.getAssistantService()
const assistant = await assistantService.describeAssistant(name)
return { success: true, data: assistant }
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to describe assistant'
return { success: false, error: message }
}
})

ipcMain.handle('assistant:update', async (_event, profileId: string, name: string, params: { instructions?: string; metadata?: Record<string, string> }) => {
try {
const service = pineconeConnectionPool.getConnection(profileId)
if (!service) {
return { success: false, error: 'Not connected to Pinecone' }
}
const assistantService = service.getAssistantService()
const assistant = await assistantService.updateAssistant(name, params)
return { success: true, data: assistant }
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to update assistant'
return { success: false, error: message }
}
})

ipcMain.handle('assistant:delete', async (_event, profileId: string, name: string) => {
try {
const service = pineconeConnectionPool.getConnection(profileId)
if (!service) {
return { success: false, error: 'Not connected to Pinecone' }
}
const assistantService = service.getAssistantService()
await assistantService.deleteAssistant(name)
return { success: true }
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to delete assistant'
return { success: false, error: message }
}
})

// ============================================================================
// Window Management IPC Handlers
// ============================================================================
Expand Down
15 changes: 15 additions & 0 deletions electron/pinecone-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
} from './types'
import { EmbeddingService, SparseVector, EmbeddingResult } from './embedding-service'
import { withRetry } from './retry-utils'
import { AssistantService } from './assistant-service'

/**
* Main Pinecone service class
Expand All @@ -36,6 +37,7 @@ class PineconeService {

private client: Pinecone | null = null
private embeddingService: EmbeddingService | null = null
private assistantService: AssistantService | null = null
private profile: ConnectionProfile | null = null
private indexCache: Map<string, Index<RecordMetadata>> = new Map()
private indexInfoCache: Map<string, IndexInfo> = new Map()
Expand All @@ -44,6 +46,19 @@ class PineconeService {
return this.profile
}

/**
* Get the AssistantService for this connection
*/
getAssistantService(): AssistantService {
if (!this.client) {
throw new Error('Not connected to Pinecone')
}
if (!this.assistantService) {
this.assistantService = new AssistantService(this.client)
}
return this.assistantService
}

/**
* Connect to Pinecone with the given profile
*/
Expand Down
39 changes: 39 additions & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
HybridEmbeddingConfig,
GetVectorsPaginatedParams,
PaginatedVectorsResult,
AssistantModel,
CreateAssistantParams,
UpdateAssistantParams,
} from './types'

console.log('Preload script is running!')
Expand Down Expand Up @@ -308,6 +311,42 @@ contextBridge.exposeInMainWorld('electronAPI', {
}
},
},
assistant: {
list: async (profileId: string): Promise<AssistantModel[]> => {
const result = await ipcRenderer.invoke('assistant:list', profileId)
if (!result.success) {
throw new Error(result.error)
}
return result.data
},
create: async (profileId: string, params: CreateAssistantParams): Promise<AssistantModel> => {
const result = await ipcRenderer.invoke('assistant:create', profileId, params)
if (!result.success) {
throw new Error(result.error)
}
return result.data
},
describe: async (profileId: string, name: string): Promise<AssistantModel> => {
const result = await ipcRenderer.invoke('assistant:describe', profileId, name)
if (!result.success) {
throw new Error(result.error)
}
return result.data
},
update: async (profileId: string, name: string, params: UpdateAssistantParams): Promise<AssistantModel> => {
const result = await ipcRenderer.invoke('assistant:update', profileId, name, params)
if (!result.success) {
throw new Error(result.error)
}
return result.data
},
delete: async (profileId: string, name: string): Promise<void> => {
const result = await ipcRenderer.invoke('assistant:delete', profileId, name)
if (!result.success) {
throw new Error(result.error)
}
},
},
window: {
createConnection: async (profile: ConnectionProfile): Promise<{ windowId: string }> => {
const result = await ipcRenderer.invoke('window:create-connection', profile)
Expand Down
40 changes: 40 additions & 0 deletions electron/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,3 +398,43 @@ export interface GetVectorsPaginatedParams {
cursor?: string
}

// ============================================================================
// Assistant API Types
// ============================================================================

/**
* Assistant status
*/
export type AssistantStatus = 'Initializing' | 'Ready' | 'Failed' | 'Terminating'

/**
* Assistant model representing a Pinecone Assistant
*/
export interface AssistantModel {
name: string
status: AssistantStatus
instructions?: string
metadata?: Record<string, string>
host?: string
createdAt?: string
updatedAt?: string
}

/**
* Parameters for creating a new assistant
*/
export interface CreateAssistantParams {
name: string
instructions?: string
metadata?: Record<string, string>
region?: 'us' | 'eu'
}

/**
* Parameters for updating an assistant
*/
export interface UpdateAssistantParams {
instructions?: string
metadata?: Record<string, string>
}

26 changes: 13 additions & 13 deletions src/context/ModeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ export function ModeProvider({ children }: { children: ReactNode }) {
})
}, [currentProfile])

const setMode = useCallback((newMode: ExplorerMode) => {
setModeState(newMode)

// Persist to electron-store
if (currentProfile) {
window.electronAPI.profiles.setPreferredMode(currentProfile.id, newMode)
.catch((err) => {
console.warn('Failed to persist mode preference:', err)
})
}
}, [currentProfile])

// Handle keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
Expand All @@ -49,19 +61,7 @@ export function ModeProvider({ children }: { children: ReactNode }) {

window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, []) // eslint-disable-line react-hooks/exhaustive-deps

const setMode = useCallback((newMode: ExplorerMode) => {
setModeState(newMode)

// Persist to electron-store
if (currentProfile) {
window.electronAPI.profiles.setPreferredMode(currentProfile.id, newMode)
.catch((err) => {
console.warn('Failed to persist mode preference:', err)
})
}
}, [currentProfile])
}, [setMode])

// Don't render children until we've loaded the initial mode
if (!isInitialized) {
Expand Down
32 changes: 32 additions & 0 deletions src/types/electron.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ declare global {

type ExplorerMode = 'index' | 'assistant'

// Assistant API Types
type AssistantStatus = 'Initializing' | 'Ready' | 'Failed' | 'Terminating'

interface AssistantModel {
name: string
status: AssistantStatus
instructions?: string
metadata?: Record<string, string>
host?: string
createdAt?: string
updatedAt?: string
}

interface CreateAssistantParams {
name: string
instructions?: string
metadata?: Record<string, string>
region?: 'us' | 'eu'
}

interface UpdateAssistantParams {
instructions?: string
metadata?: Record<string, string>
}

interface ConnectionProfile {
id: string
name: string
Expand Down Expand Up @@ -314,6 +339,13 @@ declare global {
getPreferredMode: (profileId: string) => Promise<ExplorerMode | null>
setPreferredMode: (profileId: string, mode: ExplorerMode) => Promise<void>
}
assistant: {
list: (profileId: string) => Promise<AssistantModel[]>
create: (profileId: string, params: CreateAssistantParams) => Promise<AssistantModel>
describe: (profileId: string, name: string) => Promise<AssistantModel>
update: (profileId: string, name: string, params: UpdateAssistantParams) => Promise<AssistantModel>
delete: (profileId: string, name: string) => Promise<void>
}
window: {
createConnection: (profile: ConnectionProfile) => Promise<{ windowId: string }>
getInfo: () => Promise<{ type: string; windowId?: string; profileId?: string }>
Expand Down