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. -
- -
- -
- setModelName(event.target.value)} - value={modelName} - /> -
-
-
-
- ); -}; 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. +
+ +
+ +
+ + ) : ( +
+
+ + +
+ setModel(event.target.value)} + value={model} + /> +
+ )} + + )} +
+
+
+
+ ); +}; 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);