diff --git a/ui/desktop/src/components/settings/providers/BaseProviderGrid.tsx b/ui/desktop/src/components/settings/providers/BaseProviderGrid.tsx index a30bc4224..19799c1d6 100644 --- a/ui/desktop/src/components/settings/providers/BaseProviderGrid.tsx +++ b/ui/desktop/src/components/settings/providers/BaseProviderGrid.tsx @@ -32,6 +32,19 @@ function getArticle(word: string): string { return 'aeiouAEIOU'.indexOf(word[0]) >= 0 ? 'an' : 'a'; } +export function getProviderDescription(provider) { + const descriptions = { + OpenAI: 'Access GPT-4, GPT-3.5 Turbo, and other OpenAI models', + Anthropic: 'Access Claude and other Anthropic models', + Google: 'Access Gemini and other Google AI models', + Groq: 'Access Mixtral and other Groq-hosted models', + Databricks: 'Access models hosted on your Databricks instance', + OpenRouter: 'Access a variety of AI models through OpenRouter', + Ollama: 'Run and use open-source models locally', + }; + return descriptions[provider] || `Access ${provider} models`; +} + function BaseProviderCard({ name, description, @@ -51,8 +64,12 @@ function BaseProviderCard({ return (
-
- + {/* Glowing ring */} +
isSelectable && isConfigured && onSelect?.()} className={`relative bg-bgApp rounded-lg diff --git a/ui/desktop/src/components/settings/providers/ConfigureProviders.tsx b/ui/desktop/src/components/settings/providers/ConfigureProviders.tsx index bd472ba6f..68aa6ee0a 100644 --- a/ui/desktop/src/components/settings/providers/ConfigureProviders.tsx +++ b/ui/desktop/src/components/settings/providers/ConfigureProviders.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import { Providers } from './Provider'; import { ScrollArea } from '../../ui/scroll-area'; import BackButton from '../../ui/BackButton'; import { ConfigureProvidersGrid } from './ConfigureProvidersGrid'; diff --git a/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx b/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx index fa69cf84b..2e83621f8 100644 --- a/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx +++ b/ui/desktop/src/components/settings/providers/ConfigureProvidersGrid.tsx @@ -1,8 +1,7 @@ import React, { useMemo, useState } from 'react'; import { useActiveKeys } from '../api_keys/ActiveKeysContext'; -import { BaseProviderGrid } from './BaseProviderGrid'; +import { BaseProviderGrid, getProviderDescription } from './BaseProviderGrid'; import { supported_providers, provider_aliases, required_keys } from '../models/hardcoded_stuff'; -import { getProviderDescription } from './Provider'; import { ProviderSetupModal } from '../ProviderSetupModal'; import { getApiUrl, getSecretKey } from '../../../config'; import { toast } from 'react-toastify'; diff --git a/ui/desktop/src/components/settings/providers/Header.tsx b/ui/desktop/src/components/settings/providers/Header.tsx deleted file mode 100644 index ff67e98a0..000000000 --- a/ui/desktop/src/components/settings/providers/Header.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; -import { FaArrowLeft } from 'react-icons/fa'; - -export default function Header() { - const navigate = useNavigate(); - - return ( -
- -

Providers

-
- ); -} diff --git a/ui/desktop/src/components/settings/providers/Provider.tsx b/ui/desktop/src/components/settings/providers/Provider.tsx deleted file mode 100644 index 82956604b..000000000 --- a/ui/desktop/src/components/settings/providers/Provider.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import { supported_providers, required_keys, provider_aliases } from '../models/hardcoded_stuff'; -import { useActiveKeys } from '../api_keys/ActiveKeysContext'; -import { ProviderSetupModal } from '../ProviderSetupModal'; -import React from 'react'; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '@radix-ui/react-accordion'; -import { Check, ChevronDown, Edit2, Plus, X } from 'lucide-react'; -import { Button } from '../../ui/button'; -import { getApiUrl, getSecretKey } from '../../../config'; -import { getActiveProviders } from '../api_keys/utils'; -import { toast } from 'react-toastify'; -import { useModel } from '../models/ModelContext'; - -function ConfirmationModal({ message, onConfirm, onCancel }) { - return ( -
-
-

{message}

-
- - -
-
-
- ); -} - -// Utility Functions -export function getProviderDescription(provider) { - const descriptions = { - OpenAI: 'Access GPT-4, GPT-3.5 Turbo, and other OpenAI models', - Anthropic: 'Access Claude and other Anthropic models', - Google: 'Access Gemini and other Google AI models', - Groq: 'Access Mixtral and other Groq-hosted models', - Databricks: 'Access models hosted on your Databricks instance', - OpenRouter: 'Access a variety of AI models through OpenRouter', - Ollama: 'Run and use open-source models locally', - }; - return descriptions[provider] || `Access ${provider} models`; -} - -function useProviders(activeKeys) { - return React.useMemo(() => { - return supported_providers.map((providerName) => { - const alias = - provider_aliases.find((p) => p.provider === providerName)?.alias || - providerName.toLowerCase(); - const requiredKeys = required_keys[providerName] || []; - const isConfigured = activeKeys.includes(providerName); - - return { - id: alias, - name: providerName, - keyName: requiredKeys, - isConfigured, - description: getProviderDescription(providerName), - }; - }); - }, [activeKeys]); -} - -// Reusable Components -function ProviderStatus({ isConfigured }) { - return isConfigured ? ( -
- - Configured -
- ) : ( -
- - Not Configured -
- ); -} - -function ProviderKeyList({ keyNames, activeKeys }) { - return keyNames.length > 0 ? ( -
- Required API Keys: - {keyNames.map((key) => ( -
- {key} - {activeKeys.includes(key) && } -
- ))} -
- ) : ( -
No API keys required
- ); -} - -function ProviderActions({ provider, onEdit, onDelete, onAdd }) { - if (!provider.keyName || provider.keyName.length === 0) { - return null; - } - - return provider.isConfigured ? ( -
- - -
- ) : ( - - ); -} - -function ProviderItem({ provider, activeKeys, onEdit, onDelete, onAdd }) { - return ( - - -
-
-
{provider.name}
- -
- -
-
- -
-

{provider.description}

-
- - -
-
-
-
- ); -} - -// Main Component -export function Providers() { - const { activeKeys, setActiveKeys } = useActiveKeys(); - const providers = useProviders(activeKeys); - const [selectedProvider, setSelectedProvider] = React.useState(null); - const [isModalOpen, setIsModalOpen] = React.useState(false); - const [isConfirmationOpen, setIsConfirmationOpen] = React.useState(false); - const { currentModel } = useModel(); - - const handleEdit = (provider) => { - setSelectedProvider(provider); - setIsModalOpen(true); - }; - - const handleAdd = (provider) => { - setSelectedProvider(provider); - setIsModalOpen(true); - }; - - const handleModalSubmit = async (apiKey) => { - if (!selectedProvider) return; - - const provider = selectedProvider.name; - const keyName = required_keys[provider]?.[0]; // Get the first key, assuming one key per provider - - if (!keyName) { - console.error(`No key found for provider ${provider}`); - return; - } - - try { - if (selectedProvider.isConfigured) { - // Delete existing key logic if configured - const deleteResponse = await fetch(getApiUrl('/secrets/delete'), { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ key: keyName }), - }); - - if (!deleteResponse.ok) { - const errorText = await deleteResponse.text(); - console.error('Delete response error:', errorText); - throw new Error('Failed to delete old key'); - } - - console.log('Old key deleted successfully.'); - } - - // Store new key logic - const storeResponse = await fetch(getApiUrl('/secrets/store'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ - key: keyName, - value: apiKey.trim(), - }), - }); - - if (!storeResponse.ok) { - const errorText = await storeResponse.text(); - console.error('Store response error:', errorText); - throw new Error('Failed to store new key'); - } - - console.log('Key stored successfully.'); - - // Show success toast - toast.success( - selectedProvider.isConfigured - ? `Successfully updated API key for ${provider}` - : `Successfully added API key for ${provider}` - ); - - // Update active keys - const updatedKeys = await getActiveProviders(); - setActiveKeys(updatedKeys); - - setIsModalOpen(false); - } catch (error) { - console.error('Error handling modal submit:', error); - } - }; - - const handleDelete = (provider) => { - setSelectedProvider(provider); - setIsConfirmationOpen(true); - }; - - const confirmDelete = async () => { - if (!selectedProvider) return; - - const provider = selectedProvider.name; - const keyName = required_keys[provider]?.[0]; // Get the first key, assuming one key per provider - - if (!keyName) { - console.error(`No key found for provider ${provider}`); - return; - } - - try { - // Check if the selected provider is currently active - if (currentModel?.provider === provider) { - toast.error( - `Cannot delete the API key for ${provider} because it's the provider of the current model (${currentModel.name}). Please switch to a different model first.` - ); - setIsConfirmationOpen(false); - return; - } - - // Delete old key logic - const deleteResponse = await fetch(getApiUrl('/secrets/delete'), { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ key: keyName }), - }); - - if (!deleteResponse.ok) { - const errorText = await deleteResponse.text(); - console.error('Delete response error:', errorText); - throw new Error('Failed to delete key'); - } - - console.log('Key deleted successfully.'); - // Show success toast - toast.success(`Successfully deleted API key for ${provider}`); - - // Update active keys - const updatedKeys = await getActiveProviders(); - setActiveKeys(updatedKeys); - - setIsConfirmationOpen(false); - } catch (error) { - console.error('Error confirming delete:', error); - // Show success toast - toast.error(`Unable to delete API key for ${provider}`); - } - }; - - return ( -
-
- Configure your AI model providers by adding their API keys. Your keys are stored securely - and encrypted locally. -
- - - {providers.map((provider) => ( - - ))} - - - {isModalOpen && selectedProvider && ( - setIsModalOpen(false)} - /> - )} - - {isConfirmationOpen && selectedProvider && ( - setIsConfirmationOpen(false)} - /> - )} -
- ); -} diff --git a/ui/desktop/src/components/settings/providers/ProviderGrid.tsx b/ui/desktop/src/components/welcome_screen/ProviderGrid.tsx similarity index 57% rename from ui/desktop/src/components/settings/providers/ProviderGrid.tsx rename to ui/desktop/src/components/welcome_screen/ProviderGrid.tsx index c985af7b1..dde700cf8 100644 --- a/ui/desktop/src/components/settings/providers/ProviderGrid.tsx +++ b/ui/desktop/src/components/welcome_screen/ProviderGrid.tsx @@ -1,105 +1,22 @@ import React from 'react'; -import { Check, Plus } from 'lucide-react'; -import { Button } from '../../ui/button'; -import { supported_providers, required_keys, provider_aliases } from '../models/hardcoded_stuff'; -import { useActiveKeys } from '../api_keys/ActiveKeysContext'; -import { getProviderDescription } from './Provider'; -import { ProviderSetupModal } from '../ProviderSetupModal'; -import { useModel } from '../models/ModelContext'; -import { useRecentModels } from '../models/RecentModels'; -import { createSelectedModel } from '../models/utils'; -import { getDefaultModel } from '../models/hardcoded_stuff'; -import { initializeSystem } from '../../../utils/providerUtils'; -import { getApiUrl, getSecretKey } from '../../../config'; +import { Button } from '../ui/button'; +import { + supported_providers, + required_keys, + provider_aliases, +} from '../settings/models/hardcoded_stuff'; +import { useActiveKeys } from '../settings/api_keys/ActiveKeysContext'; +import { ProviderSetupModal } from '../settings/ProviderSetupModal'; +import { useModel } from '../settings/models/ModelContext'; +import { useRecentModels } from '../settings/models/RecentModels'; +import { createSelectedModel } from '../settings/models/utils'; +import { getDefaultModel } from '../settings/models/hardcoded_stuff'; +import { initializeSystem } from '../../utils/providerUtils'; +import { getApiUrl, getSecretKey } from '../../config'; import { toast } from 'react-toastify'; -import { getActiveProviders } from '../api_keys/utils'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/Tooltip'; +import { getActiveProviders } from '../settings/api_keys/utils'; import { useNavigate } from 'react-router-dom'; -import { BaseProviderGrid } from './BaseProviderGrid'; - -interface ProviderCardProps { - name: string; - description: string; - isConfigured: boolean; - onConfigure: () => void; - onAddKeys: () => void; - isSelected: boolean; - onSelect: () => void; -} - -function getArticle(word: string): string { - return 'aeiouAEIOU'.indexOf(word[0]) >= 0 ? 'an' : 'a'; -} - -function ProviderCard({ - name, - description, - isConfigured, - onConfigure, - onAddKeys, - isSelected, - onSelect, -}: ProviderCardProps) { - return ( -
isConfigured && onSelect()} - className={`relative bg-white dark:bg-gray-800 rounded-lg border - ${ - isSelected - ? 'border-blue-500 dark:border-blue-400 shadow-[0_0_0_1px] shadow-blue-500/50' - : 'border-gray-200 dark:border-gray-700' - } - p-3 transition-all duration-200 h-[140px] overflow-hidden - ${isConfigured ? 'cursor-pointer hover:border-blue-400 dark:hover:border-blue-300' : ''} - `} - > -
-
-

- {name} -

- {isConfigured && ( - - - -
- -
-
- -

- You have {getArticle(name)} {name} API Key set in your environment -

-
-
-
- )} -
-
- -

- {description} -

- -
- {!isConfigured && ( - - )} -
-
- ); -} +import { BaseProviderGrid, getProviderDescription } from '../settings/providers/BaseProviderGrid'; interface ProviderGridProps { onSubmit?: () => void; @@ -249,20 +166,16 @@ export function ProviderGrid({ onSubmit }: ProviderGridProps) { const provider = providers.find((p) => p.id === selectedId); if (provider) handleConfigure(provider); }} - className="rounded-full px-6 py-2 min-w-[160px] bg-blue-600 hover:bg-blue-700 dark:bg-blue-600 dark:hover:bg-blue-700 text-white dark:text-white text-sm font-medium shadow-md hover:shadow-lg transition-all" + className={ + 'bg-black dark:bg-white dark:hover:bg-gray-200 text-white dark:!text-black border-borderStandard hover:bg-slate text-sm whitespace-nowrap shrink-0 bg-bgSubtle text-textStandard rounded-full shadow-none border px-4 py-2' + } > - Select {providers.find((p) => p.id === selectedId)?.name} + Let's takeoff
)}
-
- Configure your AI model providers by adding their API keys. Your keys are stored securely - and encrypted locally. You can change your provider and select specific models in the - settings. -
- +
{/* Draggable title bar region */}
{/* Content area - explicitly set as non-draggable */}
-
- {/* Content Area */} -
-
-
-

Choose a Provider

-
- -
+
+ {/* Header Section */} +
+ +

+ Welcome to goose +

+

+ Your intelligent AI assistant for seamless productivity and creativity. +

+
+ + {/* ProviderGrid */} +
+

+ Choose a Provider +

+

+ Select an AI model provider to get started with goose. +

+

+ Click on a provider to configure its API keys and start using goose. Your keys are + stored securely and encrypted locally. You can change your provider and select + specific models in the settings. +

+ +
+ + {/* Get started (now less prominent) */} +
+

+ Not sure where to start?{' '} + +