feat: add onboarding step 3 (#1324)

* feat: add OnboardingContext

* feat: add Step3 boilerplate

* feat: activate step3

* feat: add <Step3/> content

* feat: add shouldStream guard on useStreamText
This commit is contained in:
Mamadou DICKO 2023-10-04 16:26:56 +02:00 committed by GitHub
parent 945178de39
commit 85eba3da70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 180 additions and 46 deletions

View File

@ -16,7 +16,7 @@ export const ActionsBar = (): JSX.Element => {
return (
<>
{hasPendingRequests && (
<div className="flex mt-1 flex-col md:flex-row w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6 pl-6">
<div className="flex mt-1 flex-col md:flex-row w-full shadow-md dark:shadow-primary/25 hover:shadow-xl transition-shadow rounded-xl bg-white dark:bg-black border border-black/10 dark:border-white/25 p-2 md:p-6 pl-6 mb-3">
<div className="flex flex-1 items-center mb-2 md:mb-0">
<span className="text-sm md:text-1xl">{t("feedingBrain")}</span>
</div>

View File

@ -1,3 +1,5 @@
/*eslint max-lines: ["error", 200 ]*/
import { UUID } from "crypto";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
@ -10,6 +12,7 @@ import { useChatContext } from "@/lib/context";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { useToast } from "@/lib/hooks";
import { useOnboardingContext } from "@/lib/hooks/useOnboardingContext";
import { FeedItemCrawlType, FeedItemUploadType } from "../../../types";
@ -23,7 +26,7 @@ export const useFeedBrainInChat = ({
const { t } = useTranslation(["upload"]);
const router = useRouter();
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
const { setCurrentStep } = useOnboardingContext();
const { currentBrainId } = useBrainContext();
const { setKnowledgeToFeed, knowledgeToFeed } = useKnowledgeToFeedContext();
const [hasPendingRequests, setHasPendingRequests] = useState(false);
@ -61,6 +64,7 @@ export const useFeedBrainInChat = ({
return;
}
try {
setCurrentStep("UPLOADED");
dispatchHasPendingRequests();
setShouldDisplayFeedCard(false);
setHasPendingRequests(true);

View File

@ -1,16 +1,13 @@
import { useState } from "react";
import { Step1 } from "./components";
import { Step2 } from "./components/Step2";
import { OnboardingState } from "../types";
import { Step3 } from "./components/Step3";
export const Onboarding = (): JSX.Element => {
const [currentStep, setCurrentStep] = useState<OnboardingState>("DOWNLOAD");
return (
<div className="flex flex-col gap-2">
<Step1 changeStateTo={setCurrentStep} currentStep={currentStep} />
<Step2 currentStep={currentStep} />
<div className="flex flex-col gap-2 mb-3">
<Step1 />
<Step2 />
<Step3 />
</div>
);
};

View File

@ -4,27 +4,25 @@ import { useTranslation } from "react-i18next";
import { RiDownloadLine } from "react-icons/ri";
import Button from "@/lib/components/ui/Button";
import { OnboardingState } from "@/lib/context/OnboardingContext/types";
import { useOnboardingContext } from "@/lib/hooks/useOnboardingContext";
import { MessageRow } from "../../QADisplay";
import { OnboardingState } from "../../types";
import { checkIfShouldDisplayStep } from "../helpers/checkIfShouldDisplayStep";
import { useStreamText } from "../hooks/useStreamText";
import { stepsContainerStyle } from "../styles";
type Step1Props = {
currentStep: OnboardingState;
changeStateTo: (state: OnboardingState) => void;
};
const stepId: OnboardingState = "DOWNLOAD";
export const Step1 = ({
currentStep,
changeStateTo,
}: Step1Props): JSX.Element => {
export const Step1 = (): JSX.Element => {
const { currentStep, setCurrentStep } = useOnboardingContext();
const shouldStepBeDisplayed = checkIfShouldDisplayStep({
currentStep,
step: "DOWNLOAD",
step: stepId,
});
const shouldStreamMessage = currentStep === stepId;
const { t } = useTranslation(["chat"]);
const firstMessage = t("onboarding.download_message_1");
const secondMessageStream = t("onboarding.download_message_2");
@ -33,11 +31,13 @@ export const Step1 = ({
useStreamText({
text: firstMessage,
enabled: shouldStepBeDisplayed,
shouldStream: shouldStreamMessage,
});
const { streamingText: firstMessageStrem, isDone: isStep1Done } =
useStreamText({
text: secondMessageStream,
enabled: isAssistantDone && shouldStepBeDisplayed,
shouldStream: shouldStreamMessage,
});
if (!shouldStepBeDisplayed) {
@ -56,7 +56,7 @@ export const Step1 = ({
download
target="_blank"
referrerPolicy="no-referrer"
onClick={() => changeStateTo("UPLOAD")}
onClick={() => setCurrentStep("UPLOAD")}
>
<Button className="bg-black p-2 ml-2 rounded-full inline-flex">
<RiDownloadLine />

View File

@ -1,35 +1,40 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { OnboardingState } from "@/lib/context/OnboardingContext/types";
import { useOnboardingContext } from "@/lib/hooks/useOnboardingContext";
import { MessageRow } from "../../QADisplay";
import { OnboardingState } from "../../types";
import { checkIfShouldDisplayStep } from "../helpers/checkIfShouldDisplayStep";
import { useStreamText } from "../hooks/useStreamText";
import { stepsContainerStyle } from "../styles";
type Step1Props = {
currentStep: OnboardingState;
};
const stepId: OnboardingState = "UPLOAD";
export const Step2 = ({ currentStep }: Step1Props): JSX.Element => {
export const Step2 = (): JSX.Element => {
const { currentStep } = useOnboardingContext();
const shouldStepBeDisplayed = checkIfShouldDisplayStep({
currentStep,
step: "UPLOAD",
step: stepId,
});
const { t } = useTranslation(["chat"]);
const firstMessage = t("onboarding.upload_message_1");
const secondMessageStream = t("onboarding.upload_message_2");
const shouldStreamMessage = currentStep === stepId;
const { streamingText: streamingAssistantMessage, isDone: isAssistantDone } =
useStreamText({
text: firstMessage,
enabled: shouldStepBeDisplayed,
shouldStream: shouldStreamMessage,
});
const { streamingText: firstMessageStream } = useStreamText({
text: secondMessageStream,
enabled: isAssistantDone && shouldStepBeDisplayed,
shouldStream: shouldStreamMessage,
});
if (!shouldStepBeDisplayed) {

View File

@ -0,0 +1,51 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { OnboardingState } from "@/lib/context/OnboardingContext/types";
import { useOnboardingContext } from "@/lib/hooks/useOnboardingContext";
import { MessageRow } from "../../QADisplay";
import { checkIfShouldDisplayStep } from "../helpers/checkIfShouldDisplayStep";
import { useStreamText } from "../hooks/useStreamText";
import { stepsContainerStyle } from "../styles";
const stepId: OnboardingState = "UPLOADED";
export const Step3 = (): JSX.Element => {
const { currentStep } = useOnboardingContext();
const shouldStepBeDisplayed = checkIfShouldDisplayStep({
currentStep,
step: stepId,
});
const { t } = useTranslation(["chat"]);
const firstMessage = t("onboarding.last_step");
const secondMessageStream = t("onboarding.ask_question_to_file");
const shouldStreamMessage = currentStep === stepId;
const { streamingText: streamingAssistantMessage, isDone: isAssistantDone } =
useStreamText({
text: firstMessage,
enabled: shouldStepBeDisplayed,
shouldStream: shouldStreamMessage,
});
const { streamingText: firstMessageStream } = useStreamText({
text: secondMessageStream,
enabled: isAssistantDone && shouldStepBeDisplayed,
shouldStream: shouldStreamMessage,
});
if (!shouldStepBeDisplayed) {
return <Fragment />;
}
return (
<MessageRow speaker={"assistant"} brainName={"Quivr"}>
<div className={stepsContainerStyle}>
<p>{streamingAssistantMessage}</p>
<p>{firstMessageStream}</p>
</div>
</MessageRow>
);
};

View File

@ -1,8 +1,9 @@
import { OnboardingState } from "../../types";
import { OnboardingState } from "@/lib/context/OnboardingContext/types";
const onboardingStepToState: Record<OnboardingState, OnboardingState[]> = {
DOWNLOAD: ["DOWNLOAD", "UPLOAD"],
UPLOAD: ["UPLOAD"],
const requiredStateForDisplaying: Record<OnboardingState, OnboardingState[]> = {
DOWNLOAD: ["DOWNLOAD", "UPLOAD", "UPLOADED"],
UPLOAD: ["UPLOAD", "UPLOADED"],
UPLOADED: ["UPLOADED"],
};
type CheckIfShouldDisplayStepProps = {
@ -14,5 +15,5 @@ export const checkIfShouldDisplayStep = ({
currentStep,
step,
}: CheckIfShouldDisplayStepProps): boolean => {
return onboardingStepToState[step].includes(currentStep);
return requiredStateForDisplaying[step].includes(currentStep);
};

View File

@ -3,9 +3,14 @@ import { useEffect, useState } from "react";
type UseStreamTextProps = {
text: string;
enabled?: boolean;
shouldStream?: boolean;
};
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useStreamText = ({ text, enabled = true }: UseStreamTextProps) => {
export const useStreamText = ({
text,
enabled = true,
shouldStream = true,
}: UseStreamTextProps) => {
const [streamingText, setStreamingText] = useState<string>("");
const [currentIndex, setCurrentIndex] = useState(0);
@ -18,6 +23,13 @@ export const useStreamText = ({ text, enabled = true }: UseStreamTextProps) => {
return;
}
if (!shouldStream) {
setStreamingText(text);
setCurrentIndex(text.length);
return;
}
const messageInterval = setInterval(() => {
if (currentIndex < text.length) {
setStreamingText((prevText) => prevText + (text[currentIndex] ?? ""));
@ -30,7 +42,7 @@ export const useStreamText = ({ text, enabled = true }: UseStreamTextProps) => {
return () => {
clearInterval(messageInterval);
};
}, [text, currentIndex, enabled]);
}, [text, currentIndex, enabled, shouldStream]);
return { streamingText, isDone };
};

View File

@ -1 +0,0 @@
export type OnboardingState = "DOWNLOAD" | "UPLOAD";

View File

@ -18,7 +18,8 @@ export const useChatsList = () => {
const fetchAllChats = async () => {
try {
const response = await getChats();
setAllChats(response.reverse());
return response.reverse();
} catch (error) {
console.error(error);
publish({

View File

@ -3,6 +3,7 @@ import { ReactNode } from "react";
import { ChatProvider, KnowledgeToFeedProvider } from "@/lib/context";
import { ChatsProvider } from "@/lib/context/ChatsProvider/chats-provider";
import { OnboardingContextProvider } from "@/lib/context/OnboardingContext/knowledgeToFeed-provider";
import { useSupabase } from "@/lib/context/SupabaseProvider";
import { redirectToLogin } from "@/lib/router/redirectToLogin";
@ -23,10 +24,12 @@ const Layout = ({ children }: LayoutProps): JSX.Element => {
<KnowledgeToFeedProvider>
<ChatsProvider>
<ChatProvider>
<div className="relative h-full w-full flex justify-stretch items-stretch">
<ChatsList />
{children}
</div>
<OnboardingContextProvider>
<div className="relative h-full w-full flex justify-stretch items-stretch">
<ChatsList />
{children}
</div>
</OnboardingContextProvider>
</ChatProvider>
</ChatsProvider>
</KnowledgeToFeedProvider>

View File

@ -0,0 +1,33 @@
"use client";
import { createContext, useState } from "react";
import { OnboardingState } from "./types";
type OnboardingContextType = {
currentStep: OnboardingState;
setCurrentStep: React.Dispatch<React.SetStateAction<OnboardingState>>;
};
export const OnboardingContext = createContext<
OnboardingContextType | undefined
>(undefined);
export const OnboardingContextProvider = ({
children,
}: {
children: React.ReactNode;
}): JSX.Element => {
const [currentStep, setCurrentStep] = useState<OnboardingState>("DOWNLOAD");
return (
<OnboardingContext.Provider
value={{
currentStep,
setCurrentStep,
}}
>
{children}
</OnboardingContext.Provider>
);
};

View File

@ -0,0 +1 @@
export type OnboardingState = "DOWNLOAD" | "UPLOAD" | "UPLOADED";

View File

@ -0,0 +1,15 @@
import { useContext } from "react";
import { OnboardingContext } from "../context/OnboardingContext/knowledgeToFeed-provider";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useOnboardingContext = () => {
const context = useContext(OnboardingContext);
if (!context) {
throw new Error(
"useOnboardingContext must be used within a OnboardingContextProvider"
);
}
return context;
};

View File

@ -41,6 +41,8 @@
"upload_message_2":"Step 2: Now Drag and drop it on the chat or in the 📎",
"how_to_use_quivr": "How to user Quivr",
"what_is_quivr":"What is Quivr ?",
"what_is_brain": "What is a brain ?"
"what_is_brain": "What is a brain ?",
"last_step":"Last step 🥳",
"ask_question_to_file":"Ask a question to your file. Ex : 'What are you talking about ?'"
}
}

View File

@ -41,6 +41,8 @@
"upload_message_2": "Paso 2: Ahora, arrástralo y suéltalo en el chat o en el 📎",
"how_to_use_quivr": "Cómo usar Quivr",
"what_is_quivr": "¿Qué es Quivr?",
"what_is_brain": "¿Qué es un cerebro?"
"what_is_brain": "¿Qué es un cerebro?",
"last_step":"Último paso 🥳",
"ask_question_to_file":"Haz una pregunta a tu archivo. Ej: '¿De qué estás hablando?'"
}
}

View File

@ -41,6 +41,8 @@
"upload_message_2": "Étape 2 : Maintenant, faites glisser et déposez-le dans le chat ou dans 📎",
"how_to_use_quivr": "Comment utiliser Quivr",
"what_is_quivr": "Qu'est-ce que Quivr ?",
"what_is_brain": "Qu'est-ce qu'un cerveau ?"
"what_is_brain": "Qu'est-ce qu'un cerveau ?",
"last_step":"Dernière étape 🥳",
"ask_question_to_file":"Posez une question à votre fichier. Ex : 'De quoi parlez-vous ?'"
}
}

View File

@ -41,6 +41,8 @@
"upload_message_2": "Passo 2: Agora, arraste e solte no chat ou na 📎",
"how_to_use_quivr": "Como usar o Quivr",
"what_is_quivr": "O que é o Quivr?",
"what_is_brain": "O que é um cérebro?"
"what_is_brain": "O que é um cérebro?",
"last_step":"Dernière étape 🥳",
"ask_question_to_file":"Posez une question à votre fichier. Ex : 'De quoi parlez-vous ?'"
}
}

View File

@ -41,6 +41,8 @@
"upload_message_2": "Шаг 2: Теперь перетащите его в чат или в 📎",
"how_to_use_quivr": "Как использовать Quivr",
"what_is_quivr": "Что такое Quivr?",
"what_is_brain": "Что такое мозг?"
"what_is_brain": "Что такое мозг?",
"last_step":"Последний шаг 🥳",
"ask_question_to_file":"Задайте вопрос своему файлу. Например: 'О чем вы говорите?'"
}
}

View File

@ -42,6 +42,8 @@
"upload_message_2": "第2步现在将其拖放到聊天框或 📎 中",
"how_to_use_quivr": "如何使用Quivr",
"what_is_quivr": "什么是Quivr",
"what_is_brain": "什么是大脑?"
"what_is_brain": "什么是大脑?",
"last_step":"最后一步 🥳",
"ask_question_to_file":"向您的文件提问。例如:'你在谈论什么?'"
}
}