diff --git a/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx b/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx index 98a6f629..8a6b7b24 100644 --- a/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx +++ b/auto-claude-ui/src/renderer/components/onboarding/GraphitiStep.tsx @@ -38,14 +38,28 @@ interface GraphitiConfig { falkorDbUri: string; llmProvider: GraphitiProviderType; apiKey: string; + ollamaBaseUrl: string; // For Ollama provider (no API key needed) } // 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' }, +const PROVIDER_INFO: Record = { + openai: { name: 'OpenAI', placeholder: 'sk-...', link: 'https://platform.openai.com/api-keys', requiresApiKey: true }, + anthropic: { name: 'Anthropic', placeholder: 'sk-ant-...', link: 'https://console.anthropic.com/settings/keys', requiresApiKey: true }, + google: { name: 'Google (Gemini)', placeholder: 'AIza...', link: 'https://aistudio.google.com/apikey', requiresApiKey: true }, + groq: { name: 'Groq', placeholder: 'gsk_...', link: 'https://console.groq.com/keys', requiresApiKey: true }, + ollama: { + name: 'Ollama', + placeholder: 'http://localhost:11434', + link: 'https://ollama.ai', + requiresApiKey: false, + description: 'Local LLM - no API key required' + }, }; // Helper to get the saved API key for a provider from settings @@ -55,10 +69,16 @@ function getApiKeyForProvider(provider: GraphitiProviderType, settings: Record): string { + return (settings.ollamaBaseUrl as string) || 'http://localhost:11434'; +} + interface ValidationStatus { falkordb: { tested: boolean; success: boolean; message: string } | null; llm: { tested: boolean; success: boolean; message: string } | null; @@ -77,7 +97,8 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { enabled: false, falkorDbUri: 'bolt://localhost:6379', // Standard FalkorDB port, will be auto-detected from Docker llmProvider: savedProvider, - apiKey: getApiKeyForProvider(savedProvider, settings as Record) + apiKey: getApiKeyForProvider(savedProvider, settings as Record), + ollamaBaseUrl: getOllamaBaseUrl(settings as Record) }); const [showApiKey, setShowApiKey] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -128,19 +149,32 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { }; const handleProviderChange = (provider: GraphitiProviderType) => { - // Load saved API key for the selected provider + // Load saved API key or base URL for the selected provider const savedKey = getApiKeyForProvider(provider, settings as Record); - setConfig(prev => ({ ...prev, llmProvider: provider, apiKey: savedKey })); + const savedOllamaUrl = getOllamaBaseUrl(settings as Record); + setConfig(prev => ({ + ...prev, + llmProvider: provider, + apiKey: savedKey, + ollamaBaseUrl: savedOllamaUrl + })); setValidationStatus(prev => ({ ...prev, llm: null })); setError(null); }; const handleTestConnection = async () => { const providerName = PROVIDER_INFO[config.llmProvider].name; - if (!config.apiKey.trim()) { + const providerInfo = PROVIDER_INFO[config.llmProvider]; + + // Validate input based on provider type + if (providerInfo.requiresApiKey && !config.apiKey.trim()) { setError(`Please enter a ${providerName} API key to test the connection`); return; } + if (config.llmProvider === 'ollama' && !config.ollamaBaseUrl.trim()) { + setError('Please enter the Ollama server URL to test the connection'); + return; + } setIsValidating(true); setError(null); @@ -149,9 +183,13 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { try { // For now, we still use the OpenAI test endpoint, but pass the provider info // TODO: Add provider-specific validation endpoints + // For Ollama, pass the base URL instead of API key + const testCredential = config.llmProvider === 'ollama' + ? config.ollamaBaseUrl.trim() + : config.apiKey.trim(); const result = await window.electronAPI.testGraphitiConnection( config.falkorDbUri, - config.apiKey.trim() + testCredential ); if (result?.success && result?.data) { @@ -198,10 +236,17 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { } const providerName = PROVIDER_INFO[config.llmProvider].name; - if (!config.apiKey.trim()) { + const providerInfo = PROVIDER_INFO[config.llmProvider]; + + // Validate input based on provider type + if (providerInfo.requiresApiKey && !config.apiKey.trim()) { setError(`${providerName} API key is required for Graphiti`); return; } + if (config.llmProvider === 'ollama' && !config.ollamaBaseUrl.trim()) { + setError('Ollama server URL is required for Graphiti'); + return; + } setIsSaving(true); setError(null); @@ -212,7 +257,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { graphitiLlmProvider: config.llmProvider, }; - // Save the API key for the selected provider + // Save the API key or base URL for the selected provider if (config.llmProvider === 'openai') { settingsUpdate.globalOpenAIApiKey = config.apiKey.trim(); } else if (config.llmProvider === 'anthropic') { @@ -221,6 +266,8 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { settingsUpdate.globalGoogleApiKey = config.apiKey.trim(); } else if (config.llmProvider === 'groq') { settingsUpdate.globalGroqApiKey = config.apiKey.trim(); + } else if (config.llmProvider === 'ollama') { + settingsUpdate.ollamaBaseUrl = config.ollamaBaseUrl.trim(); } const result = await window.electronAPI.saveSettings(settingsUpdate); @@ -236,6 +283,8 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { storeUpdate.globalGoogleApiKey = config.apiKey.trim(); } else if (config.llmProvider === 'groq') { storeUpdate.globalGroqApiKey = config.apiKey.trim(); + } else if (config.llmProvider === 'ollama') { + storeUpdate.ollamaBaseUrl = config.ollamaBaseUrl.trim(); } updateSettings(storeUpdate); // Proceed to next step immediately after successful save @@ -404,7 +453,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { Enable Graphiti Memory

- Requires FalkorDB (Docker) and an LLM API key + Requires FalkorDB (Docker) and an LLM provider (API key or local Ollama)

@@ -439,6 +488,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) { Anthropic (Claude) Google (Gemini) Groq (Llama) + Ollama (Local) @@ -482,76 +532,124 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {

- {/* Dynamic API Key based on provider */} -
-
- - {validationStatus.llm && ( -
- {validationStatus.llm.success ? ( - - ) : ( - - )} - - {validationStatus.llm.success ? 'Valid' : 'Invalid'} - -
- )} -
-
+ {/* Dynamic credential field based on provider */} + {config.llmProvider === 'ollama' ? ( + /* Ollama Base URL field */ +
+
+ + {validationStatus.llm && ( +
+ {validationStatus.llm.success ? ( + + ) : ( + + )} + + {validationStatus.llm.success ? 'Connected' : 'Failed'} + +
+ )} +
{ - setConfig(prev => ({ ...prev, apiKey: e.target.value })); + setConfig(prev => ({ ...prev, ollamaBaseUrl: e.target.value })); setValidationStatus(prev => ({ ...prev, llm: null })); }} - placeholder={PROVIDER_INFO[config.llmProvider].placeholder} - className="pr-10 font-mono text-sm" + placeholder="http://localhost:11434" + className="font-mono text-sm" disabled={isSaving || isValidating} /> - - - - - - {showApiKey ? 'Hide API key' : 'Show API key'} - - +

+ No API key required. Make sure{' '} + + Ollama + + {' '}is running locally. +

-

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

-
+ ) : ( + /* API Key field for other providers */ +
+
+ + {validationStatus.llm && ( +
+ {validationStatus.llm.success ? ( + + ) : ( + + )} + + {validationStatus.llm.success ? 'Valid' : 'Invalid'} + +
+ )} +
+
+ { + setConfig(prev => ({ ...prev, apiKey: e.target.value })); + setValidationStatus(prev => ({ ...prev, llm: null })); + }} + placeholder={PROVIDER_INFO[config.llmProvider].placeholder} + className="pr-10 font-mono text-sm" + disabled={isSaving || isValidating} + /> + + + + + + {showApiKey ? 'Hide API key' : 'Show API key'} + + +
+

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

+
+ )} {/* Test Connection Button */}
)} @@ -603,7 +706,7 @@ export function GraphitiStep({ onNext, onBack, onSkip }: GraphitiStepProps) {