diff --git a/sample.config.toml b/sample.config.toml index 7b09d67..9e3a06f 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 KEEP_ALIVE = "5m" # How long to keep Ollama models loaded into memory. (Instead of using -1 use "-1m") [MODELS.OPENAI] @@ -18,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 diff --git a/src/config.ts b/src/config.ts index ab2a5db..42f52b8 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; KEEP_ALIVE: string; }; MODELS: { @@ -51,6 +55,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 getKeepAlive = () => loadConfig().GENERAL.KEEP_ALIVE; export const getOpenaiApiKey = () => loadConfig().MODELS.OPENAI.API_KEY; diff --git a/src/routes/config.ts b/src/routes/config.ts index 18b370d..d1773b4 100644 --- a/src/routes/config.ts +++ b/src/routes/config.ts @@ -10,6 +10,10 @@ import { getGeminiApiKey, getOpenaiApiKey, updateConfig, + getConfigPassword, + isLibraryEnabled, + isCopilotEnabled, + isDiscoverEnabled, getCustomOpenaiApiUrl, getCustomOpenaiApiKey, getCustomOpenaiModelName, @@ -18,8 +22,16 @@ import logger from '../utils/logger'; const router = express.Router(); -router.get('/', async (_, res) => { +router.get('/', async (req, res) => { try { + 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([ @@ -69,9 +81,22 @@ router.get('/', async (_, res) => { }); 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, + }, MODELS: { OPENAI: { API_KEY: config.openaiApiKey, @@ -101,4 +126,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 b5fbe12..32ad813 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] = {}; + }); + }); Object.keys(chatModelProviders).forEach((provider) => { Object.keys(chatModelProviders[provider]).forEach((model) => { diff --git a/src/websocket/messageHandler.ts b/src/websocket/messageHandler.ts index 395c0de..e53ab6a 100644 --- a/src/websocket/messageHandler.ts +++ b/src/websocket/messageHandler.ts @@ -5,8 +5,9 @@ import type { Embeddings } from '@langchain/core/embeddings'; import logger from '../utils/logger'; import db from '../db'; import { chats, messages as messagesSchema } from '../db/schema'; -import { eq, asc, gt, and } from 'drizzle-orm'; +import { eq, gt, and } from 'drizzle-orm'; import crypto from 'crypto'; +import { isLibraryEnabled } from '../config'; import { getFileDetails } from '../utils/files'; import MetaSearchAgent, { MetaSearchAgentType, @@ -94,6 +95,8 @@ const handleEmitterEvents = ( let recievedMessage = ''; let sources = []; + const libraryEnabled = isLibraryEnabled(); + emitter.on('data', (data) => { const parsedData = JSON.parse(data); if (parsedData.type === 'response') { @@ -119,18 +122,20 @@ const handleEmitterEvents = ( emitter.on('end', () => { ws.send(JSON.stringify({ type: 'messageEnd', messageId: messageId })); - db.insert(messagesSchema) - .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(messagesSchema) + .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); @@ -188,6 +193,8 @@ export const handleMessage = async ( const handler: MetaSearchAgentType = searchHandlers[parsedWSMessage.focusMode]; + const libraryEnabled = isLibraryEnabled(); + if (handler) { try { const emitter = await handler.searchAndAnswer( @@ -201,50 +208,52 @@ export const handleMessage = async ( handleEmitterEvents(emitter, ws, aiMessageId, 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, - files: parsedWSMessage.files.map(getFileDetails), - }) - .execute(); - } + if (!chat) { + await db + .insert(chats) + .values({ + id: parsedMessage.chatId, + title: parsedMessage.content, + createdAt: new Date().toString(), + focusMode: parsedWSMessage.focusMode, + files: parsedWSMessage.files.map(getFileDetails), + }) + .execute(); + } - const messageExists = await db.query.messages.findFirst({ - where: eq(messagesSchema.messageId, humanMessageId), - }); + const messageExists = await db.query.messages.findFirst({ + where: eq(messagesSchema.messageId, humanMessageId), + }); - if (!messageExists) { - await db - .insert(messagesSchema) - .values({ - content: parsedMessage.content, - chatId: parsedMessage.chatId, - messageId: humanMessageId, - role: 'user', - metadata: JSON.stringify({ - createdAt: new Date(), - }), - }) - .execute(); - } else { - await db - .delete(messagesSchema) - .where( - and( - gt(messagesSchema.id, messageExists.id), - eq(messagesSchema.chatId, parsedMessage.chatId), - ), - ) - .execute(); + if (!messageExists) { + await db + .insert(messagesSchema) + .values({ + content: parsedMessage.content, + chatId: parsedMessage.chatId, + messageId: humanMessageId, + role: 'user', + metadata: JSON.stringify({ + createdAt: new Date(), + }), + }) + .execute(); + } else { + await db + .delete(messagesSchema) + .where( + and( + gt(messagesSchema.id, messageExists.id), + eq(messagesSchema.chatId, parsedMessage.chatId), + ), + ) + .execute(); + } } } catch (err) { console.log(err); 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 +
++ Password is incorrect +
+ )} + +