From fdfe8d1f4112e1b5fbfdda848350b29abfd4959d Mon Sep 17 00:00:00 2001 From: ItzCrazyKns Date: Fri, 2 Aug 2024 19:32:38 +0530 Subject: [PATCH 1/6] feat(app): add password auth for settings --- sample.config.toml | 4 ++ src/config.ts | 13 ++++++ src/routes/config.ts | 40 ++++++++++++++++- src/routes/models.ts | 31 +++++++++++-- src/websocket/messageHandler.ts | 77 ++++++++++++++++++--------------- ui/app/library/layout.tsx | 26 ++++++++++- ui/app/library/page.tsx | 2 +- 7 files changed, 152 insertions(+), 41 deletions(-) diff --git a/sample.config.toml b/sample.config.toml index f6c6943..781761a 100644 --- a/sample.config.toml +++ b/sample.config.toml @@ -1,6 +1,10 @@ [GENERAL] PORT = 3001 # Port to run the server on SIMILARITY_MEASURE = "cosine" # "cosine" or "dot" +CONFIG_PASSWORD = "lorem_ipsum" # Password to access config +DISCOVER_ENABLED = true +LIBRARY_ENABLED = true +COPILOT_ENABLED = true [API_KEYS] OPENAI = "" # OpenAI API key - sk-1234567890abcdef1234567890abcdef diff --git a/src/config.ts b/src/config.ts index 9ebc182..c343d69 100644 --- a/src/config.ts +++ b/src/config.ts @@ -8,6 +8,10 @@ interface Config { GENERAL: { PORT: number; SIMILARITY_MEASURE: string; + CONFIG_PASSWORD: string; + DISCOVER_ENABLED: boolean; + LIBRARY_ENABLED: boolean; + COPILOT_ENABLED: boolean; }; API_KEYS: { OPENAI: string; @@ -34,6 +38,14 @@ export const getPort = () => loadConfig().GENERAL.PORT; export const getSimilarityMeasure = () => loadConfig().GENERAL.SIMILARITY_MEASURE; +export const getConfigPassword = () => loadConfig().GENERAL.CONFIG_PASSWORD; + +export const isDiscoverEnabled = () => loadConfig().GENERAL.DISCOVER_ENABLED; + +export const isLibraryEnabled = () => loadConfig().GENERAL.LIBRARY_ENABLED; + +export const isCopilotEnabled = () => loadConfig().GENERAL.COPILOT_ENABLED; + export const getOpenaiApiKey = () => loadConfig().API_KEYS.OPENAI; export const getGroqApiKey = () => loadConfig().API_KEYS.GROQ; @@ -53,6 +65,7 @@ export const updateConfig = (config: RecursivePartial) => { if (typeof currentConfig[key] === 'object' && currentConfig[key] !== null) { for (const nestedKey in currentConfig[key]) { if ( + typeof config[key][nestedKey] !== 'boolean' && !config[key][nestedKey] && currentConfig[key][nestedKey] && config[key][nestedKey] !== '' diff --git a/src/routes/config.ts b/src/routes/config.ts index f255560..8993715 100644 --- a/src/routes/config.ts +++ b/src/routes/config.ts @@ -9,11 +9,23 @@ import { getAnthropicApiKey, getOpenaiApiKey, updateConfig, + getConfigPassword, + isLibraryEnabled, + isCopilotEnabled, + isDiscoverEnabled, } from '../config'; const router = express.Router(); -router.get('/', async (_, res) => { +router.get('/', async (req, res) => { + const authHeader = req.headers['authorization']?.split(' ')[1]; + const password = getConfigPassword(); + + if (authHeader !== password) { + res.status(401).json({ message: 'Unauthorized' }); + return; + } + const config = {}; const [chatModelProviders, embeddingModelProviders] = await Promise.all([ @@ -40,14 +52,30 @@ router.get('/', async (_, res) => { config['ollamaApiUrl'] = getOllamaApiEndpoint(); config['anthropicApiKey'] = getAnthropicApiKey(); config['groqApiKey'] = getGroqApiKey(); + config['isLibraryEnabled'] = isLibraryEnabled(); + config['isCopilotEnabled'] = isCopilotEnabled(); + config['isDiscoverEnabled'] = isDiscoverEnabled(); res.status(200).json(config); }); router.post('/', async (req, res) => { + const authHeader = req.headers['authorization']?.split(' ')[1]; + const password = getConfigPassword(); + + if (authHeader !== password) { + res.status(401).json({ message: 'Unauthorized' }); + return; + } + const config = req.body; const updatedConfig = { + GENERAL: { + DISCOVER_ENABLED: config.isDiscoverEnabled, + LIBRARY_ENABLED: config.isLibraryEnabled, + COPILOT_ENABLED: config.isCopilotEnabled, + }, API_KEYS: { OPENAI: config.openaiApiKey, GROQ: config.groqApiKey, @@ -63,4 +91,14 @@ router.post('/', async (req, res) => { res.status(200).json({ message: 'Config updated' }); }); +router.get('/preferences', (_, res) => { + const preferences = { + isLibraryEnabled: isLibraryEnabled(), + isCopilotEnabled: isCopilotEnabled(), + isDiscoverEnabled: isDiscoverEnabled(), + }; + + res.status(200).json(preferences); +}); + export default router; diff --git a/src/routes/models.ts b/src/routes/models.ts index 36df25a..17b9629 100644 --- a/src/routes/models.ts +++ b/src/routes/models.ts @@ -9,10 +9,33 @@ const router = express.Router(); router.get('/', async (req, res) => { try { - const [chatModelProviders, embeddingModelProviders] = await Promise.all([ - getAvailableChatModelProviders(), - getAvailableEmbeddingModelProviders(), - ]); + const [chatModelProvidersRaw, embeddingModelProvidersRaw] = + await Promise.all([ + getAvailableChatModelProviders(), + getAvailableEmbeddingModelProviders(), + ]); + + const chatModelProviders = {}; + + const chatModelProvidersKeys = Object.keys(chatModelProvidersRaw); + chatModelProvidersKeys.forEach((provider) => { + chatModelProviders[provider] = {}; + const models = Object.keys(chatModelProvidersRaw[provider]); + models.forEach((model) => { + chatModelProviders[provider][model] = {}; + }); + }); + + const embeddingModelProviders = {}; + + const embeddingModelProvidersKeys = Object.keys(embeddingModelProvidersRaw); + embeddingModelProvidersKeys.forEach((provider) => { + embeddingModelProviders[provider] = {}; + const models = Object.keys(embeddingModelProvidersRaw[provider]); + models.forEach((model) => { + embeddingModelProviders[provider][model] = {}; + }); + }); res.status(200).json({ chatModelProviders, embeddingModelProviders }); } catch (err) { diff --git a/src/websocket/messageHandler.ts b/src/websocket/messageHandler.ts index 0afda9f..b0b2d4d 100644 --- a/src/websocket/messageHandler.ts +++ b/src/websocket/messageHandler.ts @@ -13,6 +13,7 @@ import db from '../db'; import { chats, messages } from '../db/schema'; import { eq } from 'drizzle-orm'; import crypto from 'crypto'; +import { isLibraryEnabled } from '../config'; type Message = { messageId: string; @@ -46,6 +47,8 @@ const handleEmitterEvents = ( let recievedMessage = ''; let sources = []; + const libraryEnabled = isLibraryEnabled(); + emitter.on('data', (data) => { const parsedData = JSON.parse(data); if (parsedData.type === 'response') { @@ -71,18 +74,20 @@ const handleEmitterEvents = ( emitter.on('end', () => { ws.send(JSON.stringify({ type: 'messageEnd', messageId: messageId })); - db.insert(messages) - .values({ - content: recievedMessage, - chatId: chatId, - messageId: messageId, - role: 'assistant', - metadata: JSON.stringify({ - createdAt: new Date(), - ...(sources && sources.length > 0 && { sources }), - }), - }) - .execute(); + if (libraryEnabled) { + db.insert(messages) + .values({ + content: recievedMessage, + chatId: chatId, + messageId: messageId, + role: 'assistant', + metadata: JSON.stringify({ + createdAt: new Date(), + ...(sources && sources.length > 0 && { sources }), + }), + }) + .execute(); + } }); emitter.on('error', (data) => { const parsedData = JSON.parse(data); @@ -132,6 +137,8 @@ export const handleMessage = async ( if (parsedWSMessage.type === 'message') { const handler = searchHandlers[parsedWSMessage.focusMode]; + const libraryEnabled = isLibraryEnabled(); + if (handler) { const emitter = handler( parsedMessage.content, @@ -142,34 +149,36 @@ export const handleMessage = async ( handleEmitterEvents(emitter, ws, id, parsedMessage.chatId); - const chat = await db.query.chats.findFirst({ - where: eq(chats.id, parsedMessage.chatId), - }); + if (libraryEnabled) { + const chat = await db.query.chats.findFirst({ + where: eq(chats.id, parsedMessage.chatId), + }); + + if (!chat) { + await db + .insert(chats) + .values({ + id: parsedMessage.chatId, + title: parsedMessage.content, + createdAt: new Date().toString(), + focusMode: parsedWSMessage.focusMode, + }) + .execute(); + } - if (!chat) { await db - .insert(chats) + .insert(messages) .values({ - id: parsedMessage.chatId, - title: parsedMessage.content, - createdAt: new Date().toString(), - focusMode: parsedWSMessage.focusMode, + content: parsedMessage.content, + chatId: parsedMessage.chatId, + messageId: id, + role: 'user', + metadata: JSON.stringify({ + createdAt: new Date(), + }), }) .execute(); } - - await db - .insert(messages) - .values({ - content: parsedMessage.content, - chatId: parsedMessage.chatId, - messageId: id, - role: 'user', - metadata: JSON.stringify({ - createdAt: new Date(), - }), - }) - .execute(); } else { ws.send( JSON.stringify({ diff --git a/ui/app/library/layout.tsx b/ui/app/library/layout.tsx index 00d4a3b..d1a1cf6 100644 --- a/ui/app/library/layout.tsx +++ b/ui/app/library/layout.tsx @@ -5,7 +5,31 @@ export const metadata: Metadata = { title: 'Library - Perplexica', }; -const Layout = ({ children }: { children: React.ReactNode }) => { +const Layout = async ({ children }: { children: React.ReactNode }) => { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/config/preferences`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + const data = await res.json(); + + const { isLibraryEnabled } = data; + + if (!isLibraryEnabled) { + return ( +
+

+ Library is disabled +

+
+ ); + } + return
{children}
; }; diff --git a/ui/app/library/page.tsx b/ui/app/library/page.tsx index 8294fc1..586765d 100644 --- a/ui/app/library/page.tsx +++ b/ui/app/library/page.tsx @@ -2,7 +2,7 @@ import DeleteChat from '@/components/DeleteChat'; import { formatTimeDifference } from '@/lib/utils'; -import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react'; +import { BookOpenText, ClockIcon } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useState } from 'react'; From 5779701b7d3f30649c7babc0df9b21a43d8b4db4 Mon Sep 17 00:00:00 2001 From: ItzCrazyKns Date: Fri, 2 Aug 2024 19:35:57 +0530 Subject: [PATCH 2/6] feat(sidebar): respect preferences --- ui/components/Sidebar.tsx | 94 +++++++++++++++++++++++++++++++-------- 1 file changed, 75 insertions(+), 19 deletions(-) diff --git a/ui/components/Sidebar.tsx b/ui/components/Sidebar.tsx index cc2097d..546e2fd 100644 --- a/ui/components/Sidebar.tsx +++ b/ui/components/Sidebar.tsx @@ -4,10 +4,16 @@ import { cn } from '@/lib/utils'; import { BookOpenText, Home, Search, SquarePen, Settings } from 'lucide-react'; import Link from 'next/link'; import { useSelectedLayoutSegments } from 'next/navigation'; -import React, { useState, type ReactNode } from 'react'; +import React, { useEffect, useMemo, useState, type ReactNode } from 'react'; import Layout from './Layout'; import SettingsDialog from './SettingsDialog'; +export type Preferences = { + isLibraryEnabled: boolean; + isDiscoverEnabled: boolean; + isCopilotEnabled: boolean; +}; + const VerticalIconContainer = ({ children }: { children: ReactNode }) => { return (
{children}
@@ -18,6 +24,31 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => { const segments = useSelectedLayoutSegments(); const [isSettingsOpen, setIsSettingsOpen] = useState(false); + const [preferences, setPreferences] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchPreferences = async () => { + setLoading(true); + + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/config/preferences`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + const data = await res.json(); + + setPreferences(data); + setLoading(false); + }; + + fetchPreferences(); + }, []); const navLinks = [ { @@ -25,22 +56,44 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => { href: '/', active: segments.length === 0 || segments.includes('c'), label: 'Home', + show: true, }, { icon: Search, href: '/discover', active: segments.includes('discover'), label: 'Discover', + show: preferences?.isDiscoverEnabled, }, { icon: BookOpenText, href: '/library', active: segments.includes('library'), label: 'Library', + show: preferences?.isLibraryEnabled, }, ]; - return ( + return loading ? ( +
+ +
+ ) : (
@@ -48,23 +101,26 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => { - {navLinks.map((link, i) => ( - - - {link.active && ( -
- )} - - ))} + {navLinks.map( + (link, i) => + link.show === true && ( + + + {link.active && ( +
+ )} + + ), + )} Date: Fri, 2 Aug 2024 19:36:39 +0530 Subject: [PATCH 3/6] feat(settings): add preferences --- ui/components/SettingsDialog.tsx | 765 ++++++++++++++++++------------- 1 file changed, 449 insertions(+), 316 deletions(-) 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. -

- -
+ + + )}
From a88104434d0b9adc859dd92f09a3b6a80f28aa21 Mon Sep 17 00:00:00 2001 From: ItzCrazyKns Date: Fri, 2 Aug 2024 19:36:50 +0530 Subject: [PATCH 4/6] feat(copilot): respect preferences --- ui/components/MessageInputActions/Copilot.tsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ui/components/MessageInputActions/Copilot.tsx b/ui/components/MessageInputActions/Copilot.tsx index 5a3e476..63f0607 100644 --- a/ui/components/MessageInputActions/Copilot.tsx +++ b/ui/components/MessageInputActions/Copilot.tsx @@ -1,5 +1,6 @@ import { cn } from '@/lib/utils'; import { Switch } from '@headlessui/react'; +import { useEffect } from 'react'; const CopilotToggle = ({ copilotEnabled, @@ -8,11 +9,33 @@ const CopilotToggle = ({ copilotEnabled: boolean; setCopilotEnabled: (enabled: boolean) => void; }) => { + const fetchAndSetCopilotEnabled = async () => { + const res = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/config/preferences`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + const preferences = await res.json(); + + setCopilotEnabled(preferences.isCopilotEnabled); + }; + + useEffect(() => { + fetchAndSetCopilotEnabled(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return (
Copilot From 639fbd7a15b28654e61309a9f0dc9993b2000fad Mon Sep 17 00:00:00 2001 From: ItzCrazyKns Date: Fri, 2 Aug 2024 19:37:20 +0530 Subject: [PATCH 5/6] feat(chat-window): lint & beautify --- ui/components/ChatWindow.tsx | 85 ++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/ui/components/ChatWindow.tsx b/ui/components/ChatWindow.tsx index ea9a93d..5e6d382 100644 --- a/ui/components/ChatWindow.tsx +++ b/ui/components/ChatWindow.tsx @@ -38,53 +38,54 @@ const useSocket = ( 'embeddingModelProvider', ); + const providers = await fetch( + `${process.env.NEXT_PUBLIC_API_URL}/models`, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ).then(async (res) => await res.json()); + if ( !chatModel || !chatModelProvider || !embeddingModel || !embeddingModelProvider ) { - const providers = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/models`, - { - headers: { - 'Content-Type': 'application/json', - }, - }, - ).then(async (res) => await res.json()); + if (!chatModel || !chatModelProvider) { + const chatModelProviders = providers.chatModelProviders; - const chatModelProviders = providers.chatModelProviders; + chatModelProvider = Object.keys(chatModelProviders)[0]; - chatModelProvider = Object.keys(chatModelProviders)[0]; - - if (chatModelProvider === 'custom_openai') { - toast.error( - 'Seems like you are using the custom OpenAI provider, please open the settings and configure the API key and base URL', - ); - setError(true); - return; - } else { - chatModel = Object.keys(chatModelProviders[chatModelProvider])[0]; - - if ( - !chatModelProviders || - Object.keys(chatModelProviders).length === 0 - ) - return toast.error('No chat models available'); + if (chatModelProvider === 'custom_openai') { + toast.error('Seems like you are using the custom OpenAI provider, please open the settings and configure the API key and base URL'); + setError(true); + return; + } else { + chatModel = Object.keys(chatModelProviders[chatModelProvider])[0]; + if ( + !chatModelProviders || + Object.keys(chatModelProviders).length === 0 + ) + return toast.error('No chat models available'); + } } - const embeddingModelProviders = providers.embeddingModelProviders; + if (!embeddingModel || !embeddingModelProvider) { + const embeddingModelProviders = providers.embeddingModelProviders; - if ( - !embeddingModelProviders || - Object.keys(embeddingModelProviders).length === 0 - ) - return toast.error('No embedding models available'); + if ( + !embeddingModelProviders || + Object.keys(embeddingModelProviders).length === 0 + ) + return toast.error('No embedding models available'); - embeddingModelProvider = Object.keys(embeddingModelProviders)[0]; - embeddingModel = Object.keys( - embeddingModelProviders[embeddingModelProvider], - )[0]; + embeddingModelProvider = Object.keys(embeddingModelProviders)[0]; + embeddingModel = Object.keys( + embeddingModelProviders[embeddingModelProvider], + )[0]; + } localStorage.setItem('chatModel', chatModel!); localStorage.setItem('chatModelProvider', chatModelProvider); @@ -94,15 +95,6 @@ const useSocket = ( embeddingModelProvider, ); } else { - const providers = await fetch( - `${process.env.NEXT_PUBLIC_API_URL}/models`, - { - headers: { - 'Content-Type': 'app lication/json', - }, - }, - ).then(async (res) => await res.json()); - const chatModelProviders = providers.chatModelProviders; const embeddingModelProviders = providers.embeddingModelProviders; @@ -171,8 +163,6 @@ const useSocket = ( const timeoutId = setTimeout(() => { if (ws.readyState !== 1) { - ws.close(); - setError(true); toast.error( 'Failed to connect to the server. Please try again later.', ); @@ -182,7 +172,6 @@ const useSocket = ( ws.onopen = () => { console.log('[DEBUG] open'); clearTimeout(timeoutId); - setError(false); setIsWSReady(true); }; @@ -203,7 +192,7 @@ const useSocket = ( if (data.type === 'error') { toast.error(data.data); } - }); + }) setWs(ws); }; From d95849e538b978e7487bf2ae626bd7a631514b7b Mon Sep 17 00:00:00 2001 From: AnotiaWang Date: Fri, 7 Mar 2025 23:54:53 +0800 Subject: [PATCH 6/6] fix: missing `MODEL_NAME` in config sample --- sample.config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/sample.config.toml b/sample.config.toml index 356c50a..9e3a06f 100644 --- a/sample.config.toml +++ b/sample.config.toml @@ -22,6 +22,7 @@ API_KEY = "" [MODELS.CUSTOM_OPENAI] API_KEY = "" API_URL = "" +MODEL_NAME = "" [MODELS.OLLAMA] API_URL = "" # Ollama API URL - http://host.docker.internal:11434