Onboarding V2 (#502)

* WIP onboarding v2

* Welcome screen for onboarding complete

* fix home page and WIP create skeleton for llm preference search/options

* render llms as options

* add search functionality to llm preference & add survey step

* fix openai settings undefined & create custom logo onboarding page

* add user setup UI

* add data handling & privacy onboarding screen

* add create workspace onboarding screen

* fix survey width in onboarding

* create vector database connection onboarding page

* add workspace image & all skeleton ui complete

* fix navigation buttons and ui tweaks to fit on screen

* WIP LLMPreference

* LLM Preference screen fully functional

* create components for vector db options and fix styling of azure options

* remove unneeded comment

* vector db connection onboarding screen complete

* minor ui tweak to searchbar

* user setup page fully working

* create workspace onboarding page fully working

* useNavigate for navigation between pages

* mobile layout, cleanup old files, survey functionality implemented

* fix default logo appearing when should be blank & password setup bug fix

* Modify flow of onboarding
todo: embedding set up

* Add embedder setup screen & insert into flow

* update embedding back button
auto-dismiss toasts on each step

* move page defs under imports
fix bg color on mobile styling

---------

Co-authored-by: shatfield4 <seanhatfield5@gmail.com>
This commit is contained in:
Timothy Carambat 2024-01-04 15:54:31 -08:00 committed by GitHub
parent 92da23e963
commit d8ca92df88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2223 additions and 1791 deletions

View File

@ -120,6 +120,7 @@ export default function App() {
{/* Onboarding Flow */}
<Route path="/onboarding" element={<OnboardingFlow />} />
<Route path="/onboarding/:step" element={<OnboardingFlow />} />
</Routes>
<ToastContainer />
</PfpProvider>

View File

@ -1,53 +1,55 @@
export default function AzureAiOptions({ settings }) {
return (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI API Key"
defaultValue={settings?.AzureOpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI API Key"
defaultValue={settings?.AzureOpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
</>
</div>
);
}

View File

@ -10,72 +10,72 @@ export default function LocalAiOptions({ settings }) {
const [apiKey, setApiKey] = useState(settings?.LocalAiApiKey);
return (
<>
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
LocalAI Base URL
</label>
<input
type="url"
name="EmbeddingBasePath"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:8080/v1"
defaultValue={settings?.EmbeddingBasePath}
onChange={(e) => setBasePathValue(e.target.value)}
onBlur={() => setBasePath(basePathValue)}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<LocalAIModelSelection
settings={settings}
apiKey={apiKey}
basePath={basePath}
/>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Max embedding chunk length
</label>
<input
type="number"
name="EmbeddingModelMaxChunkLength"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="1000"
min={1}
onScroll={(e) => e.target.blur()}
defaultValue={settings?.EmbeddingModelMaxChunkLength}
required={false}
autoComplete="off"
/>
</div>
</div>
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-semibold block">
Local AI API Key
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
LocalAI Base URL
</label>
<p className="text-xs italic text-white/60">
optional API key to use if running LocalAI with API keys.
</p>
<input
type="url"
name="EmbeddingBasePath"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:8080/v1"
defaultValue={settings?.EmbeddingBasePath}
onChange={(e) => setBasePathValue(e.target.value)}
onBlur={() => setBasePath(basePathValue)}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<input
type="password"
name="LocalAiApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="sk-mysecretkey"
defaultValue={settings?.LocalAiApiKey ? "*".repeat(20) : ""}
autoComplete="off"
spellCheck={false}
onChange={(e) => setApiKeyValue(e.target.value)}
onBlur={() => setApiKey(apiKeyValue)}
<LocalAIModelSelection
settings={settings}
apiKey={apiKey}
basePath={basePath}
/>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Max embedding chunk length
</label>
<input
type="number"
name="EmbeddingModelMaxChunkLength"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="1000"
min={1}
onScroll={(e) => e.target.blur()}
defaultValue={settings?.EmbeddingModelMaxChunkLength}
required={false}
autoComplete="off"
/>
</div>
</div>
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<div className="flex flex-col gap-y-1 mb-4">
<label className="text-white text-sm font-semibold flex items-center gap-x-2">
Local AI API Key{" "}
<p className="!text-xs !italic !font-thin">optional</p>
</label>
</div>
<input
type="password"
name="LocalAiApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="sk-mysecretkey"
defaultValue={settings?.LocalAiApiKey ? "*".repeat(20) : ""}
autoComplete="off"
spellCheck={false}
onChange={(e) => setApiKeyValue(e.target.value)}
onBlur={() => setApiKey(apiKeyValue)}
/>
</div>
</div>
</div>
</>
</div>
);
}

View File

@ -1,34 +1,36 @@
export default function OpenAiOptions({ settings }) {
return (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="OpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="OpenAI API Key"
defaultValue={settings?.OpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="OpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="OpenAI API Key"
defaultValue={settings?.OpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Model Preference
</label>
<select
disabled={true}
className="cursor-not-allowed bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
<option disabled={true} selected={true}>
text-embedding-ada-002
</option>
</select>
</div>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Model Preference
</label>
<select
disabled={true}
className="cursor-not-allowed bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg block w-full p-2.5"
>
<option disabled={true} selected={true}>
text-embedding-ada-002
</option>
</select>
</div>
</>
</div>
);
}

View File

@ -1,87 +1,92 @@
export default function AzureAiOptions({ settings }) {
return (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Azure Service Endpoint
</label>
<input
type="url"
name="AzureOpenAiEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="https://my-azure.openai.azure.com"
defaultValue={settings?.AzureOpenAiEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI API Key"
defaultValue={settings?.AzureOpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Deployment Name
</label>
<input
type="text"
name="AzureOpenAiModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI chat model deployment name"
defaultValue={settings?.AzureOpenAiModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="AzureOpenAiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI API Key"
defaultValue={settings?.AzureOpenAiKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Model Token Limit
</label>
<select
name="AzureOpenAiTokenLimit"
defaultValue={settings?.AzureOpenAiTokenLimit || 4096}
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
required={true}
>
<option value={4096}>4,096 (gpt-3.5-turbo)</option>
<option value={16384}>16,384 (gpt-3.5-16k)</option>
<option value={8192}>8,192 (gpt-4)</option>
<option value={32768}>32,768 (gpt-4-32k)</option>
<option value={128000}>128,000 (gpt-4-turbo)</option>
</select>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Deployment Name
</label>
<input
type="text"
name="AzureOpenAiModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI chat model deployment name"
defaultValue={settings?.AzureOpenAiModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex-flex-col w-60"></div>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chat Model Token Limit
</label>
<select
name="AzureOpenAiTokenLimit"
defaultValue={settings?.AzureOpenAiTokenLimit || 4096}
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
required={true}
>
<option value={4096}>4,096 (gpt-3.5-turbo)</option>
<option value={16384}>16,384 (gpt-3.5-16k)</option>
<option value={8192}>8,192 (gpt-4)</option>
<option value={32768}>32,768 (gpt-4-32k)</option>
<option value={128000}>128,000 (gpt-4-turbo)</option>
</select>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Embedding Deployment Name
</label>
<input
type="text"
name="AzureOpenAiEmbeddingModelPref"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Azure OpenAI embedding model deployment name"
defaultValue={settings?.AzureOpenAiEmbeddingModelPref}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
</div>
);
}

View File

@ -6,7 +6,7 @@ export default function OpenAiOptions({ settings }) {
const [openAIKey, setOpenAIKey] = useState(settings?.OpenAiKey);
return (
<>
<div className="flex gap-x-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
@ -25,7 +25,7 @@ export default function OpenAiOptions({ settings }) {
/>
</div>
<OpenAIModelSelection settings={settings} apiKey={openAIKey} />
</>
</div>
);
}
@ -87,7 +87,7 @@ function OpenAIModelSelection({ apiKey, settings }) {
<option
key={model}
value={model}
selected={settings.OpenAiModelPref === model}
selected={settings?.OpenAiModelPref === model}
>
{model}
</option>
@ -102,7 +102,7 @@ function OpenAIModelSelection({ apiKey, settings }) {
<option
key={model.id}
value={model.id}
selected={settings.OpenAiModelPref === model.id}
selected={settings?.OpenAiModelPref === model.id}
>
{model.id}
</option>

View File

@ -89,7 +89,7 @@ export function AdminRoute({ Component }) {
if (isAuthd === null) return <FullScreenLoader />;
if (shouldRedirectToOnboarding) {
return <Navigate to={paths.onboarding()} />;
return <Navigate to={paths.onboarding.home()} />;
}
const user = userFromStorage();
@ -110,7 +110,7 @@ export function ManagerRoute({ Component }) {
if (isAuthd === null) return <FullScreenLoader />;
if (shouldRedirectToOnboarding) {
return <Navigate to={paths.onboarding()} />;
return <Navigate to={paths.onboarding.home()} />;
}
const user = userFromStorage();

View File

@ -0,0 +1,51 @@
export default function ChromaDBOptions({ settings }) {
return (
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chroma Endpoint
</label>
<input
type="url"
name="ChromaEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:8000"
defaultValue={settings?.ChromaEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Header
</label>
<input
name="ChromaApiHeader"
autoComplete="off"
type="text"
defaultValue={settings?.ChromaApiHeader}
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="X-Api-Key"
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
name="ChromaApiKey"
autoComplete="off"
type="password"
defaultValue={settings?.ChromaApiKey ? "*".repeat(20) : ""}
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="sk-myApiKeyToAccessMyChromaInstance"
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,9 @@
export default function LanceDBOptions() {
return (
<div className="w-full h-10 items-center justify-center flex">
<p className="text-sm font-base text-white text-opacity-60">
There is no configuration needed for LanceDB.
</p>
</div>
);
}

View File

@ -0,0 +1,55 @@
export default function PineconeDBOptions({ settings }) {
return (
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Pinecone DB API Key
</label>
<input
type="password"
name="PineConeKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Pinecone API Key"
defaultValue={settings?.PineConeKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Pinecone Index Environment
</label>
<input
type="text"
name="PineConeEnvironment"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="us-gcp-west-1"
defaultValue={settings?.PineConeEnvironment}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Pinecone Index Name
</label>
<input
type="text"
name="PineConeIndex"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="my-index"
defaultValue={settings?.PineConeIndex}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,38 @@
export default function QDrantDBOptions({ settings }) {
return (
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
QDrant API Endpoint
</label>
<input
type="url"
name="QdrantEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:6633"
defaultValue={settings?.QdrantEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="QdrantApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="wOeqxsYP4....1244sba"
defaultValue={settings?.QdrantApiKey}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,38 @@
export default function WeaviateDBOptions({ settings }) {
return (
<div className="w-full flex flex-col gap-y-4">
<div className="w-full flex items-center gap-4">
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Weaviate Endpoint
</label>
<input
type="url"
name="WeaviateEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:8080"
defaultValue={settings?.WeaviateEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="WeaviateApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="sk-123Abcweaviate"
defaultValue={settings?.WeaviateApiKey}
autoComplete="off"
spellCheck={false}
/>
</div>
</div>
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,145 +0,0 @@
import React, { memo, useEffect, useState } from "react";
import System from "@/models/system";
import AnythingLLM from "@/media/logo/anything-llm.png";
import useLogo from "@/hooks/useLogo";
import { Plus } from "@phosphor-icons/react";
import showToast from "@/utils/toast";
function AppearanceSetup({ prevStep, nextStep }) {
const { logo: _initLogo, setLogo: _setLogo } = useLogo();
const [logo, setLogo] = useState("");
const [isDefaultLogo, setIsDefaultLogo] = useState(true);
useEffect(() => {
async function logoInit() {
setLogo(_initLogo || "");
const _isDefaultLogo = await System.isDefaultLogo();
setIsDefaultLogo(_isDefaultLogo);
}
logoInit();
}, [_initLogo]);
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return false;
const objectURL = URL.createObjectURL(file);
setLogo(objectURL);
const formData = new FormData();
formData.append("logo", file);
const { success, error } = await System.uploadLogo(formData);
if (!success) {
showToast(`Failed to upload logo: ${error}`, "error");
setLogo(_initLogo);
return;
}
const logoURL = await System.fetchLogo();
_setLogo(logoURL);
showToast("Image uploaded successfully.", "success");
setIsDefaultLogo(false);
};
const handleRemoveLogo = async () => {
setLogo("");
setIsDefaultLogo(true);
const { success, error } = await System.removeCustomLogo();
if (!success) {
console.error("Failed to remove logo:", error);
showToast(`Failed to remove logo: ${error}`, "error");
const logoURL = await System.fetchLogo();
setLogo(logoURL);
setIsDefaultLogo(false);
return;
}
const logoURL = await System.fetchLogo();
_setLogo(logoURL);
showToast("Image successfully removed.", "success");
};
return (
<div className="w-full">
<div className="flex flex-col w-full px-8 py-4">
<div className="flex flex-col gap-y-2">
<h2 className="text-white text-sm font-medium">Custom Logo</h2>
<p className="text-sm font-base text-white/60">
Upload your custom logo to make your chatbot yours.
</p>
</div>
<div className="flex md:flex-row flex-col items-center">
<img
src={logo}
alt="Uploaded Logo"
className="w-48 h-48 object-contain mr-6"
hidden={isDefaultLogo}
onError={(e) => (e.target.src = AnythingLLM)}
/>
<div className="flex flex-row gap-x-8">
<label className="mt-5 hover:opacity-60" hidden={!isDefaultLogo}>
<input
id="logo-upload"
type="file"
accept="image/*"
className="hidden"
onChange={handleFileUpload}
/>
<div
className="w-80 py-4 bg-zinc-900/50 rounded-2xl border-2 border-dashed border-white border-opacity-60 justify-center items-center inline-flex cursor-pointer"
htmlFor="logo-upload"
>
<div className="flex flex-col items-center justify-center">
<div className="rounded-full bg-white/40">
<Plus className="w-6 h-6 text-black/80 m-2" />
</div>
<div className="text-white text-opacity-80 text-sm font-semibold py-1">
Add a custom logo
</div>
<div className="text-white text-opacity-60 text-xs font-medium py-1">
Recommended size: 800 x 200
</div>
</div>
</div>
</label>
<button
onClick={handleRemoveLogo}
className="text-white text-base font-medium hover:text-opacity-60"
>
Delete
</button>
</div>
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 space-x-6 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>
<div className="flex gap-2">
<button
onClick={() => nextStep("user_mode_setup")}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
>
Skip
</button>
<button
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"
>
Continue
</button>
</div>
</div>
</div>
);
}
export default memo(AppearanceSetup);

View File

@ -1,68 +0,0 @@
import React, { memo } from "react";
import { useNavigate } from "react-router-dom";
import paths from "@/utils/paths";
import Workspace from "@/models/workspace";
function CreateFirstWorkspace({ prevStep }) {
const navigate = useNavigate();
const handleCreate = async (e) => {
e.preventDefault();
const form = new FormData(e.target);
const { workspace, error } = await Workspace.new({
name: form.get("name"),
onboardingComplete: true,
});
if (!!workspace) {
navigate(paths.home());
} else {
alert(error);
}
};
return (
<div>
<form onSubmit={handleCreate} className="flex flex-col w-full">
<div className="flex flex-col w-full md:px-8 py-12">
<div className="space-y-6 flex h-full w-96">
<div className="w-full flex flex-col gap-y-4">
<div>
<label
htmlFor="name"
className="block mb-2 text-sm font-medium text-white"
>
Workspace name
</label>
<input
name="name"
type="text"
className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="My workspace"
minLength={4}
required={true}
autoComplete="off"
/>
</div>
</div>
</div>
</div>
<div className="flex w-full justify-end items-center px-6 py-4 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
type="submit"
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"
>
Finish
</button>
</div>
</form>
</div>
);
}
export default memo(CreateFirstWorkspace);

View File

@ -1,136 +0,0 @@
import React, { memo, useEffect, useState } 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 System from "@/models/system";
import PreLoader from "@/components/Preloader";
import LLMProviderOption from "@/components/LLMSelection/LLMProviderOption";
import OpenAiOptions from "@/components/EmbeddingSelection/OpenAiOptions";
import AzureAiOptions from "@/components/EmbeddingSelection/AzureAiOptions";
import LocalAiOptions from "@/components/EmbeddingSelection/LocalAiOptions";
import NativeEmbeddingOptions from "@/components/EmbeddingSelection/NativeEmbeddingOptions";
function EmbeddingSelection({ nextStep, prevStep, currentStep }) {
const [embeddingChoice, setEmbeddingChoice] = useState("native");
const [settings, setSettings] = useState(null);
const [loading, setLoading] = useState(true);
const updateChoice = (selection) => {
setEmbeddingChoice(selection);
};
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setEmbeddingChoice(_settings?.EmbeddingEngine || "native");
setLoading(false);
}
fetchKeys();
}, [currentStep]);
const handleSubmit = async (e) => {
e.preventDefault();
const form = e.target;
const data = {};
const formData = new FormData(form);
for (var [key, value] of formData.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
if (error) {
alert(`Failed to save LLM settings: ${error}`, "error");
return;
}
nextStep("vector_database");
return;
};
if (loading)
return (
<div className="w-full h-full flex justify-center items-center p-20">
<PreLoader />
</div>
);
return (
<div className="w-full">
<form onSubmit={handleSubmit} className="flex flex-col w-full">
<div className="flex flex-col w-full px-1 md:px-8 py-4">
<div className="text-white text-sm font-medium pb-4">
Embedding Provider
</div>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[752px]">
<input
hidden={true}
name="EmbeddingEngine"
value={embeddingChoice}
/>
<LLMProviderOption
name="AnythingLLM Embedder"
value="native"
description="Use the built-in embedding engine for AnythingLLM. Zero setup!"
checked={embeddingChoice === "native"}
image={AnythingLLMIcon}
onClick={updateChoice}
/>
<LLMProviderOption
name="OpenAI"
value="openai"
link="openai.com"
description="The standard option for most non-commercial use."
checked={embeddingChoice === "openai"}
image={OpenAiLogo}
onClick={updateChoice}
/>
<LLMProviderOption
name="Azure OpenAI"
value="azure"
link="azure.microsoft.com"
description="The enterprise option of OpenAI hosted on Azure services."
checked={embeddingChoice === "azure"}
image={AzureOpenAiLogo}
onClick={updateChoice}
/>
<LLMProviderOption
name="LocalAI"
value="localai"
link="localai.io"
description="Self hosted LocalAI embedding engine."
checked={embeddingChoice === "localai"}
image={LocalAiLogo}
onClick={updateChoice}
/>
</div>
<div className="mt-4 flex flex-wrap gap-4 max-w-[752px]">
{embeddingChoice === "native" && <NativeEmbeddingOptions />}
{embeddingChoice === "openai" && (
<OpenAiOptions settings={settings} />
)}
{embeddingChoice === "azure" && (
<AzureAiOptions settings={settings} />
)}
{embeddingChoice === "localai" && (
<LocalAiOptions settings={settings} />
)}
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 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
type="submit"
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>
</form>
</div>
);
}
export default memo(EmbeddingSelection);

View File

@ -1,183 +0,0 @@
import React, { memo, useEffect, useState } 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 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 System from "@/models/system";
import PreLoader from "@/components/Preloader";
import LLMProviderOption from "@/components/LLMSelection/LLMProviderOption";
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";
function LLMSelection({ nextStep, prevStep, currentStep }) {
const [llmChoice, setLLMChoice] = useState("openai");
const [settings, setSettings] = useState(null);
const [loading, setLoading] = useState(true);
const updateLLMChoice = (selection) => {
setLLMChoice(selection);
};
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setLLMChoice(_settings?.LLMProvider || "openai");
setLoading(false);
}
if (currentStep === "llm_preference") {
fetchKeys();
}
}, []);
const handleSubmit = async (e) => {
e.preventDefault();
const form = e.target;
const data = {};
const formData = new FormData(form);
for (var [key, value] of formData.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
if (error) {
alert(`Failed to save LLM settings: ${error}`, "error");
return;
}
nextStep("embedding_preferences");
};
if (loading)
return (
<div className="w-full h-full flex justify-center items-center p-20">
<PreLoader />
</div>
);
return (
<div>
<form onSubmit={handleSubmit} className="flex flex-col w-full">
<div className="flex flex-col w-full px-1 md:px-8 py-4">
<div className="text-white text-sm font-medium pb-4">
LLM Providers
</div>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[752px]">
<input hidden={true} name="LLMProvider" value={llmChoice} />
<LLMProviderOption
name="OpenAI"
value="openai"
link="openai.com"
description="The standard option for most non-commercial use."
checked={llmChoice === "openai"}
image={OpenAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Azure OpenAI"
value="azure"
link="azure.microsoft.com"
description="The enterprise option of OpenAI hosted on Azure services."
checked={llmChoice === "azure"}
image={AzureOpenAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Anthropic Claude 2"
value="anthropic"
link="anthropic.com"
description="A friendly AI Assistant hosted by Anthropic. Provides chat services only!"
checked={llmChoice === "anthropic"}
image={AnthropicLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Google Gemini"
value="gemini"
link="ai.google.dev"
description="Google's largest and most capable AI model"
checked={llmChoice === "gemini"}
image={GeminiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="LM Studio"
value="lmstudio"
link="lmstudio.ai"
description="Discover, download, and run thousands of cutting edge LLMs in a few clicks."
checked={llmChoice === "lmstudio"}
image={LMStudioLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Local AI"
value="localai"
link="localai.io"
description="Run LLMs locally on your own machine."
checked={llmChoice === "localai"}
image={LocalAiLogo}
onClick={updateLLMChoice}
/>
<LLMProviderOption
name="Ollama"
value="ollama"
link="ollama.ai"
description="Run LLMs locally on your own machine."
checked={llmChoice === "ollama"}
image={OllamaLogo}
onClick={updateLLMChoice}
/>
{!window.location.hostname.includes("useanything.com") && (
<LLMProviderOption
name="Custom Llama Model"
value="native"
description="Use a downloaded custom Llama model for chatting on this AnythingLLM instance."
checked={llmChoice === "native"}
image={AnythingLLMIcon}
onClick={updateLLMChoice}
/>
)}
</div>
<div className="mt-4 flex flex-wrap gap-4 max-w-[752px]">
{llmChoice === "openai" && <OpenAiOptions settings={settings} />}
{llmChoice === "azure" && <AzureAiOptions settings={settings} />}
{llmChoice === "anthropic" && (
<AnthropicAiOptions settings={settings} />
)}
{llmChoice === "gemini" && <GeminiLLMOptions settings={settings} />}
{llmChoice === "lmstudio" && (
<LMStudioOptions settings={settings} />
)}
{llmChoice === "localai" && <LocalAiOptions settings={settings} />}
{llmChoice === "ollama" && <OllamaLLMOptions settings={settings} />}
{llmChoice === "native" && <NativeLLMOptions settings={settings} />}
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 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
type="submit"
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>
</form>
</div>
);
}
export default memo(LLMSelection);

View File

@ -1,117 +0,0 @@
import React, { useState, memo } from "react";
import System from "@/models/system";
import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
import debounce from "lodash.debounce";
// Multi-user mode step
function MultiUserSetup({ nextStep, prevStep }) {
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) {
alert(error);
return;
}
// 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);
nextStep("data_handling");
};
const setNewUsername = (e) => setUsername(e.target.value);
const setNewPassword = (e) => setPassword(e.target.value);
const handleUsernameChange = debounce(setNewUsername, 500);
const handlePasswordChange = debounce(setNewPassword, 500);
return (
<div>
<form onSubmit={handleSubmit}>
<div className="flex flex-col w-full md:px-8 py-4">
<div className="space-y-6 flex h-full w-96">
<div className="w-full flex flex-col gap-y-4">
<div>
<label
htmlFor="name"
className="block mb-2 text-sm font-medium text-white"
>
Admin account username
</label>
<input
name="username"
type="text"
className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="Your admin username"
minLength={6}
required={true}
autoComplete="off"
onChange={handleUsernameChange}
/>
</div>
<div>
<label
htmlFor="name"
className="block mb-2 text-sm font-medium text-white"
>
Admin account password
</label>
<input
name="password"
type="password"
className="bg-zinc-900 border border-gray-500 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="Your admin password"
minLength={8}
required={true}
autoComplete="off"
onChange={handlePasswordChange}
/>
</div>
<p className="w-96 text-white text-opacity-80 text-xs font-base">
Username must be at least 6 characters long. Password must be at
least 8 characters long.
</p>
</div>
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 space-x-6 border-t rounded-b border-gray-500/50">
<div className="w-96 text-white text-opacity-80 text-xs font-base">
By default, you will be the only admin. As an admin you will need to
create accounts for all new users or admins. Do not lose your
password as only admins can reset passwords.
</div>
<div className="flex gap-2">
<button
onClick={prevStep}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
>
Back
</button>
<button
type="submit"
className="border px-4 py-2 rounded-lg text-sm items-center flex gap-x-2
border-slate-200 text-slate-800 bg-slate-200 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow
disabled:border-gray-400 disabled:text-slate-800 disabled:bg-gray-400 disabled:cursor-not-allowed"
disabled={!(!!username && !!password)}
>
Continue
</button>
</div>
</div>
</form>
</div>
);
}
export default memo(MultiUserSetup);

View File

@ -1,103 +0,0 @@
import React, { memo, useState } from "react";
import System from "@/models/system";
import { AUTH_TIMESTAMP, AUTH_TOKEN, AUTH_USER } from "@/utils/constants";
import debounce from "lodash.debounce";
function PasswordProtection({ nextStep, prevStep }) {
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) {
alert(`Failed to set password: ${error}`, "error");
return;
}
// 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);
nextStep("data_handling");
return;
};
const handleSkip = () => {
nextStep("data_handling");
};
const setNewPassword = (e) => setPassword(e.target.value);
const handlePasswordChange = debounce(setNewPassword, 500);
return (
<div className="w-full">
<form className="flex flex-col w-full" onSubmit={handleSubmit}>
<div className="flex flex-col w-full px-1 md:px-8 py-4">
<div className="w-full flex flex-col gap-y-2 my-5">
<div className="w-80">
<div className="flex flex-col mb-3 ">
<label
htmlFor="password"
className="block font-medium text-white"
>
New Password
</label>
<p className="text-slate-300 text-xs">
must be at least 8 characters.
</p>
</div>
<input
onChange={handlePasswordChange}
name="password"
type="text"
className="bg-zinc-900 text-white text-sm rounded-lg focus:border-blue-500 block w-full p-2.5 placeholder-white placeholder-opacity-60 focus:ring-blue-500"
placeholder="Your Instance Password"
minLength={8}
required={true}
autoComplete="off"
/>
</div>
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 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>
<div className="flex gap-2">
<button
onClick={handleSkip}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
>
Skip
</button>
<button
type="submit"
disabled={!password}
className="border px-4 py-2 rounded-lg text-sm items-center flex gap-x-2
border-slate-200 text-slate-800 bg-slate-200 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow
disabled:border-gray-400 disabled:text-slate-800 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
Continue
</button>
</div>
</div>
</form>
</div>
);
}
export default memo(PasswordProtection);

View File

@ -1,47 +0,0 @@
import React, { memo } from "react";
// How many people will be using your instance step
function UserModeSelection({ nextStep, prevStep }) {
const justMeClicked = () => {
nextStep("password_protection");
};
const myTeamClicked = () => {
nextStep("multi_user_mode");
};
return (
<div>
<div className="flex flex-col justify-center items-center px-20 py-14">
<div className="w-80 text-white text-center text-2xl font-base">
How many people will be using your instance?
</div>
<div className="flex gap-4 justify-center my-8">
<button
onClick={justMeClicked}
className="transition-all duration-200 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"
>
Just Me
</button>
<button
onClick={myTeamClicked}
className="transition-all duration-200 border border-slate-200 px-5 py-2.5 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"
>
My Team
</button>
</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 transition-all duration-300"
>
Back
</button>
</div>
</div>
);
}
export default memo(UserModeSelection);

View File

@ -1,240 +0,0 @@
import { COMPLETE_QUESTIONNAIRE } from "@/utils/constants";
import paths from "@/utils/paths";
import { CheckCircle, Circle } from "@phosphor-icons/react";
import React, { memo } from "react";
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);
});
}
function UserQuestionnaire({ nextStep, prevStep }) {
const handleSubmit = async (e) => {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
nextStep("create_workspace");
await sendQuestionnaire({
email: formData.get("email"),
useCase: formData.get("use_case") || "other",
comment: formData.get("comment") || null,
});
return;
};
const handleSkip = () => {
nextStep("create_workspace");
};
if (!!window?.localStorage?.getItem(COMPLETE_QUESTIONNAIRE)) {
return (
<div className="w-full">
<div className="w-full flex items-center justify-center px-1 md:px-8 py-4">
<div className="w-auto flex flex-col gap-y-1 items-center">
<CheckCircle size={60} className="text-green-500" />
<p className="text-zinc-300">Thank you for your feedback!</p>
<a
href={paths.mailToMintplex()}
className="text-blue-400 underline text-xs"
>
team@mintplexlabs.com
</a>
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 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>
<div className="flex gap-2">
<button
onClick={handleSkip}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
>
Skip
</button>
<button
type="submit"
className="border px-4 py-2 rounded-lg text-sm items-center flex gap-x-2
border-slate-200 text-slate-800 bg-slate-200 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow
disabled:border-gray-400 disabled:text-slate-800 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
Continue
</button>
</div>
</div>
</div>
);
}
return (
<div className="w-full">
<form className="flex flex-col w-full" onSubmit={handleSubmit}>
<div className="flex flex-col w-full px-1 md:px-8 py-4">
<div className="w-full flex flex-col gap-y-2 my-5">
<div className="w-80">
<div className="flex flex-col mb-3 ">
<label htmlFor="email" className="block font-medium text-white">
What is your email?
</label>
</div>
<input
name="email"
type="email"
className="bg-zinc-900 text-white text-sm rounded-lg focus:border-blue-500 block w-full p-2.5 placeholder-white placeholder-opacity-60 focus:ring-blue-500"
placeholder="you@gmail.com"
required={true}
autoComplete="off"
/>
</div>
</div>
<div className="w-full flex flex-col gap-y-2 my-5">
<div className="w-full">
<div className="flex flex-col mb-3 ">
<label
htmlFor="use_case"
className="block font-medium text-white"
>
How are you planning to use AnythingLLM?
</label>
</div>
<div className="flex flex-col gap-y-2">
<div class="flex items-center ps-4 border border-zinc-400 rounded group radio-container hover:bg-blue-400/10">
<input
id="bordered-radio-1"
type="radio"
value="business"
name="use_case"
class="sr-only peer"
/>
<Circle
weight="fill"
className="fill-transparent border border-gray-300 rounded-full peer-checked:fill-blue-500 peer-checked:border-none"
/>
<label
for="bordered-radio-1"
class="w-full py-4 ms-2 text-sm font-medium text-gray-300"
>
For my business
</label>
</div>
<div class="flex items-center ps-4 border border-zinc-400 rounded group radio-container hover:bg-blue-400/10">
<input
id="bordered-radio-2"
type="radio"
value="personal"
name="use_case"
class="sr-only peer"
/>
<Circle
weight="fill"
className="fill-transparent border border-gray-300 rounded-full peer-checked:fill-blue-500 peer-checked:border-none"
/>
<label
for="bordered-radio-2"
class="w-full py-4 ms-2 text-sm font-medium text-gray-300"
>
For personal use
</label>
</div>
<div class="flex items-center ps-4 border border-zinc-400 rounded group radio-container hover:bg-blue-400/10">
<input
id="bordered-radio-3"
type="radio"
value="other"
name="use_case"
class="sr-only peer"
/>
<Circle
weight="fill"
className="fill-transparent border border-gray-300 rounded-full peer-checked:fill-blue-500 peer-checked:border-none"
/>
<label
for="bordered-radio-3"
class="w-full py-4 ms-2 text-sm font-medium text-gray-300"
>
I'm not sure yet
</label>
</div>
</div>
</div>
</div>
<div className="w-full flex flex-col gap-y-2 my-5">
<div className="w-full">
<div className="flex flex-col mb-3 ">
<label
htmlFor="comments"
className="block font-medium text-white"
>
Any comments for the team?
</label>
</div>
<textarea
name="comment"
rows={5}
className="bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="If you have any questions or comments right now, you can leave them here and we will get back to you. You can also email team@mintplexlabs.com"
wrap="soft"
autoComplete="off"
/>
</div>
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 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>
<div className="flex gap-2">
<button
onClick={handleSkip}
type="button"
className="px-4 py-2 rounded-lg text-white hover:bg-sidebar"
>
Skip
</button>
<button
type="submit"
className="border px-4 py-2 rounded-lg text-sm items-center flex gap-x-2
border-slate-200 text-slate-800 bg-slate-200 hover:text-white hover:bg-transparent focus:ring-gray-800 font-semibold shadow
disabled:border-gray-400 disabled:text-slate-800 disabled:bg-gray-400 disabled:cursor-not-allowed"
>
Continue
</button>
</div>
</div>
</form>
</div>
);
}
export default memo(UserQuestionnaire);

View File

@ -1,310 +0,0 @@
import React, { memo, useEffect, useState } from "react";
import VectorDBOption from "@/components/VectorDBOption";
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 PreLoader from "@/components/Preloader";
function VectorDatabaseConnection({ nextStep, prevStep, currentStep }) {
const [vectorDB, setVectorDB] = useState("lancedb");
const [settings, setSettings] = useState({});
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchKeys() {
const _settings = await System.keys();
setSettings(_settings);
setVectorDB(_settings?.VectorDB || "lancedb");
setLoading(false);
}
if (currentStep === "vector_database") {
fetchKeys();
}
}, [currentStep]);
const updateVectorChoice = (selection) => {
setVectorDB(selection);
};
const handleSubmit = async (e, formElement) => {
e.preventDefault();
const form = formElement || e.target;
const data = {};
const formData = new FormData(form);
for (var [key, value] of formData.entries()) data[key] = value;
const { error } = await System.updateSystem(data);
if (error) {
alert(`Failed to save settings: ${error}`, "error");
return;
}
nextStep("appearance");
return;
};
if (loading)
return (
<div className="w-full h-full flex justify-center items-center p-20">
<PreLoader />
</div>
);
return (
<div>
<form onSubmit={handleSubmit} className="flex flex-col w-full">
<div className="flex flex-col w-full px-1 md:px-8 py-4">
<div className="text-white text-sm font-medium pb-4">
Select your preferred vector database provider
</div>
<div className="w-full flex md:flex-wrap overflow-x-scroll gap-4 max-w-[752px]">
<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="QDrant"
value="qdrant"
link="qdrant.tech"
description="Open source local and distributed cloud vector database."
checked={vectorDB === "qdrant"}
image={QDrantLogo}
onClick={updateVectorChoice}
/>
<VectorDBOption
name="Weaviate"
value="weaviate"
link="weaviate.io"
description="Open source local and cloud hosted multi-modal vector database."
checked={vectorDB === "weaviate"}
image={WeaviateLogo}
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>
<div className="mt-4 flex flex-wrap gap-4 max-w-[752px]">
{vectorDB === "pinecone" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Pinecone DB API Key
</label>
<input
type="password"
name="PineConeKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="Pinecone API Key"
defaultValue={settings?.PineConeKey ? "*".repeat(20) : ""}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Pinecone Index Environment
</label>
<input
type="text"
name="PineConeEnvironment"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="us-gcp-west-1"
defaultValue={settings?.PineConeEnvironment}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Pinecone Index Name
</label>
<input
type="text"
name="PineConeIndex"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="my-index"
defaultValue={settings?.PineConeIndex}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{vectorDB === "chroma" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Chroma Endpoint
</label>
<input
type="url"
name="ChromaEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:8000"
defaultValue={settings?.ChromaEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Header
</label>
<input
name="ChromaApiHeader"
autoComplete="off"
type="text"
defaultValue={settings?.ChromaApiHeader}
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="X-Api-Key"
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
name="ChromaApiKey"
autoComplete="off"
type="password"
defaultValue={settings?.ChromaApiKey ? "*".repeat(20) : ""}
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="sk-myApiKeyToAccessMyChromaInstance"
/>
</div>
</>
)}
{vectorDB === "lancedb" && (
<div className="w-full h-10 items-center justify-center flex">
<p className="text-sm font-base text-white text-opacity-60">
There is no configuration needed for LanceDB.
</p>
</div>
)}
{vectorDB === "qdrant" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
QDrant API Endpoint
</label>
<input
type="url"
name="QdrantEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:6633"
defaultValue={settings?.QdrantEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="QdrantApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="wOeqxsYP4....1244sba"
defaultValue={settings?.QdrantApiKey}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
{vectorDB === "weaviate" && (
<>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
Weaviate Endpoint
</label>
<input
type="url"
name="WeaviateEndpoint"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="http://localhost:8080"
defaultValue={settings?.WeaviateEndpoint}
required={true}
autoComplete="off"
spellCheck={false}
/>
</div>
<div className="flex flex-col w-60">
<label className="text-white text-sm font-semibold block mb-4">
API Key
</label>
<input
type="password"
name="WeaviateApiKey"
className="bg-zinc-900 text-white placeholder-white placeholder-opacity-60 text-sm rounded-lg focus:border-white block w-full p-2.5"
placeholder="sk-123Abcweaviate"
defaultValue={settings?.WeaviateApiKey}
autoComplete="off"
spellCheck={false}
/>
</div>
</>
)}
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 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
type="submit"
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>
</form>
</div>
);
}
export default memo(VectorDatabaseConnection);

View File

@ -1,136 +0,0 @@
import React, { useState } from "react";
import { X } from "@phosphor-icons/react";
import LLMSelection from "./Steps/LLMSelection";
import VectorDatabaseConnection from "./Steps/VectorDatabaseConnection";
import AppearanceSetup from "./Steps/AppearanceSetup";
import UserModeSelection from "./Steps/UserModeSelection";
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";
import UserQuestionnaire from "./Steps/UserQuestionnaire";
const DIALOG_ID = "onboarding-modal";
const STEPS = {
llm_preference: {
title: "LLM Preference",
description:
"These are the credentials and settings for your preferred LLM chat & embedding provider.",
component: LLMSelection,
},
embedding_preferences: {
title: "Embedding Preference",
description: "Choose a provider for embedding files and text.",
component: EmbeddingSelection,
},
vector_database: {
title: "Vector Database",
description:
"These are the credentials and settings for how your AnythingLLM instance will function.",
component: VectorDatabaseConnection,
},
appearance: {
title: "Appearance",
description:
"Customize the appearance of your AnythingLLM instance.\nFind more customization options on the appearance settings page.",
component: AppearanceSetup,
},
user_mode_setup: {
title: "User Mode Setup",
description: "Choose how many people will be using your instance.",
component: UserModeSelection,
},
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,
},
multi_user_mode: {
title: "Multi-User Mode",
description:
"Setup your instance to support your team by activating multi-user mode.",
component: MultiUserSetup,
},
data_handling: {
title: "Data Handling",
description:
"We are committed to transparency and control when it comes to your personal data.",
component: DataHandling,
},
user_questionnaire: {
title: "A little about yourself",
description:
"We use information about how you use AnythingLLM to make our product better.",
component: UserQuestionnaire,
},
create_workspace: {
title: "Create Workspace",
description: "To get started, create a new workspace.",
component: CreateFirstWorkspace,
},
};
export const OnboardingModalId = DIALOG_ID;
export default function OnboardingModal({ setModalVisible }) {
const [currentStep, setCurrentStep] = useState("llm_preference");
const [history, setHistory] = useState(["llm_preference"]);
function hideModal() {
setModalVisible(false);
}
const nextStep = (stepKey) => {
setCurrentStep(stepKey);
setHistory([...history, stepKey]);
};
const prevStep = () => {
const currentStepIdx = history.indexOf(currentStep);
if (currentStepIdx === -1 || currentStepIdx === 0) {
setCurrentStep("llm_preference");
setHistory(["llm_preference"]);
return hideModal();
}
const prevStep = history[currentStepIdx - 1];
const _history = [...history].slice(0, currentStepIdx);
setCurrentStep(prevStep);
setHistory(_history);
};
const { component: StepComponent, ...step } = STEPS[currentStep];
return (
<dialog id={DIALOG_ID} className="bg-transparent outline-none">
<div className="relative max-h-full">
<div className="relative bg-main-gradient rounded-2xl shadow border-2 border-slate-300/10">
<div className="flex items-start justify-between px-6 py-4 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 whitespace-pre">
{step.description || ""}
</p>
</div>
<button
onClick={hideModal}
type="button"
className="text-gray-400 bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:border-white/60 bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
>
<X className="text-gray-300 text-lg" />
</button>
</div>
<div className="space-y-6 flex h-full w-full justify-center">
<StepComponent
currentStep={currentStep}
nextStep={nextStep}
prevStep={prevStep}
/>
</div>
</div>
</div>
</dialog>
);
}

View File

@ -0,0 +1,99 @@
import React, { useEffect, useRef, useState } from "react";
import illustration from "@/media/illustrations/create-workspace.png";
import paths from "@/utils/paths";
import showToast from "@/utils/toast";
import { useNavigate } from "react-router-dom";
import Workspace from "@/models/workspace";
const TITLE = "Create your first workspace";
const DESCRIPTION =
"Create your first workspace and get started with AnythingLLM.";
export default function CreateWorkspace({
setHeader,
setForwardBtn,
setBackBtn,
}) {
const [workspaceName, setWorkspaceName] = useState("");
const navigate = useNavigate();
const createWorkspaceRef = useRef();
useEffect(() => {
setHeader({ title: TITLE, description: DESCRIPTION });
setBackBtn({ showing: false, disabled: false, onClick: handleBack });
}, []);
useEffect(() => {
if (workspaceName.length > 3) {
setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
} else {
setForwardBtn({ showing: true, disabled: true, onClick: handleForward });
}
}, [workspaceName]);
const handleCreate = async (e) => {
e.preventDefault();
const form = new FormData(e.target);
const { workspace, error } = await Workspace.new({
name: form.get("name"),
onboardingComplete: true,
});
if (!!workspace) {
showToast(
"Workspace created successfully! Taking you to home...",
"success"
);
await new Promise((resolve) => setTimeout(resolve, 1000));
navigate(paths.home());
} else {
showToast(`Failed to create workspace: ${error}`, "error");
}
};
function handleForward() {
createWorkspaceRef.current.click();
}
function handleBack() {
navigate(paths.onboarding.survey());
}
return (
<form
onSubmit={handleCreate}
className="w-full flex items-center justify-center flex-col gap-y-2"
>
<img src={illustration} alt="Create workspace" />
<div className="flex flex-col gap-y-4 w-full max-w-[600px]">
{" "}
<div className="w-full mt-4">
<label
htmlFor="name"
className="block mb-3 text-sm font-medium text-white"
>
Workspace Name
</label>
<input
name="name"
type="text"
className="bg-zinc-900 text-white text-sm rounded-lg block w-full p-2.5"
placeholder="My Workspace"
minLength={4}
required={true}
autoComplete="off"
onChange={(e) => setWorkspaceName(e.target.value)}
/>
<div className="mt-4 text-white text-opacity-80 text-xs font-base -mb-2">
Workspace name must be at least 4 characters.
</div>
</div>
</div>
<button
type="submit"
ref={createWorkspaceRef}
hidden
aria-hidden="true"
></button>
</form>
);
}

View File

@ -0,0 +1,136 @@
import useLogo from "@/hooks/useLogo";
import System from "@/models/system";
import showToast from "@/utils/toast";
import { Plus } from "@phosphor-icons/react";
import React, { useState, useEffect } from "react";
import AnythingLLM from "@/media/logo/anything-llm.png";
import paths from "@/utils/paths";
import { useNavigate } from "react-router-dom";
const TITLE = "Custom Logo";
const DESCRIPTION =
"Upload your custom logo to make your chatbot yours. Optional.";
export default function CustomLogo({ setHeader, setForwardBtn, setBackBtn }) {
const navigate = useNavigate();
function handleForward() {
navigate(paths.onboarding.userSetup());
}
function handleBack() {
navigate(paths.onboarding.vectorDatabase());
}
useEffect(() => {
setHeader({ title: TITLE, description: DESCRIPTION });
setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
setBackBtn({ showing: true, disabled: false, onClick: handleBack });
}, []);
const { logo: _initLogo, setLogo: _setLogo } = useLogo();
const [logo, setLogo] = useState("");
const [isDefaultLogo, setIsDefaultLogo] = useState(true);
useEffect(() => {
async function logoInit() {
setLogo(_initLogo || "");
const _isDefaultLogo = await System.isDefaultLogo();
setIsDefaultLogo(_isDefaultLogo);
}
logoInit();
}, [_initLogo]);
const handleFileUpload = async (event) => {
const file = event.target.files[0];
if (!file) return false;
const objectURL = URL.createObjectURL(file);
setLogo(objectURL);
const formData = new FormData();
formData.append("logo", file);
const { success, error } = await System.uploadLogo(formData);
if (!success) {
showToast(`Failed to upload logo: ${error}`, "error");
setLogo(_initLogo);
return;
}
const logoURL = await System.fetchLogo();
_setLogo(logoURL);
showToast("Image uploaded successfully.", "success", { clear: true });
setIsDefaultLogo(false);
};
const handleRemoveLogo = async () => {
setLogo("");
setIsDefaultLogo(true);
const { success, error } = await System.removeCustomLogo();
if (!success) {
console.error("Failed to remove logo:", error);
showToast(`Failed to remove logo: ${error}`, "error");
const logoURL = await System.fetchLogo();
setLogo(logoURL);
setIsDefaultLogo(false);
return;
}
const logoURL = await System.fetchLogo();
_setLogo(logoURL);
showToast("Image successfully removed.", "success", { clear: true });
};
return (
<div className="flex items-center w-full">
<div className="flex gap-x-8 flex-col w-full">
{isDefaultLogo ? (
<label className="mt-5 hover:opacity-60 w-full flex justify-center transition-all duration-300">
<input
id="logo-upload"
type="file"
accept="image/*"
className="hidden"
onChange={handleFileUpload}
/>
<div
className="max-w-[600px] w-full h-64 max-h-[600px] py-4 bg-zinc-900/50 rounded-2xl border-2 border-dashed border-white border-opacity-60 justify-center items-center inline-flex cursor-pointer"
htmlFor="logo-upload"
>
<div className="flex flex-col items-center justify-center">
<div className="rounded-full bg-white/40">
<Plus className="w-6 h-6 text-black/80 m-2" />
</div>
<div className="text-white text-opacity-80 text-sm font-semibold py-1">
Add a custom logo
</div>
<div className="text-white text-opacity-60 text-xs font-medium py-1">
Recommended size: 800 x 200
</div>
</div>
</div>
</label>
) : (
<div className="w-full flex justify-center">
<img
src={logo}
alt="Uploaded Logo"
className="w-48 h-48 object-contain mr-6"
hidden={isDefaultLogo}
onError={(e) => (e.target.src = AnythingLLM)}
/>
</div>
)}
<button
onClick={handleRemoveLogo}
className="text-white text-base font-medium hover:text-opacity-60 mt-8"
>
Remove logo
</button>
</div>
</div>
);
}

View File

@ -1,4 +1,4 @@
import React, { memo, useEffect, useState } from "react";
import PreLoader from "@/components/Preloader";
import System from "@/models/system";
import AnythingLLMIcon from "@/media/logo/anything-llm-icon.png";
import OpenAiLogo from "@/media/llmprovider/openai.png";
@ -13,8 +13,13 @@ 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";
import React, { useState, useEffect } from "react";
import paths from "@/utils/paths";
import { useNavigate } from "react-router-dom";
const TITLE = "Data Handling & Privacy";
const DESCRIPTION =
"We are committed to transparency and control when it comes to your personal data.";
const LLM_SELECTION_PRIVACY = {
openai: {
name: "OpenAI",
@ -151,26 +156,36 @@ const EMBEDDING_ENGINE_PRIVACY = {
},
};
function DataHandling({ nextStep, prevStep, currentStep }) {
export default function DataHandling({ setHeader, setForwardBtn, setBackBtn }) {
const [llmChoice, setLLMChoice] = useState("openai");
const [loading, setLoading] = useState(true);
const [vectorDb, setVectorDb] = useState("pinecone");
const [embeddingEngine, setEmbeddingEngine] = useState("openai");
const navigate = useNavigate();
useEffect(() => {
setHeader({ title: TITLE, description: DESCRIPTION });
setForwardBtn({ showing: true, disabled: false, onClick: handleForward });
setBackBtn({ showing: false, disabled: false, onClick: handleBack });
async function fetchKeys() {
const _settings = await System.keys();
setLLMChoice(_settings?.LLMProvider);
setVectorDb(_settings?.VectorDB);
setEmbeddingEngine(_settings?.EmbeddingEngine);
setLLMChoice(_settings?.LLMProvider || "openai");
setVectorDb(_settings?.VectorDB || "pinecone");
setEmbeddingEngine(_settings?.EmbeddingEngine || "openai");
setLoading(false);
}
if (currentStep === "data_handling") {
fetchKeys();
}
fetchKeys();
}, []);
function handleForward() {
navigate(paths.onboarding.survey());
}
function handleBack() {
navigate(paths.onboarding.userSetup());
}
if (loading)
return (
<div className="w-full h-full flex justify-center items-center p-20">
@ -179,7 +194,7 @@ function DataHandling({ nextStep, prevStep, currentStep }) {
);
return (
<div className="max-w-[750px]">
<div className="w-full flex items-center justify-center flex-col gap-y-6">
<div className="p-8 flex flex-col gap-8">
<div className="flex flex-col gap-y-2 border-b border-zinc-500/50 pb-4">
<div className="text-white text-base font-bold">LLM Selection</div>
@ -239,23 +254,6 @@ function DataHandling({ nextStep, prevStep, currentStep }) {
</ul>
</div>
</div>
<div className="flex w-[650px] justify-between items-center px-6 py-4 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("user_questionnaire")}
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);

View File

@ -0,0 +1,39 @@
export default function EmbedderItem({
name,
value,
image,
description,
checked,
onClick,
}) {
return (
<div
onClick={() => onClick(value)}
className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
checked && "bg-white/10"
}`}
>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<div className="flex gap-x-4 items-center">
<img
src={image}
alt={`${name} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex flex-col gap-y-1">
<div className="text-sm font-semibold">{name}</div>
<div className="mt-2 text-xs text-white tracking-wide">
{description}
</div>
</div>
</div>
</div>
);
}

View File

@ -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: <NativeEmbeddingOptions settings={settings} />,
description:
"Use the built-in embedding engine for AnythingLLM. Zero setup!",
},
{
name: "OpenAI",
value: "openai",
logo: OpenAiLogo,
options: <OpenAiOptions settings={settings} />,
description: "The standard option for most non-commercial use.",
},
{
name: "Azure OpenAI",
value: "azure",
logo: AzureOpenAiLogo,
options: <AzureAiOptions settings={settings} />,
description: "The enterprise option of OpenAI hosted on Azure services.",
},
{
name: "Local AI",
value: "localai",
logo: LocalAiLogo,
options: <LocalAiOptions settings={settings} />,
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 (
<div>
<form ref={formRef} onSubmit={handleSubmit} className="w-full">
<div className="w-full relative border-slate-300/40 shadow border-2 rounded-lg text-white">
<div className="w-full p-4 absolute top-0 rounded-t-lg bg-accent/50">
<div className="w-full flex items-center sticky top-0 z-20">
<MagnifyingGlass
size={16}
weight="bold"
className="absolute left-4 z-30 text-white"
/>
<input
type="text"
placeholder="Search Embedding providers"
className="bg-zinc-600 z-20 pl-10 rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
onChange={(e) => setSearchQuery(e.target.value)}
autoComplete="off"
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
/>
</div>
</div>
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll pb-4">
{filteredEmbedders.map((embedder) => {
if (embedder.value === "native" && isHosted) {
return null;
}
return (
<EmbedderItem
key={embedder.name}
name={embedder.name}
value={embedder.value}
image={embedder.logo}
description={embedder.description}
checked={selectedEmbedder === embedder.value}
onClick={() => setSelectedEmbedder(embedder.value)}
/>
);
})}
</div>
</div>
<div className="mt-4 flex flex-col gap-y-1">
{selectedEmbedder &&
EMBEDDERS.find((embedder) => embedder.value === selectedEmbedder)
?.options}
</div>
<button
type="submit"
ref={hiddenSubmitButtonRef}
hidden
aria-hidden="true"
></button>
</form>
</div>
);
}

View File

@ -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 (
<>
<div className="relative w-screen h-screen flex overflow-hidden bg-[#2C2F35] md:bg-main-gradient">
<div
className="hidden md:block fixed bottom-10 left-10 w-[320px] h-[320px] bg-no-repeat bg-contain"
style={{ backgroundImage: `url(${LGroupImg})` }}
></div>
<div
className="hidden md:block fixed top-10 right-10 w-[320px] h-[320px] bg-no-repeat bg-contain"
style={{ backgroundImage: `url(${RGroupImg})` }}
></div>
<div className="relative flex justify-center items-center m-auto">
<div className="flex flex-col justify-center items-center">
<p className="text-zinc-300 font-thin text-[24px]">Welcome to</p>
<img
src={AnythingLLMLogo}
alt="AnythingLLM"
className="md:h-[50px] flex-shrink-0 max-w-[300px]"
/>
<button
onClick={() => navigate(paths.onboarding.llmPreference())}
className="animate-pulse w-full md:max-w-[350px] md:min-w-[300px] text-center py-3 bg-white text-black font-semibold text-sm my-10 rounded-md hover:bg-gray-200"
>
Get started
</button>
</div>
</div>
</div>
</>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -0,0 +1,39 @@
export default function LLMItem({
name,
value,
image,
description,
checked,
onClick,
}) {
return (
<div
onClick={() => onClick(value)}
className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
checked && "bg-white/10"
}`}
>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<div className="flex gap-x-4 items-center">
<img
src={image}
alt={`${name} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex flex-col gap-y-1">
<div className="text-sm font-semibold">{name}</div>
<div className="mt-2 text-xs text-white tracking-wide">
{description}
</div>
</div>
</div>
</div>
);
}

View File

@ -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: <OpenAiOptions settings={settings} />,
description: "The standard option for most non-commercial use.",
},
{
name: "Azure OpenAI",
value: "azure",
logo: AzureOpenAiLogo,
options: <AzureAiOptions settings={settings} />,
description: "The enterprise option of OpenAI hosted on Azure services.",
},
{
name: "Anthropic",
value: "anthropic",
logo: AnthropicLogo,
options: <AnthropicAiOptions settings={settings} />,
description: "A friendly AI Assistant hosted by Anthropic.",
},
{
name: "Gemini",
value: "gemini",
logo: GeminiLogo,
options: <GeminiLLMOptions settings={settings} />,
description: "Google's largest and most capable AI model",
},
{
name: "Ollama",
value: "ollama",
logo: OllamaLogo,
options: <OllamaLLMOptions settings={settings} />,
description: "Run LLMs locally on your own machine.",
},
{
name: "LM Studio",
value: "lmstudio",
logo: LMStudioLogo,
options: <LMStudioOptions settings={settings} />,
description:
"Discover, download, and run thousands of cutting edge LLMs in a few clicks.",
},
{
name: "Local AI",
value: "localai",
logo: LocalAiLogo,
options: <LocalAiOptions settings={settings} />,
description: "Run LLMs locally on your own machine.",
},
{
name: "Native",
value: "native",
logo: AnythingLLMIcon,
options: <NativeLLMOptions settings={settings} />,
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 (
<div>
<form ref={formRef} onSubmit={handleSubmit} className="w-full">
<div className="w-full relative border-slate-300/40 shadow border-2 rounded-lg text-white">
<div className="w-full p-4 absolute top-0 rounded-t-lg bg-accent/50">
<div className="w-full flex items-center sticky top-0 z-20">
<MagnifyingGlass
size={16}
weight="bold"
className="absolute left-4 z-30 text-white"
/>
<input
type="text"
placeholder="Search LLM providers"
className="bg-zinc-600 z-20 pl-10 rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
onChange={(e) => setSearchQuery(e.target.value)}
autoComplete="off"
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
/>
</div>
</div>
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll pb-4">
{filteredLLMs.map((llm) => {
if (llm.value === "native" && isHosted) return null;
return (
<LLMItem
key={llm.name}
name={llm.name}
value={llm.value}
image={llm.logo}
description={llm.description}
checked={selectedLLM === llm.value}
onClick={() => setSelectedLLM(llm.value)}
/>
);
})}
</div>
</div>
<div className="mt-4 flex flex-col gap-y-1">
{selectedLLM &&
LLMS.find((llm) => llm.value === selectedLLM)?.options}
</div>
<button
type="submit"
ref={hiddenSubmitButtonRef}
hidden
aria-hidden="true"
></button>
</form>
</div>
);
}

View File

@ -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 (
<div className="w-full flex justify-center items-center py-40">
<div className="w-full flex items-center justify-center px-1 md:px-8 py-4">
<div className="w-auto flex flex-col gap-y-1 items-center">
<CheckCircle size={60} className="text-green-500" />
<p className="text-white text-lg">Thank you for your feedback!</p>
<a
href={paths.mailToMintplex()}
className="text-sky-400 underline text-xs"
>
team@mintplexlabs.com
</a>
</div>
</div>
</div>
);
}
return (
<div className="w-full flex justify-center">
<form onSubmit={handleSubmit} ref={formRef} className="">
<div className="md:min-w-[400px]">
<label htmlFor="email" className="text-white text-base font-medium">
What's your email?{" "}
</label>
<input
name="email"
type="email"
placeholder="you@gmail.com"
required={true}
className="mt-2 bg-zinc-900 text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight w-full h-11 p-2.5 bg-zinc-900 rounded-lg"
/>
</div>
<div className="mt-8">
<label
className="text-white text-base font-medium"
htmlFor="use_case"
>
What will you use AnythingLLM for?{" "}
</label>
<div className="mt-2 gap-y-3 flex flex-col">
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "business"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"business"}
checked={selectedOption === "business"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "business" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my business
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "personal"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"personal"}
checked={selectedOption === "personal"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "personal" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For personal use
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "education"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"education"}
checked={selectedOption === "education"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "education" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my education
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "side_hustle"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"side_hustle"}
checked={selectedOption === "side_hustle"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "side_hustle" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my side-hustle
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "job" ? "border-white border-opacity-40" : ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"job"}
checked={selectedOption === "job"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "job" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
For my job
</div>
</label>
<label
className={`transition-all duration-300 w-full h-11 p-2.5 bg-white/10 rounded-lg flex justify-start items-center gap-2.5 cursor-pointer border border-transparent ${
selectedOption === "other"
? "border-white border-opacity-40"
: ""
} hover:border-white/60`}
>
<input
type="radio"
name="use_case"
value={"other"}
checked={selectedOption === "other"}
onChange={(e) => setSelectedOption(e.target.value)}
className="hidden"
/>
<div
className={`w-4 h-4 rounded-full border-2 border-white mr-2 ${
selectedOption === "other" ? "bg-white" : ""
}`}
></div>
<div className="text-white text-sm font-medium font-['Plus Jakarta Sans'] leading-tight">
Other
</div>
</label>
</div>
</div>
<div className="mt-8">
<label htmlFor="comment" className="text-white text-base font-medium">
Any comments for the team?{" "}
<span className="text-neutral-400 text-base font-light">
(Optional)
</span>
</label>
<textarea
name="comment"
rows={5}
className="mt-2 bg-zinc-900 text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder="If you have any questions or comments right now, you can leave them here and we will get back to you. You can also email team@mintplexlabs.com"
wrap="soft"
autoComplete="off"
/>
</div>
<button
type="submit"
ref={submitRef}
hidden
aria-hidden="true"
></button>
<div className="w-full flex items-center justify-center">
<button
type="button"
onClick={skipSurvey}
className="text-white text-base font-medium text-opacity-30 hover:text-opacity-100 mt-8"
>
Skip Survey
</button>
</div>
</form>
</div>
);
}

View File

@ -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 (
<div className="w-full flex items-center justify-center flex-col gap-y-6">
<div className="flex flex-col border rounded-lg border-white/20 p-8 items-center gap-y-4 w-full max-w-[600px]">
<div className=" text-white text-sm font-semibold md:-ml-44">
How many people will be using your instance?
</div>
<div className="flex flex-col md:flex-row gap-6 w-full justify-center">
<button
onClick={() => setSelectedOption("just_me")}
className={`${
selectedOption === "just_me"
? "text-sky-400 border-sky-400/70"
: "text-white border-white/40"
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
>
<div className="text-center text-sm font-bold">Just me</div>
</button>
<button
onClick={() => setSelectedOption("my_team")}
className={`${
selectedOption === "my_team"
? "text-sky-400 border-sky-400/70"
: "text-white border-white/40"
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
>
<div className="text-center text-sm font-bold">My team</div>
</button>
</div>
</div>
{selectedOption === "just_me" && (
<JustMe
setSingleUserPasswordValid={setSingleUserPasswordValid}
enablePassword={enablePassword}
setEnablePassword={setEnablePassword}
justMeSubmitRef={justMeSubmitRef}
navigate={navigate}
/>
)}
{selectedOption === "my_team" && (
<MyTeam
setMultiUserLoginValid={setMultiUserLoginValid}
myTeamSubmitRef={myTeamSubmitRef}
navigate={navigate}
/>
)}
</div>
);
}
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 (
<div className="w-full flex items-center justify-center flex-col gap-y-6">
<div className="flex flex-col border rounded-lg border-white/20 p-8 items-center gap-y-4 w-full max-w-[600px]">
<div className=" text-white text-sm font-semibold md:-ml-56">
Would you like to set up a password?
</div>
<div className="flex flex-col md:flex-row gap-6 w-full justify-center">
<button
onClick={handleYes}
className={`${
enablePassword && itemSelected
? "text-sky-400 border-sky-400/70"
: "text-white border-white/40"
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
>
<div className="text-center text-sm font-bold">Yes</div>
</button>
<button
onClick={handleNo}
className={`${
!enablePassword && itemSelected
? "text-sky-400 border-sky-400/70"
: "text-white border-white/40"
} min-w-[230px] h-11 p-4 rounded-[10px] border-2 justify-center items-center gap-[100px] inline-flex hover:border-sky-400/70 hover:text-sky-400 transition-all duration-300`}
>
<div className="text-center text-sm font-bold">No</div>
</button>
</div>
{enablePassword && (
<form className="w-full mt-4" onSubmit={handleSubmit}>
<label
htmlFor="name"
className="block mb-3 text-sm font-medium text-white"
>
Instance Password
</label>
<input
name="password"
type="password"
className="bg-zinc-900 text-white text-sm rounded-lg block w-full p-2.5"
placeholder="Your admin password"
minLength={6}
required={true}
autoComplete="off"
onChange={handlePasswordChange}
/>
<div className="mt-4 text-white text-opacity-80 text-xs font-base -mb-2">
Passwords must be at least 8 characters.
<br />
<i>
It's important to save this password because there is no
recovery method.
</i>{" "}
</div>
<button
type="submit"
ref={justMeSubmitRef}
hidden
aria-hidden="true"
></button>
</form>
)}
</div>
</div>
);
};
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 (
<div className="w-full flex items-center justify-center border max-w-[600px] rounded-lg border-white/20">
<form onSubmit={handleSubmit}>
<div className="flex flex-col w-full md:px-8 px-2 py-4">
<div className="space-y-6 flex h-full w-full">
<div className="w-full flex flex-col gap-y-4">
<div>
<label
htmlFor="name"
className="block mb-3 text-sm font-medium text-white"
>
Admin account username
</label>
<input
name="username"
type="text"
className="bg-zinc-900 text-white text-sm rounded-lg block w-full p-2.5"
placeholder="Your admin username"
minLength={6}
required={true}
autoComplete="off"
onChange={handleUsernameChange}
/>
</div>
<div className="mt-4">
<label
htmlFor="name"
className="block mb-3 text-sm font-medium text-white"
>
Admin account password
</label>
<input
name="password"
type="password"
className="bg-zinc-900 text-white text-sm rounded-lg block w-full p-2.5"
placeholder="Your admin password"
minLength={8}
required={true}
autoComplete="off"
onChange={handlePasswordChange}
/>
</div>
<p className="w-96 text-white text-opacity-80 text-xs font-base">
Username must be at least 6 characters long. Password must be at
least 8 characters long.
</p>
</div>
</div>
</div>
<div className="flex w-full justify-between items-center px-6 py-4 space-x-6 border-t rounded-b border-gray-500/50">
<div className=" text-white text-opacity-80 text-xs font-base">
By default, you will be the only admin. Once onboarding is completed
you can create and invite others to be users or admins. Do not lose
your password as only admins can reset passwords.
</div>
</div>
<button
type="submit"
ref={myTeamSubmitRef}
hidden
aria-hidden="true"
></button>
</form>
</div>
);
};

View File

@ -0,0 +1,37 @@
export default function VectorDatabaseItem({
name,
value,
image,
description,
checked,
onClick,
}) {
return (
<div
onClick={() => onClick(value)}
className={`w-full hover:bg-white/10 p-2 rounded-md hover:cursor-pointer ${
checked ? "bg-white/10" : ""
}`}
>
<input
type="checkbox"
value={value}
className="peer hidden"
checked={checked}
readOnly={true}
formNoValidate={true}
/>
<div className="flex gap-x-4 items-center">
<img
src={image}
alt={`${name} logo`}
className="w-10 h-10 rounded-md"
/>
<div className="flex flex-col gap-y-1">
<div className="text-sm font-semibold">{name}</div>
<div className="text-xs text-white tracking-wide">{description}</div>
</div>
</div>
</div>
);
}

View File

@ -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: <LanceDBOptions />,
description:
"100% local vector DB that runs on the same instance as AnythingLLM.",
},
{
name: "Chroma",
value: "chroma",
logo: ChromaLogo,
options: <ChromaDBOptions settings={settings} />,
description:
"Open source vector database you can host yourself or on the cloud.",
},
{
name: "Pinecone",
value: "pinecone",
logo: PineconeLogo,
options: <PineconeDBOptions settings={settings} />,
description: "100% cloud-based vector database for enterprise use cases.",
},
{
name: "QDrant",
value: "qdrant",
logo: QDrantLogo,
options: <QDrantDBOptions settings={settings} />,
description: "Open source local and distributed cloud vector database.",
},
{
name: "Weaviate",
value: "weaviate",
logo: WeaviateLogo,
options: <WeaviateDBOptions settings={settings} />,
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 (
<>
<form ref={formRef} onSubmit={handleSubmit} className="w-full">
<div className="w-full relative border-slate-300/40 shadow border-2 rounded-lg text-white pb-4">
<div className="w-full p-4 absolute top-0 rounded-t-lg bg-accent/50">
<div className="w-full flex items-center sticky top-0 z-20">
<MagnifyingGlass
size={16}
weight="bold"
className="absolute left-4 z-30 text-white"
/>
<input
type="text"
placeholder="Search vector databases"
className="bg-zinc-600 z-20 pl-10 rounded-full w-full px-4 py-1 text-sm border-2 border-slate-300/40 outline-none focus:border-white text-white"
onChange={(e) => setSearchQuery(e.target.value)}
autoComplete="off"
onKeyDown={(e) => {
if (e.key === "Enter") e.preventDefault();
}}
/>
</div>
</div>
<div className="px-4 pt-[70px] flex flex-col gap-y-1 max-h-[390px] overflow-y-auto no-scroll">
{filteredVDBs.map((vdb) => (
<VectorDatabaseItem
key={vdb.name}
name={vdb.name}
value={vdb.value}
image={vdb.logo}
description={vdb.description}
checked={selectedVDB === vdb.value}
onClick={setSelectedVDB}
/>
))}
</div>
</div>
<div className="mt-4 flex flex-col gap-y-1">
{selectedVDB &&
VECTOR_DBS.find((vdb) => vdb.value === selectedVDB)?.options}
</div>
<button
type="submit"
ref={hiddenSubmitButtonRef}
hidden
aria-hidden="true"
></button>
</form>
</>
);
}

View File

@ -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 (
<div className="w-screen h-screen overflow-y-auto bg-[#2C2F35] overflow-hidden">
<div className="flex flex-col">
<div className="w-full relative py-10 px-2">
<div className="flex flex-col w-fit mx-auto gap-y-1 mb-[55px]">
<h1 className="text-white font-semibold text-center text-2xl">
{header.title}
</h1>
<p className="text-zinc-400 text-base text-center">
{header.description}
</p>
</div>
{children(setHeader, setBackBtn, setForwardBtn)}
</div>
<div className="flex w-full justify-center gap-x-4 pb-20">
<div className="flex justify-center items-center">
{backBtn.showing && (
<button
disabled={backBtn.disabled}
onClick={backBtn.onClick}
className="group p-2 rounded-lg border-2 border-zinc-300 disabled:border-zinc-600 h-fit w-fit disabled:not-allowed hover:bg-zinc-100 disabled:hover:bg-transparent"
>
<ArrowLeft
className="text-white group-hover:text-black group-disabled:text-gray-500"
size={30}
/>
</button>
)}
</div>
<div className="flex justify-center items-center">
{forwardBtn.showing && (
<button
disabled={forwardBtn.disabled}
onClick={forwardBtn.onClick}
className="group p-2 rounded-lg border-2 border-zinc-300 disabled:border-zinc-600 h-fit w-fit disabled:not-allowed hover:bg-zinc-100 disabled:hover:bg-transparent"
>
<ArrowRight
className="text-white group-hover:text-black group-disabled:text-gray-500"
size={30}
/>
</button>
)}
</div>
</div>
</div>
</div>
);
}
return (
<div className="w-screen overflow-y-auto bg-[#2C2F35] md:bg-main-gradient flex justify-center overflow-hidden">
<div className="flex w-1/5 h-screen justify-center items-center">
{backBtn.showing && (
<button
disabled={backBtn.disabled}
onClick={backBtn.onClick}
className="group p-2 rounded-lg border-2 border-zinc-300 disabled:border-zinc-600 h-fit w-fit disabled:not-allowed hover:bg-zinc-100 disabled:hover:bg-transparent"
>
<ArrowLeft
className="text-white group-hover:text-black group-disabled:text-gray-500"
size={30}
/>
</button>
)}
</div>
<div className="w-full md:w-3/5 relative h-full py-10">
<div className="flex flex-col w-fit mx-auto gap-y-1 mb-[55px]">
<h1 className="text-white font-semibold text-center text-2xl">
{header.title}
</h1>
<p className="text-zinc-400 text-base text-center">
{header.description}
</p>
</div>
{children(setHeader, setBackBtn, setForwardBtn)}
</div>
<div className="flex w-1/5 h-screen justify-center items-center">
{forwardBtn.showing && (
<button
disabled={forwardBtn.disabled}
onClick={forwardBtn.onClick}
className="group p-2 rounded-lg border-2 border-zinc-300 disabled:border-zinc-600 h-fit w-fit disabled:not-allowed hover:bg-zinc-100 disabled:hover:bg-transparent"
>
<ArrowRight
className="text-white group-hover:text-black group-disabled:text-gray-500"
size={30}
/>
</button>
)}
</div>
</div>
);
}

View File

@ -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 (
<div className="w-screen h-full bg-sidebar flex items-center justify-center">
<div className="w-fit p-20 py-24 border-2 border-slate-300/10 rounded-2xl bg-main-gradient shadow-lg">
<div className="text-white text-2xl font-base text-center">
Welcome to
</div>
<img src={logo} alt="logo" className="w-80 mx-auto m-3 mb-11" />
<div className="flex justify-center items-center">
<p className="text-white text-sm italic text-center">
Please use a desktop browser to continue onboarding.
</p>
</div>
</div>
</div>
);
}
const { step } = useParams();
const StepPage = OnboardingSteps[step || "home"];
if (step === "home" || !step) return <StepPage />;
return (
<div className="w-screen h-full bg-sidebar flex items-center justify-center">
<div className="w-fit p-20 py-24 border-2 border-slate-300/10 rounded-2xl bg-main-gradient shadow-lg">
<div className="text-white text-2xl font-base text-center">
Welcome to
</div>
<img src={logo} alt="logo" className="w-80 mx-auto m-3 mb-11" />
<div className="flex justify-center items-center">
<button
className="border border-slate-200 px-5 py-2.5 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 animate-pulse"
onClick={showModal}
>
Get Started
</button>
</div>
</div>
{modalVisible && <OnboardingModal setModalVisible={setModalVisible} />}
</div>
<OnboardingLayout>
{(setHeader, setBackBtn, setForwardBtn) => (
<StepPage
setHeader={setHeader}
setBackBtn={setBackBtn}
setForwardBtn={setForwardBtn}
/>
)}
</OnboardingLayout>
);
}

View File

@ -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";

View File

@ -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);
})