diff --git a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx index 3eda7950dd41..5d47c08b4104 100644 --- a/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx +++ b/ui/desktop/src/components/settings_v2/extensions/ExtensionsSection.tsx @@ -86,8 +86,12 @@ export default function ExtensionsSection() { extensionConfig: extensionConfig, addToConfig: addExtension, }); - handleModalClose(); + + // First refresh the extensions list await fetchExtensions(); + + // Then close the modal after data is refreshed + handleModalClose(); }; const handleDeleteExtension = async (name: string) => { diff --git a/ui/desktop/src/components/settings_v2/extensions/extension-manager.ts b/ui/desktop/src/components/settings_v2/extensions/extension-manager.ts index f025ab24f568..6378a69262ea 100644 --- a/ui/desktop/src/components/settings_v2/extensions/extension-manager.ts +++ b/ui/desktop/src/components/settings_v2/extensions/extension-manager.ts @@ -1,5 +1,5 @@ import type { ExtensionConfig } from '../../../api/types.gen'; -import { ToastServiceOptions } from '../../../toasts'; +import { toastService, ToastServiceOptions } from '../../../toasts'; import { addToAgent, removeFromAgent } from './agent-api'; interface ActivateExtensionProps { @@ -147,6 +147,11 @@ export async function updateExtension({ console.error('[updateExtension]: Failed to update disabled extension in config:', error); throw error; } + // show a toast that it was successfully updated + toastService.success({ + title: `Update extension`, + msg: `Successfully updated ${extensionConfig.name} extension`, + }); } } diff --git a/ui/desktop/src/components/settings_v2/extensions/modal/EnvVarsSection.tsx b/ui/desktop/src/components/settings_v2/extensions/modal/EnvVarsSection.tsx index ecb565ed23fa..45380405b812 100644 --- a/ui/desktop/src/components/settings_v2/extensions/modal/EnvVarsSection.tsx +++ b/ui/desktop/src/components/settings_v2/extensions/modal/EnvVarsSection.tsx @@ -17,6 +17,7 @@ export default function EnvVarsSection({ onAdd, onRemove, onChange, + submitAttempted, }: EnvVarsSectionProps) { const [newKey, setNewKey] = React.useState(''); const [newValue, setNewValue] = React.useState(''); @@ -51,12 +52,22 @@ export default function EnvVarsSection({ setInvalidFields({ key: false, value: false }); }; + const isFieldInvalid = (index: number, field: 'key' | 'value') => { + if (!submitAttempted) return false; + const value = envVars[index][field].trim(); + return value === ''; + }; + return (
+

+ Add key-value pairs for environment variables. Click the "+" button to add after filling + both fields. +

{/* Existing environment variables */} @@ -67,8 +78,10 @@ export default function EnvVarsSection({ value={envVar.key} onChange={(e) => onChange(index, 'key', e.target.value)} placeholder="Variable name" - className={`w-full border-borderSubtle text-textStandard`} - disabled + className={cn( + 'w-full border-borderSubtle text-textStandard', + isFieldInvalid(index, 'key') && 'border-red-500 focus:border-red-500' + )} />
@@ -76,8 +89,10 @@ export default function EnvVarsSection({ value={envVar.value} onChange={(e) => onChange(index, 'value', e.target.value)} placeholder="Value" - className={`w-full border-borderSubtle text-textStandard`} - disabled + className={cn( + 'w-full border-borderSubtle text-textStandard', + isFieldInvalid(index, 'value') && 'border-red-500 focus:border-red-500' + )} />
{validationError &&
{validationError}
} diff --git a/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx b/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx index 7aa9f1a0af78..94b1a6ee5529 100644 --- a/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx +++ b/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import type { View } from '../../../App'; import ModelSettingsButtons from './subcomponents/ModelSettingsButtons'; import { useConfig } from '../../ConfigContext'; @@ -15,59 +15,46 @@ export default function ModelsSection({ setView }: ModelsSectionProps) { const [model, setModel] = useState(''); const { read, getProviders } = useConfig(); - // Use a ref to prevent multiple loads - const isLoadingRef = useRef(false); - const isLoadedRef = useRef(false); + // Function to load model data + const loadModelData = async () => { + try { + const gooseModel = (await read('GOOSE_MODEL', false)) as string; + const gooseProvider = (await read('GOOSE_PROVIDER', false)) as string; + const providers = await getProviders(true); - useEffect(() => { - // Prevent the effect from running again if it's already loading or loaded - if (isLoadingRef.current || isLoadedRef.current) return; - - // Mark as loading - isLoadingRef.current = true; - - const loadModelData = async () => { - try { - const gooseModel = (await read('GOOSE_MODEL', false)) as string; - const gooseProvider = (await read('GOOSE_PROVIDER', false)) as string; - const providers = await getProviders(true); - - // lookup display name - const providerDetailsList = providers.filter((provider) => provider.name === gooseProvider); - - if (providerDetailsList.length != 1) { - toastError({ - title: UNKNOWN_PROVIDER_TITLE, - msg: UNKNOWN_PROVIDER_MSG, - }); - setModel(gooseModel); - setProvider(gooseProvider); - } else { - const providerDisplayName = providerDetailsList[0].metadata.display_name; - setModel(gooseModel); - setProvider(providerDisplayName); - } + // lookup display name + const providerDetailsList = providers.filter((provider) => provider.name === gooseProvider); - // Mark as loaded and not loading - isLoadedRef.current = true; - isLoadingRef.current = false; - } catch (error) { - console.error('Error loading model data:', error); - isLoadingRef.current = false; + if (providerDetailsList.length != 1) { + toastError({ + title: UNKNOWN_PROVIDER_TITLE, + msg: UNKNOWN_PROVIDER_MSG, + }); + setModel(gooseModel); + setProvider(gooseProvider); + } else { + const providerDisplayName = providerDetailsList[0].metadata.display_name; + setModel(gooseModel); + setProvider(providerDisplayName); } - }; + } catch (error) { + console.error('Error loading model data:', error); + } + }; + useEffect(() => { + // Initial load loadModelData(); - // Clean up function + // Set up polling interval to check for changes + const interval = setInterval(() => { + loadModelData(); + }, 1000); // Check every second + + // Clean up interval on unmount return () => { - isLoadingRef.current = false; - isLoadedRef.current = false; + clearInterval(interval); }; - - // Run this effect only once when the component mounts - // We're using refs to control the actual execution, so we don't need dependencies - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (