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 { Dialog, Transition } from '@headlessui/react';
|
||||
import { Dialog, Switch, Transition } from '@headlessui/react';
|
||||
import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react';
|
||||
import React, {
|
||||
Fragment,
|
||||
|
@ -8,6 +8,7 @@ import React, {
|
|||
type SelectHTMLAttributes,
|
||||
} from 'react';
|
||||
import ThemeSwitcher from './theme/Switcher';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
|
@ -58,6 +59,9 @@ interface SettingsType {
|
|||
groqApiKey: string;
|
||||
anthropicApiKey: string;
|
||||
ollamaApiUrl: string;
|
||||
isCopilotEnabled: boolean;
|
||||
isDiscoverEnabled: boolean;
|
||||
isLibraryEnabled: boolean;
|
||||
}
|
||||
|
||||
const SettingsDialog = ({
|
||||
|
@ -84,22 +88,34 @@ const SettingsDialog = ({
|
|||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
const fetchConfig = async () => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [passwordSubmitted, setPasswordSubmitted] = useState(false);
|
||||
const [isPasswordValid, setIsPasswordValid] = useState(true);
|
||||
|
||||
const handlePasswordSubmit = async () => {
|
||||
setIsLoading(true);
|
||||
setPasswordSubmitted(true);
|
||||
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${password}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
setIsPasswordValid(false);
|
||||
setPasswordSubmitted(false);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
} else {
|
||||
setIsPasswordValid(true);
|
||||
}
|
||||
|
||||
const data = (await res.json()) as SettingsType;
|
||||
setConfig(data);
|
||||
|
||||
const chatModelProvidersKeys = Object.keys(
|
||||
data.chatModelProviders || {},
|
||||
);
|
||||
const chatModelProvidersKeys = Object.keys(data.chatModelProviders || {});
|
||||
const embeddingModelProvidersKeys = Object.keys(
|
||||
data.embeddingModelProviders || {},
|
||||
);
|
||||
|
@ -139,23 +155,24 @@ const SettingsDialog = ({
|
|||
setIsLoading(false);
|
||||
};
|
||||
|
||||
fetchConfig();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isOpen]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
setIsUpdating(true);
|
||||
|
||||
try {
|
||||
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${password}`,
|
||||
},
|
||||
body: JSON.stringify(config),
|
||||
});
|
||||
|
||||
if (res.status === 401) {
|
||||
toast.error('Unauthorized');
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem('chatModelProvider', selectedChatModelProvider!);
|
||||
localStorage.setItem('chatModel', selectedChatModel!);
|
||||
localStorage.setItem(
|
||||
|
@ -205,6 +222,8 @@ const SettingsDialog = ({
|
|||
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">
|
||||
{isPasswordValid && passwordSubmitted && (
|
||||
<>
|
||||
<Dialog.Title className="text-xl font-medium leading-6 dark:text-white">
|
||||
Settings
|
||||
</Dialog.Title>
|
||||
|
@ -216,6 +235,81 @@ const SettingsDialog = ({
|
|||
</p>
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
<div className="flex flex-col items-start space-y-2">
|
||||
<p className="text-black/70 dark:text-white/70 text-sm">
|
||||
Copilot enabled
|
||||
</p>
|
||||
<Switch
|
||||
checked={config.isCopilotEnabled}
|
||||
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">
|
||||
|
@ -229,18 +323,20 @@ const SettingsDialog = ({
|
|||
setSelectedChatModel('');
|
||||
} else {
|
||||
setSelectedChatModel(
|
||||
config.chatModelProviders[e.target.value][0],
|
||||
config.chatModelProviders[
|
||||
e.target.value
|
||||
][0],
|
||||
);
|
||||
}
|
||||
}}
|
||||
options={Object.keys(config.chatModelProviders).map(
|
||||
(provider) => ({
|
||||
options={Object.keys(
|
||||
config.chatModelProviders,
|
||||
).map((provider) => ({
|
||||
value: provider,
|
||||
label:
|
||||
provider.charAt(0).toUpperCase() +
|
||||
provider.slice(1),
|
||||
}),
|
||||
)}
|
||||
}))}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@ -337,11 +433,17 @@ const SettingsDialog = ({
|
|||
Embedding model Provider
|
||||
</p>
|
||||
<Select
|
||||
value={selectedEmbeddingModelProvider ?? undefined}
|
||||
value={
|
||||
selectedEmbeddingModelProvider ?? undefined
|
||||
}
|
||||
onChange={(e) => {
|
||||
setSelectedEmbeddingModelProvider(e.target.value);
|
||||
setSelectedEmbeddingModelProvider(
|
||||
e.target.value,
|
||||
);
|
||||
setSelectedEmbeddingModel(
|
||||
config.embeddingModelProviders[e.target.value][0],
|
||||
config.embeddingModelProviders[
|
||||
e.target.value
|
||||
][0],
|
||||
);
|
||||
}}
|
||||
options={Object.keys(
|
||||
|
@ -379,7 +481,8 @@ const SettingsDialog = ({
|
|||
}))
|
||||
: [
|
||||
{
|
||||
label: 'No embedding models available',
|
||||
label:
|
||||
'No embedding models available',
|
||||
value: '',
|
||||
disabled: true,
|
||||
},
|
||||
|
@ -483,6 +586,36 @@ const SettingsDialog = ({
|
|||
)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!passwordSubmitted && (
|
||||
<>
|
||||
<Dialog.Title className="text-sm dark:font-white/80 font-black/80">
|
||||
Enter the password to access the settings
|
||||
</Dialog.Title>
|
||||
<div className="flex flex-col">
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
className="mt-4"
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{!isPasswordValid && (
|
||||
<p className="text-xs text-red-500 mt-2">
|
||||
Password is incorrect
|
||||
</p>
|
||||
)}
|
||||
<button
|
||||
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>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue