From 47508695265e418228184e9501a05868d4e87bd3 Mon Sep 17 00:00:00 2001 From: adryserage <17680194+adryserage@users.noreply.github.com> Date: Fri, 19 Dec 2025 06:30:11 -0500 Subject: [PATCH] feat(ui): add LLM provider selection to Graphiti onboarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add provider dropdown to the Memory & Context onboarding step allowing users to choose between OpenAI, Anthropic (Claude), Google (Gemini), and Groq (Llama) for Graphiti memory operations. Changes: - Add provider selection dropdown with 4 LLM options - Dynamic API key field that updates label, placeholder, and link based on selected provider - Update validation and save logic to handle multiple providers - Fix node-pty imports to use @lydell/node-pty directly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- auto-claude-ui/electron.vite.config.ts | 2 +- .../src/main/terminal/pty-daemon.ts | 2 +- .../src/main/terminal/pty-manager.ts | 2 +- auto-claude-ui/src/main/terminal/types.ts | 2 +- .../components/onboarding/GraphitiStep.tsx | 134 +++++++++++++----- 5 files changed, 101 insertions(+), 41 deletions(-) diff --git a/auto-claude-ui/electron.vite.config.ts b/auto-claude-ui/electron.vite.config.ts index 7d3b23aa..968c7fc3 100644 --- a/auto-claude-ui/electron.vite.config.ts +++ b/auto-claude-ui/electron.vite.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ index: resolve(__dirname, 'src/main/index.ts') }, // Only node-pty needs to be external (native module rebuilt by electron-builder) - external: ['node-pty'] + external: ['@lydell/node-pty'] } } }, diff --git a/auto-claude-ui/src/main/terminal/pty-daemon.ts b/auto-claude-ui/src/main/terminal/pty-daemon.ts index 9c430efc..7e5bf2d5 100644 --- a/auto-claude-ui/src/main/terminal/pty-daemon.ts +++ b/auto-claude-ui/src/main/terminal/pty-daemon.ts @@ -10,7 +10,7 @@ import * as net from 'net'; import * as fs from 'fs'; -import * as pty from 'node-pty'; +import * as pty from '@lydell/node-pty'; const SOCKET_PATH = process.platform === 'win32' diff --git a/auto-claude-ui/src/main/terminal/pty-manager.ts b/auto-claude-ui/src/main/terminal/pty-manager.ts index 26c61855..d118dca7 100644 --- a/auto-claude-ui/src/main/terminal/pty-manager.ts +++ b/auto-claude-ui/src/main/terminal/pty-manager.ts @@ -3,7 +3,7 @@ * Handles low-level PTY process creation and lifecycle */ -import * as pty from 'node-pty'; +import * as pty from '@lydell/node-pty'; import * as os from 'os'; import type { TerminalProcess, WindowGetter } from './types'; import { IPC_CHANNELS } from '../../shared/constants'; diff --git a/auto-claude-ui/src/main/terminal/types.ts b/auto-claude-ui/src/main/terminal/types.ts index 5cbd2eca..7a361890 100644 --- a/auto-claude-ui/src/main/terminal/types.ts +++ b/auto-claude-ui/src/main/terminal/types.ts @@ -1,4 +1,4 @@ -import type * as pty from 'node-pty'; +import type * as pty from '@lydell/node-pty'; import type { BrowserWindow } from 'electron'; /** diff --git a/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx b/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx index c2fe54d7..107b5edc 100644 --- a/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx +++ b/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx @@ -18,12 +18,14 @@ import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Card, CardContent } from '../ui/card'; import { Switch } from '../ui/switch'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { useSettingsStore } from '../../stores/settings-store'; +import type { GraphitiProviderType } from '../../../shared/types'; interface GraphitiStepProps { onNext: () => void; @@ -34,12 +36,21 @@ interface GraphitiStepProps { interface GraphitiConfig { enabled: boolean; falkorDbUri: string; - openAiApiKey: string; + llmProvider: GraphitiProviderType; + apiKey: string; } +// Provider display info +const PROVIDER_INFO: Record = { + openai: { name: 'OpenAI', placeholder: 'sk-...', link: 'https://platform.openai.com/api-keys' }, + anthropic: { name: 'Anthropic', placeholder: 'sk-ant-...', link: 'https://console.anthropic.com/settings/keys' }, + google: { name: 'Google (Gemini)', placeholder: 'AIza...', link: 'https://aistudio.google.com/apikey' }, + groq: { name: 'Groq', placeholder: 'gsk_...', link: 'https://console.groq.com/keys' }, +}; + interface ValidationStatus { falkordb: { tested: boolean; success: boolean; message: string } | null; - openai: { tested: boolean; success: boolean; message: string } | null; + llm: { tested: boolean; success: boolean; message: string } | null; } /** @@ -52,7 +63,8 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { const [config, setConfig] = useState({ enabled: false, falkorDbUri: 'bolt://localhost:6379', // Standard FalkorDB port, will be auto-detected from Docker - openAiApiKey: settings.globalOpenAIApiKey || '' + llmProvider: 'openai', + apiKey: settings.globalOpenAIApiKey || '' }); const [showApiKey, setShowApiKey] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -63,7 +75,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { const [isValidating, setIsValidating] = useState(false); const [validationStatus, setValidationStatus] = useState({ falkordb: null, - openai: null + llm: null }); // Check Docker/Infrastructure availability on mount @@ -99,23 +111,32 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { setError(null); setSuccess(false); // Reset validation status when toggling - setValidationStatus({ falkordb: null, openai: null }); + setValidationStatus({ falkordb: null, llm: null }); + }; + + const handleProviderChange = (provider: GraphitiProviderType) => { + setConfig(prev => ({ ...prev, llmProvider: provider, apiKey: '' })); + setValidationStatus(prev => ({ ...prev, llm: null })); + setError(null); }; const handleTestConnection = async () => { - if (!config.openAiApiKey.trim()) { - setError('Please enter an OpenAI API key to test the connection'); + const providerName = PROVIDER_INFO[config.llmProvider].name; + if (!config.apiKey.trim()) { + setError(`Please enter a ${providerName} API key to test the connection`); return; } setIsValidating(true); setError(null); - setValidationStatus({ falkordb: null, openai: null }); + setValidationStatus({ falkordb: null, llm: null }); try { + // For now, we still use the OpenAI test endpoint, but pass the provider info + // TODO: Add provider-specific validation endpoints const result = await window.electronAPI.testGraphitiConnection( config.falkorDbUri, - config.openAiApiKey.trim() + config.apiKey.trim() ); if (result?.success && result?.data) { @@ -125,7 +146,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { success: result.data.falkordb.success, message: result.data.falkordb.message }, - openai: { + llm: { tested: true, success: result.data.openai.success, message: result.data.openai.message @@ -138,7 +159,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { errors.push(`FalkorDB: ${result.data.falkordb.message}`); } if (!result.data.openai.success) { - errors.push(`OpenAI: ${result.data.openai.message}`); + errors.push(`${providerName}: ${result.data.openai.message}`); } if (errors.length > 0) { setError(errors.join('\n')); @@ -161,8 +182,9 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { return; } - if (!config.openAiApiKey.trim()) { - setError('OpenAI API key is required for Graphiti embeddings'); + const providerName = PROVIDER_INFO[config.llmProvider].name; + if (!config.apiKey.trim()) { + setError(`${providerName} API key is required for Graphiti`); return; } @@ -170,14 +192,29 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { setError(null); try { - // Save OpenAI API key to global settings - const result = await window.electronAPI.saveSettings({ - globalOpenAIApiKey: config.openAiApiKey.trim() - }); + // Build settings update based on selected provider + const settingsUpdate: Record = { + graphitiLlmProvider: config.llmProvider, + }; + + // Save the API key for the selected provider + if (config.llmProvider === 'openai') { + settingsUpdate.globalOpenAIApiKey = config.apiKey.trim(); + } else if (config.llmProvider === 'anthropic') { + settingsUpdate.globalAnthropicApiKey = config.apiKey.trim(); + } else if (config.llmProvider === 'google') { + settingsUpdate.globalGoogleApiKey = config.apiKey.trim(); + } else if (config.llmProvider === 'groq') { + settingsUpdate.globalGroqApiKey = config.apiKey.trim(); + } + + const result = await window.electronAPI.saveSettings(settingsUpdate); if (result?.success) { // Update local settings store - updateSettings({ globalOpenAIApiKey: config.openAiApiKey.trim() }); + if (config.llmProvider === 'openai') { + updateSettings({ globalOpenAIApiKey: config.apiKey.trim() }); + } // Proceed to next step immediately after successful save onNext(); } else { @@ -344,7 +381,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { Enable Graphiti Memory

- Requires FalkorDB (Docker) and OpenAI API key + Requires FalkorDB (Docker) and an LLM API key

@@ -360,6 +397,29 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { {/* Configuration fields (shown when enabled) */} {config.enabled && (
+ {/* LLM Provider Selection */} +
+ +

+ Select the AI provider for graph operations +

+ +
+ {/* FalkorDB URI */}
@@ -399,35 +459,35 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {

- {/* OpenAI API Key */} + {/* Dynamic API Key based on provider */}
-
{ - setConfig(prev => ({ ...prev, openAiApiKey: e.target.value })); - setValidationStatus(prev => ({ ...prev, openai: null })); + setConfig(prev => ({ ...prev, apiKey: e.target.value })); + setValidationStatus(prev => ({ ...prev, llm: null })); }} - placeholder="sk-..." + placeholder={PROVIDER_INFO[config.llmProvider].placeholder} className="pr-10 font-mono text-sm" disabled={isSaving || isValidating} /> @@ -451,14 +511,14 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {

- Required for generating embeddings. Get your key from{' '} + Required for graph operations. Get your key from{' '} - OpenAI + {PROVIDER_INFO[config.llmProvider].name}

@@ -468,7 +528,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { - {validationStatus.falkordb?.success && validationStatus.openai?.success && ( + {validationStatus.falkordb?.success && validationStatus.llm?.success && (

All connections validated successfully!

@@ -515,7 +575,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {