From 458ffed0c77b9417b56134492b845d1176e5fa28 Mon Sep 17 00:00:00 2001
From: Sean Hatfield <seanhatfield5@gmail.com>
Date: Wed, 8 Nov 2023 19:00:12 -0800
Subject: [PATCH] added onboarding data handling modal (#342)

* added onboarding data handling modal

* adding data handling modal component

* update element to list
Update copy

* remove useEffect dep

* refactor onboarding navigation using history

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
---
 .../Steps/AppearanceSetup/index.jsx           |  19 +-
 .../Steps/DataHandling/index.jsx              | 171 ++++++++++++++++++
 .../Steps/EmbeddingSelection/index.jsx        |   6 +-
 .../Steps/LLMSelection/index.jsx              |  11 +-
 .../Steps/MultiUserSetup/index.jsx            |   2 +-
 .../Steps/PasswordProtection/index.jsx        |   6 +-
 .../Steps/UserModeSelection/index.jsx         |   6 +-
 .../Steps/VectorDatabaseConnection/index.jsx  |   4 +-
 .../OnboardingFlow/OnboardingModal/index.jsx  |  52 ++++--
 9 files changed, 232 insertions(+), 45 deletions(-)
 create mode 100644 frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx

diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx
index 7fcd46c13..3ccfa8c58 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/AppearanceSetup/index.jsx
@@ -5,7 +5,7 @@ import useLogo from "../../../../../hooks/useLogo";
 import { Plus } from "@phosphor-icons/react";
 import showToast from "../../../../../utils/toast";
 
-function AppearanceSetup({ nextStep }) {
+function AppearanceSetup({ prevStep, nextStep }) {
   const { logo: _initLogo } = useLogo();
   const [logo, setLogo] = useState("");
   const [isDefaultLogo, setIsDefaultLogo] = useState(true);
@@ -57,7 +57,7 @@ function AppearanceSetup({ nextStep }) {
   };
 
   return (
-    <div>
+    <div className="w-full">
       <div className="flex flex-col w-full px-10 py-12">
         <div className="flex flex-col gap-y-2">
           <h2 className="text-white text-sm font-medium">Custom Logo</h2>
@@ -109,20 +109,23 @@ function AppearanceSetup({ nextStep }) {
         </div>
       </div>
       <div className="flex w-full justify-between items-center p-6 space-x-6 border-t rounded-b border-gray-500/50">
-        <div className="w-96 text-white text-opacity-80 text-xs font-base">
-          Want to customize the automatic messages in your chat? Find more
-          customization options on the appearance settings page.
-        </div>
+        <button
+          onClick={prevStep}
+          type="button"
+          className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
+        >
+          Back
+        </button>
         <div className="flex gap-2">
           <button
-            onClick={nextStep}
+            onClick={() => nextStep("user_mode_setup")}
             type="button"
             className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
           >
             Skip
           </button>
           <button
-            onClick={nextStep}
+            onClick={() => nextStep("user_mode_setup")}
             type="button"
             className="border border-slate-200 px-4 py-2 rounded-lg text-slate-800 bg-slate-200 text-sm items-center flex gap-x-2 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow"
           >
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx
new file mode 100644
index 000000000..ce0aa91b4
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx
@@ -0,0 +1,171 @@
+import React, { memo, useEffect, useState } from "react";
+import System from "../../../../../models/system";
+import OpenAiLogo from "../../../../../media/llmprovider/openai.png";
+import AzureOpenAiLogo from "../../../../../media/llmprovider/azure.png";
+import AnthropicLogo from "../../../../../media/llmprovider/anthropic.png";
+import ChromaLogo from "../../../../../media/vectordbs/chroma.png";
+import PineconeLogo from "../../../../../media/vectordbs/pinecone.png";
+import LanceDbLogo from "../../../../../media/vectordbs/lancedb.png";
+import WeaviateLogo from "../../../../../media/vectordbs/weaviate.png";
+import QDrantLogo from "../../../../../media/vectordbs/qdrant.png";
+import PreLoader from "../../../../../components/Preloader";
+
+const LLM_SELECTION_PRIVACY = {
+  openai: {
+    name: "OpenAI",
+    description: [
+      "Your chats will not be used for training",
+      "Your prompts and document text used in responses are visible to OpenAI",
+    ],
+    logo: OpenAiLogo,
+  },
+  azure: {
+    name: "Azure OpenAI",
+    description: [
+      "Your chats will not be used for training",
+      "Your text and embedding text are not visible to OpenAI or Microsoft",
+    ],
+    logo: AzureOpenAiLogo,
+  },
+  anthropic: {
+    name: "Anthropic",
+    description: [
+      "Your chats will not be used for training",
+      "Your prompts and document text used in responses are visible to Anthropic",
+    ],
+    logo: AnthropicLogo,
+  },
+};
+
+const VECTOR_DB_PRIVACY = {
+  chroma: {
+    name: "Chroma",
+    description: [
+      "Your embedded text not visible outside of your Chroma instance",
+      "Access to your instance is managed by you",
+    ],
+    logo: ChromaLogo,
+  },
+  pinecone: {
+    name: "Pinecone",
+    description: [
+      "Your embedded text and vectors are visible to Pinecone, but is not accessed",
+      "They manage your data and access to their servers",
+    ],
+    logo: PineconeLogo,
+  },
+  qdrant: {
+    name: "Qdrant",
+    description: [
+      "Your embedded text is visible to Qdrant if using a hosted instance",
+      "Your embedded text is not visible to Qdrant if using a self-hosted instance",
+      "Your data is stored on your Qdrant instance",
+    ],
+    logo: QDrantLogo,
+  },
+  weaviate: {
+    name: "Weaviate",
+    description: [
+      "Your embedded text is visible to Weaviate, if using a hosted instance",
+      "Your embedded text is not visible to Weaviate, if using a self-hosted instance",
+      "Your data is stored on your Weaviate instance",
+    ],
+    logo: WeaviateLogo,
+  },
+  lancedb: {
+    name: "LanceDB",
+    description: [
+      "Your embedded text and vectors are only accessible by this AnythingLLM instance",
+    ],
+    logo: LanceDbLogo,
+  },
+};
+
+function DataHandling({ nextStep, prevStep, currentStep }) {
+  const [llmChoice, setLLMChoice] = useState("openai");
+  const [loading, setLoading] = useState(true);
+  const [vectorDb, setVectorDb] = useState("pinecone");
+
+  useEffect(() => {
+    async function fetchKeys() {
+      const _settings = await System.keys();
+      setLLMChoice(_settings?.LLMProvider);
+      setVectorDb(_settings?.VectorDB);
+
+      setLoading(false);
+    }
+    if (currentStep === "data_handling") {
+      fetchKeys();
+    }
+  }, []);
+
+  if (loading)
+    return (
+      <div className="w-full h-full flex justify-center items-center p-20">
+        <PreLoader />
+      </div>
+    );
+
+  return (
+    <div className="max-w-[750px]">
+      <div className="p-8 flex gap-x-16">
+        <div className="w-1/2 flex flex-col gap-y-3.5">
+          <div className="text-white text-base font-bold">LLM Selection</div>
+          <div className="flex items-center gap-2.5">
+            <img
+              src={LLM_SELECTION_PRIVACY[llmChoice].logo}
+              alt="LLM Logo"
+              className="w-8 h-8 rounded"
+            />
+            <p className="text-white text-sm font-bold">
+              {LLM_SELECTION_PRIVACY[llmChoice].name}
+            </p>
+          </div>
+          <div className="flex flex-col">
+            {LLM_SELECTION_PRIVACY[llmChoice].description.map((desc) => (
+              <p className="text-white/90 text-sm">
+                <b>•</b> {desc}
+              </p>
+            ))}
+          </div>
+        </div>
+
+        <div className="w-1/2 flex flex-col gap-y-3.5">
+          <div className="text-white text-base font-bold">Vector Database</div>
+          <div className="flex items-center gap-2.5">
+            <img
+              src={VECTOR_DB_PRIVACY[vectorDb].logo}
+              alt="Vector DB Logo"
+              className="w-8 h-8 rounded"
+            />
+            <p className="text-white text-sm font-bold">
+              {VECTOR_DB_PRIVACY[vectorDb].name}
+            </p>
+          </div>
+          <ul className="flex flex-col list-disc">
+            {VECTOR_DB_PRIVACY[vectorDb].description.map((desc) => (
+              <li className="text-white/90 text-sm">{desc}</li>
+            ))}
+          </ul>
+        </div>
+      </div>
+      <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
+        <button
+          onClick={prevStep}
+          type="button"
+          className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
+        >
+          Back
+        </button>
+        <button
+          onClick={() => nextStep("create_workspace")}
+          className="border border-slate-200 px-4 py-2 rounded-lg text-slate-800 bg-slate-200 text-sm items-center flex gap-x-2 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow"
+        >
+          Continue
+        </button>
+      </div>
+    </div>
+  );
+}
+
+export default memo(DataHandling);
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx
index 27fe27525..4edde9be0 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/EmbeddingSelection/index.jsx
@@ -5,7 +5,7 @@ import System from "../../../../../models/system";
 import PreLoader from "../../../../../components/Preloader";
 import LLMProviderOption from "../../../../../components/LLMSelection/LLMProviderOption";
 
-function EmbeddingSelection({ nextStep, prevStep, currentStep, goToStep }) {
+function EmbeddingSelection({ nextStep, prevStep, currentStep }) {
   const [embeddingChoice, setEmbeddingChoice] = useState("openai");
   const [settings, setSettings] = useState(null);
   const [loading, setLoading] = useState(true);
@@ -35,7 +35,7 @@ function EmbeddingSelection({ nextStep, prevStep, currentStep, goToStep }) {
       alert(`Failed to save LLM settings: ${error}`, "error");
       return;
     }
-    goToStep(2);
+    nextStep("vector_database");
     return;
   };
 
@@ -156,7 +156,7 @@ function EmbeddingSelection({ nextStep, prevStep, currentStep, goToStep }) {
         </div>
         <div className="flex w-full justify-between items-center p-6 space-x-2 border-t rounded-b border-gray-500/50">
           <button
-            onClick={() => goToStep(1)}
+            onClick={prevStep}
             type="button"
             className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
           >
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx
index a813dbd9e..3a19d3874 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx
@@ -9,7 +9,7 @@ import OpenAiOptions from "../../../../../components/LLMSelection/OpenAiOptions"
 import AzureAiOptions from "../../../../../components/LLMSelection/AzureAiOptions";
 import AnthropicAiOptions from "../../../../../components/LLMSelection/AnthropicAiOptions";
 
-function LLMSelection({ nextStep, prevStep, currentStep, goToStep }) {
+function LLMSelection({ nextStep, prevStep, currentStep }) {
   const [llmChoice, setLLMChoice] = useState("openai");
   const [settings, setSettings] = useState(null);
   const [loading, setLoading] = useState(true);
@@ -26,10 +26,10 @@ function LLMSelection({ nextStep, prevStep, currentStep, goToStep }) {
       setLoading(false);
     }
 
-    if (currentStep === 1) {
+    if (currentStep === "llm_preference") {
       fetchKeys();
     }
-  }, [currentStep]);
+  }, []);
 
   const handleSubmit = async (e) => {
     e.preventDefault();
@@ -45,11 +45,10 @@ function LLMSelection({ nextStep, prevStep, currentStep, goToStep }) {
 
     switch (data.LLMProvider) {
       case "anthropic":
-        goToStep(7);
+        return nextStep("embedding_preferences");
       default:
-        nextStep();
+        return nextStep("vector_database");
     }
-    return;
   };
 
   if (loading)
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/MultiUserSetup/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/MultiUserSetup/index.jsx
index 726a70276..3b34f2ae6 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/MultiUserSetup/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/MultiUserSetup/index.jsx
@@ -33,7 +33,7 @@ function MultiUserSetup({ nextStep, prevStep }) {
     window.localStorage.setItem(AUTH_TOKEN, token);
     window.localStorage.removeItem(AUTH_TIMESTAMP);
 
-    nextStep();
+    nextStep("data_handling");
   };
 
   const setNewUsername = (e) => setUsername(e.target.value);
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/PasswordProtection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/PasswordProtection/index.jsx
index 1baca6d23..51ae732c1 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/PasswordProtection/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/PasswordProtection/index.jsx
@@ -7,7 +7,7 @@ import {
 } from "../../../../../utils/constants";
 import debounce from "lodash.debounce";
 
-function PasswordProtection({ goToStep, prevStep }) {
+function PasswordProtection({ nextStep, prevStep }) {
   const [password, setPassword] = useState("");
   const handleSubmit = async (e) => {
     e.preventDefault();
@@ -32,12 +32,12 @@ function PasswordProtection({ goToStep, prevStep }) {
     window.localStorage.removeItem(AUTH_TIMESTAMP);
     window.localStorage.setItem(AUTH_TOKEN, token);
 
-    goToStep(7);
+    nextStep("data_handling");
     return;
   };
 
   const handleSkip = () => {
-    goToStep(7);
+    nextStep("data_handling");
   };
 
   const setNewPassword = (e) => setPassword(e.target.value);
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserModeSelection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserModeSelection/index.jsx
index 1d981b35f..6625257f5 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserModeSelection/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserModeSelection/index.jsx
@@ -1,13 +1,13 @@
 import React, { memo } from "react";
 
 // How many people will be using your instance step
-function UserModeSelection({ goToStep, prevStep }) {
+function UserModeSelection({ nextStep, prevStep }) {
   const justMeClicked = () => {
-    goToStep(5);
+    nextStep("password_protection");
   };
 
   const myTeamClicked = () => {
-    goToStep(6);
+    nextStep("multi_user_mode");
   };
 
   return (
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/VectorDatabaseConnection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/VectorDatabaseConnection/index.jsx
index 47fbb870c..d8766c816 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/VectorDatabaseConnection/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/VectorDatabaseConnection/index.jsx
@@ -21,7 +21,7 @@ function VectorDatabaseConnection({ nextStep, prevStep, currentStep }) {
       setVectorDB(_settings?.VectorDB || "lancedb");
       setLoading(false);
     }
-    if (currentStep === 2) {
+    if (currentStep === "vector_database") {
       fetchKeys();
     }
   }, [currentStep]);
@@ -41,7 +41,7 @@ function VectorDatabaseConnection({ nextStep, prevStep, currentStep }) {
       alert(`Failed to save settings: ${error}`, "error");
       return;
     }
-    nextStep();
+    nextStep("appearance");
     return;
   };
 
diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx
index a412ecc52..c53ba2c1a 100644
--- a/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx
@@ -8,6 +8,7 @@ import PasswordProtection from "./Steps/PasswordProtection";
 import MultiUserSetup from "./Steps/MultiUserSetup";
 import CreateFirstWorkspace from "./Steps/CreateFirstWorkspace";
 import EmbeddingSelection from "./Steps/EmbeddingSelection";
+import DataHandling from "./Steps/DataHandling";
 
 const DIALOG_ID = "onboarding-modal";
 
@@ -16,46 +17,53 @@ function hideModal() {
 }
 
 const STEPS = {
-  1: {
+  llm_preference: {
     title: "LLM Preference",
     description:
       "These are the credentials and settings for your preferred LLM chat & embedding provider.",
     component: LLMSelection,
   },
-  2: {
+  vector_database: {
     title: "Vector Database",
     description:
       "These are the credentials and settings for how your AnythingLLM instance will function.",
     component: VectorDatabaseConnection,
   },
-  3: {
+  appearance: {
     title: "Appearance",
-    description: "Customize the appearance of your AnythingLLM instance.",
+    description:
+      "Customize the appearance of your AnythingLLM instance.\nFind more customization options on the appearance settings page.",
     component: AppearanceSetup,
   },
-  4: {
+  user_mode_setup: {
     title: "User Mode Setup",
     description: "Choose how many people will be using your instance.",
     component: UserModeSelection,
   },
-  5: {
+  password_protection: {
     title: "Password Protect",
     description:
       "Protect your instance with a password. It is important to save this password as it cannot be recovered.",
     component: PasswordProtection,
   },
-  6: {
+  multi_user_mode: {
     title: "Multi-User Mode",
     description:
       "Setup your instance to support your team by activating multi-user mode.",
     component: MultiUserSetup,
   },
-  7: {
+  data_handling: {
+    title: "Data Handling",
+    description:
+      "We are committed to transparency and control when it comes to your personal data.",
+    component: DataHandling,
+  },
+  create_workspace: {
     title: "Create Workspace",
     description: "To get started, create a new workspace.",
     component: CreateFirstWorkspace,
   },
-  8: {
+  embedding_preferences: {
     title: "Embedding Preference",
     description:
       "Due to your LLM selection you need to set up a provider for embedding files and text.",
@@ -65,19 +73,26 @@ const STEPS = {
 
 export const OnboardingModalId = DIALOG_ID;
 export default function OnboardingModal() {
-  const [currentStep, setCurrentStep] = useState(1);
+  const [currentStep, setCurrentStep] = useState("llm_preference");
+  const [history, setHistory] = useState(["llm_preference"]);
 
-  const nextStep = () => {
-    setCurrentStep((prevStep) => prevStep + 1);
+  const nextStep = (stepKey) => {
+    setCurrentStep(stepKey);
+    setHistory([...history, stepKey]);
   };
 
   const prevStep = () => {
-    if (currentStep === 1) return hideModal();
-    setCurrentStep((prevStep) => prevStep - 1);
-  };
+    const currentStepIdx = history.indexOf(currentStep);
+    if (currentStepIdx === -1 || currentStepIdx === 0) {
+      setCurrentStep("llm_preference");
+      setHistory(["llm_preference"]);
+      return hideModal();
+    }
 
-  const goToStep = (step) => {
-    setCurrentStep(step);
+    const prevStep = history[currentStepIdx - 1];
+    const _history = [...history].slice(0, currentStepIdx);
+    setCurrentStep(prevStep);
+    setHistory(_history);
   };
 
   const { component: StepComponent, ...step } = STEPS[currentStep];
@@ -88,7 +103,7 @@ export default function OnboardingModal() {
           <div className="flex items-start justify-between p-8 border-b rounded-t border-gray-500/50">
             <div className="flex flex-col gap-2">
               <h3 className="text-xl font-semibold text-white">{step.title}</h3>
-              <p className="text-sm font-base text-white text-opacity-60">
+              <p className="text-sm font-base text-white text-opacity-60 whitespace-pre">
                 {step.description || ""}
               </p>
             </div>
@@ -106,7 +121,6 @@ export default function OnboardingModal() {
               currentStep={currentStep}
               nextStep={nextStep}
               prevStep={prevStep}
-              goToStep={goToStep}
             />
           </div>
         </div>