diff --git a/frontend/src/index.css b/frontend/src/index.css index 937631beba23eecccd81cbc9a03c0c77d943cd3a..f0f04bbcc3c86c2437c4ef53176cd8dd295c6d8f 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -379,3 +379,9 @@ dialog::backdrop { opacity: 0; } } + +@layer components { + .radio-container:has(input:checked) { + @apply border-blue-500 bg-blue-400/10 text-blue-800; + } +} diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/CreateFirstWorkspace/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/CreateFirstWorkspace/index.jsx index 3c9949f89ba3dd26ca252db4ad5718df74f62bcd..d2624ef6a103324a133a8d86dbf6dd28181dcca9 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/CreateFirstWorkspace/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/CreateFirstWorkspace/index.jsx @@ -3,7 +3,7 @@ import { useNavigate } from "react-router-dom"; import paths from "@/utils/paths"; import Workspace from "@/models/workspace"; -function CreateFirstWorkspace() { +function CreateFirstWorkspace({ prevStep }) { const navigate = useNavigate(); const handleCreate = async (e) => { @@ -47,6 +47,13 @@ function CreateFirstWorkspace() { </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" diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx index 6f089c5aac3c98122b51e88e862579ca2010a24e..98a1671cbe2f19d8fad914e3199f7a271eddc1d1 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/DataHandling/index.jsx @@ -231,7 +231,7 @@ function DataHandling({ nextStep, prevStep, currentStep }) { Back </button> <button - onClick={() => nextStep("create_workspace")} + 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 diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx index f77a8b68861195bbcc1bcb3ecaf3eb92e17f75f3..bb87486ba02960939b4f39d779aa25f9bdb1ab6c 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/LLMSelection/index.jsx @@ -28,7 +28,7 @@ function LLMSelection({ nextStep, prevStep, currentStep }) { async function fetchKeys() { const _settings = await System.keys(); setSettings(_settings); - setLLMChoice(_settings?.LLMProvider); + setLLMChoice(_settings?.LLMProvider || "openai"); setLoading(false); } diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserQuestionnaire/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserQuestionnaire/index.jsx new file mode 100644 index 0000000000000000000000000000000000000000..50f10372583badc5e43ec5535abd7102897527b1 --- /dev/null +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/Steps/UserQuestionnaire/index.jsx @@ -0,0 +1,239 @@ +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); + await sendQuestionnaire({ + email: formData.get("email"), + useCase: formData.get("use_case") || "other", + comment: formData.get("comment") || null, + }); + nextStep("create_workspace"); + 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); diff --git a/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx b/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx index 3fd35c4cb66116486a29e61ba4183558e6e1e907..9815a7e80dd1bbb7bee50827baaa6d93c22a1b8b 100644 --- a/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx +++ b/frontend/src/pages/OnboardingFlow/OnboardingModal/index.jsx @@ -9,6 +9,7 @@ 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"; @@ -19,6 +20,11 @@ const STEPS = { "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: @@ -54,16 +60,17 @@ const STEPS = { "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, }, - embedding_preferences: { - title: "Embedding Preference", - description: "Choose a provider for embedding files and text.", - component: EmbeddingSelection, - }, }; export const OnboardingModalId = DIALOG_ID; diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js index e86aa8aecfc2365e9b414c0c42459735512ce939..11b8da976c60036ff9fb4d39d1fbb0f44508daca 100644 --- a/frontend/src/utils/constants.js +++ b/frontend/src/utils/constants.js @@ -3,6 +3,7 @@ export const API_BASE = import.meta.env.VITE_API_BASE || "/api"; export const AUTH_USER = "anythingllm_user"; export const AUTH_TOKEN = "anythingllm_authToken"; export const AUTH_TIMESTAMP = "anythingllm_authTimestamp"; +export const COMPLETE_QUESTIONNAIRE = "anythingllm_completed_questionnaire"; export const USER_BACKGROUND_COLOR = "bg-historical-msg-user"; export const AI_BACKGROUND_COLOR = "bg-historical-msg-system"; diff --git a/server/utils/files/pfp.js b/server/utils/files/pfp.js index 30c42a519358cc6b625a1e267ed29f8b737e4135..943aa595f0e8864b9ff4eec3caec2a402da45f09 100644 --- a/server/utils/files/pfp.js +++ b/server/utils/files/pfp.js @@ -26,7 +26,7 @@ function fetchPfp(pfpPath) { async function determinePfpFilepath(id) { const numberId = Number(id); const user = await User.get({ id: numberId }); - const pfpFilename = user.pfpFilename; + const pfpFilename = user?.pfpFilename || null; if (!pfpFilename) return null; const basePath = process.env.STORAGE_DIR