mirror of
https://github.com/StanGirard/quivr.git
synced 2024-12-25 12:22:58 +03:00
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:
parent
945178de39
commit
85eba3da70
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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 />
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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);
|
||||
};
|
||||
|
@ -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 };
|
||||
};
|
||||
|
@ -1 +0,0 @@
|
||||
export type OnboardingState = "DOWNLOAD" | "UPLOAD";
|
@ -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({
|
||||
|
@ -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>
|
||||
|
@ -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
frontend/lib/context/OnboardingContext/types.ts
Normal file
1
frontend/lib/context/OnboardingContext/types.ts
Normal file
@ -0,0 +1 @@
|
||||
export type OnboardingState = "DOWNLOAD" | "UPLOAD" | "UPLOADED";
|
15
frontend/lib/hooks/useOnboardingContext.ts
Normal file
15
frontend/lib/hooks/useOnboardingContext.ts
Normal 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;
|
||||
};
|
@ -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 ?'"
|
||||
}
|
||||
}
|
||||
|
@ -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?'"
|
||||
}
|
||||
}
|
||||
|
@ -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 ?'"
|
||||
}
|
||||
}
|
||||
|
@ -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 ?'"
|
||||
}
|
||||
}
|
||||
|
@ -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":"Задайте вопрос своему файлу. Например: 'О чем вы говорите?'"
|
||||
}
|
||||
}
|
||||
|
@ -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":"向您的文件提问。例如:'你在谈论什么?'"
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user