mirror of
https://github.com/aljazceru/goose.git
synced 2026-01-05 23:44:28 +01:00
Settings v2 Add Model (#1708)
This commit is contained in:
@@ -53,11 +53,13 @@ export default function Modal({
|
|||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
ref={modalRef}
|
ref={modalRef}
|
||||||
className="relative w-[500px] max-w-full bg-bgApp rounded-xl shadow-none my-10 overflow-hidden max-h-[90vh] flex flex-col"
|
className="relative w-[500px] max-w-full bg-bgApp rounded-xl my-10 max-h-[90vh] flex flex-col"
|
||||||
>
|
>
|
||||||
<div className="p-6 overflow-y-auto max-h-[calc(90vh-180px)]">{children}</div>
|
<div className="p-8 max-h-[calc(90vh-180px)]">{children}</div>
|
||||||
{footer && (
|
{footer && (
|
||||||
<div className="border-t border-borderSubtle bg-bgApp w-full mt-auto">{footer}</div>
|
<div className="border-t border-borderSubtle bg-bgApp w-full rounded-b-xl overflow-hidden">
|
||||||
|
{footer}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useConfig } from '../ConfigContext';
|
|||||||
import { Button } from '../ui/button';
|
import { Button } from '../ui/button';
|
||||||
import { Plus, Sliders } from 'lucide-react';
|
import { Plus, Sliders } from 'lucide-react';
|
||||||
import ExtensionsSection from './extensions/ExtensionsSection';
|
import ExtensionsSection from './extensions/ExtensionsSection';
|
||||||
|
import { AddModelButton } from './models/AddModelButton';
|
||||||
|
|
||||||
interface ModelOption {
|
interface ModelOption {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -101,10 +102,7 @@ export default function SettingsView({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4 pt-4 w-full">
|
<div className="flex gap-4 pt-4 w-full">
|
||||||
<Button className="flex items-center gap-2 flex-1 justify-center text-textSubtle bg-black dark:bg-white hover:bg-subtle">
|
<AddModelButton />
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
Add Model
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-2 flex-1 justify-center text-textSubtle bg-white dark:bg-black hover:bg-subtle dark:border dark:border-gray-500 dark:hover:border-gray-400"
|
className="flex items-center gap-2 flex-1 justify-center text-textSubtle bg-white dark:bg-black hover:bg-subtle dark:border dark:border-gray-500 dark:hover:border-gray-400"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
116
ui/desktop/src/components/settings_v2/models/AddModelModal.tsx
Normal file
116
ui/desktop/src/components/settings_v2/models/AddModelModal.tsx
Normal file
@@ -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}
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
};
|
||||||
20
ui/desktop/src/components/ui/Select.tsx
Normal file
20
ui/desktop/src/components/ui/Select.tsx
Normal file
@@ -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',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user