diff --git a/ui/components/SettingsDialog.tsx b/ui/components/SettingsDialog.tsx index 171e812..37879e4 100644 --- a/ui/components/SettingsDialog.tsx +++ b/ui/components/SettingsDialog.tsx @@ -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 {} @@ -58,6 +59,9 @@ interface SettingsType { groqApiKey: string; anthropicApiKey: string; ollamaApiUrl: string; + isCopilotEnabled: boolean; + isDiscoverEnabled: boolean; + isLibraryEnabled: boolean; } const SettingsDialog = ({ @@ -84,78 +88,91 @@ const SettingsDialog = ({ const [isLoading, setIsLoading] = useState(false); const [isUpdating, setIsUpdating] = useState(false); - useEffect(() => { - if (isOpen) { - const fetchConfig = async () => { - setIsLoading(true); - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, { - headers: { - 'Content-Type': 'application/json', - }, - }); + const [password, setPassword] = useState(''); + const [passwordSubmitted, setPasswordSubmitted] = useState(false); + const [isPasswordValid, setIsPasswordValid] = useState(true); - const data = (await res.json()) as SettingsType; - setConfig(data); + const handlePasswordSubmit = async () => { + setIsLoading(true); + setPasswordSubmitted(true); - const chatModelProvidersKeys = Object.keys( - data.chatModelProviders || {}, - ); - const embeddingModelProvidersKeys = Object.keys( - data.embeddingModelProviders || {}, - ); + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/config`, { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${password}`, + }, + }); - 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); - }; - - fetchConfig(); + if (res.status === 401) { + setIsPasswordValid(false); + setPasswordSubmitted(false); + setIsLoading(false); + return; + } else { + setIsPasswordValid(true); } - // 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 () => { 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,284 +222,400 @@ const SettingsDialog = ({ leaveTo="opacity-0 scale-95" > - - Settings - - {config && !isLoading && ( -
-
-

- Theme -

- -
- {config.chatModelProviders && ( -
-

- Chat model Provider -

- - setSelectedChatModel(e.target.value) - } - options={(() => { - const chatModelProvider = - config.chatModelProviders[ - selectedChatModelProvider - ]; + +
+
+

+ Copilot enabled +

+ { + 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" + > + Copilot + + +
+
+

+ Discover enabled +

+ { + 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" + > + Discover + + +
+
+

+ Library enabled +

+ { + 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" + > + Library + + +
+ {config.chatModelProviders && ( +
+

+ Chat model Provider +

+ + setSelectedChatModel(e.target.value) + } + options={(() => { + const chatModelProvider = + config.chatModelProviders[ + selectedChatModelProvider + ]; - return chatModelProvider - ? chatModelProvider.length > 0 - ? chatModelProvider.map((model) => ({ - value: model, - label: model, - })) + return chatModelProvider + ? chatModelProvider.length > 0 + ? chatModelProvider.map((model) => ({ + value: model, + label: model, + })) + : [ + { + value: '', + label: 'No models available', + disabled: true, + }, + ] + : [ + { + value: '', + label: + 'Invalid provider, please check backend logs', + disabled: true, + }, + ]; + })()} + /> +
+ )} + {selectedChatModelProvider && + selectedChatModelProvider === 'custom_openai' && ( + <> +
+

+ Model name +

+ + setSelectedChatModel(e.target.value) + } + /> +
+
+

+ Custom OpenAI API Key +

+ + setCustomOpenAIApiKey(e.target.value) + } + /> +
+
+

+ Custom OpenAI Base URL +

+ + setCustomOpenAIBaseURL(e.target.value) + } + /> +
+ + )} + {/* Embedding models */} + {config.embeddingModelProviders && ( +
+

+ Embedding model Provider +

+ + 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: '', - label: 'No models available', disabled: true, }, - ] - : [ - { - value: '', - label: - 'Invalid provider, please check backend logs', - disabled: true, - }, - ]; - })()} + ]; + })()} + /> +
+ )} +
+

+ OpenAI API Key +

+ + setConfig({ + ...config, + openaiApiKey: e.target.value, + }) + } + /> +
+
+

+ Ollama API URL +

+ + setConfig({ + ...config, + ollamaApiUrl: e.target.value, + }) + } + /> +
+
+

+ GROQ API Key +

+ + setConfig({ + ...config, + groqApiKey: e.target.value, + }) + } + /> +
+
+

+ Anthropic API Key +

+ + setConfig({ + ...config, + anthropicApiKey: e.target.value, + }) + } />
- )} - {selectedChatModelProvider && - selectedChatModelProvider === 'custom_openai' && ( - <> -
-

- Model name -

- - setSelectedChatModel(e.target.value) - } - /> -
-
-

- Custom OpenAI API Key -

- - setCustomOpenAIApiKey(e.target.value) - } - /> -
-
-

- Custom OpenAI Base URL -

- - setCustomOpenAIBaseURL(e.target.value) - } - /> -
- - )} - {/* Embedding models */} - {config.embeddingModelProviders && ( -
-

- Embedding model Provider -

- - 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, - }, - ]; - })()} - /> + {isLoading && ( +
+
)} -
-

- OpenAI API Key +

+

+ We'll refresh the page after updating the settings.

- - setConfig({ - ...config, - openaiApiKey: e.target.value, - }) - } - /> +
-
-

- Ollama API URL -

- - setConfig({ - ...config, - ollamaApiUrl: e.target.value, - }) - } - /> -
-
-

- GROQ API Key -

- - setConfig({ - ...config, - groqApiKey: e.target.value, - }) - } - /> -
-
-

- Anthropic API Key -

- - setConfig({ - ...config, - anthropicApiKey: e.target.value, - }) - } - /> -
-
+ )} - {isLoading && ( -
- -
- )} -
-

- We'll refresh the page after updating the settings. -

- -
+ + + )}