From 9f327d015a6beb618d0a6284a97e2bf00282b859 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 22 May 2024 09:58:10 -0500 Subject: [PATCH 01/21] update error handling for OpenAI providers --- server/utils/AiProviders/genericOpenAi/index.js | 2 +- server/utils/AiProviders/groq/index.js | 2 +- server/utils/AiProviders/koboldCPP/index.js | 2 +- server/utils/AiProviders/liteLLM/index.js | 2 +- server/utils/AiProviders/openAi/index.js | 2 +- server/utils/AiProviders/openRouter/index.js | 2 +- server/utils/AiProviders/perplexity/index.js | 2 +- server/utils/AiProviders/textGenWebUI/index.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/utils/AiProviders/genericOpenAi/index.js b/server/utils/AiProviders/genericOpenAi/index.js index 46b8aefb..8d0c0f34 100644 --- a/server/utils/AiProviders/genericOpenAi/index.js +++ b/server/utils/AiProviders/genericOpenAi/index.js @@ -97,7 +97,7 @@ class GenericOpenAiLLM { max_tokens: this.maxTokens, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/groq/index.js b/server/utils/AiProviders/groq/index.js index 24a430e6..ba081219 100644 --- a/server/utils/AiProviders/groq/index.js +++ b/server/utils/AiProviders/groq/index.js @@ -103,7 +103,7 @@ class GroqLLM { temperature, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/koboldCPP/index.js b/server/utils/AiProviders/koboldCPP/index.js index 90186aab..15163648 100644 --- a/server/utils/AiProviders/koboldCPP/index.js +++ b/server/utils/AiProviders/koboldCPP/index.js @@ -92,7 +92,7 @@ class KoboldCPPLLM { temperature, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/liteLLM/index.js b/server/utils/AiProviders/liteLLM/index.js index 6152ea29..2c7fa823 100644 --- a/server/utils/AiProviders/liteLLM/index.js +++ b/server/utils/AiProviders/liteLLM/index.js @@ -93,7 +93,7 @@ class LiteLLM { max_tokens: parseInt(this.maxTokens), // LiteLLM requires int }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/openAi/index.js b/server/utils/AiProviders/openAi/index.js index 8037d23d..9e55074c 100644 --- a/server/utils/AiProviders/openAi/index.js +++ b/server/utils/AiProviders/openAi/index.js @@ -130,7 +130,7 @@ class OpenAiLLM { temperature, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/openRouter/index.js b/server/utils/AiProviders/openRouter/index.js index bb8cf447..301ed704 100644 --- a/server/utils/AiProviders/openRouter/index.js +++ b/server/utils/AiProviders/openRouter/index.js @@ -142,7 +142,7 @@ class OpenRouterLLM { temperature, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/perplexity/index.js b/server/utils/AiProviders/perplexity/index.js index d3f50de7..d821b2e3 100644 --- a/server/utils/AiProviders/perplexity/index.js +++ b/server/utils/AiProviders/perplexity/index.js @@ -93,7 +93,7 @@ class PerplexityLLM { temperature, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) diff --git a/server/utils/AiProviders/textGenWebUI/index.js b/server/utils/AiProviders/textGenWebUI/index.js index 72f116f8..71d50577 100644 --- a/server/utils/AiProviders/textGenWebUI/index.js +++ b/server/utils/AiProviders/textGenWebUI/index.js @@ -89,7 +89,7 @@ class TextGenWebUILLM { temperature, }) .catch((e) => { - throw new Error(e.response.data.error.message); + throw new Error(e.message); }); if (!result.hasOwnProperty("choices") || result.choices.length === 0) From a256db132dd6f8bad7309655826243090c69fcf0 Mon Sep 17 00:00:00 2001 From: Shixian Sheng Date: Wed, 22 May 2024 11:06:39 -0400 Subject: [PATCH 02/21] Fixed links (#1485) * Update CHROMA_SETUP.md * Update ASTRA_SETUP.md --- server/utils/vectorDbProviders/astra/ASTRA_SETUP.md | 2 +- server/utils/vectorDbProviders/chroma/CHROMA_SETUP.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/utils/vectorDbProviders/astra/ASTRA_SETUP.md b/server/utils/vectorDbProviders/astra/ASTRA_SETUP.md index e3749f07..f91885c5 100644 --- a/server/utils/vectorDbProviders/astra/ASTRA_SETUP.md +++ b/server/utils/vectorDbProviders/astra/ASTRA_SETUP.md @@ -10,7 +10,7 @@ **Instructions** -- [Create an Astra account or sign in to an existing Astra account](astra.datastax.com) +- [Create an Astra account or sign in to an existing Astra account](https://astra.datastax.com) - Create an Astra Serverless(Vector) Database. - Make sure DB is in active state. - Get `API ENDPOINT`and `Application Token` from Overview screen diff --git a/server/utils/vectorDbProviders/chroma/CHROMA_SETUP.md b/server/utils/vectorDbProviders/chroma/CHROMA_SETUP.md index 46dac0db..e6d6fee9 100644 --- a/server/utils/vectorDbProviders/chroma/CHROMA_SETUP.md +++ b/server/utils/vectorDbProviders/chroma/CHROMA_SETUP.md @@ -1,6 +1,6 @@ # How to setup a local (or remote) Chroma Vector Database -[Official Chroma Docs](https://docs.trychroma.com/usage-guide#running-chroma-in-clientserver-mode) for reference. +[Official Chroma Docs](https://docs.trychroma.com/guides#running-chroma-in-clientserver-mode) for reference. ### How to get started From 8c5a30db9da64e94033a138e173c846985ce999d Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Wed, 22 May 2024 10:53:49 -0500 Subject: [PATCH 03/21] Support dynamic context length - VoyageAI (#1489) --- .../GeneralSettings/LLMPreference/index.jsx | 9 +++++---- server/utils/EmbeddingEngines/voyageAi/index.js | 17 ++++++++++++++++- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx index 5c4b0b2f..70d1d135 100644 --- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx @@ -394,16 +394,17 @@ export default function GeneralLLMPreference() { >
{`${selectedLLMObject.name}
- {selectedLLMObject.name} + {selectedLLMObject?.name || "None selected"}
- {selectedLLMObject.description} + {selectedLLMObject?.description || + "You need to select an LLM"}
diff --git a/server/utils/EmbeddingEngines/voyageAi/index.js b/server/utils/EmbeddingEngines/voyageAi/index.js index b25d3208..fe2a3964 100644 --- a/server/utils/EmbeddingEngines/voyageAi/index.js +++ b/server/utils/EmbeddingEngines/voyageAi/index.js @@ -15,7 +15,22 @@ class VoyageAiEmbedder { // Limit of how many strings we can process in a single pass to stay with resource or network limits this.batchSize = 128; // Voyage AI's limit per request is 128 https://docs.voyageai.com/docs/rate-limits#use-larger-batches - this.embeddingMaxChunkLength = 4000; // https://docs.voyageai.com/docs/embeddings - assume a token is roughly 4 letters with some padding + this.embeddingMaxChunkLength = this.#getMaxEmbeddingLength(); + } + + // https://docs.voyageai.com/docs/embeddings + #getMaxEmbeddingLength() { + switch (this.model) { + case "voyage-large-2-instruct": + case "voyage-law-2": + case "voyage-code-2": + case "voyage-large-2": + return 16_000; + case "voyage-2": + return 4_000; + default: + return 4_000; + } } async embedTextInput(textInput) { From e2439c6d4c3cfdacd96cd1b7b92d1f89c3cc8459 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 22 May 2024 11:34:15 -0500 Subject: [PATCH 04/21] patch endpoint without exception handler --- server/endpoints/workspaces.js | 131 ++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 60 deletions(-) diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index 81cbd615..61571659 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -111,39 +111,45 @@ function workspaceEndpoints(app) { handleFileUpload, ], async function (request, response) { - const Collector = new CollectorApi(); - const { originalname } = request.file; - const processingOnline = await Collector.online(); + try { + const Collector = new CollectorApi(); + const { originalname } = request.file; + const processingOnline = await Collector.online(); - if (!processingOnline) { - response - .status(500) - .json({ - success: false, - error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`, - }) - .end(); - return; + if (!processingOnline) { + response + .status(500) + .json({ + success: false, + error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`, + }) + .end(); + return; + } + + const { success, reason } = + await Collector.processDocument(originalname); + if (!success) { + response.status(500).json({ success: false, error: reason }).end(); + return; + } + + Collector.log( + `Document ${originalname} uploaded processed and successfully. It is now available in documents.` + ); + await Telemetry.sendTelemetry("document_uploaded"); + await EventLogs.logEvent( + "document_uploaded", + { + documentName: originalname, + }, + response.locals?.user?.id + ); + response.status(200).json({ success: true, error: null }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); } - - const { success, reason } = await Collector.processDocument(originalname); - if (!success) { - response.status(500).json({ success: false, error: reason }).end(); - return; - } - - Collector.log( - `Document ${originalname} uploaded processed and successfully. It is now available in documents.` - ); - await Telemetry.sendTelemetry("document_uploaded"); - await EventLogs.logEvent( - "document_uploaded", - { - documentName: originalname, - }, - response.locals?.user?.id - ); - response.status(200).json({ success: true, error: null }); } ); @@ -151,37 +157,42 @@ function workspaceEndpoints(app) { "/workspace/:slug/upload-link", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { - const Collector = new CollectorApi(); - const { link = "" } = reqBody(request); - const processingOnline = await Collector.online(); + try { + const Collector = new CollectorApi(); + const { link = "" } = reqBody(request); + const processingOnline = await Collector.online(); - if (!processingOnline) { - response - .status(500) - .json({ - success: false, - error: `Document processing API is not online. Link ${link} will not be processed automatically.`, - }) - .end(); - return; + if (!processingOnline) { + response + .status(500) + .json({ + success: false, + error: `Document processing API is not online. Link ${link} will not be processed automatically.`, + }) + .end(); + return; + } + + const { success, reason } = await Collector.processLink(link); + if (!success) { + response.status(500).json({ success: false, error: reason }).end(); + return; + } + + Collector.log( + `Link ${link} uploaded processed and successfully. It is now available in documents.` + ); + await Telemetry.sendTelemetry("link_uploaded"); + await EventLogs.logEvent( + "link_uploaded", + { link }, + response.locals?.user?.id + ); + response.status(200).json({ success: true, error: null }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); } - - const { success, reason } = await Collector.processLink(link); - if (!success) { - response.status(500).json({ success: false, error: reason }).end(); - return; - } - - Collector.log( - `Link ${link} uploaded processed and successfully. It is now available in documents.` - ); - await Telemetry.sendTelemetry("link_uploaded"); - await EventLogs.logEvent( - "link_uploaded", - { link }, - response.locals?.user?.id - ); - response.status(200).json({ success: true, error: null }); } ); From e208074ef4c240fe03e4147ab097ec3b52b97619 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 22 May 2024 11:50:01 -0500 Subject: [PATCH 05/21] patch path normalization --- collector/utils/files/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collector/utils/files/index.js b/collector/utils/files/index.js index 9b56bb5b..51d0a682 100644 --- a/collector/utils/files/index.js +++ b/collector/utils/files/index.js @@ -122,7 +122,7 @@ function isWithin(outer, inner) { function normalizePath(filepath = "") { const result = path - .normalize(filepath.trim()) + .normalize(filepath.replace(/\s/g, "-").trim()) .replace(/^(\.\.(\/|\\|$))+/, "") .trim(); if (["..", ".", "/"].includes(result)) throw new Error("Invalid path."); From c2d37ccce5f889e9a2a2ae7f30e846d5f1493a67 Mon Sep 17 00:00:00 2001 From: Timothy Carambat Date: Wed, 22 May 2024 12:32:39 -0500 Subject: [PATCH 06/21] Limit return object of `user` when returned in some endpoints (#1492) --- server/endpoints/admin.js | 5 +---- server/endpoints/api/admin/index.js | 5 +---- server/endpoints/system.js | 6 +++--- server/models/user.js | 21 +++++++++++++++++++-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index 959e023f..9b836b19 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -33,10 +33,7 @@ function adminEndpoints(app) { [validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], async (_request, response) => { try { - const users = (await User.where()).map((user) => { - const { password, ...rest } = user; - return rest; - }); + const users = await User.where(); response.status(200).json({ users }); } catch (e) { console.error(e); diff --git a/server/endpoints/api/admin/index.js b/server/endpoints/api/admin/index.js index 228777ab..95b8e791 100644 --- a/server/endpoints/api/admin/index.js +++ b/server/endpoints/api/admin/index.js @@ -73,10 +73,7 @@ function apiAdminEndpoints(app) { return; } - const users = (await User.where()).map((user) => { - const { password, ...rest } = user; - return rest; - }); + const users = await User.where(); response.status(200).json({ users }); } catch (e) { console.error(e); diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 86aacac4..f4057a40 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -110,7 +110,7 @@ function systemEndpoints(app) { if (await SystemSettings.isMultiUserMode()) { const { username, password } = reqBody(request); - const existingUser = await User.get({ username: String(username) }); + const existingUser = await User._get({ username: String(username) }); if (!existingUser) { await EventLogs.logEvent( @@ -188,7 +188,7 @@ function systemEndpoints(app) { // Return recovery codes to frontend response.status(200).json({ valid: true, - user: existingUser, + user: User.filterFields(existingUser), token: makeJWT( { id: existingUser.id, username: existingUser.username }, "30d" @@ -201,7 +201,7 @@ function systemEndpoints(app) { response.status(200).json({ valid: true, - user: existingUser, + user: User.filterFields(existingUser), token: makeJWT( { id: existingUser.id, username: existingUser.username }, "30d" diff --git a/server/models/user.js b/server/models/user.js index ecb620ee..a1aeb2c6 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -19,6 +19,12 @@ const User = { return String(value); } }, + + filterFields: function (user = {}) { + const { password, ...rest } = user; + return { ...rest }; + }, + create: async function ({ username, password, role = "default" }) { const passwordCheck = this.checkPasswordComplexity(password); if (!passwordCheck.checkedOK) { @@ -35,7 +41,7 @@ const User = { role, }, }); - return { user, error: null }; + return { user: this.filterFields(user), error: null }; } catch (error) { console.error("FAILED TO CREATE USER.", error.message); return { user: null, error: error.message }; @@ -127,6 +133,17 @@ const User = { }, get: async function (clause = {}) { + try { + const user = await prisma.users.findFirst({ where: clause }); + return user ? this.filterFields({ ...user }) : null; + } catch (error) { + console.error(error.message); + return null; + } + }, + + // Returns user object with all fields + _get: async function (clause = {}) { try { const user = await prisma.users.findFirst({ where: clause }); return user ? { ...user } : null; @@ -162,7 +179,7 @@ const User = { where: clause, ...(limit !== null ? { take: limit } : {}), }); - return users; + return users.map((usr) => this.filterFields(usr)); } catch (error) { console.error(error.message); return []; From 323e37e68448b82839e6fac4065a85aee698db88 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 22 May 2024 12:49:12 -0500 Subject: [PATCH 07/21] add fallback footer icon --- frontend/src/components/Footer/index.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Footer/index.jsx b/frontend/src/components/Footer/index.jsx index 6e80f0df..2dfccc3f 100644 --- a/frontend/src/components/Footer/index.jsx +++ b/frontend/src/components/Footer/index.jsx @@ -108,10 +108,13 @@ export default function Footer() { rel="noreferrer" className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border" > - {React.createElement(ICON_COMPONENTS[item.icon], { - weight: "fill", - className: "h-5 w-5", - })} + {React.createElement( + ICON_COMPONENTS?.[item.icon] ?? ICON_COMPONENTS.Info, + { + weight: "fill", + className: "h-5 w-5", + } + )} ))} {!isMobile && } From 3ef009de73c837f9025df8bba62572885c70c72f Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 22 May 2024 13:21:26 -0500 Subject: [PATCH 08/21] enfore min and max username lengths to prevent DOS via spam-length names --- server/endpoints/system.js | 2 +- server/models/user.js | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/server/endpoints/system.js b/server/endpoints/system.js index f4057a40..6c941b1c 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -1024,7 +1024,7 @@ function systemEndpoints(app) { const updates = {}; if (username) { - updates.username = String(username); + updates.username = User.validations.username(String(username)); } if (password) { updates.password = String(password); diff --git a/server/models/user.js b/server/models/user.js index a1aeb2c6..f08548af 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -10,6 +10,20 @@ const User = { "role", "suspended", ], + validations: { + username: (newValue = "") => { + try { + if (String(newValue).length > 100) + throw new Error("Username cannot be longer than 100 characters"); + if (String(newValue).length < 2) + throw new Error("Username must be at least 2 characters"); + return String(newValue); + } catch (e) { + throw new Error(e.message); + } + }, + }, + // validations for the above writable fields. castColumnValue: function (key, value) { switch (key) { @@ -36,9 +50,9 @@ const User = { const hashedPassword = bcrypt.hashSync(password, 10); const user = await prisma.users.create({ data: { - username, + username: this.validations.username(username), password: hashedPassword, - role, + role: String(role), }, }); return { user: this.filterFields(user), error: null }; @@ -75,7 +89,13 @@ const User = { // and force-casts to the proper type; Object.entries(updates).forEach(([key, value]) => { if (this.writable.includes(key)) { - updates[key] = this.castColumnValue(key, value); + if (this.validations.hasOwnProperty(key)) { + updates[key] = this.validations[key]( + this.castColumnValue(key, value) + ); + } else { + updates[key] = this.castColumnValue(key, value); + } return; } delete updates[key]; From 7bace207698e32d5a656d36366d210fe9cd8b6f2 Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Wed, 22 May 2024 13:42:48 -0500 Subject: [PATCH 09/21] Improve VoyageAI error responses and textChunk handler resolves #1491 --- server/utils/EmbeddingEngines/voyageAi/index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server/utils/EmbeddingEngines/voyageAi/index.js b/server/utils/EmbeddingEngines/voyageAi/index.js index fe2a3964..65126613 100644 --- a/server/utils/EmbeddingEngines/voyageAi/index.js +++ b/server/utils/EmbeddingEngines/voyageAi/index.js @@ -38,7 +38,10 @@ class VoyageAiEmbedder { Array.isArray(textInput) ? textInput : [textInput], { modelName: this.model } ); - return result || []; + + // If given an array return the native Array[Array] format since that should be the outcome. + // But if given a single string, we need to flatten it so that we have a 1D array. + return (Array.isArray(textInput) ? result : result.flat()) || []; } async embedChunks(textChunks = []) { @@ -50,6 +53,12 @@ class VoyageAiEmbedder { return embeddings; } catch (error) { console.error("Voyage AI Failed to embed:", error); + if ( + error.message.includes( + "Cannot read properties of undefined (reading '0')" + ) + ) + throw new Error("Voyage AI failed to embed: Rate limit reached"); throw error; } } From b4b29550b70a27f5c03e5199b3632ad503d4f1ec Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Wed, 22 May 2024 14:19:25 -0700 Subject: [PATCH 10/21] Fix chat width on larger screens (#1493) * fix chat width on larger screens * update loading layout padding --------- Co-authored-by: timothycarambat --- frontend/src/components/ChatBubble/index.jsx | 4 +--- frontend/src/components/DefaultChat/index.jsx | 18 +++++++++--------- .../ChatHistory/Chartable/index.jsx | 4 ++-- .../ChatHistory/HistoricalMessage/index.jsx | 4 +--- .../ChatHistory/PromptReply/index.jsx | 6 +++--- .../ChatContainer/ChatHistory/index.jsx | 2 +- .../WorkspaceChat/LoadingChat/index.jsx | 12 ++++++------ 7 files changed, 23 insertions(+), 27 deletions(-) diff --git a/frontend/src/components/ChatBubble/index.jsx b/frontend/src/components/ChatBubble/index.jsx index 72002ab2..8d311883 100644 --- a/frontend/src/components/ChatBubble/index.jsx +++ b/frontend/src/components/ChatBubble/index.jsx @@ -9,9 +9,7 @@ export default function ChatBubble({ message, type, popMsg }) { return (
-
+
@@ -67,7 +67,7 @@ export default function DefaultChatContainer() { className={`flex justify-center items-end w-full ${AI_BACKGROUND_COLOR}`} >
@@ -90,7 +90,7 @@ export default function DefaultChatContainer() { className={`flex justify-center items-end w-full ${AI_BACKGROUND_COLOR}`} >
@@ -124,7 +124,7 @@ export default function DefaultChatContainer() { className={`flex justify-center items-end w-full ${USER_BACKGROUND_COLOR}`} >
@@ -185,7 +185,7 @@ export default function DefaultChatContainer() { className={`flex justify-center items-end w-full ${USER_BACKGROUND_COLOR}`} >
@@ -248,7 +248,7 @@ export default function DefaultChatContainer() { className={`flex justify-center items-end w-full ${USER_BACKGROUND_COLOR}`} >
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Chartable/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Chartable/index.jsx index 6a6e6b13..2ce0fa5d 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Chartable/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Chartable/index.jsx @@ -368,7 +368,7 @@ export function Chartable({ props, workspace }) { if (!!props.chatId) { return (
-
+
@@ -389,7 +389,7 @@ export function Chartable({ props, workspace }) { return (
-
+
{renderChart()}
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx index 19817d7d..ae8f10b4 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/index.jsx @@ -29,9 +29,7 @@ const HistoricalMessage = ({ role === "user" ? USER_BACKGROUND_COLOR : AI_BACKGROUND_COLOR }`} > -
+
{error ? ( diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx index 858c773e..07f8280a 100644 --- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx +++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/PromptReply/index.jsx @@ -21,7 +21,7 @@ const PromptReply = ({
-
+
@@ -36,7 +36,7 @@ const PromptReply = ({
-
+
-
+
-
+
From acbad2f2cf7c13e03e0a0dc1067041ba4c59930c Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Wed, 22 May 2024 14:27:56 -0700 Subject: [PATCH 11/21] [FEAT] Clear all chats button + export button styles updates (#1495) * implement clear all chats button + export button styles updates * Hide clear button if no chats are visible update clear function --------- Co-authored-by: timothycarambat --- .../src/pages/GeneralSettings/Chats/index.jsx | 78 ++++++++++++++----- server/endpoints/system.js | 4 +- 2 files changed, 60 insertions(+), 22 deletions(-) diff --git a/frontend/src/pages/GeneralSettings/Chats/index.jsx b/frontend/src/pages/GeneralSettings/Chats/index.jsx index d5b3d509..52a3c434 100644 --- a/frontend/src/pages/GeneralSettings/Chats/index.jsx +++ b/frontend/src/pages/GeneralSettings/Chats/index.jsx @@ -7,7 +7,7 @@ import useQuery from "@/hooks/useQuery"; import ChatRow from "./ChatRow"; import showToast from "@/utils/toast"; import System from "@/models/system"; -import { CaretDown, Download } from "@phosphor-icons/react"; +import { CaretDown, Download, Trash } from "@phosphor-icons/react"; import { saveAs } from "file-saver"; const exportOptions = { @@ -49,6 +49,12 @@ export default function WorkspaceChats() { const [showMenu, setShowMenu] = useState(false); const menuRef = useRef(); const openMenuButton = useRef(); + const query = useQuery(); + const [loading, setLoading] = useState(true); + const [chats, setChats] = useState([]); + const [offset, setOffset] = useState(Number(query.get("offset") || 0)); + const [canNext, setCanNext] = useState(false); + const handleDumpChats = async (exportType) => { const chats = await System.exportChats(exportType); if (!!chats) { @@ -62,6 +68,18 @@ export default function WorkspaceChats() { } }; + const handleClearAllChats = async () => { + if ( + !window.confirm( + `Are you sure you want to clear all chats?\n\nThis action is irreversible.` + ) + ) + return false; + await System.deleteChat(-1); + setChats([]); + showToast("Cleared all chats.", "success"); + }; + const toggleMenu = () => { setShowMenu(!showMenu); }; @@ -83,6 +101,16 @@ export default function WorkspaceChats() { }; }, []); + useEffect(() => { + async function fetchChats() { + const { chats: _chats, hasPages = false } = await System.chats(offset); + setChats(_chats); + setCanNext(hasPages); + setLoading(false); + } + fetchChats(); + }, [offset]); + return (
@@ -100,7 +128,7 @@ export default function WorkspaceChats() {
+ {chats.length > 0 && ( + + )}

These are all the recorded chats and messages that have been sent by users ordered by their creation date.

- +
); } -function ChatsContainer() { - const query = useQuery(); - const [loading, setLoading] = useState(true); - const [chats, setChats] = useState([]); - const [offset, setOffset] = useState(Number(query.get("offset") || 0)); - const [canNext, setCanNext] = useState(false); - +function ChatsContainer({ + loading, + chats, + setChats, + offset, + setOffset, + canNext, +}) { const handlePrevious = () => { setOffset(Math.max(offset - 1, 0)); }; @@ -155,20 +200,11 @@ function ChatsContainer() { setOffset(offset + 1); }; - const handleDeleteChat = (chatId) => { + const handleDeleteChat = async (chatId) => { + await System.deleteChat(chatId); setChats((prevChats) => prevChats.filter((chat) => chat.id !== chatId)); }; - useEffect(() => { - async function fetchChats() { - const { chats: _chats, hasPages = false } = await System.chats(offset); - setChats(_chats); - setCanNext(hasPages); - setLoading(false); - } - fetchChats(); - }, [offset]); - if (loading) { return ( { try { const { id } = request.params; - await WorkspaceChats.delete({ id: Number(id) }); + Number(id) === -1 + ? await WorkspaceChats.delete({}, true) + : await WorkspaceChats.delete({ id: Number(id) }); response.json({ success: true, error: null }); } catch (e) { console.error(e); From 96c658b9ab91bc18534c39930651da440fc721ea Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Thu, 23 May 2024 08:27:18 -0700 Subject: [PATCH 12/21] K8 Manifest resolves #1463 --- cloud-deployments/k8/manifest.yaml | 214 +++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 cloud-deployments/k8/manifest.yaml diff --git a/cloud-deployments/k8/manifest.yaml b/cloud-deployments/k8/manifest.yaml new file mode 100644 index 00000000..9aeef6a2 --- /dev/null +++ b/cloud-deployments/k8/manifest.yaml @@ -0,0 +1,214 @@ +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: anything-llm-volume + annotations: + pv.beta.kubernetes.io/uid: "1000" + pv.beta.kubernetes.io/gid: "1000" +spec: + storageClassName: gp2 + capacity: + storage: 5Gi + accessModes: + - ReadWriteOnce + awsElasticBlockStore: + # This is the volume UUID from AWS EC2 EBS Volumes list. + volumeID: "{{ anythingllm_awsElasticBlockStore_volumeID }}" + fsType: ext4 + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: topology.kubernetes.io/zone + operator: In + values: + - us-east-1c +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: anything-llm-volume-claim + namespace: "{{ namespace }}" +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: anything-llm + namespace: "{{ namespace }}" + labels: + anything-llm: "true" +spec: + selector: + matchLabels: + k8s-app: anything-llm + replicas: 1 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0% + maxUnavailable: 100% + template: + metadata: + labels: + anything-llm: "true" + k8s-app: anything-llm + app.kubernetes.io/name: anything-llm + app.kubernetes.io/part-of: anything-llm + annotations: + prometheus.io/scrape: "true" + prometheus.io/path: /metrics + prometheus.io/port: "9090" + spec: + serviceAccountName: "default" + terminationGracePeriodSeconds: 10 + securityContext: + fsGroup: 1000 + runAsNonRoot: true + runAsGroup: 1000 + runAsUser: 1000 + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: topology.kubernetes.io/zone + operator: In + values: + - us-east-1c + containers: + - name: anything-llm + resources: + limits: + memory: "1Gi" + cpu: "500m" + requests: + memory: "512Mi" + cpu: "250m" + imagePullPolicy: IfNotPresent + image: "mintplexlabs/anythingllm:render" + securityContext: + allowPrivilegeEscalation: true + capabilities: + add: + - SYS_ADMIN + runAsNonRoot: true + runAsGroup: 1000 + runAsUser: 1000 + command: + # Specify a command to override the Dockerfile's ENTRYPOINT. + - /bin/bash + - -c + - | + set -x -e + sleep 3 + echo "AWS_REGION: $AWS_REGION" + echo "SERVER_PORT: $SERVER_PORT" + echo "NODE_ENV: $NODE_ENV" + echo "STORAGE_DIR: $STORAGE_DIR" + { + cd /app/server/ && + npx prisma generate --schema=./prisma/schema.prisma && + npx prisma migrate deploy --schema=./prisma/schema.prisma && + node /app/server/index.js + echo "Server process exited with status $?" + } & + { + node /app/collector/index.js + echo "Collector process exited with status $?" + } & + wait -n + exit $? + readinessProbe: + httpGet: + path: /v1/api/health + port: 8888 + initialDelaySeconds: 15 + periodSeconds: 5 + successThreshold: 2 + livenessProbe: + httpGet: + path: /v1/api/health + port: 8888 + initialDelaySeconds: 15 + periodSeconds: 5 + failureThreshold: 3 + env: + - name: AWS_REGION + value: "{{ aws_region }}" + - name: AWS_ACCESS_KEY_ID + value: "{{ aws_access_id }}" + - name: AWS_SECRET_ACCESS_KEY + value: "{{ aws_access_secret }}" + - name: SERVER_PORT + value: "3001" + - name: JWT_SECRET + value: "my-random-string-for-seeding" # Please generate random string at least 12 chars long. + - name: STORAGE_DIR + value: "/storage" + - name: NODE_ENV + value: "production" + - name: UID + value: "1000" + - name: GID + value: "1000" + volumeMounts: + - name: anything-llm-server-storage-volume-mount + mountPath: /storage + volumes: + - name: anything-llm-server-storage-volume-mount + persistentVolumeClaim: + claimName: anything-llm-volume-claim +--- +# This serves the UI and the backend. +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: anything-llm-ingress + namespace: "{{ namespace }}" + annotations: + external-dns.alpha.kubernetes.io/hostname: "{{ namespace }}-chat.{{ base_domain }}" + kubernetes.io/ingress.class: "internal-ingress" + nginx.ingress.kubernetes.io/rewrite-target: / + ingress.kubernetes.io/ssl-redirect: "false" +spec: + rules: + - host: "{{ namespace }}-chat.{{ base_domain }}" + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: anything-llm-svc + port: + number: 3001 + tls: # < placing a host in the TLS config will indicate a cert should be created + - hosts: + - "{{ namespace }}-chat.{{ base_domain }}" + secretName: letsencrypt-prod +--- +apiVersion: v1 +kind: Service +metadata: + labels: + kubernetes.io/name: anything-llm + name: anything-llm-svc + namespace: "{{ namespace }}" +spec: + ports: + # "port" is external port, and "targetPort" is internal. + - port: 3301 + targetPort: 3001 + name: traffic + - port: 9090 + targetPort: 9090 + name: metrics + selector: + k8s-app: anything-llm \ No newline at end of file From 961630f7d5051766fe76296cc9d3bdc24afbb93f Mon Sep 17 00:00:00 2001 From: timothycarambat Date: Thu, 23 May 2024 08:29:25 -0700 Subject: [PATCH 13/21] fix example call param --- server/utils/agents/aibitat/plugins/web-scraping.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/utils/agents/aibitat/plugins/web-scraping.js b/server/utils/agents/aibitat/plugins/web-scraping.js index 2888445b..f5c8d41f 100644 --- a/server/utils/agents/aibitat/plugins/web-scraping.js +++ b/server/utils/agents/aibitat/plugins/web-scraping.js @@ -20,11 +20,11 @@ const webScraping = { examples: [ { prompt: "What is useanything.com about?", - call: JSON.stringify({ uri: "https://useanything.com" }), + call: JSON.stringify({ url: "https://useanything.com" }), }, { prompt: "Scrape https://example.com", - call: JSON.stringify({ uri: "https://example.com" }), + call: JSON.stringify({ url: "https://example.com" }), }, ], parameters: { From cc7e7fb3ac493765d3887b12792120391e77f45f Mon Sep 17 00:00:00 2001 From: Sean Hatfield Date: Thu, 23 May 2024 09:42:30 -0700 Subject: [PATCH 14/21] [FEAT] Add support for gemini-1.5-flash-latest model (#1502) * add support for gemini-1.5-flash-latest * update comment in gemini LLM provider --- .../LLMSelection/GeminiLLMOptions/index.jsx | 6 +++++- frontend/src/hooks/useGetProvidersModels.js | 2 +- server/utils/AiProviders/gemini/index.js | 14 +++++++++++--- server/utils/helpers/updateENV.js | 6 +++++- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/LLMSelection/GeminiLLMOptions/index.jsx b/frontend/src/components/LLMSelection/GeminiLLMOptions/index.jsx index d2846704..87e05882 100644 --- a/frontend/src/components/LLMSelection/GeminiLLMOptions/index.jsx +++ b/frontend/src/components/LLMSelection/GeminiLLMOptions/index.jsx @@ -30,7 +30,11 @@ export default function GeminiLLMOptions({ settings }) { required={true} className="bg-zinc-900 border-gray-500 text-white text-sm rounded-lg block w-full p-2.5" > - {["gemini-pro", "gemini-1.5-pro-latest"].map((model) => { + {[ + "gemini-pro", + "gemini-1.5-pro-latest", + "gemini-1.5-flash-latest", + ].map((model) => { return (
diff --git a/frontend/src/components/Modals/Password/index.jsx b/frontend/src/components/Modals/Password/index.jsx index 9305d032..8f86b611 100644 --- a/frontend/src/components/Modals/Password/index.jsx +++ b/frontend/src/components/Modals/Password/index.jsx @@ -9,10 +9,9 @@ import { } from "../../../utils/constants"; import useLogo from "../../../hooks/useLogo"; import illustration from "@/media/illustrations/login-illustration.svg"; -import loginLogo from "@/media/illustrations/login-logo.svg"; export default function PasswordModal({ mode = "single" }) { - const { logo: _initLogo } = useLogo(); + const { loginLogo } = useLogo(); return (
logo {mode === "single" ? : }
diff --git a/frontend/src/hooks/useLogo.js b/frontend/src/hooks/useLogo.js index 4834b7a8..9ae741f7 100644 --- a/frontend/src/hooks/useLogo.js +++ b/frontend/src/hooks/useLogo.js @@ -2,6 +2,6 @@ import { useContext } from "react"; import { LogoContext } from "../LogoContext"; export default function useLogo() { - const { logo, setLogo } = useContext(LogoContext); - return { logo, setLogo }; + const { logo, setLogo, loginLogo, isCustomLogo } = useContext(LogoContext); + return { logo, setLogo, loginLogo, isCustomLogo }; } diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index f8f12344..d2252be1 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -6,6 +6,7 @@ const System = { cacheKeys: { footerIcons: "anythingllm_footer_links", supportEmail: "anythingllm_support_email", + customAppName: "anythingllm_custom_app_name", }, ping: async function () { return await fetch(`${API_BASE}/ping`) @@ -305,19 +306,58 @@ const System = { ); return { email: supportEmail, error: null }; }, + + fetchCustomAppName: async function () { + const cache = window.localStorage.getItem(this.cacheKeys.customAppName); + const { appName, lastFetched } = cache + ? safeJsonParse(cache, { appName: "", lastFetched: 0 }) + : { appName: "", lastFetched: 0 }; + + if (!!appName && Date.now() - lastFetched < 3_600_000) + return { appName: appName, error: null }; + + const { customAppName, error } = await fetch( + `${API_BASE}/system/custom-app-name`, + { + method: "GET", + cache: "no-cache", + headers: baseHeaders(), + } + ) + .then((res) => res.json()) + .catch((e) => { + console.log(e); + return { customAppName: "", error: e.message }; + }); + + if (!customAppName || !!error) { + window.localStorage.removeItem(this.cacheKeys.customAppName); + return { appName: "", error: null }; + } + + window.localStorage.setItem( + this.cacheKeys.customAppName, + JSON.stringify({ appName: customAppName, lastFetched: Date.now() }) + ); + return { appName: customAppName, error: null }; + }, fetchLogo: async function () { return await fetch(`${API_BASE}/system/logo`, { method: "GET", cache: "no-cache", }) - .then((res) => { - if (res.ok && res.status !== 204) return res.blob(); + .then(async (res) => { + if (res.ok && res.status !== 204) { + const isCustomLogo = res.headers.get("X-Is-Custom-Logo") === "true"; + const blob = await res.blob(); + const logoURL = URL.createObjectURL(blob); + return { isCustomLogo, logoURL }; + } throw new Error("Failed to fetch logo!"); }) - .then((blob) => URL.createObjectURL(blob)) .catch((e) => { console.log(e); - return null; + return { isCustomLogo: false, logoURL: null }; }); }, fetchPfp: async function (id) { diff --git a/frontend/src/pages/GeneralSettings/Appearance/CustomAppName/index.jsx b/frontend/src/pages/GeneralSettings/Appearance/CustomAppName/index.jsx new file mode 100644 index 00000000..48efa508 --- /dev/null +++ b/frontend/src/pages/GeneralSettings/Appearance/CustomAppName/index.jsx @@ -0,0 +1,100 @@ +import Admin from "@/models/admin"; +import System from "@/models/system"; +import showToast from "@/utils/toast"; +import { useEffect, useState } from "react"; + +export default function CustomAppName() { + const [loading, setLoading] = useState(true); + const [hasChanges, setHasChanges] = useState(false); + const [customAppName, setCustomAppName] = useState(""); + const [originalAppName, setOriginalAppName] = useState(""); + const [canCustomize, setCanCustomize] = useState(false); + + useEffect(() => { + const fetchInitialParams = async () => { + const settings = await System.keys(); + if (!settings?.MultiUserMode && !settings?.RequiresAuth) { + setCanCustomize(false); + return false; + } + + const { appName } = await System.fetchCustomAppName(); + setCustomAppName(appName || ""); + setOriginalAppName(appName || ""); + setCanCustomize(true); + setLoading(false); + }; + fetchInitialParams(); + }, []); + + const updateCustomAppName = async (e, newValue = null) => { + e.preventDefault(); + let custom_app_name = newValue; + if (newValue === null) { + const form = new FormData(e.target); + custom_app_name = form.get("customAppName"); + } + const { success, error } = await Admin.updateSystemPreferences({ + custom_app_name, + }); + if (!success) { + showToast(`Failed to update custom app name: ${error}`, "error"); + return; + } else { + showToast("Successfully updated custom app name.", "success"); + window.localStorage.removeItem(System.cacheKeys.customAppName); + setCustomAppName(custom_app_name); + setOriginalAppName(custom_app_name); + setHasChanges(false); + } + }; + + const handleChange = (e) => { + setCustomAppName(e.target.value); + setHasChanges(true); + }; + + if (!canCustomize || loading) return null; + + return ( + +
+

+ Custom App Name +

+

+ Set a custom app name that is displayed on the login page. +

+
+
+ + {originalAppName !== "" && ( + + )} +
+ {hasChanges && ( + + )} + + ); +} diff --git a/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx b/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx index 8b2b5cab..5de37e3f 100644 --- a/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx +++ b/frontend/src/pages/GeneralSettings/Appearance/CustomLogo/index.jsx @@ -2,7 +2,6 @@ import useLogo from "@/hooks/useLogo"; import System from "@/models/system"; import showToast from "@/utils/toast"; import { useEffect, useRef, useState } from "react"; -import AnythingLLM from "@/media/logo/anything-llm.png"; import { Plus } from "@phosphor-icons/react"; export default function CustomLogo() { @@ -36,7 +35,7 @@ export default function CustomLogo() { return; } - const logoURL = await System.fetchLogo(); + const { logoURL } = await System.fetchLogo(); _setLogo(logoURL); showToast("Image uploaded successfully.", "success"); @@ -51,13 +50,13 @@ export default function CustomLogo() { if (!success) { console.error("Failed to remove logo:", error); showToast(`Failed to remove logo: ${error}`, "error"); - const logoURL = await System.fetchLogo(); + const { logoURL } = await System.fetchLogo(); setLogo(logoURL); setIsDefaultLogo(false); return; } - const logoURL = await System.fetchLogo(); + const { logoURL } = await System.fetchLogo(); _setLogo(logoURL); showToast("Image successfully removed.", "success"); diff --git a/frontend/src/pages/GeneralSettings/Appearance/index.jsx b/frontend/src/pages/GeneralSettings/Appearance/index.jsx index bb2c7989..d7352998 100644 --- a/frontend/src/pages/GeneralSettings/Appearance/index.jsx +++ b/frontend/src/pages/GeneralSettings/Appearance/index.jsx @@ -4,6 +4,7 @@ import FooterCustomization from "./FooterCustomization"; import SupportEmail from "./SupportEmail"; import CustomLogo from "./CustomLogo"; import CustomMessages from "./CustomMessages"; +import CustomAppName from "./CustomAppName"; export default function Appearance() { return ( @@ -25,6 +26,7 @@ export default function Appearance() {

+ diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index 9b836b19..59d64544 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -355,6 +355,9 @@ function adminEndpoints(app) { ?.value, [] ) || [], + custom_app_name: + (await SystemSettings.get({ label: "custom_app_name" }))?.value || + null, }; response.status(200).json({ settings }); } catch (e) { diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 472e3aa7..6ab30c5c 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -526,17 +526,24 @@ function systemEndpoints(app) { const defaultFilename = getDefaultFilename(); const logoPath = await determineLogoFilepath(defaultFilename); const { found, buffer, size, mime } = fetchLogo(logoPath); + if (!found) { response.sendStatus(204).end(); return; } + const currentLogoFilename = await SystemSettings.currentLogoFilename(); response.writeHead(200, { + "Access-Control-Expose-Headers": + "Content-Disposition,X-Is-Custom-Logo,Content-Type,Content-Length", "Content-Type": mime || "image/png", "Content-Disposition": `attachment; filename=${path.basename( logoPath )}`, "Content-Length": size, + "X-Is-Custom-Logo": + currentLogoFilename !== null && + currentLogoFilename !== defaultFilename, }); response.end(Buffer.from(buffer, "base64")); return; @@ -573,6 +580,22 @@ function systemEndpoints(app) { } }); + // No middleware protection in order to get this on the login page + app.get("/system/custom-app-name", async (_, response) => { + try { + const customAppName = + ( + await SystemSettings.get({ + label: "custom_app_name", + }) + )?.value ?? null; + response.status(200).json({ customAppName: customAppName }); + } catch (error) { + console.error("Error fetching custom app name:", error); + response.status(500).json({ message: "Internal server error" }); + } + }); + app.get( "/system/pfp/:id", [validatedRequest, flexUserRoleValid([ROLES.all])], diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index 70913fd9..52393a02 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -27,6 +27,7 @@ const SystemSettings = { "agent_search_provider", "default_agent_skills", "agent_sql_connections", + "custom_app_name", ], validations: { footer_data: (updates) => {