-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Settings v2 Add Model #1708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Settings v2 Add Model #1708
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| 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 ( | ||
| <> | ||
| <Button | ||
| className="flex items-center gap-2 flex-1 justify-center text-white dark:text-textSubtle bg-black dark:bg-white hover:bg-subtle" | ||
| onClick={() => setIsAddModelModalOpen(true)} | ||
| > | ||
| <Plus className="h-4 w-4" /> | ||
| Add Model | ||
| </Button> | ||
| {isAddModelModalOpen ? <AddModelModal onClose={() => setIsAddModelModalOpen(false)} /> : null} | ||
| </> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| 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 { ToastFailureGeneral, ToastSuccessModelSwitch } from '../../settings/models/toasts'; | ||
| import { initializeSystem } from '../../../../src/utils/providerUtils'; | ||
|
|
||
| const ModalButtons = ({ onSubmit, onCancel }) => ( | ||
| <div> | ||
| <Button | ||
| type="submit" | ||
| variant="ghost" | ||
| onClick={onSubmit} | ||
| className="w-full h-[60px] rounded-none border-borderSubtle text-base hover:bg-bgSubtle text-textProminent font-regular" | ||
| > | ||
| Add model | ||
| </Button> | ||
| <Button | ||
| type="button" | ||
| variant="ghost" | ||
| onClick={onCancel} | ||
| className="w-full h-[60px] rounded-none border-t border-borderSubtle hover:text-textStandard text-textSubtle hover:bg-bgSubtle text-base font-regular" | ||
| > | ||
| Cancel | ||
| </Button> | ||
| </div> | ||
| ); | ||
|
|
||
| type AddModelModalProps = { onClose: () => void }; | ||
| export const AddModelModal = ({ onClose }: AddModelModalProps) => { | ||
| const { getProviders, upsert } = useConfig(); | ||
| const [providerOptions, setProviderOptions] = useState([]); | ||
| const [provider, setProvider] = useState<string | null>(null); | ||
| const [modelName, setModelName] = useState<string>(''); | ||
|
|
||
| const changeModel = async () => { | ||
| try { | ||
| await upsert('GOOSE_PROVIDER', provider, false); | ||
| await upsert('GOOSE_MODEL', modelName, false); | ||
| await initializeSystem(provider, modelName); | ||
| ToastSuccessModelSwitch({ provider, name: modelName }); | ||
| onClose(); | ||
| } catch (e) { | ||
| ToastFailureGeneral(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 ( | ||
| <div className="z-10"> | ||
| <Modal onClose={onClose} footer={<ModalButtons onSubmit={changeModel} onCancel={onClose} />}> | ||
| <div className="flex flex-col items-center gap-8"> | ||
| <div className="flex flex-col items-center gap-3"> | ||
| <Plus size={24} className="text-textStandard" /> | ||
| <div className="text-textStandard font-medium">Add model</div> | ||
| <div className="text-textSubtle text-center"> | ||
| Configure your AI model providers by adding their API keys. your Keys are stored | ||
| securely and encrypted locally. | ||
| </div> | ||
| <div> | ||
| <a | ||
| href={QUICKSTART_GUIDE_URL} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="flex items-center justify-center text-textStandard font-medium text-sm" | ||
| > | ||
| <ExternalLink size={16} className="mr-1" /> | ||
| View quick start guide | ||
| </a> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div className="w-full flex flex-col gap-4"> | ||
| <Select | ||
| options={providerOptions} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. idea: maybe last option in dropdown list is
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a super good idea, I'm all for it. |
||
| value={providerOptions.find((option) => option.value === provider) || null} | ||
| onChange={(option) => { | ||
| setProvider(option?.value || null); | ||
| setModelName(''); | ||
| }} | ||
| placeholder="Provider" | ||
| isClearable | ||
| /> | ||
| <Input | ||
| className="border-2 px-4 py-5" | ||
| placeholder="GPT" | ||
| onChange={(event) => setModelName(event.target.value)} | ||
| value={modelName} | ||
| /> | ||
| </div> | ||
| </div> | ||
| </Modal> | ||
| </div> | ||
| ); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import React from 'react'; | ||
| import ReactSelect from 'react-select'; | ||
|
|
||
| export const Select = (props) => { | ||
| return ( | ||
| <ReactSelect | ||
| {...props} | ||
| unstyled | ||
| classNames={{ | ||
| container: () => 'w-full cursor-pointer', | ||
| indicatorSeparator: () => 'h-0', | ||
| control: ({ isFocused }) => | ||
| `border-2 ${isFocused ? 'border-borderStandard' : 'border-borderSubtle'} focus:border-borderStandard hover:border-borderStandard rounded-md w-full px-4 py-2.5 text-sm text-textSubtle`, | ||
| menu: () => 'mt-3 bg-bgStandard shadow-xl rounded-md text-textSubtle overflow-hidden', | ||
| option: () => | ||
| 'py-4 px-4 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 text-textProminent font-medium', | ||
| }} | ||
| /> | ||
| ); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noticed that if the list of configured providers is quite long, we the list gets cut off
Screen.Recording.2025-03-16.at.10.49.06.AM.mov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! I made some changes to Modal to allow for overflow. I checked some of the other modals in Settings V2 and they still look good to me.