From f098fed862d2a72726ad0610c171d3a9e77237cd Mon Sep 17 00:00:00 2001
From: Lily Delalande <119957291+lily-de@users.noreply.github.com>
Date: Tue, 25 Mar 2025 21:06:23 -0400
Subject: [PATCH] ui: models dropdown (#1860)
---
ui/desktop/src/agent/UpdateAgent.tsx | 8 +-
ui/desktop/src/agent/index.ts | 21 ++
ui/desktop/src/components/BottomMenu.tsx | 121 +++++------
.../components/settings_v2/SettingsView.tsx | 81 +-------
.../settings_v2/models/AddModelButton.tsx | 21 --
.../settings_v2/models/AddModelModal.tsx | 122 -----------
.../settings_v2/models/ModelsSection.tsx | 61 ++++++
.../components/settings_v2/models/index.ts | 160 +++++++++++++++
.../models/subcomponents/AddModelButton.tsx | 28 +++
.../models/subcomponents/AddModelModal.tsx | 192 ++++++++++++++++++
.../subcomponents/ModelSettingsButtons.tsx | 26 +++
.../models/subcomponents/ModelsBottomBar.tsx | 72 +++++++
ui/desktop/src/extensions.tsx | 2 +-
13 files changed, 631 insertions(+), 284 deletions(-)
create mode 100644 ui/desktop/src/agent/index.ts
delete mode 100644 ui/desktop/src/components/settings_v2/models/AddModelButton.tsx
delete mode 100644 ui/desktop/src/components/settings_v2/models/AddModelModal.tsx
create mode 100644 ui/desktop/src/components/settings_v2/models/ModelsSection.tsx
create mode 100644 ui/desktop/src/components/settings_v2/models/index.ts
create mode 100644 ui/desktop/src/components/settings_v2/models/subcomponents/AddModelButton.tsx
create mode 100644 ui/desktop/src/components/settings_v2/models/subcomponents/AddModelModal.tsx
create mode 100644 ui/desktop/src/components/settings_v2/models/subcomponents/ModelSettingsButtons.tsx
create mode 100644 ui/desktop/src/components/settings_v2/models/subcomponents/ModelsBottomBar.tsx
diff --git a/ui/desktop/src/agent/UpdateAgent.tsx b/ui/desktop/src/agent/UpdateAgent.tsx
index 09ad1ce1..d44d7413 100644
--- a/ui/desktop/src/agent/UpdateAgent.tsx
+++ b/ui/desktop/src/agent/UpdateAgent.tsx
@@ -33,7 +33,7 @@ export const useAgent = () => {
console.error('Failed to initialize agent:', error);
ToastError({
title: 'Failed to initialize agent',
- errorMessage: error instanceof Error ? error.message : 'Unknown error',
+ traceback: error instanceof Error ? error.message : 'Unknown error',
});
return false;
}
@@ -131,7 +131,7 @@ export const useAgent = () => {
ToastError({
title: extension.name,
msg: 'Failed to add extension',
- errorMessage: errorMsg,
+ traceback: errorMsg,
});
}
return response;
@@ -167,7 +167,7 @@ export const useAgent = () => {
ToastError({
title: extension.name,
msg: 'Failed to add extension',
- errorMessage: data.message,
+ traceback: data.message,
});
return response;
@@ -178,7 +178,7 @@ export const useAgent = () => {
ToastError({
title: extension.name,
msg: 'Failed to add extension',
- errorMessage: error.message,
+ traceback: error.message,
});
throw error;
}
diff --git a/ui/desktop/src/agent/index.ts b/ui/desktop/src/agent/index.ts
new file mode 100644
index 00000000..0e80b957
--- /dev/null
+++ b/ui/desktop/src/agent/index.ts
@@ -0,0 +1,21 @@
+import { getApiUrl, getSecretKey } from '../config';
+
+interface initializeAgentProps {
+ model: string;
+ provider: string;
+}
+
+export async function initializeAgent({ model, provider }: initializeAgentProps) {
+ const response = await fetch(getApiUrl('/agent'), {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Secret-Key': getSecretKey(),
+ },
+ body: JSON.stringify({
+ provider: provider.toLowerCase().replace(/ /g, '_'),
+ model: model,
+ }),
+ });
+ return response;
+}
diff --git a/ui/desktop/src/components/BottomMenu.tsx b/ui/desktop/src/components/BottomMenu.tsx
index 57e3ee58..a9e3134b 100644
--- a/ui/desktop/src/components/BottomMenu.tsx
+++ b/ui/desktop/src/components/BottomMenu.tsx
@@ -6,6 +6,7 @@ import { ModelRadioList } from './settings/models/ModelRadioList';
import { Document, ChevronUp, ChevronDown } from './icons';
import type { View } from '../App';
import { BottomMenuModeSelection } from './BottomMenuModeSelection';
+import ModelsBottomBar from './settings_v2/models/subcomponents/ModelsBottomBar';
export default function BottomMenu({
hasMessages,
@@ -76,71 +77,77 @@ export default function BottomMenu({
{/* Goose Mode Selector Dropdown */}
- {/* Model Selector Dropdown - Only in development */}
-
-
setIsModelMenuOpen(!isModelMenuOpen)}
- >
- {(currentModel?.alias ?? currentModel?.name) || 'Select Model'}
- {isModelMenuOpen ? (
-
- ) : (
-
- )}
-
+ {/* Model Selector Dropdown */}
+ {process.env.ALPHA ? (
+
+ ) : (
+
+
setIsModelMenuOpen(!isModelMenuOpen)}
+ >
+ {(currentModel?.alias ?? currentModel?.name) || 'Select Model'}
+ {isModelMenuOpen ? (
+
+ ) : (
+
+ )}
+
- {/* Dropdown Menu */}
- {isModelMenuOpen && (
-
-
-
(
-
- )}
-
+ )}
+
+ )}
);
}
diff --git a/ui/desktop/src/components/settings_v2/SettingsView.tsx b/ui/desktop/src/components/settings_v2/SettingsView.tsx
index 07a017c1..c0362712 100644
--- a/ui/desktop/src/components/settings_v2/SettingsView.tsx
+++ b/ui/desktop/src/components/settings_v2/SettingsView.tsx
@@ -3,33 +3,8 @@ import { ScrollArea } from '../ui/scroll-area';
import BackButton from '../ui/BackButton';
import type { View } from '../../App';
import { useConfig } from '../ConfigContext';
-import { Button } from '../ui/button';
-import { Plus, Sliders } from 'lucide-react';
import ExtensionsSection from './extensions/ExtensionsSection';
-import { AddModelButton } from './models/AddModelButton';
-
-interface ModelOption {
- id: string;
- name: string;
- description: string;
- selected: boolean;
-}
-
-// Mock data - replace with actual data source
-const defaultModelOptions: ModelOption[] = [
- {
- id: 'gpt-4',
- name: 'GPT-4',
- description: 'Most capable model, best for complex tasks',
- selected: true,
- },
- {
- id: 'gpt-3.5',
- name: 'GPT-3.5',
- description: 'Fast and efficient for most tasks',
- selected: false,
- },
-];
+import ModelsSection from './models/ModelsSection';
export type SettingsViewOptions = {
extensionId?: string;
@@ -45,21 +20,10 @@ export default function SettingsView({
setView: (view: View) => void;
viewOptions: SettingsViewOptions;
}) {
- const [modelOptions, setModelOptions] = React.useState(defaultModelOptions);
-
const { config } = useConfig();
console.log(config);
- const handleModelSelect = (selectedId: string) => {
- setModelOptions(
- modelOptions.map((model) => ({
- ...model,
- selected: model.id === selectedId,
- }))
- );
- };
-
return (
@@ -74,48 +38,7 @@ export default function SettingsView({
{/* Models Section */}
-
-
-
Models
-
-
-
- {modelOptions.map((model, index) => (
-
-
-
-
{model.name}
-
{model.description}
-
-
handleModelSelect(model.id)}
- className="h-4 w-4 text-white accent-[#393838] bg-[#393838] border-[#393838] checked:bg-[#393838] focus:ring-0 focus:ring-offset-0"
- />
-
- {index < modelOptions.length - 1 && (
-
- )}
-
- ))}
-
-
-
-
-
-
-
-
+
{/* Extensions Section */}
diff --git a/ui/desktop/src/components/settings_v2/models/AddModelButton.tsx b/ui/desktop/src/components/settings_v2/models/AddModelButton.tsx
deleted file mode 100644
index cfd5e899..00000000
--- a/ui/desktop/src/components/settings_v2/models/AddModelButton.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import React, { useState } from 'react';
-import { Plus } from 'lucide-react';
-import { Button } from '../../ui/button';
-import { AddModelModal } from './AddModelModal';
-
-export const AddModelButton = () => {
- const [isAddModelModalOpen, setIsAddModelModalOpen] = useState(false);
-
- return (
- <>
-
- {isAddModelModalOpen ?
setIsAddModelModalOpen(false)} /> : null}
- >
- );
-};
diff --git a/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx b/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx
deleted file mode 100644
index f717f123..00000000
--- a/ui/desktop/src/components/settings_v2/models/AddModelModal.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React, { useEffect, useState } from 'react';
-import { ExternalLink, Plus } from 'lucide-react';
-
-import Modal from '../../Modal';
-import { Button } from '../../ui/button';
-import { QUICKSTART_GUIDE_URL } from '../providers/modal/constants';
-import { Input } from '../../ui/input';
-import { Select } from '../../ui/Select';
-import { useConfig } from '../../ConfigContext';
-import { ToastError, ToastSuccess } from '../../settings/models/toasts';
-import { initializeSystem } from '../../../../src/utils/providerUtils';
-
-const ModalButtons = ({ onSubmit, onCancel }) => (
-
-
-
-
-);
-
-type AddModelModalProps = { onClose: () => void };
-export const AddModelModal = ({ onClose }: AddModelModalProps) => {
- const { getProviders, upsert } = useConfig();
- const [providerOptions, setProviderOptions] = useState([]);
- const [provider, setProvider] = useState(null);
- const [modelName, setModelName] = useState('');
-
- const changeModel = async () => {
- try {
- await upsert('GOOSE_PROVIDER', provider, false);
- await upsert('GOOSE_MODEL', modelName, false);
- await initializeSystem(provider, modelName);
- ToastSuccess({
- title: 'Model changed',
- msg: `Switched to ${modelName}.`,
- });
- onClose();
- } catch (e) {
- ToastError({
- title: 'Failed to add model',
- traceback: e.message,
- });
- }
- };
-
- useEffect(() => {
- (async () => {
- try {
- const providersResponse = await getProviders(false);
- const activeProviders = providersResponse.filter((provider) => provider.is_configured);
- setProviderOptions(
- activeProviders.map(({ metadata, name }) => ({
- value: name,
- label: metadata.display_name,
- }))
- );
- } catch (error) {
- console.error('Failed to load providers:', error);
- }
- })();
- }, [getProviders]);
-
- return (
-
-
}>
-
-
-
-
Add model
-
- Configure your AI model providers by adding their API keys. your Keys are stored
- securely and encrypted locally.
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx b/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx
new file mode 100644
index 00000000..0e988e76
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/models/ModelsSection.tsx
@@ -0,0 +1,61 @@
+import React, { useEffect, useState } from 'react';
+import type { View } from '../../../App';
+import ModelSettingsButtons from './subcomponents/ModelSettingsButtons';
+import { useConfig } from '../../ConfigContext';
+import { ToastError } from '../../settings/models/toasts';
+
+interface ModelsSectionProps {
+ setView: (view: View) => void;
+}
+
+const UNKNOWN_PROVIDER_TITLE = 'Provider name error';
+const UNKNOWN_PROVIDER_MSG = 'Unknown provider in config -- please inspect your config.yaml';
+
+// todo: use for block settings
+export default function ModelsSection({ setView }: ModelsSectionProps) {
+ const [provider, setProvider] = useState(null);
+ const [model, setModel] = useState('');
+ const { read, getProviders } = useConfig();
+
+ useEffect(() => {
+ const currentModel = async () => {
+ 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);
+ return;
+ }
+ const providerDisplayName = providerDetailsList[0].metadata.display_name;
+ setModel(gooseModel);
+ setProvider(providerDisplayName);
+ };
+ (async () => {
+ await currentModel();
+ })();
+ }, [getProviders, read]);
+
+ return (
+
+
+
Models
+
+
+
+
{model}
+ {provider}
+
+
+
+
+ );
+}
diff --git a/ui/desktop/src/components/settings_v2/models/index.ts b/ui/desktop/src/components/settings_v2/models/index.ts
new file mode 100644
index 00000000..950a6205
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/models/index.ts
@@ -0,0 +1,160 @@
+import { initializeAgent } from '../../../agent/index';
+import { ToastError, ToastSuccess } from '../../settings/models/toasts';
+import { ProviderDetails } from '@/src/api';
+
+// titles
+const CHANGE_MODEL_TOAST_TITLE = 'Model selected';
+const START_AGENT_TITLE = 'Initialize agent';
+const UNKNOWN_PROVIDER_TITLE = 'Provider name lookup';
+
+// errors
+const SWITCH_MODEL_AGENT_ERROR_MSG = 'Failed to start agent with selected model';
+const CONFIG_UPDATE_ERROR_MSG = 'Failed to update configuration settings';
+const CONFIG_READ_MODEL_ERROR_MSG = 'Failed to read GOOSE_MODEL or GOOSE_PROVIDER from config';
+const UNKNOWN_PROVIDER_MSG = 'Unknown provider in config -- please inspect your config.yaml';
+
+// success
+const SWITCH_MODEL_SUCCESS_MSG = 'Successfully switched models';
+const INITIALIZE_SYSTEM_WITH_MODEL_SUCCESS_MSG = 'Successfully started Goose';
+
+interface changeModelProps {
+ model: string;
+ provider: string;
+ writeToConfig: (key: string, value: unknown, is_secret: boolean) => Promise;
+}
+
+// TODO: error handling
+export async function changeModel({ model, provider, writeToConfig }: changeModelProps) {
+ try {
+ await initializeAgent({ model: model, provider: provider });
+ } catch (error) {
+ console.error(`Failed to change model at agent step -- ${model} ${provider}`);
+ // show toast with error
+ ToastError({
+ title: CHANGE_MODEL_TOAST_TITLE,
+ msg: SWITCH_MODEL_AGENT_ERROR_MSG,
+ traceback: error,
+ });
+ // don't write to config
+ return;
+ }
+
+ try {
+ await writeToConfig('GOOSE_PROVIDER', provider, false);
+ await writeToConfig('GOOSE_MODEL', model, false);
+ } catch (error) {
+ console.error(`Failed to change model at config step -- ${model} ${provider}`);
+ // show toast with error
+ ToastError({
+ title: CHANGE_MODEL_TOAST_TITLE,
+ msg: CONFIG_UPDATE_ERROR_MSG,
+ traceback: error,
+ });
+ // agent and config will be out of sync at this point
+ // TODO: reset agent to use current config settings
+ } finally {
+ // show toast
+ ToastSuccess({
+ title: CHANGE_MODEL_TOAST_TITLE,
+ msg: `${SWITCH_MODEL_SUCCESS_MSG} -- using ${model} from ${provider}`,
+ });
+ }
+}
+
+interface startAgentFromConfigProps {
+ readFromConfig: (key: string, is_secret: boolean) => Promise;
+}
+
+// starts agent with the values for GOOSE_PROVIDER and GOOSE_MODEL that are in the config
+export async function startAgentFromConfig({ readFromConfig }: startAgentFromConfigProps) {
+ let modelProvider: { model: string; provider: string };
+
+ // read from config
+ try {
+ modelProvider = await getCurrentModelAndProvider({ readFromConfig: readFromConfig });
+ } catch (error) {
+ // show toast with error
+ ToastError({
+ title: START_AGENT_TITLE,
+ msg: CONFIG_READ_MODEL_ERROR_MSG,
+ traceback: error,
+ });
+ return;
+ }
+
+ const model = modelProvider.model;
+ const provider = modelProvider.provider;
+
+ console.log(`Starting agent with GOOSE_MODEL=${model} and GOOSE_PROVIDER=${provider}`);
+
+ try {
+ await initializeAgent({ model: model, provider: provider });
+ } catch (error) {
+ console.error(`Failed to change model at agent step -- ${model} ${provider}`);
+ // show toast with error
+ ToastError({
+ title: CHANGE_MODEL_TOAST_TITLE,
+ msg: SWITCH_MODEL_AGENT_ERROR_MSG,
+ traceback: error,
+ });
+ return;
+ } finally {
+ // success toast
+ ToastSuccess({
+ title: CHANGE_MODEL_TOAST_TITLE,
+ msg: `${INITIALIZE_SYSTEM_WITH_MODEL_SUCCESS_MSG} with ${model} from ${provider}`,
+ });
+ }
+}
+
+interface getCurrentModelAndProviderProps {
+ readFromConfig: (key: string, is_secret: boolean) => Promise;
+}
+
+export async function getCurrentModelAndProvider({
+ readFromConfig,
+}: getCurrentModelAndProviderProps) {
+ let model: string;
+ let provider: string;
+
+ // read from config
+ try {
+ model = (await readFromConfig('GOOSE_MODEL', false)) as string;
+ provider = (await readFromConfig('GOOSE_PROVIDER', false)) as string;
+ } catch (error) {
+ console.error(`Failed to read GOOSE_MODEL or GOOSE_PROVIDER from config`);
+ throw error;
+ }
+ return { model: model, provider: provider };
+}
+
+interface getCurrentModelAndProviderForDisplayProps {
+ readFromConfig: (key: string, is_secret: boolean) => Promise;
+ getProviders: (b: boolean) => Promise;
+}
+
+// returns display name of the provider
+export async function getCurrentModelAndProviderForDisplay({
+ readFromConfig,
+ getProviders,
+}: getCurrentModelAndProviderForDisplayProps) {
+ const modelProvider = await getCurrentModelAndProvider({ readFromConfig: readFromConfig });
+ const gooseModel = modelProvider.model;
+ const gooseProvider = modelProvider.provider;
+
+ const providers = await getProviders(false);
+
+ // lookup display name
+ const providerDetailsList = providers.filter((provider) => provider.name === gooseProvider);
+
+ if (providerDetailsList.length != 1) {
+ ToastError({
+ title: UNKNOWN_PROVIDER_TITLE,
+ msg: UNKNOWN_PROVIDER_MSG,
+ });
+ return { model: gooseModel, provider: gooseProvider };
+ }
+ const providerDisplayName = providerDetailsList[0].metadata.display_name;
+
+ return { model: gooseModel, provider: providerDisplayName };
+}
diff --git a/ui/desktop/src/components/settings_v2/models/subcomponents/AddModelButton.tsx b/ui/desktop/src/components/settings_v2/models/subcomponents/AddModelButton.tsx
new file mode 100644
index 00000000..149daf0e
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/models/subcomponents/AddModelButton.tsx
@@ -0,0 +1,28 @@
+import React, { useState } from 'react';
+import { Button } from '../../../ui/button';
+import { AddModelModal } from './AddModelModal';
+import { Gear } from '../../../icons';
+import type { View } from '../../../../App';
+
+interface AddModelButtonProps {
+ setView: (view: View) => void;
+}
+
+export const AddModelButton = ({ setView }: AddModelButtonProps) => {
+ const [isAddModelModalOpen, setIsAddModelModalOpen] = useState(false);
+
+ return (
+ <>
+
+ {isAddModelModalOpen ? (
+ setIsAddModelModalOpen(false)} />
+ ) : null}
+ >
+ );
+};
diff --git a/ui/desktop/src/components/settings_v2/models/subcomponents/AddModelModal.tsx b/ui/desktop/src/components/settings_v2/models/subcomponents/AddModelModal.tsx
new file mode 100644
index 00000000..b26cadf3
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/models/subcomponents/AddModelModal.tsx
@@ -0,0 +1,192 @@
+import React, { useEffect, useState } from 'react';
+import { ExternalLink, Plus } from 'lucide-react';
+
+import Modal from '../../../Modal';
+import { Button } from '../../../ui/button';
+import { QUICKSTART_GUIDE_URL } from '../../providers/modal/constants';
+import { Input } from '../../../ui/input';
+import { Select } from '../../../ui/Select';
+import { useConfig } from '../../../ConfigContext';
+import { changeModel as switchModel } from '../index';
+import type { View } from '../../../../App';
+
+const ModalButtons = ({ onSubmit, onCancel }) => (
+
+
+
+
+);
+
+type AddModelModalProps = {
+ onClose: () => void;
+ setView: (view: View) => void;
+};
+export const AddModelModal = ({ onClose, setView }: AddModelModalProps) => {
+ const { getProviders, upsert } = useConfig();
+ const [providerOptions, setProviderOptions] = useState([]);
+ const [modelOptions, setModelOptions] = useState([]);
+ const [provider, setProvider] = useState(null);
+ const [model, setModel] = useState('');
+ const [isCustomModel, setIsCustomModel] = useState(false);
+
+ const changeModel = async () => {
+ await switchModel({ model: model, provider: provider, writeToConfig: upsert });
+ };
+
+ useEffect(() => {
+ (async () => {
+ try {
+ const providersResponse = await getProviders(false);
+ const activeProviders = providersResponse.filter((provider) => provider.is_configured);
+ // Create provider options and add "Use other provider" option
+ setProviderOptions([
+ ...activeProviders.map(({ metadata, name }) => ({
+ value: name,
+ label: metadata.display_name,
+ })),
+ {
+ value: 'configure_providers',
+ label: 'Use other provider',
+ },
+ ]);
+
+ // Format model options by provider
+ const formattedModelOptions = [];
+ activeProviders.forEach(({ metadata, name }) => {
+ if (metadata.known_models && metadata.known_models.length > 0) {
+ formattedModelOptions.push({
+ label: metadata.display_name,
+ options: metadata.known_models.map((modelName) => ({
+ value: modelName,
+ label: modelName,
+ provider: name,
+ })),
+ });
+ }
+ });
+
+ // Add the "Custom model" option to each provider group
+ formattedModelOptions.forEach((group) => {
+ group.options.push({
+ value: 'custom',
+ label: 'Use custom model',
+ provider: group.options[0]?.provider,
+ });
+ });
+
+ setModelOptions(formattedModelOptions);
+ } catch (error) {
+ console.error('Failed to load providers:', error);
+ }
+ })();
+ }, [getProviders]);
+
+ // Filter model options based on selected provider
+ const filteredModelOptions = provider
+ ? modelOptions.filter((group) => group.options[0]?.provider === provider)
+ : [];
+
+ // Handle model selection change
+ const handleModelChange = (selectedOption) => {
+ if (selectedOption?.value === 'custom') {
+ setIsCustomModel(true);
+ setModel('');
+ } else {
+ setIsCustomModel(false);
+ setModel(selectedOption?.value || '');
+ }
+ };
+
+ return (
+
+
}>
+
+
+
+
Add model
+
+ Configure your AI model providers by adding their API keys. Your keys are stored
+ securely and encrypted locally.
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/ui/desktop/src/components/settings_v2/models/subcomponents/ModelSettingsButtons.tsx b/ui/desktop/src/components/settings_v2/models/subcomponents/ModelSettingsButtons.tsx
new file mode 100644
index 00000000..ab3ae3da
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/models/subcomponents/ModelSettingsButtons.tsx
@@ -0,0 +1,26 @@
+import { AddModelButton } from './AddModelButton';
+import { Button } from '../../../ui/button';
+import { Sliders } from 'lucide-react';
+import React from 'react';
+import type { View } from '../../../../App';
+
+interface ConfigureModelButtonsProps {
+ setView: (view: View) => void;
+}
+
+export default function ModelSettingsButtons({ setView }: ConfigureModelButtonsProps) {
+ return (
+
+
+
+
+ );
+}
diff --git a/ui/desktop/src/components/settings_v2/models/subcomponents/ModelsBottomBar.tsx b/ui/desktop/src/components/settings_v2/models/subcomponents/ModelsBottomBar.tsx
new file mode 100644
index 00000000..59aa7b3a
--- /dev/null
+++ b/ui/desktop/src/components/settings_v2/models/subcomponents/ModelsBottomBar.tsx
@@ -0,0 +1,72 @@
+import { ChevronDown, ChevronUp } from '../../../icons';
+import { Sliders } from 'lucide-react';
+import React, { useEffect, useState } from 'react';
+import { useConfig } from '../../../ConfigContext';
+import { getCurrentModelAndProviderForDisplay } from '../index';
+import { AddModelModal } from './AddModelModal';
+import type { View } from '../../../../App';
+
+interface ModelsBottomBarProps {
+ dropdownRef: any;
+ setView: (view: View) => void;
+}
+export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBarProps) {
+ const { read, getProviders } = useConfig();
+ const [isModelMenuOpen, setIsModelMenuOpen] = useState(false);
+ const [provider, setProvider] = useState(null);
+ const [model, setModel] = useState('');
+ const [isAddModelModalOpen, setIsAddModelModalOpen] = useState(false);
+
+ useEffect(() => {
+ (async () => {
+ const modelProvider = await getCurrentModelAndProviderForDisplay({
+ readFromConfig: read,
+ getProviders,
+ });
+ setProvider(modelProvider.provider);
+ setModel(modelProvider.model);
+ })();
+ }, [read, getProviders]);
+
+ return (
+
+
setIsModelMenuOpen(!isModelMenuOpen)}
+ >
+ {model}
+ {isModelMenuOpen ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Dropdown Menu */}
+ {isModelMenuOpen && (
+
+
+
Current:
+
+ {model} -- {provider}
+
+
{
+ setIsModelMenuOpen(false);
+ setIsAddModelModalOpen(true);
+ }}
+ >
+ Change Model
+
+
+
+
+ )}
+ {isAddModelModalOpen ? (
+
setIsAddModelModalOpen(false)} />
+ ) : null}
+
+ );
+}
diff --git a/ui/desktop/src/extensions.tsx b/ui/desktop/src/extensions.tsx
index 0c607ca4..611b1abe 100644
--- a/ui/desktop/src/extensions.tsx
+++ b/ui/desktop/src/extensions.tsx
@@ -260,7 +260,7 @@ function handleError(message: string, shouldThrow = false): void {
ToastError({
title: 'Failed to install extension',
msg: message,
- errorMessage: message,
+ traceback: message,
toastOptions: { autoClose: false },
});
console.error(message);