import { Dialog, Transition } from '@headlessui/react'; import { CloudUpload, RefreshCcw, RefreshCw } from 'lucide-react'; import React, { Fragment, useEffect, useState } from 'react'; interface SettingsType { providers: { [key: string]: string[]; }; selectedProvider: string; selectedChatModel: string; openeaiApiKey: string; ollamaApiUrl: string; } const SettingsDialog = ({ isOpen, setIsOpen, }: { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; }) => { const [config, setConfig] = useState<SettingsType | null>(null); 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`); const data = await res.json(); setConfig(data); 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`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(config), }); } catch (err) { console.log(err); } finally { setIsUpdating(false); setIsOpen(false); window.location.reload(); } }; return ( <Transition appear show={isOpen} as={Fragment}> <Dialog as="div" className="relative z-50" onClose={() => setIsOpen(false)} > <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0" enterTo="opacity-100" leave="ease-in duration-200" leaveFrom="opacity-100" leaveTo="opacity-0" > <div className="fixed inset-0 bg-black/50" /> </Transition.Child> <div className="fixed inset-0 overflow-y-auto"> <div className="flex min-h-full items-center justify-center p-4 text-center"> <Transition.Child as={Fragment} enter="ease-out duration-200" enterFrom="opacity-0 scale-95" enterTo="opacity-100 scale-100" leave="ease-in duration-100" leaveFrom="opacity-100 scale-200" leaveTo="opacity-0 scale-95" > <Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-[#111111] border border-[#1c1c1c] p-6 text-left align-middle shadow-xl transition-all"> <Dialog.Title className="text-xl font-medium leading-6 text-white"> Settings </Dialog.Title> {config && !isLoading && ( <div className="flex flex-col space-y-4 mt-6"> {config.providers && ( <div className="flex flex-col space-y-1"> <p className="text-white/70 text-sm"> Chat model Provider </p> <select onChange={(e) => setConfig({ ...config, selectedProvider: e.target.value, selectedChatModel: config.providers[e.target.value][0], }) } className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm" > {Object.keys(config.providers).map((provider) => ( <option key={provider} value={provider} selected={provider === config.selectedProvider} > {provider.charAt(0).toUpperCase() + provider.slice(1)} </option> ))} </select> </div> )} {config.selectedProvider && ( <div className="flex flex-col space-y-1"> <p className="text-white/70 text-sm">Chat Model</p> <select onChange={(e) => setConfig({ ...config, selectedChatModel: e.target.value, }) } className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm" > {config.providers[config.selectedProvider] ? ( config.providers[config.selectedProvider].length > 0 ? ( config.providers[config.selectedProvider].map( (model) => ( <option key={model} value={model} selected={ model === config.selectedChatModel } > {model} </option> ), ) ) : ( <option value="" disabled selected> No models available </option> ) ) : ( <option value="" disabled selected> Invalid provider, please check backend logs </option> )} </select> </div> )} <div className="flex flex-col space-y-1"> <p className="text-white/70 text-sm">OpenAI API Key</p> <input type="text" placeholder="OpenAI API Key" defaultValue={config.openeaiApiKey} onChange={(e) => setConfig({ ...config, openeaiApiKey: e.target.value, }) } className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm" /> </div> <div className="flex flex-col space-y-1"> <p className="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, }) } className="bg-[#111111] px-3 py-2 flex items-center overflow-hidden border border-[#1C1C1C] text-white rounded-lg text-sm" /> </div> </div> )} {isLoading && ( <div className="w-full flex items-center justify-center mt-6 text-white/70 py-6"> <RefreshCcw className="animate-spin" /> </div> )} <div className="w-full mt-6 space-y-2"> <p className="text-xs text-white/50"> We'll refresh the page after updating the settings. </p> <button onClick={handleSubmit} 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" disabled={isLoading || isUpdating} > {isUpdating ? ( <RefreshCw size={20} className="animate-spin" /> ) : ( <CloudUpload size={20} /> )} </button> </div> </Dialog.Panel> </Transition.Child> </div> </div> </Dialog> </Transition> ); }; export default SettingsDialog;