diff --git a/README.md b/README.md index 9b646ee..c472369 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ For setups without Docker: ## Upcoming Features - [ ] Finalizing Copilot Mode +- [x] Add settings page - [x] Adding support for local LLMs - [ ] Adding Discover and History Saving features - [x] Introducing various Focus Modes diff --git a/ui/components/SettingsDialog.tsx b/ui/components/SettingsDialog.tsx new file mode 100644 index 0000000..2487c71 --- /dev/null +++ b/ui/components/SettingsDialog.tsx @@ -0,0 +1,229 @@ +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(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 ( + + setIsOpen(false)} + > + +
+ +
+
+ + + + Settings + + {config && !isLoading && ( +
+ {config.providers && ( +
+

LLM Provider

+ +
+ )} + {config.selectedProvider && ( +
+

Chat Model

+ +
+ )} + {config.selectedProvider === 'openai' && ( +
+

OpenAI API Key

+ + 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" + /> +
+ )} + {config.selectedProvider === 'ollama' && ( +
+

Ollama API URL

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

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

+ +
+
+
+
+
+
+
+ ); +}; + +export default SettingsDialog; diff --git a/ui/components/Sidebar.tsx b/ui/components/Sidebar.tsx index e562160..aef1153 100644 --- a/ui/components/Sidebar.tsx +++ b/ui/components/Sidebar.tsx @@ -1,16 +1,19 @@ 'use client'; import { cn } from '@/lib/utils'; -import { BookOpenText, Home, Search, SquarePen } from 'lucide-react'; -import { SiGithub } from '@icons-pack/react-simple-icons'; +import { BookOpenText, Home, Search, SquarePen, Settings } from 'lucide-react'; import Link from 'next/link'; import { useSelectedLayoutSegments } from 'next/navigation'; -import React from 'react'; +import React, { Fragment, useState } from 'react'; import Layout from './Layout'; +import { Dialog, Transition } from '@headlessui/react'; +import SettingsDialog from './SettingsDialog'; const Sidebar = ({ children }: { children: React.ReactNode }) => { const segments = useSelectedLayoutSegments(); + const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const navLinks = [ { icon: Home, @@ -56,16 +59,14 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => { ))} - - - + setIsSettingsOpen(!isSettingsOpen)} + className="text-white cursor-pointer" + /> +