From 6e8d81c01efbeeb4e373c23a9bb0268d43c3616d Mon Sep 17 00:00:00 2001
From: Timothy Carambat <rambat1010@gmail.com>
Date: Thu, 3 Aug 2023 15:59:51 -0700
Subject: [PATCH] better vector db selection UI on settings (#175)

update logout button
---
 .../Modals/Settings/VectorDbs/index.jsx       | 232 ++++++++++++++++++
 .../src/components/Modals/Settings/index.jsx  |  39 +--
 frontend/src/components/Sidebar/index.jsx     |  27 ++
 frontend/src/media/vectordbs/chroma.png       | Bin 0 -> 2006 bytes
 frontend/src/media/vectordbs/lancedb.png      | Bin 0 -> 1896 bytes
 frontend/src/media/vectordbs/pinecone.png     | Bin 0 -> 2166 bytes
 server/utils/helpers/updateENV.js             |   4 +-
 7 files changed, 274 insertions(+), 28 deletions(-)
 create mode 100644 frontend/src/components/Modals/Settings/VectorDbs/index.jsx
 create mode 100644 frontend/src/media/vectordbs/chroma.png
 create mode 100644 frontend/src/media/vectordbs/lancedb.png
 create mode 100644 frontend/src/media/vectordbs/pinecone.png

diff --git a/frontend/src/components/Modals/Settings/VectorDbs/index.jsx b/frontend/src/components/Modals/Settings/VectorDbs/index.jsx
new file mode 100644
index 000000000..0c4f5a386
--- /dev/null
+++ b/frontend/src/components/Modals/Settings/VectorDbs/index.jsx
@@ -0,0 +1,232 @@
+import React, { useState } from "react";
+import System from "../../../../models/system";
+import ChromaLogo from "../../../../media/vectordbs/chroma.png";
+import PineconeLogo from "../../../../media/vectordbs/pinecone.png";
+import LanceDbLogo from "../../../../media/vectordbs/lancedb.png";
+
+const noop = () => false;
+export default function VectorDBSelection({
+  hideModal = noop,
+  user,
+  settings = {},
+}) {
+  const [hasChanges, setHasChanges] = useState(false);
+  const [vectorDB, setVectorDB] = useState(settings?.VectorDB || "lancedb");
+  const [saving, setSaving] = useState(false);
+  const [error, setError] = useState(null);
+  const canDebug = settings.MultiUserMode
+    ? settings?.CanDebug && user?.role === "admin"
+    : settings?.CanDebug;
+
+  function updateVectorChoice(selection) {
+    if (!canDebug || selection === vectorDB) return false;
+    setHasChanges(true);
+    setVectorDB(selection);
+  }
+
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    setSaving(true);
+    setError(null);
+    const data = {};
+    const form = new FormData(e.target);
+    for (var [key, value] of form.entries()) data[key] = value;
+    const { error } = await System.updateSystem(data);
+    setError(error);
+    setSaving(false);
+    setHasChanges(!!error ? true : false);
+  };
+  return (
+    <div className="relative w-full max-w-2xl max-h-full">
+      <div className="relative bg-white rounded-lg shadow dark:bg-stone-700">
+        <div className="flex items-start justify-between px-6 py-4">
+          <p className="text-gray-800 dark:text-stone-200 text-base ">
+            These are the credentials and settings for how your AnythingLLM
+            instance will function. Its important these keys are current and
+            correct.
+          </p>
+        </div>
+
+        {!!error && (
+          <div className="mb-8 bg-red-700 dark:bg-orange-800 bg-opacity-30 border border-red-800 dark:border-orange-600 p-4 rounded-lg w-[90%] flex mx-auto">
+            <p className="text-red-800 dark:text-orange-300 text-sm">{error}</p>
+          </div>
+        )}
+
+        <form onSubmit={handleSubmit} onChange={() => setHasChanges(true)}>
+          <div className="px-6 space-y-6 flex h-full w-full">
+            <div className="w-full flex flex-col gap-y-4">
+              <p className="block text-sm font-medium text-gray-800 dark:text-slate-200">
+                Vector database provider
+              </p>
+              <div className="w-full flex overflow-x-scroll gap-x-4 no-scroll">
+                <input hidden={true} name="VectorDB" value={vectorDB} />
+                <VectorDBOption
+                  name="Chroma"
+                  value="chroma"
+                  link="trychroma.com"
+                  description="Open source vector database you can host yourself or on the cloud."
+                  checked={vectorDB === "chroma"}
+                  image={ChromaLogo}
+                  onClick={updateVectorChoice}
+                />
+                <VectorDBOption
+                  name="Pinecone"
+                  value="pinecone"
+                  link="pinecone.io"
+                  description="100% cloud-based vector database for enterprise use cases."
+                  checked={vectorDB === "pinecone"}
+                  image={PineconeLogo}
+                  onClick={updateVectorChoice}
+                />
+                <VectorDBOption
+                  name="LanceDB"
+                  value="lancedb"
+                  link="lancedb.com"
+                  description="100% local vector DB that runs on the same instance as AnythingLLM."
+                  checked={vectorDB === "lancedb"}
+                  image={LanceDbLogo}
+                  onClick={updateVectorChoice}
+                />
+              </div>
+              {vectorDB === "pinecone" && (
+                <>
+                  <div>
+                    <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
+                      Pinecone DB API Key
+                    </label>
+                    <input
+                      type="text"
+                      name="PineConeKey"
+                      disabled={!canDebug}
+                      className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
+                      placeholder="Pinecone API Key"
+                      defaultValue={settings?.PineConeKey ? "*".repeat(20) : ""}
+                      required={true}
+                      autoComplete="off"
+                      spellCheck={false}
+                    />
+                  </div>
+
+                  <div>
+                    <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
+                      Pinecone Index Environment
+                    </label>
+                    <input
+                      type="text"
+                      name="PineConeEnvironment"
+                      disabled={!canDebug}
+                      className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
+                      placeholder="us-gcp-west-1"
+                      defaultValue={settings?.PineConeEnvironment}
+                      required={true}
+                      autoComplete="off"
+                      spellCheck={false}
+                    />
+                  </div>
+
+                  <div>
+                    <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
+                      Pinecone Index Name
+                    </label>
+                    <input
+                      type="text"
+                      name="PineConeIndex"
+                      disabled={!canDebug}
+                      className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
+                      placeholder="my-index"
+                      defaultValue={settings?.PineConeIndex}
+                      required={true}
+                      autoComplete="off"
+                      spellCheck={false}
+                    />
+                  </div>
+                </>
+              )}
+
+              {vectorDB === "chroma" && (
+                <>
+                  <div>
+                    <label className="block mb-2 text-sm font-medium text-gray-800 dark:text-slate-200">
+                      Chroma Endpoint
+                    </label>
+                    <input
+                      type="url"
+                      name="ChromaEndpoint"
+                      disabled={!canDebug}
+                      className="bg-gray-50 border border-gray-500 text-gray-900 placeholder-gray-500 text-sm rounded-lg dark:bg-stone-700 focus:border-stone-500 block w-full p-2.5 dark:text-slate-200 dark:placeholder-stone-500 dark:border-slate-200"
+                      placeholder="http://localhost:8000"
+                      defaultValue={settings?.ChromaEndpoint}
+                      required={true}
+                      autoComplete="off"
+                      spellCheck={false}
+                    />
+                  </div>
+                </>
+              )}
+              {vectorDB === "lancedb" && (
+                <div className="w-full h-40 items-center justify-center flex">
+                  <p className="text-gray-800 dark:text-slate-400">
+                    There is no configuration needed for LanceDB.
+                  </p>
+                </div>
+              )}
+            </div>
+          </div>
+          <div className="w-full p-4">
+            <button
+              hidden={!hasChanges}
+              disabled={saving}
+              type="submit"
+              className="w-full 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"
+            >
+              {saving ? "Saving..." : "Save changes"}
+            </button>
+          </div>
+        </form>
+        <div className="flex items-center p-6 space-x-2 border-t border-gray-200 rounded-b dark:border-gray-600">
+          <button
+            onClick={hideModal}
+            type="button"
+            className="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"
+          >
+            Close
+          </button>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+const VectorDBOption = ({
+  name,
+  link,
+  description,
+  value,
+  image,
+  checked = false,
+  onClick,
+}) => {
+  return (
+    <div onClick={() => onClick(value)}>
+      <input
+        type="checkbox"
+        value={value}
+        className="peer hidden"
+        checked={checked}
+        readOnly={true}
+        formNoValidate={true}
+      />
+      <label className="transition-all duration-300 inline-flex h-full w-60 cursor-pointer items-center justify-between rounded-lg border border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:dark:bg-stone-800 peer-checked:text-gray-600 dark:border-slate-200 dark:bg-stone-800 dark:text-slate-400 dark:hover:bg-stone-700 dark:hover:text-slate-300 dark:peer-checked:text-slate-300">
+        <div className="block">
+          <img src={image} alt={name} className="mb-2 h-10 w-10 rounded-full" />
+          <div className="w-full text-lg font-semibold">{name}</div>
+          <div className="flex w-full flex-col gap-y-1 text-sm">
+            <p className="text-xs text-slate-400">{link}</p>
+            {description}
+          </div>
+        </div>
+      </label>
+    </div>
+  );
+};
diff --git a/frontend/src/components/Modals/Settings/index.jsx b/frontend/src/components/Modals/Settings/index.jsx
index 9d02d94e8..a9ce6de2c 100644
--- a/frontend/src/components/Modals/Settings/index.jsx
+++ b/frontend/src/components/Modals/Settings/index.jsx
@@ -1,26 +1,27 @@
 import React, { useEffect, useState } from "react";
-import { Archive, Lock, Key, X, Users, LogOut } from "react-feather";
+import { Archive, Lock, Key, X, Users, Database } from "react-feather";
 import SystemKeys from "./Keys";
 import ExportOrImportData from "./ExportImport";
 import PasswordProtection from "./PasswordProtection";
 import System from "../../../models/system";
 import MultiUserMode from "./MultiUserMode";
-import { AUTH_TOKEN, AUTH_USER } from "../../../utils/constants";
-import paths from "../../../utils/paths";
 import useUser from "../../../hooks/useUser";
+import VectorDBSelection from "./VectorDbs";
 
 const TABS = {
   keys: SystemKeys,
   exportimport: ExportOrImportData,
   password: PasswordProtection,
   multiuser: MultiUserMode,
+  vectordb: VectorDBSelection,
 };
 
 const noop = () => false;
 export default function SystemSettingsModal({ hideModal = noop }) {
   const { user } = useUser();
   const [loading, setLoading] = useState(true);
-  const [selectedTab, setSelectedTab] = useState("keys");
+  // const [selectedTab, setSelectedTab] = useState("keys");
+  const [selectedTab, setSelectedTab] = useState("vectordb");
   const [settings, setSettings] = useState(null);
   const Component = TABS[selectedTab || "keys"];
 
@@ -93,6 +94,13 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
         icon={<Key className="h-4 w-4 flex-shrink-0" />}
         onClick={changeTab}
       />
+      <SettingTab
+        active={selectedTab === "vectordb"}
+        displayName="Vector Database"
+        tabName="vectordb"
+        icon={<Database className="h-4 w-4 flex-shrink-0" />}
+        onClick={changeTab}
+      />
       <SettingTab
         active={selectedTab === "exportimport"}
         displayName="Export or Import"
@@ -100,7 +108,7 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
         icon={<Archive className="h-4 w-4 flex-shrink-0" />}
         onClick={changeTab}
       />
-      {!settings?.MultiUserMode ? (
+      {!settings?.MultiUserMode && (
         <>
           <SettingTab
             active={selectedTab === "multiuser"}
@@ -117,8 +125,6 @@ function SettingTabs({ selectedTab, changeTab, settings, user }) {
             onClick={changeTab}
           />
         </>
-      ) : (
-        <LogoutTab user={user} />
       )}
     </ul>
   );
@@ -150,25 +156,6 @@ function SettingTab({
   );
 }
 
-function LogoutTab({ user }) {
-  if (!user) return null;
-
-  return (
-    <li className="mr-2">
-      <button
-        onClick={() => {
-          window.localStorage.removeItem(AUTH_USER);
-          window.localStorage.removeItem(AUTH_TOKEN);
-          window.location.replace(paths.home());
-        }}
-        className="flex items-center gap-x-1 p-4 border-b-2 rounded-t-lg group whitespace-nowrap border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300"
-      >
-        <LogOut className="h-4 w-4 flex-shrink-0" /> Log out of {user.username}
-      </button>
-    </li>
-  );
-}
-
 export function useSystemSettingsModal() {
   const [showing, setShowing] = useState(false);
   const showModal = () => {
diff --git a/frontend/src/components/Sidebar/index.jsx b/frontend/src/components/Sidebar/index.jsx
index 3b67abf39..8f453ca7b 100644
--- a/frontend/src/components/Sidebar/index.jsx
+++ b/frontend/src/components/Sidebar/index.jsx
@@ -4,6 +4,7 @@ import {
   Briefcase,
   Cpu,
   GitHub,
+  LogOut,
   Menu,
   Plus,
   Shield,
@@ -21,6 +22,8 @@ import ActiveWorkspaces from "./ActiveWorkspaces";
 import paths from "../../utils/paths";
 import Discord from "../Icons/Discord";
 import useUser from "../../hooks/useUser";
+import { userFromStorage } from "../../utils/request";
+import { AUTH_TOKEN, AUTH_USER } from "../../utils/constants";
 
 export default function Sidebar() {
   const sidebarRef = useRef(null);
@@ -103,6 +106,7 @@ export default function Sidebar() {
                     Enterprise Installation
                   </p>
                 </a>
+                <LogoutButton />
               </div>
 
               {/* Footer */}
@@ -269,6 +273,7 @@ export function SidebarMobileHeader() {
                       Enterprise Installation
                     </p>
                   </a>
+                  <LogoutButton />
                 </div>
 
                 {/* Footer */}
@@ -325,3 +330,25 @@ function AdminHome() {
     </a>
   );
 }
+
+function LogoutButton() {
+  if (!window.localStorage.getItem(AUTH_USER)) return null;
+  const user = userFromStorage();
+  if (!user.username) return null;
+
+  return (
+    <button
+      onClick={() => {
+        window.localStorage.removeItem(AUTH_USER);
+        window.localStorage.removeItem(AUTH_TOKEN);
+        window.location.replace(paths.home());
+      }}
+      className="flex flex-grow w-[100%] h-[36px] gap-x-2 py-[5px] px-4 border border-slate-400 dark:border-transparent rounded-lg text-slate-800 dark:text-slate-200 justify-center items-center hover:bg-slate-100 dark:bg-stone-800 dark:hover:bg-stone-900"
+    >
+      <LogOut className="h-4 w-4" />
+      <p className="text-slate-800 dark:text-slate-200 text-xs leading-loose font-semibold">
+        Log out of {user.username}
+      </p>
+    </button>
+  );
+}
diff --git a/frontend/src/media/vectordbs/chroma.png b/frontend/src/media/vectordbs/chroma.png
new file mode 100644
index 0000000000000000000000000000000000000000..fde25384d5592fe9d5eb441be28e0ccda5332d82
GIT binary patch
literal 2006
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
C(+X$+

literal 0
HcmV?d00001

diff --git a/frontend/src/media/vectordbs/lancedb.png b/frontend/src/media/vectordbs/lancedb.png
new file mode 100644
index 0000000000000000000000000000000000000000..dc653484dcdc1c842d75fdd402c4470475a9d1a1
GIT binary patch
literal 1896
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

literal 0
HcmV?d00001

diff --git a/frontend/src/media/vectordbs/pinecone.png b/frontend/src/media/vectordbs/pinecone.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f20d7f7f140387b65a82720f811a900e80cc503
GIT binary patch
literal 2166
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

literal 0
HcmV?d00001

diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js
index 54eec1e5b..0ff95fa49 100644
--- a/server/utils/helpers/updateENV.js
+++ b/server/utils/helpers/updateENV.js
@@ -92,8 +92,8 @@ function validChromaURL(input = "") {
 function updateENV(newENVs = {}) {
   let error = "";
   const validKeys = Object.keys(KEY_MAPPING);
-  const ENV_KEYS = Object.keys(newENVs).filter((key) =>
-    validKeys.includes(key)
+  const ENV_KEYS = Object.keys(newENVs).filter(
+    (key) => validKeys.includes(key) && !newENVs[key].includes("******") // strip out answers where the value is all asterisks
   );
   const newValues = {};
 
-- 
GitLab