From 31fbb0784bdca266ad7a84f43c642cd639b408e5 Mon Sep 17 00:00:00 2001
From: Sean Hatfield <seanhatfield5@gmail.com>
Date: Wed, 16 Aug 2023 17:30:46 -0700
Subject: [PATCH] Custom default messages implementation for single and
 multi-user modes (#193)

* added ui for custom welcome messages and added label for custom logo in admin settings

* linting

* fixing img to use light/dark modes

* converted ChatBubble into component

* implemented backend for welcome messages and admin appearance page

* completed custom welcome messages for admin

* finished custom messages for single user mode

* merged with master and linted

* improved UI for appearance settings pages

* linted and merged with master

* small updates

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
---
 frontend/src/components/ChatBubble/index.jsx  |  29 +++
 frontend/src/components/DefaultChat/index.jsx |  33 ++-
 .../components/EditingChatBubble/index.jsx    |  67 +++++
 frontend/src/models/admin.js                  |  16 ++
 frontend/src/models/system.js                 |  32 +++
 frontend/src/pages/Admin/Appearance/index.jsx | 188 +++++++++++---
 frontend/src/pages/System/Appearance.jsx      | 239 ++++++++++++++----
 server/endpoints/system.js                    |  48 ++++
 server/models/welcomeMessages.js              |  89 +++++++
 server/utils/database/index.js                |   2 +
 10 files changed, 653 insertions(+), 90 deletions(-)
 create mode 100644 frontend/src/components/ChatBubble/index.jsx
 create mode 100644 frontend/src/components/EditingChatBubble/index.jsx
 create mode 100644 server/models/welcomeMessages.js

diff --git a/frontend/src/components/ChatBubble/index.jsx b/frontend/src/components/ChatBubble/index.jsx
new file mode 100644
index 000000000..7ae52cb63
--- /dev/null
+++ b/frontend/src/components/ChatBubble/index.jsx
@@ -0,0 +1,29 @@
+import React from "react";
+
+export default function ChatBubble({ message, type, popMsg }) {
+  const isUser = type === "user";
+
+  return (
+    <div
+      className={`flex w-full mt-2 items-center ${
+        popMsg ? "chat__message" : ""
+      } ${isUser ? "justify-end" : "justify-start"}`}
+    >
+      <div
+        className={`p-4 max-w-full md:max-w-[75%] ${
+          isUser
+            ? "bg-slate-200 dark:bg-amber-800"
+            : "bg-orange-100 dark:bg-stone-700"
+        } rounded-b-2xl ${isUser ? "rounded-tl-2xl" : "rounded-tr-2xl"} ${
+          isUser ? "rounded-tr-sm" : "rounded-tl-sm"
+        }`}
+      >
+        {message && (
+          <p className="text-slate-800 dark:text-slate-200 font-[500] md:font-semibold text-sm md:text-base">
+            {message}
+          </p>
+        )}
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/components/DefaultChat/index.jsx b/frontend/src/components/DefaultChat/index.jsx
index 0952ebd73..1e993a4d5 100644
--- a/frontend/src/components/DefaultChat/index.jsx
+++ b/frontend/src/components/DefaultChat/index.jsx
@@ -6,9 +6,12 @@ import NewWorkspaceModal, {
 import paths from "../../utils/paths";
 import { isMobile } from "react-device-detect";
 import { SidebarMobileHeader } from "../Sidebar";
+import ChatBubble from "../ChatBubble";
+import System from "../../models/system";
 
 export default function DefaultChatContainer() {
   const [mockMsgs, setMockMessages] = useState([]);
+  const [fetchedMessages, setFetchedMessages] = useState([]);
   const {
     showing: showingNewWsModal,
     showModal: showNewWsModal,
@@ -16,6 +19,14 @@ export default function DefaultChatContainer() {
   } = useNewWorkspaceModal();
   const popMsg = !window.localStorage.getItem("anythingllm_intro");
 
+  useEffect(() => {
+    const fetchData = async () => {
+      const fetchedMessages = await System.getWelcomeMessages();
+      setFetchedMessages(fetchedMessages);
+    };
+    fetchData();
+  }, []);
+
   const MESSAGES = [
     <React.Fragment>
       <div
@@ -251,9 +262,25 @@ export default function DefaultChatContainer() {
       className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-white dark:bg-black-900 md:min-w-[82%] p-[18px] h-full overflow-y-scroll"
     >
       {isMobile && <SidebarMobileHeader />}
-      {mockMsgs.map((content, i) => {
-        return <React.Fragment key={i}>{content}</React.Fragment>;
-      })}
+      {fetchedMessages.length === 0
+        ? mockMsgs.map((content, i) => {
+            return <React.Fragment key={i}>{content}</React.Fragment>;
+          })
+        : fetchedMessages.map((fetchedMessage, i) => {
+            return (
+              <React.Fragment key={i}>
+                <ChatBubble
+                  message={
+                    fetchedMessage.user === ""
+                      ? fetchedMessage.response
+                      : fetchedMessage.user
+                  }
+                  type={fetchedMessage.user === "" ? "response" : "user"}
+                  popMsg={popMsg}
+                />
+              </React.Fragment>
+            );
+          })}
       {showingNewWsModal && <NewWorkspaceModal hideModal={hideNewWsModal} />}
     </div>
   );
diff --git a/frontend/src/components/EditingChatBubble/index.jsx b/frontend/src/components/EditingChatBubble/index.jsx
new file mode 100644
index 000000000..7d738ee01
--- /dev/null
+++ b/frontend/src/components/EditingChatBubble/index.jsx
@@ -0,0 +1,67 @@
+import React, { useState } from "react";
+import { X } from "react-feather";
+
+export default function EditingChatBubble({
+  message,
+  index,
+  type,
+  handleMessageChange,
+  removeMessage,
+}) {
+  const [isEditing, setIsEditing] = useState(false);
+  const [tempMessage, setTempMessage] = useState(message[type]);
+  const isUser = type === "user";
+
+  return (
+    <div
+      className={`flex w-full mt-2 items-center ${
+        isUser ? "justify-end" : "justify-start"
+      }`}
+    >
+      {isUser && (
+        <button
+          className="flex items-center text-red-500 hover:text-red-700 transition mr-2"
+          onClick={() => removeMessage(index)}
+        >
+          <X className="mr-2" size={20} />
+        </button>
+      )}
+      <div
+        className={`p-4 max-w-full md:max-w-[75%] ${
+          isUser
+            ? "bg-slate-200 dark:bg-amber-800"
+            : "bg-orange-100 dark:bg-stone-700"
+        } rounded-b-2xl ${isUser ? "rounded-tl-2xl" : "rounded-tr-2xl"} ${
+          isUser ? "rounded-tr-sm" : "rounded-tl-sm"
+        }`}
+        onDoubleClick={() => setIsEditing(true)}
+      >
+        {isEditing ? (
+          <input
+            value={tempMessage}
+            onChange={(e) => setTempMessage(e.target.value)}
+            onBlur={() => {
+              handleMessageChange(index, type, tempMessage);
+              setIsEditing(false);
+            }}
+            autoFocus
+          />
+        ) : (
+          tempMessage && (
+            <p className="text-slate-800 dark:text-slate-200 font-[500] md:font-semibold text-sm md:text-base">
+              {tempMessage}
+            </p>
+          )
+        )}
+      </div>
+      {!isUser && (
+        <button
+          className="flex items-center text-red-500 hover:text-red-700 transition ml-2"
+          onClick={() => removeMessage(index)}
+        >
+          <X className="mr-2" size={20} />
+        </button>
+      )}
+    </div>
+  );
+}
diff --git a/frontend/src/models/admin.js b/frontend/src/models/admin.js
index 21d40a3e9..e98a18707 100644
--- a/frontend/src/models/admin.js
+++ b/frontend/src/models/admin.js
@@ -216,6 +216,22 @@ const Admin = {
         return { success: false, error: e.message };
       });
   },
+  setWelcomeMessages: async function (messages) {
+    return fetch(`${API_BASE}/system/set-welcome-messages`, {
+      method: "POST",
+      headers: baseHeaders(),
+      body: JSON.stringify({ messages }),
+    })
+      .then((res) => {
+        if (!res.ok)
+          throw new Error(res.statusText || "Error setting welcome messages.");
+        return res.json();
+      })
+      .catch((e) => {
+        console.error(e);
+        return { success: false, error: e.message };
+      });
+  },
 };
 
 export default Admin;
diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js
index d3f0f7e6a..2405d283e 100644
--- a/frontend/src/models/system.js
+++ b/frontend/src/models/system.js
@@ -188,6 +188,38 @@ const System = {
         return { success: false, error: e.message };
       });
   },
+  getWelcomeMessages: async function () {
+    return await fetch(`${API_BASE}/system/welcome-messages`, {
+      method: "GET",
+      cache: "no-cache",
+    })
+      .then((res) => {
+        if (!res.ok) throw new Error("Could not fetch welcome messages.");
+        return res.json();
+      })
+      .then((res) => res.welcomeMessages)
+      .catch((e) => {
+        console.error(e);
+        return null;
+      });
+  },
+  setWelcomeMessages: async function (messages) {
+    return fetch(`${API_BASE}/system/set-welcome-messages`, {
+      method: "POST",
+      headers: baseHeaders(),
+      body: JSON.stringify({ messages }),
+    })
+      .then((res) => {
+        if (!res.ok) {
+          throw new Error(res.statusText || "Error setting welcome messages.");
+        }
+        return { success: true, ...res.json() };
+      })
+      .catch((e) => {
+        console.error(e);
+        return { success: false, error: e.message };
+      });
+  },
 };
 
 export default System;
diff --git a/frontend/src/pages/Admin/Appearance/index.jsx b/frontend/src/pages/Admin/Appearance/index.jsx
index e9dc51486..89e161658 100644
--- a/frontend/src/pages/Admin/Appearance/index.jsx
+++ b/frontend/src/pages/Admin/Appearance/index.jsx
@@ -7,12 +7,16 @@ import AnythingLLMDark from "../../../media/logo/anything-llm-dark.png";
 import usePrefersDarkMode from "../../../hooks/usePrefersDarkMode";
 import useLogo from "../../../hooks/useLogo";
 import System from "../../../models/system";
+import EditingChatBubble from "../../../components/EditingChatBubble";
 
 export default function Appearance() {
   const { logo: _initLogo } = useLogo();
   const [logo, setLogo] = useState("");
   const prefersDarkMode = usePrefersDarkMode();
   const [errorMsg, setErrorMsg] = useState("");
+  const [successMsg, setSuccessMsg] = useState("");
+  const [hasChanges, setHasChanges] = useState(false);
+  const [messages, setMessages] = useState([]);
 
   useEffect(() => {
     async function setInitLogo() {
@@ -27,7 +31,21 @@ export default function Appearance() {
         setErrorMsg("");
       }, 3_500);
     }
-  }, [errorMsg]);
+
+    if (!!successMsg) {
+      setTimeout(() => {
+        setSuccessMsg("");
+      }, 3_500);
+    }
+  }, [errorMsg, successMsg]);
+
+  useEffect(() => {
+    async function fetchMessages() {
+      const messages = await System.getWelcomeMessages();
+      setMessages(messages);
+    }
+    fetchMessages();
+  }, []);
 
   const handleFileUpload = async (event) => {
     const file = event.target.files[0];
@@ -62,6 +80,42 @@ export default function Appearance() {
     window.location.reload();
   };
 
+  const addMessage = (type) => {
+    if (type === "user") {
+      setMessages([
+        ...messages,
+        { user: "Double click to edit...", response: "" },
+      ]);
+    } else {
+      setMessages([
+        ...messages,
+        { user: "", response: "Double click to edit..." },
+      ]);
+    }
+  };
+
+  const removeMessage = (index) => {
+    setHasChanges(true);
+    setMessages(messages.filter((_, i) => i !== index));
+  };
+
+  const handleMessageChange = (index, type, value) => {
+    setHasChanges(true);
+    const newMessages = [...messages];
+    newMessages[index][type] = value;
+    setMessages(newMessages);
+  };
+
+  const handleMessageSave = async () => {
+    const { success, error } = await Admin.setWelcomeMessages(messages);
+    if (!success) {
+      setErrorMsg(error);
+      return;
+    }
+    setSuccessMsg("Successfully updated welcome messages.");
+    setHasChanges(false);
+  };
+
   return (
     <div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex">
       {!isMobile && <Sidebar />}
@@ -79,48 +133,118 @@ export default function Appearance() {
               Customize the appearance settings of your platform.
             </p>
           </div>
-
-          <div className="flex items-center">
-            <img
-              src={logo}
-              alt="Uploaded Logo"
-              className="w-48 h-48 object-contain mr-6"
-              onError={(e) =>
-                (e.target.src = prefersDarkMode
-                  ? AnythingLLMLight
-                  : AnythingLLMDark)
-              }
-            />
-
-            <div className="flex flex-col">
-              <div className="mb-4">
-                <label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
-                  Upload Image
-                  <input
-                    type="file"
-                    accept="image/*"
-                    className="hidden"
-                    onChange={handleFileUpload}
-                  />
-                </label>
+          <div className="mb-6">
+            <div className="flex flex-col gap-y-2">
+              <h2 className="leading-tight font-medium text-black dark:text-white">
+                Custom Logo
+              </h2>
+              <p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
+                Change the logo that appears in the sidebar.
+              </p>
+            </div>
+            <div className="flex items-center">
+              <img
+                src={logo}
+                alt="Uploaded Logo"
+                className="w-48 h-48 object-contain mr-6"
+                onError={(e) =>
+                  (e.target.src = prefersDarkMode
+                    ? AnythingLLMLight
+                    : AnythingLLMDark)
+                }
+              />
+              <div className="flex flex-col">
+                <div className="mb-4">
+                  <label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
+                    Upload Image
+                    <input
+                      type="file"
+                      accept="image/*"
+                      className="hidden"
+                      onChange={handleFileUpload}
+                    />
+                  </label>
+                  <button
+                    onClick={handleRemoveLogo}
+                    className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
+                  >
+                    Remove Custom Logo
+                  </button>
+                </div>
+                <div className="text-sm text-gray-600 dark:text-gray-300">
+                  Upload your logo. Recommended size: 800x200.
+                </div>
+              </div>
+            </div>
+          </div>
+          <div className="mb-6">
+            <div className="flex flex-col gap-y-2">
+              <h2 className="leading-tight font-medium text-black dark:text-white">
+                Custom Messages
+              </h2>
+              <p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
+                Change the default messages that are displayed to the users.
+              </p>
+            </div>
+            <div className="mt-6 flex flex-col gap-y-6">
+              {messages.map((message, index) => (
+                <div key={index} className="flex flex-col gap-y-2">
+                  {message.user && (
+                    <EditingChatBubble
+                      message={message}
+                      index={index}
+                      type="user"
+                      handleMessageChange={handleMessageChange}
+                      removeMessage={removeMessage}
+                    />
+                  )}
+                  {message.response && (
+                    <EditingChatBubble
+                      message={message}
+                      index={index}
+                      type="response"
+                      handleMessageChange={handleMessageChange}
+                      removeMessage={removeMessage}
+                    />
+                  )}
+                </div>
+              ))}
+              <div className="flex gap-4 mt-4 justify-between">
                 <button
-                  onClick={handleRemoveLogo}
-                  className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
+                  className="self-end text-orange-500 hover:text-orange-700 transition"
+                  onClick={() => addMessage("response")}
                 >
-                  Remove Custom Logo
+                  + System Message
+                </button>
+                <button
+                  className="self-end text-orange-500 hover:text-orange-700 transition"
+                  onClick={() => addMessage("user")}
+                >
+                  + User Message
                 </button>
-              </div>
-              <div className="text-sm text-gray-600 dark:text-gray-300">
-                Upload your logo. Recommended size: 800x200.
               </div>
             </div>
+            {hasChanges && (
+              <div className="flex justify-center py-6">
+                <button
+                  className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
+                  onClick={handleMessageSave}
+                >
+                  Save Messages
+                </button>
+              </div>
+            )}
           </div>
-
           {errorMsg && (
             <div className="mt-4 text-sm text-red-600 dark:text-red-400 text-center">
               {errorMsg}
             </div>
           )}
+          {successMsg && (
+            <div className="mt-4 text-sm text-green-600 dark:text-green-400 text-center">
+              {successMsg}
+            </div>
+          )}
         </div>
       </div>
     </div>
diff --git a/frontend/src/pages/System/Appearance.jsx b/frontend/src/pages/System/Appearance.jsx
index f840e4143..df815b042 100644
--- a/frontend/src/pages/System/Appearance.jsx
+++ b/frontend/src/pages/System/Appearance.jsx
@@ -4,6 +4,10 @@ import AnythingLLMDark from "../../media/logo/anything-llm-dark.png";
 import System from "../../models/system";
 import usePrefersDarkMode from "../../hooks/usePrefersDarkMode";
 import useLogo from "../../hooks/useLogo";
+import EditingChatBubble from "../../components/EditingChatBubble";
+import { isMobile } from "react-device-detect";
+import { ArrowLeft } from "react-feather";
+import paths from "../../utils/paths";
 
 export default function Appearance() {
   const { logo: _initLogo } = useLogo();
@@ -11,6 +15,16 @@ export default function Appearance() {
   const [logo, setLogo] = useState("");
   const [errorMsg, setErrorMsg] = useState("");
   const [successMsg, setSuccessMsg] = useState("");
+  const [hasChanges, setHasChanges] = useState(false);
+  const [messages, setMessages] = useState([]);
+
+  useEffect(() => {
+    async function fetchMessages() {
+      const messages = await System.getWelcomeMessages();
+      setMessages(messages);
+    }
+    fetchMessages();
+  }, []);
 
   useEffect(() => {
     async function setInitLogo() {
@@ -68,66 +82,181 @@ export default function Appearance() {
     setErrorMsg("");
   };
 
+  const addMessage = (type) => {
+    if (type === "user") {
+      setMessages([
+        ...messages,
+        { user: "Double click to edit...", response: "" },
+      ]);
+    } else {
+      setMessages([
+        ...messages,
+        { user: "", response: "Double click to edit..." },
+      ]);
+    }
+  };
+
+  const removeMessage = (index) => {
+    setHasChanges(true);
+    setMessages(messages.filter((_, i) => i !== index));
+  };
+
+  const handleMessageChange = (index, type, value) => {
+    setHasChanges(true);
+    const newMessages = [...messages];
+    newMessages[index][type] = value;
+    setMessages(newMessages);
+  };
+
+  const handleMessageSave = async () => {
+    const { success, error } = await System.setWelcomeMessages(messages);
+    if (!success) {
+      setErrorMsg(error);
+      return;
+    }
+    setSuccessMsg("Successfully updated welcome messages.");
+    setHasChanges(false);
+  };
+
+  const handleBackNavigation = () => {
+    window.location = paths.home();
+  };
+
   return (
-    <div className="min-h-screen flex items-center justify-center bg-orange-100 dark:bg-black-900">
-      <div className="p-6 w-full max-w-xl bg-white dark:bg-stone-600 rounded-xl shadow-md space-y-4">
-        <h2 className="text-2xl font-bold text-center text-black dark:text-white">
-          Customize Appearance
-        </h2>
-        <p className="text-center text-xs font-light text-black dark:text-white">
-          Customize the logo you see on the sidebar
-        </p>
-
-        <div className="flex flex-col items-center border border-slate-200 dark:border-black-900 p-6 rounded-xl">
-          <img
-            src={logo}
-            alt="Uploaded Logo"
-            className="w-48 h-48 object-contain"
-            onError={(e) =>
-              (e.target.src = prefersDarkMode
-                ? AnythingLLMLight
-                : AnythingLLMDark)
-            }
-          />
-          <div className="flex gap-2 p-2 flex-col items-center">
-            <div className="text-sm text-gray-600 dark:text-gray-300">
-              Upload your logo
-            </div>
-            <div className="text-sm text-gray-600 dark:text-gray-300">
-              Recommended size at least 800x200
+    <div className="w-screen h-screen overflow-hidden bg-orange-100 dark:bg-stone-700 flex justify-center py-6">
+      <div
+        style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
+        className="transition-all duration-500 relative md:ml-[2px] md:mr-[8px] md:my-[16px] md:rounded-[26px] bg-white dark:bg-black-900 md:min-w-[82%] p-[18px] h-full overflow-y-scroll"
+      >
+        <div className="px-1 md:px-8">
+          <div className="mb-6">
+            <div
+              className="cursor-pointer inline-flex items-center gap-3 mb-5 py-2 pl-2 pr-4 text-white rounded-md hover:bg-gray-300 dark:hover:bg-gray-800 transition-all"
+              onClick={handleBackNavigation}
+            >
+              <ArrowLeft />
+              <span>Back</span>
             </div>
+            <p className="text-3xl font-semibold text-slate-600 dark:text-slate-200">
+              Appearance Settings
+            </p>
+            <p className="mt-2 text-sm font-base text-slate-600 dark:text-slate-200">
+              Customize the appearance settings of your platform.
+            </p>
           </div>
-        </div>
-
-        <div className="flex justify-center mt-4 gap-2">
-          <label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
-            Upload Image
-            <input
-              type="file"
-              accept="image/*"
-              className="hidden"
-              onChange={handleFileUpload}
-            />
-          </label>
-          <button
-            onClick={handleRemoveLogo}
-            className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
-          >
-            Remove Custom Logo
-          </button>
-        </div>
-
-        {errorMsg && (
-          <div className="text-sm text-red-600 dark:text-red-400 text-center">
-            {errorMsg}
+          <div className="mb-6">
+            <div className="flex flex-col gap-y-2">
+              <h2 className="leading-tight font-medium text-black dark:text-white">
+                Custom Logo
+              </h2>
+              <p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
+                Change the logo that appears in the sidebar.
+              </p>
+            </div>
+            <div className="flex items-center">
+              <img
+                src={logo}
+                alt="Uploaded Logo"
+                className="w-48 h-48 object-contain mr-6"
+                onError={(e) =>
+                  (e.target.src = prefersDarkMode
+                    ? AnythingLLMLight
+                    : AnythingLLMDark)
+                }
+              />
+              <div className="flex flex-col">
+                <div className="mb-4">
+                  <label className="cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
+                    Upload Image
+                    <input
+                      type="file"
+                      accept="image/*"
+                      className="hidden"
+                      onChange={handleFileUpload}
+                    />
+                  </label>
+                  <button
+                    onClick={handleRemoveLogo}
+                    className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
+                  >
+                    Remove Custom Logo
+                  </button>
+                </div>
+                <div className="text-sm text-gray-600 dark:text-gray-300">
+                  Upload your logo. Recommended size: 800x200.
+                </div>
+              </div>
+            </div>
           </div>
-        )}
-
-        {successMsg && (
-          <div className="text-sm text-green-600 dark:text-green-400 text-center">
-            {successMsg}
+          <div className="mb-6">
+            <div className="flex flex-col gap-y-2">
+              <h2 className="leading-tight font-medium text-black dark:text-white">
+                Custom Messages
+              </h2>
+              <p className="leading-tight text-sm text-gray-500 dark:text-slate-400">
+                Change the default messages that are displayed to the users.
+              </p>
+            </div>
+            <div className="mt-6 flex flex-col gap-y-6">
+              {messages.map((message, index) => (
+                <div key={index} className="flex flex-col gap-y-2">
+                  {message.user && (
+                    <EditingChatBubble
+                      message={message}
+                      index={index}
+                      type="user"
+                      handleMessageChange={handleMessageChange}
+                      removeMessage={removeMessage}
+                    />
+                  )}
+                  {message.response && (
+                    <EditingChatBubble
+                      message={message}
+                      index={index}
+                      type="response"
+                      handleMessageChange={handleMessageChange}
+                      removeMessage={removeMessage}
+                    />
+                  )}
+                </div>
+              ))}
+              <div className="flex gap-4 mt-4 justify-between">
+                <button
+                  className="self-end text-orange-500 hover:text-orange-700 transition"
+                  onClick={() => addMessage("response")}
+                >
+                  + System Message
+                </button>
+                <button
+                  className="self-end text-orange-500 hover:text-orange-700 transition"
+                  onClick={() => addMessage("user")}
+                >
+                  + User Message
+                </button>
+              </div>
+            </div>
+            {hasChanges && (
+              <div className="flex justify-center py-6">
+                <button
+                  className="ml-4 cursor-pointer text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600"
+                  onClick={handleMessageSave}
+                >
+                  Save Messages
+                </button>
+              </div>
+            )}
           </div>
-        )}
+          {errorMsg && (
+            <div className="mt-4 text-sm text-red-600 dark:text-red-400 text-center">
+              {errorMsg}
+            </div>
+          )}
+          {successMsg && (
+            <div className="mt-4 text-sm text-green-600 dark:text-green-400 text-center">
+              {successMsg}
+            </div>
+          )}
+        </div>
       </div>
     </div>
   );
diff --git a/server/endpoints/system.js b/server/endpoints/system.js
index 8b1588a7d..ff656a02b 100644
--- a/server/endpoints/system.js
+++ b/server/endpoints/system.js
@@ -35,6 +35,7 @@ const {
   DARK_LOGO_FILENAME,
 } = require("../utils/files/logo");
 const { Telemetry } = require("../models/telemetry");
+const { WelcomeMessages } = require("../models/welcomeMessages");
 
 function systemEndpoints(app) {
   if (!app) return;
@@ -477,6 +478,53 @@ function systemEndpoints(app) {
       }
     }
   );
+
+  app.get("/system/welcome-messages", async function (request, response) {
+    try {
+      const welcomeMessages = await WelcomeMessages.getMessages();
+      response.status(200).json({ success: true, welcomeMessages });
+    } catch (error) {
+      console.error("Error fetching welcome messages:", error);
+      response
+        .status(500)
+        .json({ success: false, message: "Internal server error" });
+    }
+  });
+
+  app.post(
+    "/system/set-welcome-messages",
+    [validatedRequest],
+    async (request, response) => {
+      try {
+        if (
+          response.locals.multiUserMode &&
+          response.locals.user?.role !== "admin"
+        ) {
+          return response.sendStatus(401).end();
+        }
+
+        const { messages = [] } = reqBody(request);
+        if (!Array.isArray(messages)) {
+          return response.status(400).json({
+            success: false,
+            message: "Invalid message format. Expected an array of messages.",
+          });
+        }
+
+        await WelcomeMessages.saveAll(messages);
+        return response.status(200).json({
+          success: true,
+          message: "Welcome messages saved successfully.",
+        });
+      } catch (error) {
+        console.error("Error processing the welcome messages:", error);
+        response.status(500).json({
+          success: true,
+          message: "Error saving the welcome messages.",
+        });
+      }
+    }
+  );
 }
 
 module.exports = { systemEndpoints };
diff --git a/server/models/welcomeMessages.js b/server/models/welcomeMessages.js
new file mode 100644
index 000000000..437dd97ce
--- /dev/null
+++ b/server/models/welcomeMessages.js
@@ -0,0 +1,89 @@
+const WelcomeMessages = {
+  tablename: "welcome_messages",
+  colsInit: `
+  id INTEGER PRIMARY KEY AUTOINCREMENT,
+  user TEXT NOT NULL,
+  response TEXT NOT NULL,
+  orderIndex INTEGER,
+  createdAt TEXT DEFAULT CURRENT_TIMESTAMP
+  `,
+
+  migrateTable: async function () {
+    const { checkForMigrations } = require("../utils/database");
+    console.log(
+      `\x1b[34m[MIGRATING]\x1b[0m Checking for Welcome Messages migrations`
+    );
+    const db = await this.db(false);
+    await checkForMigrations(this, db);
+    db.close();
+  },
+
+  migrations: function () {
+    return [];
+  },
+
+  db: async function (tracing = true) {
+    const sqlite3 = require("sqlite3").verbose();
+    const { open } = require("sqlite");
+
+    const db = await open({
+      filename: `${
+        !!process.env.STORAGE_DIR ? `${process.env.STORAGE_DIR}/` : "storage/"
+      }anythingllm.db`,
+      driver: sqlite3.Database,
+    });
+
+    await db.exec(
+      `PRAGMA foreign_keys = ON;CREATE TABLE IF NOT EXISTS ${this.tablename} (${this.colsInit})`
+    );
+
+    if (tracing) {
+      db.on("trace", (sql) => console.log(sql));
+    }
+
+    return db;
+  },
+
+  get: async function (clause = "") {
+    const db = await this.db();
+    const result = await db
+      .get(`SELECT * FROM ${this.tablename} WHERE ${clause}`)
+      .then((res) => res || null);
+    db.close();
+    return result;
+  },
+
+  where: async function (clause = null, limit = null) {
+    const db = await this.db();
+    const results = await db.all(
+      `SELECT * FROM ${this.tablename} ${clause ? `WHERE ${clause}` : ""} ${
+        !!limit ? `LIMIT ${limit}` : ""
+      }`
+    );
+    db.close();
+    return results;
+  },
+
+  saveAll: async function (messages) {
+    const db = await this.db();
+    await db.run(`DELETE FROM ${this.tablename}`);
+    for (const [index, message] of messages.entries()) {
+      await db.run(
+        `INSERT INTO ${this.tablename} (user, response, orderIndex) VALUES (?, ?, ?)`,
+        [message.user, message.response, index]
+      );
+    }
+    db.close();
+  },
+
+  getMessages: async function () {
+    const db = await this.db();
+    const results = await db.all(
+      `SELECT user, response FROM ${this.tablename} ORDER BY orderIndex ASC`
+    );
+    db.close();
+    return results;
+  },
+};
+
+module.exports.WelcomeMessages = WelcomeMessages;
diff --git a/server/utils/database/index.js b/server/utils/database/index.js
index 65f9707ea..0cdc7ba10 100644
--- a/server/utils/database/index.js
+++ b/server/utils/database/index.js
@@ -61,6 +61,7 @@ async function validateTablePragmas(force = false) {
     const { DocumentVectors } = require("../../models/vectors");
     const { WorkspaceChats } = require("../../models/workspaceChats");
     const { Invite } = require("../../models/invite");
+    const { WelcomeMessages } = require("../../models/welcomeMessages");
 
     await SystemSettings.migrateTable();
     await User.migrateTable();
@@ -70,6 +71,7 @@ async function validateTablePragmas(force = false) {
     await DocumentVectors.migrateTable();
     await WorkspaceChats.migrateTable();
     await Invite.migrateTable();
+    await WelcomeMessages.migrateTable();
   } catch (e) {
     console.error(`validateTablePragmas: Migrations failed`, e);
   }
-- 
GitLab