feat(settings): add preferences
This commit is contained in:
parent
5779701b7d
commit
a1e0d368c6
1 changed files with 449 additions and 316 deletions
|
@ -1,5 +1,5 @@
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Dialog, Transition } from '@headlessui/react';
|
import { Dialog, Switch, Transition } from '@headlessui/react';
|
||||||
import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react';
|
import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react';
|
||||||
import React, {
|
import React, {
|
||||||
Fragment,
|
Fragment,
|
||||||
|
@ -8,6 +8,7 @@ import React, {
|
||||||
type SelectHTMLAttributes,
|
type SelectHTMLAttributes,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import ThemeSwitcher from './theme/Switcher';
|
import ThemeSwitcher from './theme/Switcher';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
@ -58,6 +59,9 @@ interface SettingsType {
|
||||||
groqApiKey: string;
|
groqApiKey: string;
|
||||||
anthropicApiKey: string;
|
anthropicApiKey: string;
|
||||||
ollamaApiUrl: string;
|
ollamaApiUrl: string;
|
||||||
|
isCopilotEnabled: boolean;
|
||||||
|
isDiscoverEnabled: boolean;
|
||||||
|
isLibraryEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SettingsDialog = ({
|
const SettingsDialog = ({
|
||||||
|
@ -84,78 +88,91 @@ const SettingsDialog = ({
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isUpdating, setIsUpdating] = useState(false);
|
const [isUpdating, setIsUpdating] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
const [password, setPassword] = useState('');
|
||||||
if (isOpen) {
|
const [passwordSubmitted, setPasswordSubmitted] = useState(false);
|
||||||
const fetchConfig = async () => {
|
const [isPasswordValid, setIsPasswordValid] = useState(true);
|
||||||
setIsLoading(true);
|
|
||||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = (await res.json()) as SettingsType;
|
const handlePasswordSubmit = async () => {
|
||||||
setConfig(data);
|
setIsLoading(true);
|
||||||
|
setPasswordSubmitted(true);
|
||||||
|
|
||||||
const chatModelProvidersKeys = Object.keys(
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||||
data.chatModelProviders || {},
|
headers: {
|
||||||
);
|
'Content-Type': 'application/json',
|
||||||
const embeddingModelProvidersKeys = Object.keys(
|
Authorization: `Bearer ${password}`,
|
||||||
data.embeddingModelProviders || {},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
const defaultChatModelProvider =
|
if (res.status === 401) {
|
||||||
chatModelProvidersKeys.length > 0 ? chatModelProvidersKeys[0] : '';
|
setIsPasswordValid(false);
|
||||||
const defaultEmbeddingModelProvider =
|
setPasswordSubmitted(false);
|
||||||
embeddingModelProvidersKeys.length > 0
|
setIsLoading(false);
|
||||||
? embeddingModelProvidersKeys[0]
|
return;
|
||||||
: '';
|
} else {
|
||||||
|
setIsPasswordValid(true);
|
||||||
const chatModelProvider =
|
|
||||||
localStorage.getItem('chatModelProvider') ||
|
|
||||||
defaultChatModelProvider ||
|
|
||||||
'';
|
|
||||||
const chatModel =
|
|
||||||
localStorage.getItem('chatModel') ||
|
|
||||||
(data.chatModelProviders &&
|
|
||||||
data.chatModelProviders[chatModelProvider]?.[0]) ||
|
|
||||||
'';
|
|
||||||
const embeddingModelProvider =
|
|
||||||
localStorage.getItem('embeddingModelProvider') ||
|
|
||||||
defaultEmbeddingModelProvider ||
|
|
||||||
'';
|
|
||||||
const embeddingModel =
|
|
||||||
localStorage.getItem('embeddingModel') ||
|
|
||||||
(data.embeddingModelProviders &&
|
|
||||||
data.embeddingModelProviders[embeddingModelProvider]?.[0]) ||
|
|
||||||
'';
|
|
||||||
|
|
||||||
setSelectedChatModelProvider(chatModelProvider);
|
|
||||||
setSelectedChatModel(chatModel);
|
|
||||||
setSelectedEmbeddingModelProvider(embeddingModelProvider);
|
|
||||||
setSelectedEmbeddingModel(embeddingModel);
|
|
||||||
setCustomOpenAIApiKey(localStorage.getItem('openAIApiKey') || '');
|
|
||||||
setCustomOpenAIBaseURL(localStorage.getItem('openAIBaseURL') || '');
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchConfig();
|
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [isOpen]);
|
const data = (await res.json()) as SettingsType;
|
||||||
|
setConfig(data);
|
||||||
|
|
||||||
|
const chatModelProvidersKeys = Object.keys(data.chatModelProviders || {});
|
||||||
|
const embeddingModelProvidersKeys = Object.keys(
|
||||||
|
data.embeddingModelProviders || {},
|
||||||
|
);
|
||||||
|
|
||||||
|
const defaultChatModelProvider =
|
||||||
|
chatModelProvidersKeys.length > 0 ? chatModelProvidersKeys[0] : '';
|
||||||
|
const defaultEmbeddingModelProvider =
|
||||||
|
embeddingModelProvidersKeys.length > 0
|
||||||
|
? embeddingModelProvidersKeys[0]
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const chatModelProvider =
|
||||||
|
localStorage.getItem('chatModelProvider') ||
|
||||||
|
defaultChatModelProvider ||
|
||||||
|
'';
|
||||||
|
const chatModel =
|
||||||
|
localStorage.getItem('chatModel') ||
|
||||||
|
(data.chatModelProviders &&
|
||||||
|
data.chatModelProviders[chatModelProvider]?.[0]) ||
|
||||||
|
'';
|
||||||
|
const embeddingModelProvider =
|
||||||
|
localStorage.getItem('embeddingModelProvider') ||
|
||||||
|
defaultEmbeddingModelProvider ||
|
||||||
|
'';
|
||||||
|
const embeddingModel =
|
||||||
|
localStorage.getItem('embeddingModel') ||
|
||||||
|
(data.embeddingModelProviders &&
|
||||||
|
data.embeddingModelProviders[embeddingModelProvider]?.[0]) ||
|
||||||
|
'';
|
||||||
|
|
||||||
|
setSelectedChatModelProvider(chatModelProvider);
|
||||||
|
setSelectedChatModel(chatModel);
|
||||||
|
setSelectedEmbeddingModelProvider(embeddingModelProvider);
|
||||||
|
setSelectedEmbeddingModel(embeddingModel);
|
||||||
|
setCustomOpenAIApiKey(localStorage.getItem('openAIApiKey') || '');
|
||||||
|
setCustomOpenAIBaseURL(localStorage.getItem('openAIBaseURL') || '');
|
||||||
|
setIsLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${password}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify(config),
|
body: JSON.stringify(config),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (res.status === 401) {
|
||||||
|
toast.error('Unauthorized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
localStorage.setItem('chatModelProvider', selectedChatModelProvider!);
|
localStorage.setItem('chatModelProvider', selectedChatModelProvider!);
|
||||||
localStorage.setItem('chatModel', selectedChatModel!);
|
localStorage.setItem('chatModel', selectedChatModel!);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
@ -205,284 +222,400 @@ const SettingsDialog = ({
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
|
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
|
||||||
<Dialog.Title className="text-xl font-medium leading-6 dark:text-white">
|
{isPasswordValid && passwordSubmitted && (
|
||||||
Settings
|
<>
|
||||||
</Dialog.Title>
|
<Dialog.Title className="text-xl font-medium leading-6 dark:text-white">
|
||||||
{config && !isLoading && (
|
Settings
|
||||||
<div className="flex flex-col space-y-4 mt-6">
|
</Dialog.Title>
|
||||||
<div className="flex flex-col space-y-1">
|
{config && !isLoading && (
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
<div className="flex flex-col space-y-4 mt-6">
|
||||||
Theme
|
|
||||||
</p>
|
|
||||||
<ThemeSwitcher />
|
|
||||||
</div>
|
|
||||||
{config.chatModelProviders && (
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Chat model Provider
|
|
||||||
</p>
|
|
||||||
<Select
|
|
||||||
value={selectedChatModelProvider ?? undefined}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSelectedChatModelProvider(e.target.value);
|
|
||||||
if (e.target.value === 'custom_openai') {
|
|
||||||
setSelectedChatModel('');
|
|
||||||
} else {
|
|
||||||
setSelectedChatModel(
|
|
||||||
config.chatModelProviders[e.target.value][0],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
options={Object.keys(config.chatModelProviders).map(
|
|
||||||
(provider) => ({
|
|
||||||
value: provider,
|
|
||||||
label:
|
|
||||||
provider.charAt(0).toUpperCase() +
|
|
||||||
provider.slice(1),
|
|
||||||
}),
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{selectedChatModelProvider &&
|
|
||||||
selectedChatModelProvider != 'custom_openai' && (
|
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="flex flex-col space-y-1">
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
Chat Model
|
Theme
|
||||||
</p>
|
</p>
|
||||||
<Select
|
<ThemeSwitcher />
|
||||||
value={selectedChatModel ?? undefined}
|
</div>
|
||||||
onChange={(e) =>
|
<div className="flex flex-col items-start space-y-2">
|
||||||
setSelectedChatModel(e.target.value)
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
}
|
Copilot enabled
|
||||||
options={(() => {
|
</p>
|
||||||
const chatModelProvider =
|
<Switch
|
||||||
config.chatModelProviders[
|
checked={config.isCopilotEnabled}
|
||||||
selectedChatModelProvider
|
onChange={(checked) => {
|
||||||
];
|
setConfig({
|
||||||
|
...config,
|
||||||
|
isCopilotEnabled: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="bg-light-secondary dark:bg-dark-secondary border border-light-200/70 dark:border-dark-200 relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full active:scale-95 duration-200 transition cursor-pointer"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Copilot</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
config.isCopilotEnabled
|
||||||
|
? 'translate-x-6 bg-[#24A0ED]'
|
||||||
|
: 'translate-x-1 bg-black/50 dark:bg-white/50',
|
||||||
|
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-start space-y-2">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Discover enabled
|
||||||
|
</p>
|
||||||
|
<Switch
|
||||||
|
checked={config.isDiscoverEnabled}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
isDiscoverEnabled: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="bg-light-secondary dark:bg-dark-secondary border border-light-200/70 dark:border-dark-200 relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full active:scale-95 duration-200 transition cursor-pointer"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Discover</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
config.isDiscoverEnabled
|
||||||
|
? 'translate-x-6 bg-[#24A0ED]'
|
||||||
|
: 'translate-x-1 bg-black/50 dark:bg-white/50',
|
||||||
|
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-start space-y-2">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Library enabled
|
||||||
|
</p>
|
||||||
|
<Switch
|
||||||
|
checked={config.isLibraryEnabled}
|
||||||
|
onChange={(checked) => {
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
isLibraryEnabled: checked,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="bg-light-secondary dark:bg-dark-secondary border border-light-200/70 dark:border-dark-200 relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full active:scale-95 duration-200 transition cursor-pointer"
|
||||||
|
>
|
||||||
|
<span className="sr-only">Library</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
config.isLibraryEnabled
|
||||||
|
? 'translate-x-6 bg-[#24A0ED]'
|
||||||
|
: 'translate-x-1 bg-black/50 dark:bg-white/50',
|
||||||
|
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
{config.chatModelProviders && (
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Chat model Provider
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
value={selectedChatModelProvider ?? undefined}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedChatModelProvider(e.target.value);
|
||||||
|
if (e.target.value === 'custom_openai') {
|
||||||
|
setSelectedChatModel('');
|
||||||
|
} else {
|
||||||
|
setSelectedChatModel(
|
||||||
|
config.chatModelProviders[
|
||||||
|
e.target.value
|
||||||
|
][0],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={Object.keys(
|
||||||
|
config.chatModelProviders,
|
||||||
|
).map((provider) => ({
|
||||||
|
value: provider,
|
||||||
|
label:
|
||||||
|
provider.charAt(0).toUpperCase() +
|
||||||
|
provider.slice(1),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedChatModelProvider &&
|
||||||
|
selectedChatModelProvider != 'custom_openai' && (
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Chat Model
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
value={selectedChatModel ?? undefined}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSelectedChatModel(e.target.value)
|
||||||
|
}
|
||||||
|
options={(() => {
|
||||||
|
const chatModelProvider =
|
||||||
|
config.chatModelProviders[
|
||||||
|
selectedChatModelProvider
|
||||||
|
];
|
||||||
|
|
||||||
return chatModelProvider
|
return chatModelProvider
|
||||||
? chatModelProvider.length > 0
|
? chatModelProvider.length > 0
|
||||||
? chatModelProvider.map((model) => ({
|
? chatModelProvider.map((model) => ({
|
||||||
value: model,
|
value: model,
|
||||||
label: model,
|
label: model,
|
||||||
}))
|
}))
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
label: 'No models available',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
value: '',
|
||||||
|
label:
|
||||||
|
'Invalid provider, please check backend logs',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
})()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedChatModelProvider &&
|
||||||
|
selectedChatModelProvider === 'custom_openai' && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Model name
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Model name"
|
||||||
|
defaultValue={selectedChatModel!}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSelectedChatModel(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Custom OpenAI API Key
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Custom OpenAI API Key"
|
||||||
|
defaultValue={customOpenAIApiKey!}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCustomOpenAIApiKey(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Custom OpenAI Base URL
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Custom OpenAI Base URL"
|
||||||
|
defaultValue={customOpenAIBaseURL!}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCustomOpenAIBaseURL(e.target.value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{/* Embedding models */}
|
||||||
|
{config.embeddingModelProviders && (
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Embedding model Provider
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
selectedEmbeddingModelProvider ?? undefined
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedEmbeddingModelProvider(
|
||||||
|
e.target.value,
|
||||||
|
);
|
||||||
|
setSelectedEmbeddingModel(
|
||||||
|
config.embeddingModelProviders[
|
||||||
|
e.target.value
|
||||||
|
][0],
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={Object.keys(
|
||||||
|
config.embeddingModelProviders,
|
||||||
|
).map((provider) => ({
|
||||||
|
label:
|
||||||
|
provider.charAt(0).toUpperCase() +
|
||||||
|
provider.slice(1),
|
||||||
|
value: provider,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedEmbeddingModelProvider && (
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Embedding Model
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
value={selectedEmbeddingModel ?? undefined}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSelectedEmbeddingModel(e.target.value)
|
||||||
|
}
|
||||||
|
options={(() => {
|
||||||
|
const embeddingModelProvider =
|
||||||
|
config.embeddingModelProviders[
|
||||||
|
selectedEmbeddingModelProvider
|
||||||
|
];
|
||||||
|
|
||||||
|
return embeddingModelProvider
|
||||||
|
? embeddingModelProvider.length > 0
|
||||||
|
? embeddingModelProvider.map((model) => ({
|
||||||
|
label: model,
|
||||||
|
value: model,
|
||||||
|
}))
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
label:
|
||||||
|
'No embedding models available',
|
||||||
|
value: '',
|
||||||
|
disabled: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
|
label:
|
||||||
|
'Invalid provider, please check backend logs',
|
||||||
value: '',
|
value: '',
|
||||||
label: 'No models available',
|
|
||||||
disabled: true,
|
disabled: true,
|
||||||
},
|
},
|
||||||
]
|
];
|
||||||
: [
|
})()}
|
||||||
{
|
/>
|
||||||
value: '',
|
</div>
|
||||||
label:
|
)}
|
||||||
'Invalid provider, please check backend logs',
|
<div className="flex flex-col space-y-1">
|
||||||
disabled: true,
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
},
|
OpenAI API Key
|
||||||
];
|
</p>
|
||||||
})()}
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="OpenAI API Key"
|
||||||
|
defaultValue={config.openaiApiKey}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
openaiApiKey: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Ollama API URL
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Ollama API URL"
|
||||||
|
defaultValue={config.ollamaApiUrl}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
ollamaApiUrl: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
GROQ API Key
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="GROQ API Key"
|
||||||
|
defaultValue={config.groqApiKey}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
groqApiKey: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||||
|
Anthropic API Key
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Anthropic API key"
|
||||||
|
defaultValue={config.anthropicApiKey}
|
||||||
|
onChange={(e) =>
|
||||||
|
setConfig({
|
||||||
|
...config,
|
||||||
|
anthropicApiKey: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{selectedChatModelProvider &&
|
|
||||||
selectedChatModelProvider === 'custom_openai' && (
|
|
||||||
<>
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Model name
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Model name"
|
|
||||||
defaultValue={selectedChatModel!}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedChatModel(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Custom OpenAI API Key
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Custom OpenAI API Key"
|
|
||||||
defaultValue={customOpenAIApiKey!}
|
|
||||||
onChange={(e) =>
|
|
||||||
setCustomOpenAIApiKey(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Custom OpenAI Base URL
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Custom OpenAI Base URL"
|
|
||||||
defaultValue={customOpenAIBaseURL!}
|
|
||||||
onChange={(e) =>
|
|
||||||
setCustomOpenAIBaseURL(e.target.value)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{/* Embedding models */}
|
|
||||||
{config.embeddingModelProviders && (
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Embedding model Provider
|
|
||||||
</p>
|
|
||||||
<Select
|
|
||||||
value={selectedEmbeddingModelProvider ?? undefined}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSelectedEmbeddingModelProvider(e.target.value);
|
|
||||||
setSelectedEmbeddingModel(
|
|
||||||
config.embeddingModelProviders[e.target.value][0],
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
options={Object.keys(
|
|
||||||
config.embeddingModelProviders,
|
|
||||||
).map((provider) => ({
|
|
||||||
label:
|
|
||||||
provider.charAt(0).toUpperCase() +
|
|
||||||
provider.slice(1),
|
|
||||||
value: provider,
|
|
||||||
}))}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedEmbeddingModelProvider && (
|
{isLoading && (
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="w-full flex items-center justify-center mt-6 text-black/70 dark:text-white/70 py-6">
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
<RefreshCcw className="animate-spin" />
|
||||||
Embedding Model
|
|
||||||
</p>
|
|
||||||
<Select
|
|
||||||
value={selectedEmbeddingModel ?? undefined}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSelectedEmbeddingModel(e.target.value)
|
|
||||||
}
|
|
||||||
options={(() => {
|
|
||||||
const embeddingModelProvider =
|
|
||||||
config.embeddingModelProviders[
|
|
||||||
selectedEmbeddingModelProvider
|
|
||||||
];
|
|
||||||
|
|
||||||
return embeddingModelProvider
|
|
||||||
? embeddingModelProvider.length > 0
|
|
||||||
? embeddingModelProvider.map((model) => ({
|
|
||||||
label: model,
|
|
||||||
value: model,
|
|
||||||
}))
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
label: 'No embedding models available',
|
|
||||||
value: '',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
label:
|
|
||||||
'Invalid provider, please check backend logs',
|
|
||||||
value: '',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
})()}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col space-y-1">
|
<div className="w-full mt-6 space-y-2">
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
<p className="text-xs text-black/50 dark:text-white/50">
|
||||||
OpenAI API Key
|
We'll refresh the page after updating the settings.
|
||||||
</p>
|
</p>
|
||||||
<Input
|
<button
|
||||||
type="text"
|
onClick={handleSubmit}
|
||||||
placeholder="OpenAI API Key"
|
className="bg-[#24A0ED] flex flex-row items-center space-x-2 text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full px-4 py-2"
|
||||||
defaultValue={config.openaiApiKey}
|
disabled={isLoading || isUpdating}
|
||||||
onChange={(e) =>
|
>
|
||||||
setConfig({
|
{isUpdating ? (
|
||||||
...config,
|
<RefreshCw size={20} className="animate-spin" />
|
||||||
openaiApiKey: e.target.value,
|
) : (
|
||||||
})
|
<CloudUpload size={20} />
|
||||||
}
|
)}
|
||||||
/>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-1">
|
</>
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Ollama API URL
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Ollama API URL"
|
|
||||||
defaultValue={config.ollamaApiUrl}
|
|
||||||
onChange={(e) =>
|
|
||||||
setConfig({
|
|
||||||
...config,
|
|
||||||
ollamaApiUrl: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
GROQ API Key
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="GROQ API Key"
|
|
||||||
defaultValue={config.groqApiKey}
|
|
||||||
onChange={(e) =>
|
|
||||||
setConfig({
|
|
||||||
...config,
|
|
||||||
groqApiKey: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-1">
|
|
||||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
|
||||||
Anthropic API Key
|
|
||||||
</p>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
placeholder="Anthropic API key"
|
|
||||||
defaultValue={config.anthropicApiKey}
|
|
||||||
onChange={(e) =>
|
|
||||||
setConfig({
|
|
||||||
...config,
|
|
||||||
anthropicApiKey: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{isLoading && (
|
{!passwordSubmitted && (
|
||||||
<div className="w-full flex items-center justify-center mt-6 text-black/70 dark:text-white/70 py-6">
|
<>
|
||||||
<RefreshCcw className="animate-spin" />
|
<Dialog.Title className="text-sm dark:font-white/80 font-black/80">
|
||||||
</div>
|
Enter the password to access the settings
|
||||||
)}
|
</Dialog.Title>
|
||||||
<div className="w-full mt-6 space-y-2">
|
<div className="flex flex-col">
|
||||||
<p className="text-xs text-black/50 dark:text-white/50">
|
<Input
|
||||||
We'll refresh the page after updating the settings.
|
type="password"
|
||||||
</p>
|
placeholder="Password"
|
||||||
<button
|
className="mt-4"
|
||||||
onClick={handleSubmit}
|
disabled={isLoading}
|
||||||
className="bg-[#24A0ED] flex flex-row items-center space-x-2 text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full px-4 py-2"
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
disabled={isLoading || isUpdating}
|
/>
|
||||||
>
|
</div>
|
||||||
{isUpdating ? (
|
{!isPasswordValid && (
|
||||||
<RefreshCw size={20} className="animate-spin" />
|
<p className="text-xs text-red-500 mt-2">
|
||||||
) : (
|
Password is incorrect
|
||||||
<CloudUpload size={20} />
|
</p>
|
||||||
)}
|
)}
|
||||||
</button>
|
<button
|
||||||
</div>
|
onClick={handlePasswordSubmit}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="bg-[#24A0ED] flex flex-row items-center text-xs mt-4 text-white disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#ececec21] rounded-full px-4 py-2"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue