Skip to content

Commit

Permalink
feat: add onboarding step 3 (QuivrHQ#1324)
Browse files Browse the repository at this point in the history
* feat: add OnboardingContext

* feat: add Step3 boilerplate

* feat: activate step3

* feat: add <Step3/> content

* feat: add shouldStream guard on useStreamText
  • Loading branch information
mamadoudicko authored Oct 4, 2023
1 parent 945178d commit 85eba3d
Show file tree
Hide file tree
Showing 20 changed files with 180 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/*eslint max-lines: ["error", 200 ]*/

import { UUID } from "crypto";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
Expand All @@ -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";

Expand All @@ -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);
Expand Down Expand Up @@ -61,6 +64,7 @@ export const useFeedBrainInChat = ({
return;
}
try {
setCurrentStep("UPLOADED");
dispatchHasPendingRequests();
setShouldDisplayFeedCard(false);
setHasPendingRequests(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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) {
Expand All @@ -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 />
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -14,5 +15,5 @@ export const checkIfShouldDisplayStep = ({
currentStep,
step,
}: CheckIfShouldDisplayStepProps): boolean => {
return onboardingStepToState[step].includes(currentStep);
return requiredStateForDisplaying[step].includes(currentStep);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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] ?? ""));
Expand All @@ -30,7 +42,7 @@ export const useStreamText = ({ text, enabled = true }: UseStreamTextProps) => {
return () => {
clearInterval(messageInterval);
};
}, [text, currentIndex, enabled]);
}, [text, currentIndex, enabled, shouldStream]);

return { streamingText, isDone };
};

This file was deleted.

3 changes: 2 additions & 1 deletion frontend/app/chat/components/ChatsList/hooks/useChatsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
11 changes: 7 additions & 4 deletions frontend/app/chat/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
};
1 change: 1 addition & 0 deletions frontend/lib/context/OnboardingContext/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type OnboardingState = "DOWNLOAD" | "UPLOAD" | "UPLOADED";
15 changes: 15 additions & 0 deletions frontend/lib/hooks/useOnboardingContext.ts
Original file line number Diff line number Diff line change
@@ -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;
};
4 changes: 3 additions & 1 deletion frontend/public/locales/en/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?'"
}
}
4 changes: 3 additions & 1 deletion frontend/public/locales/es/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -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?'"
}
}
4 changes: 3 additions & 1 deletion frontend/public/locales/fr/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?'"
}
}
Loading

0 comments on commit 85eba3d

Please sign in to comment.