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 (