@@ -179,7 +194,7 @@ function DataHandling({ nextStep, prevStep, currentStep }) {
);
return (
-
+
LLM Selection
@@ -239,23 +254,6 @@ function DataHandling({ nextStep, prevStep, currentStep }) {
-
-
-
-
);
}
-
-export default memo(DataHandling);
diff --git a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/EmbedderItem.jsx b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/EmbedderItem.jsx
new file mode 100644
index 0000000000..b37b645f95
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/EmbedderItem.jsx
@@ -0,0 +1,39 @@
+export default function EmbedderItem({
+ name,
+ value,
+ image,
+ description,
+ checked,
+ onClick,
+}) {
+ return (
+
onClick(value)}
+ className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
+ checked && "bg-white/10"
+ }`}
+ >
+
+
+
+
+
{name}
+
+ {description}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx
new file mode 100644
index 0000000000..c78d3f4434
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/EmbeddingPreference/index.jsx
@@ -0,0 +1,180 @@
+import { MagnifyingGlass } from "@phosphor-icons/react";
+import { useEffect, useState, useRef } from "react";
+import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
+import OpenAiLogo from "@/media/llmprovider/openai.png";
+import AzureOpenAiLogo from "@/media/llmprovider/azure.png";
+import LocalAiLogo from "@/media/llmprovider/localai.png";
+import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions";
+import OpenAiOptions from "@/components/EmbeddingSelection/OpenAiOptions";
+import AzureAiOptions from "@/components/EmbeddingSelection/AzureAiOptions";
+import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions";
+import EmbedderItem from "./EmbedderItem";
+import System from "@/models/system";
+import paths from "@/utils/paths";
+import showToast from "@/utils/toast";
+import { useNavigate } from "react-router-dom";
+
+const TITLE = "Embedding Preference";
+const DESCRIPTION =
+ "AnythingLLM can work with many embedding models. This will be the model which turns documents into vectors.";
+
+export default function EmbeddingPreference({
+ setHeader,
+ setForwardBtn,
+ setBackBtn,
+}) {
+ const [searchQuery, setSearchQuery] = useState("");
+ const [filteredEmbedders, setFilteredEmbedders] = useState([]);
+ const [selectedEmbedder, setSelectedEmbedder] = useState(null);
+ const [settings, setSettings] = useState(null);
+ const formRef = useRef(null);
+ const hiddenSubmitButtonRef = useRef(null);
+ const isHosted = window.location.hostname.includes("useanything.com");
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ async function fetchKeys() {
+ const _settings = await System.keys();
+ setSettings(_settings);
+ setSelectedEmbedder(_settings?.EmbeddingEngine || "native");
+ }
+ fetchKeys();
+ }, []);
+
+ const EMBEDDERS = [
+ {
+ name: "AnythingLLM Embedder",
+ value: "native",
+ logo: AnythingLLMIcon,
+ options:
,
+ description:
+ "Use the built-in embedding engine for AnythingLLM. Zero setup!",
+ },
+ {
+ name: "OpenAI",
+ value: "openai",
+ logo: OpenAiLogo,
+ options:
,
+ description: "The standard option for most non-commercial use.",
+ },
+ {
+ name: "Azure OpenAI",
+ value: "azure",
+ logo: AzureOpenAiLogo,
+ options:
,
+ description: "The enterprise option of OpenAI hosted on Azure services.",
+ },
+ {
+ name: "Local AI",
+ value: "localai",
+ logo: LocalAiLogo,
+ options:
,
+ description: "Run embedding models locally on your own machine.",
+ },
+ ];
+
+ function handleForward() {
+ if (hiddenSubmitButtonRef.current) {
+ hiddenSubmitButtonRef.current.click();
+ }
+ }
+
+ function handleBack() {
+ navigate(paths.onboarding.llmPreference());
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const data = {};
+ const formData = new FormData(form);
+ data.EmbeddingEngine = selectedEmbedder;
+ for (var [key, value] of formData.entries()) data[key] = value;
+
+ const { error } = await System.updateSystem(data);
+ if (error) {
+ showToast(`Failed to save embedding settings: ${error}`, "error");
+ return;
+ }
+ showToast("Embedder settings saved successfully.", "success", {
+ clear: true,
+ });
+ navigate(paths.onboarding.vectorDatabase());
+ };
+
+ useEffect(() => {
+ setHeader({ title: TITLE, description: DESCRIPTION });
+ setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
+ setBackBtn({ showing: true, disabled: false, onClick: handleBack });
+ }, []);
+
+ useEffect(() => {
+ if (searchQuery.trim() === "") {
+ setFilteredEmbedders(EMBEDDERS);
+ } else {
+ const lowercasedQuery = searchQuery.toLowerCase();
+ const filtered = EMBEDDERS.filter((embedder) =>
+ embedder.name.toLowerCase().includes(lowercasedQuery)
+ );
+ setFilteredEmbedders(filtered);
+ }
+ }, [searchQuery]);
+
+ return (
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/Home/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/Home/index.jsx
new file mode 100644
index 0000000000..7b31e2ac3d
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/Home/index.jsx
@@ -0,0 +1,41 @@
+import paths from "@/utils/paths";
+import LGroupImg from "./l_group.png";
+import RGroupImg from "./r_group.png";
+import AnythingLLMLogo from "@/media/logo/anything-llm.png";
+import { useNavigate } from "react-router-dom";
+
+export default function OnboardingHome() {
+ const navigate = useNavigate();
+ return (
+ <>
+
+
+
+
+
+
+
+
Welcome to
+
+
+
+
+
+ >
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/Home/l_group.png b/frontend/src/pages/OnboardingFlow/Steps/Home/l_group.png
new file mode 100644
index 0000000000..2981196a33
Binary files /dev/null and b/frontend/src/pages/OnboardingFlow/Steps/Home/l_group.png differ
diff --git a/frontend/src/pages/OnboardingFlow/Steps/Home/r_group.png b/frontend/src/pages/OnboardingFlow/Steps/Home/r_group.png
new file mode 100644
index 0000000000..fc50fd52c8
Binary files /dev/null and b/frontend/src/pages/OnboardingFlow/Steps/Home/r_group.png differ
diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/LLMItem.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/LLMItem.jsx
new file mode 100644
index 0000000000..b6db5d1303
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/LLMItem.jsx
@@ -0,0 +1,39 @@
+export default function LLMItem({
+ name,
+ value,
+ image,
+ description,
+ checked,
+ onClick,
+}) {
+ return (
+
onClick(value)}
+ className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
+ checked && "bg-white/10"
+ }`}
+ >
+
+
+
+
+
{name}
+
+ {description}
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx
new file mode 100644
index 0000000000..24d561ed6d
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx
@@ -0,0 +1,211 @@
+import { MagnifyingGlass } from "@phosphor-icons/react";
+import { useEffect, useState, useRef } from "react";
+import OpenAiLogo from "@/media/llmprovider/openai.png";
+import AzureOpenAiLogo from "@/media/llmprovider/azure.png";
+import AnthropicLogo from "@/media/llmprovider/anthropic.png";
+import GeminiLogo from "@/media/llmprovider/gemini.png";
+import OllamaLogo from "@/media/llmprovider/ollama.png";
+import LMStudioLogo from "@/media/llmprovider/lmstudio.png";
+import LocalAiLogo from "@/media/llmprovider/localai.png";
+import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
+import OpenAiOptions from "@/components/LLMSelection/OpenAiOptions";
+import AzureAiOptions from "@/components/LLMSelection/AzureAiOptions";
+import AnthropicAiOptions from "@/components/LLMSelection/AnthropicAiOptions";
+import LMStudioOptions from "@/components/LLMSelection/LMStudioOptions";
+import LocalAiOptions from "@/components/LLMSelection/LocalAiOptions";
+import NativeLLMOptions from "@/components/LLMSelection/NativeLLMOptions";
+import GeminiLLMOptions from "@/components/LLMSelection/GeminiLLMOptions";
+import OllamaLLMOptions from "@/components/LLMSelection/OllamaLLMOptions";
+import LLMItem from "./LLMItem";
+import System from "@/models/system";
+import paths from "@/utils/paths";
+import showToast from "@/utils/toast";
+import { useNavigate } from "react-router-dom";
+
+const TITLE = "LLM Preference";
+const DESCRIPTION =
+ "AnythingLLM can work with many LLM providers. This will be the service which handles chatting.";
+
+export default function LLMPreference({
+ setHeader,
+ setForwardBtn,
+ setBackBtn,
+}) {
+ const [searchQuery, setSearchQuery] = useState("");
+ const [filteredLLMs, setFilteredLLMs] = useState([]);
+ const [selectedLLM, setSelectedLLM] = useState(null);
+ const [settings, setSettings] = useState(null);
+ const formRef = useRef(null);
+ const hiddenSubmitButtonRef = useRef(null);
+ const isHosted = window.location.hostname.includes("useanything.com");
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ async function fetchKeys() {
+ const _settings = await System.keys();
+ setSettings(_settings);
+ setSelectedLLM(_settings?.LLMProvider || "openai");
+ }
+ fetchKeys();
+ }, []);
+
+ const LLMS = [
+ {
+ name: "OpenAI",
+ value: "openai",
+ logo: OpenAiLogo,
+ options:
,
+ description: "The standard option for most non-commercial use.",
+ },
+ {
+ name: "Azure OpenAI",
+ value: "azure",
+ logo: AzureOpenAiLogo,
+ options:
,
+ description: "The enterprise option of OpenAI hosted on Azure services.",
+ },
+ {
+ name: "Anthropic",
+ value: "anthropic",
+ logo: AnthropicLogo,
+ options:
,
+ description: "A friendly AI Assistant hosted by Anthropic.",
+ },
+ {
+ name: "Gemini",
+ value: "gemini",
+ logo: GeminiLogo,
+ options:
,
+ description: "Google's largest and most capable AI model",
+ },
+ {
+ name: "Ollama",
+ value: "ollama",
+ logo: OllamaLogo,
+ options:
,
+ description: "Run LLMs locally on your own machine.",
+ },
+ {
+ name: "LM Studio",
+ value: "lmstudio",
+ logo: LMStudioLogo,
+ options:
,
+ description:
+ "Discover, download, and run thousands of cutting edge LLMs in a few clicks.",
+ },
+ {
+ name: "Local AI",
+ value: "localai",
+ logo: LocalAiLogo,
+ options:
,
+ description: "Run LLMs locally on your own machine.",
+ },
+ {
+ name: "Native",
+ value: "native",
+ logo: AnythingLLMIcon,
+ options:
,
+ description:
+ "Use a downloaded custom Llama model for chatting on this AnythingLLM instance.",
+ },
+ ];
+
+ function handleForward() {
+ if (hiddenSubmitButtonRef.current) {
+ hiddenSubmitButtonRef.current.click();
+ }
+ }
+
+ function handleBack() {
+ navigate(paths.onboarding.home());
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const data = {};
+ const formData = new FormData(form);
+ data.LLMProvider = selectedLLM;
+ for (var [key, value] of formData.entries()) data[key] = value;
+
+ const { error } = await System.updateSystem(data);
+ if (error) {
+ showToast(`Failed to save LLM settings: ${error}`, "error");
+ return;
+ }
+ showToast("LLM settings saved successfully.", "success", { clear: true });
+ navigate(paths.onboarding.embeddingPreference());
+ };
+
+ useEffect(() => {
+ setHeader({ title: TITLE, description: DESCRIPTION });
+ setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
+ setBackBtn({ showing: true, disabled: false, onClick: handleBack });
+ }, []);
+
+ useEffect(() => {
+ if (searchQuery.trim() === "") {
+ setFilteredLLMs(LLMS);
+ } else {
+ const lowercasedQuery = searchQuery.toLowerCase();
+ const filtered = LLMS.filter((llm) =>
+ llm.name.toLowerCase().includes(lowercasedQuery)
+ );
+ setFilteredLLMs(filtered);
+ }
+ }, [searchQuery]);
+
+ return (
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/Survey/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/Survey/index.jsx
new file mode 100644
index 0000000000..35b2f67d07
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/Survey/index.jsx
@@ -0,0 +1,297 @@
+import { COMPLETE_QUESTIONNAIRE } from "@/utils/constants";
+import paths from "@/utils/paths";
+import { CheckCircle } from "@phosphor-icons/react";
+import React, { useState, useEffect, useRef } from "react";
+import { useNavigate } from "react-router-dom";
+
+const TITLE = "Welcome to AnythingLLM";
+const DESCRIPTION = "Help us make AnythingLLM built for your needs. Optional.";
+
+async function sendQuestionnaire({ email, useCase, comment }) {
+ if (import.meta.env.DEV) return;
+ return fetch(`https://onboarding-wxich7363q-uc.a.run.app`, {
+ method: "POST",
+ body: JSON.stringify({
+ email,
+ useCase,
+ comment,
+ sourceId: "0VRjqHh6Vukqi0x0Vd0n/m8JuT7k8nOz",
+ }),
+ })
+ .then(() => {
+ window.localStorage.setItem(COMPLETE_QUESTIONNAIRE, true);
+ console.log(`✅ Questionnaire responses sent.`);
+ })
+ .catch((error) => {
+ console.error(`sendQuestionnaire`, error.message);
+ });
+}
+
+export default function Survey({ setHeader, setForwardBtn, setBackBtn }) {
+ const [selectedOption, setSelectedOption] = useState("");
+ const formRef = useRef(null);
+ const navigate = useNavigate();
+ const submitRef = useRef(null);
+
+ function handleForward() {
+ if (!!window?.localStorage?.getItem(COMPLETE_QUESTIONNAIRE)) {
+ navigate(paths.onboarding.createWorkspace());
+ return;
+ }
+ if (submitRef.current) {
+ submitRef.current.click();
+ }
+ }
+
+ function skipSurvey() {
+ navigate(paths.onboarding.createWorkspace());
+ }
+
+ function handleBack() {
+ navigate(paths.onboarding.dataHandling());
+ }
+
+ useEffect(() => {
+ setHeader({ title: TITLE, description: DESCRIPTION });
+ setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
+ setBackBtn({ showing: true, disabled: false, onClick: handleBack });
+ }, []);
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const formData = new FormData(form);
+
+ await sendQuestionnaire({
+ email: formData.get("email"),
+ useCase: formData.get("use_case") || "other",
+ comment: formData.get("comment") || null,
+ });
+
+ navigate(paths.onboarding.createWorkspace());
+ };
+
+ if (!!window?.localStorage?.getItem(COMPLETE_QUESTIONNAIRE)) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx
new file mode 100644
index 0000000000..500a483a18
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/UserSetup/index.jsx
@@ -0,0 +1,336 @@
+import System from "@/models/system";
+import showToast from "@/utils/toast";
+import React, { useState, useEffect, useRef } from "react";
+import debounce from "lodash.debounce";
+import paths from "@/utils/paths";
+import { useNavigate } from "react-router-dom";
+import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
+
+const TITLE = "User Setup";
+const DESCRIPTION = "Configure your user settings.";
+
+export default function UserSetup({ setHeader, setForwardBtn, setBackBtn }) {
+ const [selectedOption, setSelectedOption] = useState("");
+ const [singleUserPasswordValid, setSingleUserPasswordValid] = useState(false);
+ const [multiUserLoginValid, setMultiUserLoginValid] = useState(false);
+ const [enablePassword, setEnablePassword] = useState(false);
+ const myTeamSubmitRef = useRef(null);
+ const justMeSubmitRef = useRef(null);
+ const navigate = useNavigate();
+
+ function handleForward() {
+ if (selectedOption === "just_me" && enablePassword) {
+ justMeSubmitRef.current?.click();
+ } else if (selectedOption === "just_me" && !enablePassword) {
+ navigate(paths.onboarding.dataHandling());
+ } else if (selectedOption === "my_team") {
+ myTeamSubmitRef.current?.click();
+ }
+ }
+
+ function handleBack() {
+ navigate(paths.onboarding.customLogo());
+ }
+
+ useEffect(() => {
+ let isDisabled = true;
+ if (selectedOption === "just_me") {
+ isDisabled = !singleUserPasswordValid;
+ } else if (selectedOption === "my_team") {
+ isDisabled = !multiUserLoginValid;
+ }
+
+ setForwardBtn({
+ showing: true,
+ disabled: isDisabled,
+ onClick: handleForward,
+ });
+ }, [selectedOption, singleUserPasswordValid, multiUserLoginValid]);
+
+ useEffect(() => {
+ setHeader({ title: TITLE, description: DESCRIPTION });
+ setBackBtn({ showing: true, disabled: false, onClick: handleBack });
+ }, []);
+
+ return (
+
+
+
+ How many people will be using your instance?
+
+
+
+
+
+
+ {selectedOption === "just_me" && (
+
+ )}
+ {selectedOption === "my_team" && (
+
+ )}
+
+ );
+}
+
+const JustMe = ({
+ setSingleUserPasswordValid,
+ enablePassword,
+ setEnablePassword,
+ justMeSubmitRef,
+ navigate,
+}) => {
+ const [itemSelected, setItemSelected] = useState(false);
+ const [password, setPassword] = useState("");
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const formData = new FormData(form);
+ const { error } = await System.updateSystemPassword({
+ usePassword: true,
+ newPassword: formData.get("password"),
+ });
+
+ if (error) {
+ showToast(`Failed to set password: ${error}`, "error");
+ return;
+ }
+
+ showToast("Password set successfully!", "success", { clear: true });
+
+ // Auto-request token with password that was just set so they
+ // are not redirected to login after completion.
+ const { token } = await System.requestToken({
+ password: formData.get("password"),
+ });
+ window.localStorage.removeItem(AUTH_USER);
+ window.localStorage.removeItem(AUTH_TIMESTAMP);
+ window.localStorage.setItem(AUTH_TOKEN, token);
+
+ navigate(paths.onboarding.dataHandling());
+ };
+
+ const setNewPassword = (e) => setPassword(e.target.value);
+ const handlePasswordChange = debounce(setNewPassword, 500);
+
+ function handleYes() {
+ setItemSelected(true);
+ setEnablePassword(true);
+ }
+
+ function handleNo() {
+ setItemSelected(true);
+ setEnablePassword(false);
+ }
+
+ useEffect(() => {
+ if (enablePassword && itemSelected && password.length >= 8) {
+ setSingleUserPasswordValid(true);
+ } else if (!enablePassword && itemSelected) {
+ setSingleUserPasswordValid(true);
+ } else {
+ setSingleUserPasswordValid(false);
+ }
+ });
+ return (
+
+
+
+ Would you like to set up a password?
+
+
+ {enablePassword && (
+
+ )}
+
+
+ );
+};
+
+const MyTeam = ({ setMultiUserLoginValid, myTeamSubmitRef, navigate }) => {
+ const [username, setUsername] = useState("");
+ const [password, setPassword] = useState("");
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const formData = new FormData(form);
+ const data = {
+ username: formData.get("username"),
+ password: formData.get("password"),
+ };
+ const { success, error } = await System.setupMultiUser(data);
+ if (!success) {
+ showToast(`Error: ${error}`, "error");
+ return;
+ }
+
+ showToast("Multi-user login enabled.", "success", { clear: true });
+ navigate(paths.onboarding.dataHandling());
+
+ // Auto-request token with credentials that was just set so they
+ // are not redirected to login after completion.
+ const { user, token } = await System.requestToken(data);
+ window.localStorage.setItem(AUTH_USER, JSON.stringify(user));
+ window.localStorage.setItem(AUTH_TOKEN, token);
+ window.localStorage.removeItem(AUTH_TIMESTAMP);
+ };
+
+ const setNewUsername = (e) => setUsername(e.target.value);
+ const setNewPassword = (e) => setPassword(e.target.value);
+ const handleUsernameChange = debounce(setNewUsername, 500);
+ const handlePasswordChange = debounce(setNewPassword, 500);
+
+ useEffect(() => {
+ if (username.length >= 6 && password.length >= 8) {
+ setMultiUserLoginValid(true);
+ } else {
+ setMultiUserLoginValid(false);
+ }
+ }, [username, password]);
+ return (
+
+
+
+ );
+};
diff --git a/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/VectorDatabaseItem.jsx b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/VectorDatabaseItem.jsx
new file mode 100644
index 0000000000..4ecd304f7a
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/VectorDatabaseItem.jsx
@@ -0,0 +1,37 @@
+export default function VectorDatabaseItem({
+ name,
+ value,
+ image,
+ description,
+ checked,
+ onClick,
+}) {
+ return (
+
onClick(value)}
+ className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
+ checked ? "bg-white/10" : ""
+ }`}
+ >
+
+
+
+
+
{name}
+
{description}
+
+
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx
new file mode 100644
index 0000000000..17accbab00
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/VectorDatabaseConnection/index.jsx
@@ -0,0 +1,182 @@
+import React, { useEffect, useState, useRef } from "react";
+import { MagnifyingGlass } from "@phosphor-icons/react";
+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 System from "@/models/system";
+import VectorDatabaseItem from "./VectorDatabaseItem";
+import paths from "@/utils/paths";
+import PineconeDBOptions from "@/components/VectorDBSelection/PineconeDBOptions";
+import ChromaDBOptions from "@/components/VectorDBSelection/ChromaDBOptions";
+import QDrantDBOptions from "@/components/VectorDBSelection/QDrantDBOptions";
+import WeaviateDBOptions from "@/components/VectorDBSelection/WeaviateDBOptions";
+import LanceDBOptions from "@/components/VectorDBSelection/LanceDBOptions";
+import showToast from "@/utils/toast";
+import { useNavigate } from "react-router-dom";
+
+const TITLE = "Vector Database Connection";
+const DESCRIPTION =
+ "These are the credentials and settings for your vector database of choice.";
+
+export default function VectorDatabaseConnection({
+ setHeader,
+ setForwardBtn,
+ setBackBtn,
+}) {
+ const [searchQuery, setSearchQuery] = useState("");
+ const [filteredVDBs, setFilteredVDBs] = useState([]);
+ const [selectedVDB, setSelectedVDB] = useState(null);
+ const [settings, setSettings] = useState(null);
+ const formRef = useRef(null);
+ const hiddenSubmitButtonRef = useRef(null);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ async function fetchKeys() {
+ const _settings = await System.keys();
+ setSettings(_settings);
+ setSelectedVDB(_settings?.VectorDB || "lancedb");
+ }
+ fetchKeys();
+ }, []);
+
+ const VECTOR_DBS = [
+ {
+ name: "LanceDB",
+ value: "lancedb",
+ logo: LanceDbLogo,
+ options:
,
+ description:
+ "100% local vector DB that runs on the same instance as AnythingLLM.",
+ },
+ {
+ name: "Chroma",
+ value: "chroma",
+ logo: ChromaLogo,
+ options:
,
+ description:
+ "Open source vector database you can host yourself or on the cloud.",
+ },
+ {
+ name: "Pinecone",
+ value: "pinecone",
+ logo: PineconeLogo,
+ options:
,
+ description: "100% cloud-based vector database for enterprise use cases.",
+ },
+ {
+ name: "QDrant",
+ value: "qdrant",
+ logo: QDrantLogo,
+ options:
,
+ description: "Open source local and distributed cloud vector database.",
+ },
+ {
+ name: "Weaviate",
+ value: "weaviate",
+ logo: WeaviateLogo,
+ options:
,
+ description:
+ "Open source local and cloud hosted multi-modal vector database.",
+ },
+ ];
+
+ function handleForward() {
+ if (hiddenSubmitButtonRef.current) {
+ hiddenSubmitButtonRef.current.click();
+ }
+ }
+
+ function handleBack() {
+ navigate(paths.onboarding.embeddingPreference());
+ }
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ const form = e.target;
+ const data = {};
+ const formData = new FormData(form);
+ data.VectorDB = selectedVDB;
+ for (var [key, value] of formData.entries()) data[key] = value;
+ const { error } = await System.updateSystem(data);
+ if (error) {
+ showToast(`Failed to save Vector Database settings: ${error}`, "error");
+ return;
+ }
+ showToast("Vector Database settings saved successfully.", "success", {
+ clear: true,
+ });
+ navigate(paths.onboarding.customLogo());
+ };
+
+ useEffect(() => {
+ setHeader({ title: TITLE, description: DESCRIPTION });
+ setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
+ setBackBtn({ showing: true, disabled: false, onClick: handleBack });
+ }, []);
+
+ useEffect(() => {
+ if (searchQuery.trim() === "") {
+ setFilteredVDBs(VECTOR_DBS);
+ } else {
+ const lowercasedQuery = searchQuery.toLowerCase();
+ const filtered = VECTOR_DBS.filter((vdb) =>
+ vdb.name.toLowerCase().includes(lowercasedQuery)
+ );
+ setFilteredVDBs(filtered);
+ }
+ }, [searchQuery]);
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/Steps/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/index.jsx
new file mode 100644
index 0000000000..3f218d531b
--- /dev/null
+++ b/frontend/src/pages/OnboardingFlow/Steps/index.jsx
@@ -0,0 +1,130 @@
+import { ArrowLeft, ArrowRight } from "@phosphor-icons/react";
+import { lazy, useState } from "react";
+import { isMobile } from "react-device-detect";
+const OnboardingSteps = {
+ home: lazy(() => import("./Home")),
+ "llm-preference": lazy(() => import("./LLMPreference")),
+ "embedding-preference": lazy(() => import("./EmbeddingPreference")),
+ "vector-database": lazy(() => import("./VectorDatabaseConnection")),
+ "custom-logo": lazy(() => import("./CustomLogo")),
+ "user-setup": lazy(() => import("./UserSetup")),
+ "data-handling": lazy(() => import("./DataHandling")),
+ survey: lazy(() => import("./Survey")),
+ "create-workspace": lazy(() => import("./CreateWorkspace")),
+};
+
+export default OnboardingSteps;
+
+export function OnboardingLayout({ children }) {
+ const [header, setHeader] = useState({
+ title: "",
+ description: "",
+ });
+ const [backBtn, setBackBtn] = useState({
+ showing: false,
+ disabled: true,
+ onClick: () => null,
+ });
+ const [forwardBtn, setForwardBtn] = useState({
+ showing: false,
+ disabled: true,
+ onClick: () => null,
+ });
+
+ if (isMobile) {
+ return (
+
+
+
+
+
+ {header.title}
+
+
+ {header.description}
+
+
+ {children(setHeader, setBackBtn, setForwardBtn)}
+
+
+
+ {backBtn.showing && (
+
+ )}
+
+
+
+ {forwardBtn.showing && (
+
+ )}
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {backBtn.showing && (
+
+ )}
+
+
+
+
+
+ {header.title}
+
+
+ {header.description}
+
+
+ {children(setHeader, setBackBtn, setForwardBtn)}
+
+
+
+ {forwardBtn.showing && (
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/pages/OnboardingFlow/index.jsx b/frontend/src/pages/OnboardingFlow/index.jsx
index 106f7cbae1..c46b3c0bc9 100644
--- a/frontend/src/pages/OnboardingFlow/index.jsx
+++ b/frontend/src/pages/OnboardingFlow/index.jsx
@@ -1,57 +1,21 @@
-import React, { useEffect, useState } from "react";
-import OnboardingModal, { OnboardingModalId } from "./OnboardingModal";
-import useLogo from "@/hooks/useLogo";
-import { isMobile } from "react-device-detect";
+import React from "react";
+import OnboardingSteps, { OnboardingLayout } from "./Steps";
+import { useParams } from "react-router-dom";
export default function OnboardingFlow() {
- const { logo } = useLogo();
- const [modalVisible, setModalVisible] = useState(false);
-
- useEffect(() => {
- if (modalVisible) {
- document.getElementById(OnboardingModalId)?.showModal();
- }
- }, [modalVisible]);
-
- function showModal() {
- setModalVisible(true);
- }
-
- if (isMobile) {
- return (
-
-
-
- Welcome to
-
-
-
-
- Please use a desktop browser to continue onboarding.
-
-
-
-
- );
- }
+ const { step } = useParams();
+ const StepPage = OnboardingSteps[step || "home"];
+ if (step === "home" || !step) return
;
return (
-
-
-
- Welcome to
-
-
-
-
-
-
- {modalVisible &&
}
-
+
+ {(setHeader, setBackBtn, setForwardBtn) => (
+
+ )}
+
);
}
diff --git a/frontend/src/utils/paths.js b/frontend/src/utils/paths.js
index 7a0c7bb70c..547a3b3f31 100644
--- a/frontend/src/utils/paths.js
+++ b/frontend/src/utils/paths.js
@@ -7,8 +7,34 @@ export default {
login: () => {
return "/login";
},
- onboarding: () => {
- return "/onboarding";
+ onboarding: {
+ home: () => {
+ return "/onboarding";
+ },
+ survey: () => {
+ return "/onboarding/survey";
+ },
+ llmPreference: () => {
+ return "/onboarding/llm-preference";
+ },
+ embeddingPreference: () => {
+ return "/onboarding/embedding-preference";
+ },
+ vectorDatabase: () => {
+ return "/onboarding/vector-database";
+ },
+ customLogo: () => {
+ return "/onboarding/custom-logo";
+ },
+ userSetup: () => {
+ return "/onboarding/user-setup";
+ },
+ dataHandling: () => {
+ return "/onboarding/data-handling";
+ },
+ createWorkspace: () => {
+ return "/onboarding/create-workspace";
+ },
},
github: () => {
return "https://github.com/Mintplex-Labs/anything-llm";
diff --git a/server/prisma/seed.js b/server/prisma/seed.js
index 71d1e63d68..829b812ab4 100644
--- a/server/prisma/seed.js
+++ b/server/prisma/seed.js
@@ -1,12 +1,13 @@
-const { PrismaClient } = require('@prisma/client');
+const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient();
async function main() {
const settings = [
- { label: 'multi_user_mode', value: 'false' },
- { label: 'users_can_delete_workspaces', value: 'false' },
- { label: 'limit_user_messages', value: 'false' },
- { label: 'message_limit', value: '25' },
+ { label: "multi_user_mode", value: "false" },
+ { label: "users_can_delete_workspaces", value: "false" },
+ { label: "limit_user_messages", value: "false" },
+ { label: "message_limit", value: "25" },
+ { label: "logo_filename", value: "anything-llm.png" },
];
for (let setting of settings) {
@@ -24,7 +25,7 @@ async function main() {
}
main()
- .catch(e => {
+ .catch((e) => {
console.error(e);
process.exit(1);
})