From 689890d577fbc73da89ea3c633fbc9a2f52447fe Mon Sep 17 00:00:00 2001 From: Lily Delalande <119957291+lily-de@users.noreply.github.com> Date: Tue, 4 Mar 2025 12:33:28 -0500 Subject: [PATCH] ui: introduce form injection for provider modals (#1493) --- .../settings_v2/providers/ProviderGrid.tsx | 36 ++--- .../providers/ProviderRegistry.tsx | 106 +++++++------ .../providers/ProviderSettingsPage.tsx | 2 +- .../providers/interfaces/ParameterSchema.ts | 3 +- .../providers/interfaces/ProviderDetails.tsx | 12 +- .../modal/ProviderConfiguationModal.tsx | 51 ++++--- .../providers/modal/ProviderModalProvider.tsx | 11 +- .../subcomponents/ProviderSetupActions.tsx | 4 +- .../subcomponents/SecureStorageNotice.tsx | 22 +++ .../forms/DefaultProviderSetupForm.tsx | 75 +++++----- .../modal/subcomponents/forms/OllamaForm.tsx | 139 ++++++++++++++++++ .../handlers/DefaultSubmitHandler.tsx | 7 + .../handlers/OllamaSubmitHandler.tsx | 7 + .../providers/state/ActiveKeysContext.tsx | 48 ------ .../providers/state/providerState.tsx | 0 .../settings_v2/providers/state/types.ts | 21 --- .../settings_v2/providers/state/utils.tsx | 98 ------------ .../providers/subcomponents/CardBody.tsx | 10 +- .../providers/subcomponents/CardHeader.tsx | 4 - .../providers/subcomponents/ProviderCard.tsx | 97 ++++++++---- .../buttons/DefaultCardButtons.tsx | 28 +--- ui/desktop/src/components/ui/CustomRadio.tsx | 72 +++++++++ 22 files changed, 488 insertions(+), 365 deletions(-) create mode 100644 ui/desktop/src/components/settings_v2/providers/modal/subcomponents/SecureStorageNotice.tsx create mode 100644 ui/desktop/src/components/settings_v2/providers/modal/subcomponents/forms/OllamaForm.tsx create mode 100644 ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx create mode 100644 ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/OllamaSubmitHandler.tsx delete mode 100644 ui/desktop/src/components/settings_v2/providers/state/ActiveKeysContext.tsx create mode 100644 ui/desktop/src/components/settings_v2/providers/state/providerState.tsx delete mode 100644 ui/desktop/src/components/settings_v2/providers/state/types.ts delete mode 100644 ui/desktop/src/components/settings_v2/providers/state/utils.tsx create mode 100644 ui/desktop/src/components/ui/CustomRadio.tsx diff --git a/ui/desktop/src/components/settings_v2/providers/ProviderGrid.tsx b/ui/desktop/src/components/settings_v2/providers/ProviderGrid.tsx index 25424bda..64e6f5a2 100644 --- a/ui/desktop/src/components/settings_v2/providers/ProviderGrid.tsx +++ b/ui/desktop/src/components/settings_v2/providers/ProviderGrid.tsx @@ -22,32 +22,28 @@ function ProviderCards({ }) { const { openModal } = useProviderModal(); - // Define the callbacks for provider actions - const providerCallbacks = { - // Replace your OnShowSettings with the modal opener - onConfigure: (provider: ProviderState) => { - console.log('Configure button clicked for:', provider.name); - openModal(provider, { - onSubmit: (values: any) => { - console.log(`Configuring ${provider.name}:`, values); - // Your logic to save the configuration - }, - formProps: {}, - }); - console.log('openModal called'); // Check if this executes - }, - onLaunch: (provider: ProviderState) => { - OnRefresh(); - }, + const configureProviderViaModal = (provider: ProviderState) => { + openModal(provider, { + onSubmit: (values: any) => { + console.log(`Configuring ${provider.name}:`, values); + // Your logic to save the configuration + }, + formProps: {}, + }); + }; + + const handleLaunch = () => { + OnRefresh(); }; return ( <> {providers.map((provider) => ( configureProviderViaModal(provider)} + onLaunch={handleLaunch} isOnboarding={isOnboarding} /> ))} @@ -67,7 +63,7 @@ export default function ProviderGrid({ - {/* This is missing! */} + ); diff --git a/ui/desktop/src/components/settings_v2/providers/ProviderRegistry.tsx b/ui/desktop/src/components/settings_v2/providers/ProviderRegistry.tsx index 2cb4c1a8..0dd7aa16 100644 --- a/ui/desktop/src/components/settings_v2/providers/ProviderRegistry.tsx +++ b/ui/desktop/src/components/settings_v2/providers/ProviderRegistry.tsx @@ -1,28 +1,6 @@ -import React from 'react'; import ProviderDetails from './interfaces/ProviderDetails'; -import DefaultCardButtons from './subcomponents/buttons/DefaultCardButtons'; -import ButtonCallbacks from '@/src/components/settings_v2/providers/interfaces/ButtonCallbacks'; -import ProviderState from '@/src/components/settings_v2/providers/interfaces/ProviderState'; - -// Helper function to generate default actions for most providers -const getDefaultButtons = ( - provider: ProviderState, - callbacks: ButtonCallbacks, - isOnboardingPage -) => { - return [ - { - id: 'default-buttons', - renderButton: () => ( - - ), - }, - ]; -}; +import OllamaForm from './modal/subcomponents/forms/OllamaForm'; +import OllamaSubmitHandler from './modal/subcomponents/handlers/OllamaSubmitHandler'; export interface ProviderRegistry { name: string; @@ -47,13 +25,9 @@ export interface ProviderRegistry { * - Optional custom form component * - Action buttons that appear on provider cards * - * 2. Two-Level Configuration: - * a) Provider Card UI - What buttons appear on each provider card - * - Controlled by the provider's getActions() function - * - Most providers use default buttons (configure/launch) - * - Can be customized for special providers + * 2. Configuration submission: * - * b) Modal Content - What form appears in the configuration modal + * Modal Content - What form appears in the configuration modal * - A single modal component exists in the app * - Content changes dynamically based on the provider being configured * - If provider has CustomForm property, that component is rendered @@ -70,14 +44,12 @@ export interface ProviderRegistry { * --------------------- * * For a standard provider with simple configuration: - * - Define parameters array with all required fields - * - Use the default getActions function + * - Define parameters array with all required fields and any defaults that should be supplied * - No need to specify a CustomForm * * For a provider needing custom configuration: - * - Define parameters array (even if just for documentation) + * - Define parameters array (if needed, otherwise leave as an empty list) * - Create a custom form component and assign to CustomForm property - * - Use the default or custom getActions function * * This architecture centralizes provider definitions while allowing * flexibility for special cases, keeping the codebase maintainable. @@ -95,9 +67,17 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ name: 'OPENAI_API_KEY', is_secret: true, }, + { + name: 'OPENAI_HOST', + is_secret: false, + default: 'https://api.openai.com', + }, + { + name: 'OPENAI_BASE_PATH', + is_secret: false, + default: 'v1/chat/completions', + }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, { @@ -112,8 +92,6 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ is_secret: true, }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, { @@ -128,8 +106,6 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ is_secret: true, }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, { @@ -144,8 +120,6 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ is_secret: true, }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, { @@ -160,8 +134,6 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ is_secret: false, }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, { @@ -176,8 +148,6 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ is_secret: true, }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, { @@ -190,10 +160,50 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [ { name: 'OLLAMA_HOST', is_secret: false, + default: 'localhost', + }, + ], + }, + }, + { + name: 'Azure OpenAI', + details: { + id: 'azure_openai', + name: 'Azure OpenAI', + description: 'Access Azure OpenAI models', + parameters: [ + { + name: 'AZURE_OPENAI_API_KEY', + is_secret: true, + }, + { + name: 'AZURE_OPENAI_ENDPOINT', + is_secret: false, + }, + { + name: 'AZURE_OPENAI_DEPLOYMENT_NAME', + is_secret: false, + }, + ], + }, + }, + { + name: 'GCP Vertex AI', + details: { + id: 'gcp_vertex_ai', + name: 'GCP Vertex AI', + description: 'GCP Vertex AI models', + parameters: [ + { + name: 'GCP_PROJECT_ID', + is_secret: false, + }, + { + name: 'GCP_LOCATION', + is_secret: false, + default: 'us-central1', }, ], - getActions: (provider, callbacks, isOnboardingPage) => - getDefaultButtons(provider, callbacks, isOnboardingPage), }, }, ]; diff --git a/ui/desktop/src/components/settings_v2/providers/ProviderSettingsPage.tsx b/ui/desktop/src/components/settings_v2/providers/ProviderSettingsPage.tsx index cabf5753..d9131f8f 100644 --- a/ui/desktop/src/components/settings_v2/providers/ProviderSettingsPage.tsx +++ b/ui/desktop/src/components/settings_v2/providers/ProviderSettingsPage.tsx @@ -68,7 +68,7 @@ export default function ProviderSettings({ onClose }: { onClose: () => void }) { {/* Content Area */}
- +
diff --git a/ui/desktop/src/components/settings_v2/providers/interfaces/ParameterSchema.ts b/ui/desktop/src/components/settings_v2/providers/interfaces/ParameterSchema.ts index 52359c6f..b739aab8 100644 --- a/ui/desktop/src/components/settings_v2/providers/interfaces/ParameterSchema.ts +++ b/ui/desktop/src/components/settings_v2/providers/interfaces/ParameterSchema.ts @@ -1,5 +1,6 @@ export default interface ParameterSchema { name: string; is_secret: boolean; - location?: string; // env, config.yaml, and/or keychain + required?: boolean; + default?: string; // optional default values } diff --git a/ui/desktop/src/components/settings_v2/providers/interfaces/ProviderDetails.tsx b/ui/desktop/src/components/settings_v2/providers/interfaces/ProviderDetails.tsx index 6523b74c..154a1703 100644 --- a/ui/desktop/src/components/settings_v2/providers/interfaces/ProviderDetails.tsx +++ b/ui/desktop/src/components/settings_v2/providers/interfaces/ProviderDetails.tsx @@ -1,8 +1,5 @@ -// metadata and action builder -import ProviderState from './ProviderState'; -import ConfigurationAction from './ConfigurationAction'; import ParameterSchema from '../interfaces/ParameterSchema'; -import ButtonCallbacks from './ButtonCallbacks'; +import ProviderSetupFormProps from '../modal/interfaces/ProviderSetupFormProps'; export default interface ProviderDetails { id: string; @@ -10,9 +7,6 @@ export default interface ProviderDetails { description: string; parameters: ParameterSchema[]; getTags?: (name: string) => string[]; - getActions?: ( - provider: ProviderState, - callbacks: ButtonCallbacks, - isOnboardingPage: boolean - ) => ConfigurationAction[]; + customForm?: React.ComponentType; + customSubmit?: (e: any) => void; } diff --git a/ui/desktop/src/components/settings_v2/providers/modal/ProviderConfiguationModal.tsx b/ui/desktop/src/components/settings_v2/providers/modal/ProviderConfiguationModal.tsx index 18469fe9..efde63fd 100644 --- a/ui/desktop/src/components/settings_v2/providers/modal/ProviderConfiguationModal.tsx +++ b/ui/desktop/src/components/settings_v2/providers/modal/ProviderConfiguationModal.tsx @@ -6,22 +6,24 @@ import ProviderSetupActions from './subcomponents/ProviderSetupActions'; import ProviderLogo from './subcomponents/ProviderLogo'; import { useProviderModal } from './ProviderModalProvider'; import { toast } from 'react-toastify'; +import { PROVIDER_REGISTRY } from '../ProviderRegistry'; +import { SecureStorageNotice } from './subcomponents/SecureStorageNotice'; +import DefaultSubmitHandler from './subcomponents/handlers/DefaultSubmitHandler'; export default function ProviderConfigurationModal() { const { isOpen, currentProvider, modalProps, closeModal } = useProviderModal(); - console.log('currentProvider', currentProvider); const [configValues, setConfigValues] = useState({}); - // Reset form values when provider changes useEffect(() => { if (currentProvider) { // Initialize form with default values const initialValues = {}; - if (currentProvider.parameters) { - currentProvider.parameters.forEach((param) => { - initialValues[param.name] = param.defaultValue || ''; - }); - } + // FIXME + // if (currentProvider.parameters) { + // currentProvider.parameters.forEach((param) => { + // initialValues[param.name] = param.default || ''; + // }); + // } setConfigValues(initialValues); } else { setConfigValues({}); @@ -31,22 +33,31 @@ export default function ProviderConfigurationModal() { if (!isOpen || !currentProvider) return null; const headerText = `Configure ${currentProvider.name}`; - const descriptionText = `Add your generated api keys for this provider to integrate into Goose`; + const descriptionText = `Add your API key(s) for this provider to integrate into Goose`; - // Use custom form component if provider specifies one, otherwise use default - const FormComponent = currentProvider.CustomForm || DefaultProviderSetupForm; + // Find the provider in the registry to get the details with customForm + const providerEntry = PROVIDER_REGISTRY.find((p) => p.name === currentProvider.name); + + // Get the custom submit handler from the provider details + const customSubmitHandler = providerEntry?.details?.customSubmit; + + // Use custom submit handler otherwise use default + const SubmitHandler = customSubmitHandler || DefaultSubmitHandler; + + // Get the custom form component from the provider details + const CustomForm = providerEntry?.details?.customForm; + + // Use custom form component if available, otherwise use default + const FormComponent = CustomForm || DefaultProviderSetupForm; const handleSubmitForm = (e) => { e.preventDefault(); + console.log('Form submitted for:', currentProvider.name); - // Use custom submit handler if provided in modalProps - if (modalProps.onSubmit) { - modalProps.onSubmit(configValues); - } else { - // Default submit behavior - toast('Submitted configuration!'); - } + SubmitHandler(configValues); + // Close the modal unless the custom handler explicitly returns false + // This gives custom handlers the ability to keep the modal open if needed closeModal(); }; @@ -72,12 +83,14 @@ export default function ProviderConfigurationModal() { - + {providerEntry?.details?.parameters && providerEntry.details.parameters.length > 0 && ( + + )} + ); } diff --git a/ui/desktop/src/components/settings_v2/providers/modal/ProviderModalProvider.tsx b/ui/desktop/src/components/settings_v2/providers/modal/ProviderModalProvider.tsx index bbff2449..3ebccebf 100644 --- a/ui/desktop/src/components/settings_v2/providers/modal/ProviderModalProvider.tsx +++ b/ui/desktop/src/components/settings_v2/providers/modal/ProviderModalProvider.tsx @@ -1,4 +1,13 @@ import React, { createContext, useContext, useState } from 'react'; +import ProviderState from '../interfaces/ProviderState'; + +interface ProviderModalContextType { + isOpen: boolean; + currentProvider: ProviderState | null; + modalProps: any; + openModal: (provider: ProviderState, additionalProps: any) => void; + closeModal: () => void; +} const ProviderModalContext = createContext({ isOpen: false, @@ -8,7 +17,7 @@ const ProviderModalContext = createContext({ closeModal: () => {}, }); -export const useProviderModal = () => useContext(ProviderModalContext); +export const useProviderModal = () => useContext(ProviderModalContext); export const ProviderModalProvider = ({ children }) => { const [isOpen, setIsOpen] = useState(false); diff --git a/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/ProviderSetupActions.tsx b/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/ProviderSetupActions.tsx index e8cbef78..6f6d9ea9 100644 --- a/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/ProviderSetupActions.tsx +++ b/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/ProviderSetupActions.tsx @@ -3,19 +3,21 @@ import { Button } from '../../../../ui/button'; interface ProviderSetupActionsProps { onCancel: () => void; + onSubmit: (e: any) => void; } /** * Renders the "Submit" and "Cancel" buttons at the bottom. * Updated to match the design from screenshots. */ -export default function ProviderSetupActions({ onCancel }: ProviderSetupActionsProps) { +export default function ProviderSetupActions({ onCancel, onSubmit }: ProviderSetupActionsProps) { return (
{/* We rely on the
"onSubmit" for the actual Submit logic */}
+
+ + handleConnectionTypeChange('local')} + disabled={!isLocalAvailable} + /> + + + {/* Other Parameters */} + {parameters + .filter((param) => param.name !== 'host_url') // Skip host_url as we handle it above + .map((parameter) => ( +
+
+ handleInputChange(parameter.name, e.target.value)} + placeholder={ + parameter.default ? parameter.default : parameter.name.replace(/_/g, ' ') + } + className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-900 text-lg placeholder:text-gray-400 dark:placeholder:text-gray-500 font-regular text-gray-900 dark:text-gray-100" + required={parameter.default == null} + /> +
+
+ handleConnectionTypeChange('host')} + /> +
+
+ ))} + + ); +} diff --git a/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx b/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx new file mode 100644 index 00000000..939d45a3 --- /dev/null +++ b/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/DefaultSubmitHandler.tsx @@ -0,0 +1,7 @@ +export default function DefaultSubmitHandler(configValues) { + // Log each field value individually for clarity + console.log('Field values:'); + Object.entries(configValues).forEach(([key, value]) => { + console.log(`${key}: ${value}`); + }); +} diff --git a/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/OllamaSubmitHandler.tsx b/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/OllamaSubmitHandler.tsx new file mode 100644 index 00000000..8f79138f --- /dev/null +++ b/ui/desktop/src/components/settings_v2/providers/modal/subcomponents/handlers/OllamaSubmitHandler.tsx @@ -0,0 +1,7 @@ +export default function OllamaSubmitHandler(configValues) { + // Log each field value individually for clarity + console.log('Ollama field values:'); + Object.entries(configValues).forEach(([key, value]) => { + console.log(`${key}: ${value}`); + }); +} diff --git a/ui/desktop/src/components/settings_v2/providers/state/ActiveKeysContext.tsx b/ui/desktop/src/components/settings_v2/providers/state/ActiveKeysContext.tsx deleted file mode 100644 index 0528234b..00000000 --- a/ui/desktop/src/components/settings_v2/providers/state/ActiveKeysContext.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react'; -import { getActiveProviders } from './utils'; - -// Create a context for active keys -const ActiveKeysContext = createContext< - | { - activeKeys: string[]; - setActiveKeys: (keys: string[]) => void; - } - | undefined ->(undefined); - -export const ActiveKeysProvider = ({ children }: { children: ReactNode }) => { - const [activeKeys, setActiveKeys] = useState([]); // Start with an empty list - const [isLoading, setIsLoading] = useState(true); // Track loading state - - // Fetch active keys from the backend - useEffect(() => { - const fetchActiveProviders = async () => { - try { - const providers = await getActiveProviders(); // Fetch the active providers - setActiveKeys(providers); // Update state with fetched providers - } catch (error) { - console.error('Error fetching active providers:', error); - } finally { - setIsLoading(false); // Ensure loading is marked as complete - } - }; - - fetchActiveProviders(); // Call the async function - }, []); - - // Provide active keys and ability to update them - return ( - - {!isLoading ? children :
Loading...
} {/* Conditional rendering */} -
- ); -}; - -// Custom hook to access active keys -export const useActiveKeys = () => { - const context = useContext(ActiveKeysContext); - if (!context) { - throw new Error('useActiveKeys must be used within an ActiveKeysProvider'); - } - return context; -}; diff --git a/ui/desktop/src/components/settings_v2/providers/state/providerState.tsx b/ui/desktop/src/components/settings_v2/providers/state/providerState.tsx new file mode 100644 index 00000000..e69de29b diff --git a/ui/desktop/src/components/settings_v2/providers/state/types.ts b/ui/desktop/src/components/settings_v2/providers/state/types.ts deleted file mode 100644 index 78dc57b3..00000000 --- a/ui/desktop/src/components/settings_v2/providers/state/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface ProviderResponse { - supported: boolean; - name?: string; - description?: string; - models?: string[]; - config_status: Record; -} - -export interface ConfigDetails { - key: string; - is_set: boolean; - location?: string; -} - -export interface Provider { - id: string; // Lowercase key (e.g., "openai") - name: string; // Provider name (e.g., "OpenAI") - description: string; // Description of the provider - models: string[]; // List of supported models - requiredKeys: string[]; // List of required keys -} diff --git a/ui/desktop/src/components/settings_v2/providers/state/utils.tsx b/ui/desktop/src/components/settings_v2/providers/state/utils.tsx deleted file mode 100644 index 764e3e10..00000000 --- a/ui/desktop/src/components/settings_v2/providers/state/utils.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { Provider, ProviderResponse } from './types'; -import { getApiUrl, getSecretKey } from '../../../config'; -import { special_provider_cases } from '../providers/utils'; - -export function isSecretKey(keyName: string): boolean { - // Endpoints and hosts should not be stored as secrets - const nonSecretKeys = [ - 'DATABRICKS_HOST', - 'OLLAMA_HOST', - 'AZURE_OPENAI_ENDPOINT', - 'AZURE_OPENAI_DEPLOYMENT_NAME', - ]; - return !nonSecretKeys.includes(keyName); -} - -export async function getActiveProviders(): Promise { - try { - // Fetch the secrets settings - const configSettings = await getConfigSettings(); - console.log('[getActiveProviders]:', configSettings); - - // Check for special provider cases (e.g. ollama running locally) - const specialCasesResults = await Promise.all( - Object.entries(special_provider_cases).map(async ([providerName, checkFunction]) => { - const isActive = await checkFunction(); // Dynamically re-check status - console.log(`Special case result for ${providerName}:`, isActive); - return isActive ? providerName : null; - }) - ); - // Extract active providers based on `is_set` in `secret_status` or providers with no keys - const activeProviders = Object.values(configSettings) // Convert object to array - .filter((provider) => { - const apiKeyStatus = Object.values(provider.config_status || {}); // Get all key statuses - - // Include providers if all required keys are set - return apiKeyStatus.length > 0 && apiKeyStatus.every((key) => key.is_set); - }) - .map((provider) => provider.name || 'Unknown Provider'); // Extract provider name - - // Combine active providers from secrets settings and special cases (avoiding repeats) - const allActiveProviders = activeProviders.concat( - specialCasesResults.filter( - (provider) => provider !== null && !activeProviders.includes(provider) - ) - ); - - return allActiveProviders; - } catch (error) { - console.error('Failed to get active providers:', error); - return []; - } -} - -export async function getConfigSettings(): Promise> { - const providerList = await getProvidersList(); - // Extract the list of IDs - const providerIds = providerList.map((provider) => provider.id); - - // Fetch configs state (set/unset) using the provider IDs - const response = await fetch(getApiUrl('/configs/providers'), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Secret-Key': getSecretKey(), - }, - body: JSON.stringify({ - providers: providerIds, - }), - }); - - if (!response.ok) { - throw new Error('Failed to fetch secrets'); - } - - const data = (await response.json()) as Record; - return data; -} - -export async function getProvidersList(): Promise { - const response = await fetch(getApiUrl('/agent/providers'), { - method: 'GET', - }); - - if (!response.ok) { - throw new Error(`Failed to fetch providers: ${response.statusText}`); - } - - const data = await response.json(); - - // Format the response into an array of providers - return data.map((item: any) => ({ - id: item.id, // Root-level ID - name: item.details?.name || 'Unknown Provider', // Nested name in details - description: item.details?.description || 'No description available.', // Nested description - models: item.details?.models || [], // Nested models array - requiredKeys: item.details?.required_keys || [], // Nested required keys array - })); -} diff --git a/ui/desktop/src/components/settings_v2/providers/subcomponents/CardBody.tsx b/ui/desktop/src/components/settings_v2/providers/subcomponents/CardBody.tsx index 38437e8e..b5f2c1ed 100644 --- a/ui/desktop/src/components/settings_v2/providers/subcomponents/CardBody.tsx +++ b/ui/desktop/src/components/settings_v2/providers/subcomponents/CardBody.tsx @@ -3,13 +3,9 @@ import CardActions from './CardActions'; import ConfigurationAction from '../interfaces/ConfigurationAction'; interface CardBodyProps { - actions: ConfigurationAction[]; + children: React.ReactNode; } -export default function CardBody({ actions }: CardBodyProps) { - return ( -
- -
- ); +export default function CardBody({ children }: CardBodyProps) { + return
{children}
; } diff --git a/ui/desktop/src/components/settings_v2/providers/subcomponents/CardHeader.tsx b/ui/desktop/src/components/settings_v2/providers/subcomponents/CardHeader.tsx index 852eeaa5..5bb39073 100644 --- a/ui/desktop/src/components/settings_v2/providers/subcomponents/CardHeader.tsx +++ b/ui/desktop/src/components/settings_v2/providers/subcomponents/CardHeader.tsx @@ -25,7 +25,6 @@ interface ProviderNameAndStatusProps { function ProviderNameAndStatus({ name, isConfigured }: ProviderNameAndStatusProps) { console.log(`Provider Name: ${name}, Is Configured: ${isConfigured}`); - const ollamaNotConfigured = !isConfigured && name === 'Ollama'; return (
@@ -33,9 +32,6 @@ function ProviderNameAndStatus({ name, isConfigured }: ProviderNameAndStatusProp {/* Configured state: Green check */} {isConfigured && } - - {/* Not Configured + Ollama => Exclamation */} - {ollamaNotConfigured && }
); } diff --git a/ui/desktop/src/components/settings_v2/providers/subcomponents/ProviderCard.tsx b/ui/desktop/src/components/settings_v2/providers/subcomponents/ProviderCard.tsx index e89ebca8..585ad606 100644 --- a/ui/desktop/src/components/settings_v2/providers/subcomponents/ProviderCard.tsx +++ b/ui/desktop/src/components/settings_v2/providers/subcomponents/ProviderCard.tsx @@ -3,49 +3,84 @@ import CardContainer from './CardContainer'; import CardHeader from './CardHeader'; import ProviderState from '../interfaces/ProviderState'; import CardBody from './CardBody'; -import ButtonCallbacks from '../interfaces/ButtonCallbacks'; import { PROVIDER_REGISTRY } from '../ProviderRegistry'; +import DefaultCardButtons from './buttons/DefaultCardButtons'; -interface ProviderCardProps { +type ProviderCardProps = { provider: ProviderState; - buttonCallbacks: ButtonCallbacks; + onConfigure: () => void; + onLaunch: () => void; isOnboarding: boolean; -} +}; -export function ProviderCard({ provider, buttonCallbacks, isOnboarding }: ProviderCardProps) { +// export function ProviderCard({ provider, buttonCallbacks, isOnboarding }: ProviderCardProps) { +// const providerEntry = PROVIDER_REGISTRY.find((p) => p.name === provider.name); +// +// // Add safety check +// if (!providerEntry) { +// console.error(`Provider ${provider.name} not found in registry`); +// return null; +// } +// +// const providerDetails = providerEntry.details; +// // Add another safety check +// if (!providerDetails) { +// console.error(`Provider ${provider.name} has no details`); +// return null; +// } +// console.log('provider details', providerDetails); +// +// try { +// const actions = providerDetails.getActions(provider, buttonCallbacks, isOnboarding); +// +// return ( +// +// } +// body={} +// /> +// ); +// } catch (error) { +// console.error(`Error rendering provider card for ${provider.name}:`, error); +// return null; +// } +// } + +export function ProviderCard({ provider, onConfigure, onLaunch, isOnboarding }: ProviderCardProps) { const providerEntry = PROVIDER_REGISTRY.find((p) => p.name === provider.name); // Add safety check - if (!providerEntry) { - console.error(`Provider ${provider.name} not found in registry`); + if (!providerEntry?.details) { + console.error(`Provider ${provider.name} not found in registry or has no details`); return null; } const providerDetails = providerEntry.details; - // Add another safety check - if (!providerDetails) { - console.error(`Provider ${provider.name} has no details`); - return null; - } - console.log('provider details', providerDetails); - try { - const actions = providerDetails.getActions(provider, buttonCallbacks, isOnboarding); - - return ( - + } + body={ + + - } - body={} - /> - ); - } catch (error) { - console.error(`Error rendering provider card for ${provider.name}:`, error); - return null; - } + + } + /> + ); } diff --git a/ui/desktop/src/components/settings_v2/providers/subcomponents/buttons/DefaultCardButtons.tsx b/ui/desktop/src/components/settings_v2/providers/subcomponents/buttons/DefaultCardButtons.tsx index 2f5ace8d..eb9d5869 100644 --- a/ui/desktop/src/components/settings_v2/providers/subcomponents/buttons/DefaultCardButtons.tsx +++ b/ui/desktop/src/components/settings_v2/providers/subcomponents/buttons/DefaultCardButtons.tsx @@ -1,13 +1,13 @@ import React from 'react'; import { ConfigureSettingsButton, RocketButton } from './CardButtons'; -import ButtonCallbacks from '../../interfaces/ButtonCallbacks'; import ProviderState from '@/src/components/settings_v2/providers/interfaces/ProviderState'; // can define other optional callbacks as needed interface CardButtonsProps { provider: ProviderState; - isOnboardingPage?: boolean; - callbacks: ButtonCallbacks; // things like onConfigure, onDelete + isOnboardingPage: boolean; + onConfigure: (provider: ProviderState) => void; + onLaunch: (provider: ProviderState) => void; } function getDefaultTooltipMessages(name: string, actionType: string) { @@ -23,23 +23,11 @@ function getDefaultTooltipMessages(name: string, actionType: string) { } } -/// This defines a group of buttons that will appear on the card -/// Controlled by if a provider is configured and which version of the grid page we're on (onboarding vs settings page) -/// This is the default button group -/// -/// Settings page: -/// - show configure button -/// Onboarding page: -/// - show configure button if NOT configured -/// - show rocket launch button if configured -/// -/// We inject what will happen if we click on a button via on -/// - onConfigure: pop open a modal -- modal is configured dynamically -/// - onLaunch: continue to chat window export default function DefaultCardButtons({ provider, isOnboardingPage, - callbacks, + onLaunch, + onConfigure, }: CardButtonsProps) { return ( <> @@ -49,7 +37,7 @@ export default function DefaultCardButtons({ tooltip={getDefaultTooltipMessages(provider.name, 'add')} onClick={(e) => { e.stopPropagation(); - callbacks.onConfigure(provider); + onConfigure(provider); }} /> )} @@ -59,7 +47,7 @@ export default function DefaultCardButtons({ tooltip={getDefaultTooltipMessages(provider.name, 'edit')} onClick={(e) => { e.stopPropagation(); - callbacks.onConfigure(provider); + onConfigure(provider); }} /> )} @@ -68,7 +56,7 @@ export default function DefaultCardButtons({ { e.stopPropagation(); - callbacks.onLaunch(provider); + onLaunch(provider); }} /> )} diff --git a/ui/desktop/src/components/ui/CustomRadio.tsx b/ui/desktop/src/components/ui/CustomRadio.tsx new file mode 100644 index 00000000..c45d58f2 --- /dev/null +++ b/ui/desktop/src/components/ui/CustomRadio.tsx @@ -0,0 +1,72 @@ +import React from 'react'; + +/** + * CustomRadio - A reusable radio button component with dark mode support + * @param {Object} props - Component props + * @param {string} props.id - Unique identifier for the radio input + * @param {string} props.name - Name attribute for the radio input + * @param {string} props.value - Value of the radio input + * @param {boolean} props.checked - Whether the radio is checked + * @param {function} props.onChange - Function to call when radio selection changes + * @param {boolean} [props.disabled] - Whether the radio is disabled + * @param {React.ReactNode} [props.label] - Primary label content + * @param {React.ReactNode} [props.secondaryLabel] - Secondary/subtitle label content + * @param {React.ReactNode} [props.rightContent] - Optional content to display on the right side + * @param {string} [props.className] - Additional CSS classes for the main container + * @returns {JSX.Element} + */ +const CustomRadio = ({ + id, + name, + value, + checked, + onChange, + disabled = false, + label = null, + secondaryLabel = null, + rightContent = null, + className = '', +}) => { + return ( + + ); +}; + +export default CustomRadio;