mirror of
https://github.com/StanGirard/quivr.git
synced 2024-11-24 05:55:13 +03:00
feat: add brain creation step 2 (#1823)
Issue: https://github.com/StanGirard/quivr/issues/1770 Demo: https://github.com/StanGirard/quivr/assets/63923024/075aea35-892d-4103-8840-1a0721096745
This commit is contained in:
parent
8ba376e0a1
commit
27b2df8898
@ -5,6 +5,7 @@ import Button from "@/lib/components/ui/Button";
|
||||
import { Modal } from "@/lib/components/ui/Modal";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { BrainParamsStep } from "./components/BrainParamsStep/BrainParamsStep";
|
||||
import { BrainTypeSelectionStep } from "./components/BrainTypeSelectionStep/BrainTypeSelectionStep";
|
||||
import { Stepper } from "./components/Stepper/Stepper";
|
||||
import { useAddBrainConfig } from "./hooks/useAddBrainConfig";
|
||||
@ -22,37 +23,38 @@ export const AddBrainSteps = ({
|
||||
useAddBrainConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
Trigger={
|
||||
<Button
|
||||
onClick={() => void 0}
|
||||
variant={"tertiary"}
|
||||
className={cn("border-0", triggerClassName)}
|
||||
data-testid="add-brain-button"
|
||||
>
|
||||
{t("newBrain", { ns: "brain" })}
|
||||
<MdAdd className="text-xl" />
|
||||
</Button>
|
||||
}
|
||||
title={t("newBrainTitle", { ns: "brain" })}
|
||||
desc={t("newBrainSubtitle", { ns: "brain" })}
|
||||
isOpen={isBrainCreationModalOpened}
|
||||
setOpen={setIsBrainCreationModalOpened}
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
className="my-10 flex flex-col items-center gap-2 justify-center"
|
||||
<Modal
|
||||
Trigger={
|
||||
<Button
|
||||
onClick={() => void 0}
|
||||
variant={"tertiary"}
|
||||
className={cn("border-0", triggerClassName)}
|
||||
data-testid="add-brain-button"
|
||||
>
|
||||
<Stepper />
|
||||
<BrainTypeSelectionStep
|
||||
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
|
||||
/>
|
||||
</form>
|
||||
</Modal>
|
||||
</>
|
||||
{t("newBrain", { ns: "brain" })}
|
||||
<MdAdd className="text-xl" />
|
||||
</Button>
|
||||
}
|
||||
title={t("newBrainTitle", { ns: "brain" })}
|
||||
desc={t("newBrainSubtitle", { ns: "brain" })}
|
||||
isOpen={isBrainCreationModalOpened}
|
||||
setOpen={setIsBrainCreationModalOpened}
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
className="my-10 flex flex-col items-center gap-2 justify-center"
|
||||
>
|
||||
<Stepper />
|
||||
<BrainTypeSelectionStep
|
||||
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
|
||||
/>
|
||||
<BrainParamsStep
|
||||
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
|
||||
/>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,105 @@
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaArrowLeft, FaArrowRight } from "react-icons/fa";
|
||||
|
||||
import { PublicAccessConfirmationModal } from "@/lib/components/AddBrainModalOld/components/AddBrainConfig/components/PublicAccessConfirmationModal";
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { Radio } from "@/lib/components/ui/Radio";
|
||||
import { TextArea } from "@/lib/components/ui/TextArea";
|
||||
|
||||
import { useBrainParamsStep } from "./hooks/useBrainParamsStep";
|
||||
import { useBrainStatusOptions } from "./hooks/useBrainStatusOptions";
|
||||
import { usePublicAccessConfirmationModal } from "./hooks/usePublicAccessConfirmationModal";
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
import { useBrainTypeSelectionStep } from "../BrainTypeSelectionStep/hooks/useBrainTypeSelectionStep";
|
||||
|
||||
type BrainParamsStepProps = {
|
||||
onCancelBrainCreation: () => void;
|
||||
};
|
||||
|
||||
export const BrainParamsStep = ({
|
||||
onCancelBrainCreation,
|
||||
}: BrainParamsStepProps): JSX.Element => {
|
||||
const { goToNextStep, goToPreviousStep, currentStep } =
|
||||
useBrainCreationSteps();
|
||||
const { register } = useBrainTypeSelectionStep();
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
const { brainStatusOptions } = useBrainStatusOptions();
|
||||
const { isNextButtonDisabled } = useBrainParamsStep();
|
||||
const {
|
||||
isPublicAccessConfirmationModalOpened,
|
||||
onCancelPublicAccess,
|
||||
onConfirmPublicAccess,
|
||||
} = usePublicAccessConfirmationModal();
|
||||
|
||||
if (currentStep !== "BRAIN_PARAMS") {
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
label={t("brainName", { ns: "brain" })}
|
||||
autoFocus
|
||||
placeholder={t("brainNamePlaceholder", { ns: "brain" })}
|
||||
autoComplete="off"
|
||||
className="flex-1"
|
||||
required
|
||||
{...register("name")}
|
||||
/>
|
||||
<TextArea
|
||||
label={t("brainDescription", { ns: "brain" })}
|
||||
placeholder={t("brainDescriptionPlaceholder", { ns: "brain" })}
|
||||
autoComplete="off"
|
||||
className="flex-1 m-3"
|
||||
{...register("description")}
|
||||
/>
|
||||
<fieldset className="w-full flex flex-col">
|
||||
<Radio
|
||||
items={brainStatusOptions}
|
||||
label={t("brain_status_label", { ns: "brain" })}
|
||||
className="flex-1 justify-between w-[50%]"
|
||||
{...register("status")}
|
||||
/>
|
||||
</fieldset>
|
||||
<div className="flex flex-row justify-between items-center flex-1 mt-10 w-full">
|
||||
<Button
|
||||
type="button"
|
||||
variant="tertiary"
|
||||
onClick={onCancelBrainCreation}
|
||||
className="text-primary"
|
||||
>
|
||||
{t("cancel", { ns: "translation" })}
|
||||
</Button>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={goToPreviousStep}
|
||||
className="py-2 border-primary text-primary"
|
||||
>
|
||||
<FaArrowLeft className="text-xl" size={16} />
|
||||
{t("previous", { ns: "translation" })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="bg-primary text-white py-2 border-none"
|
||||
type="button"
|
||||
onClick={goToNextStep}
|
||||
disabled={isNextButtonDisabled}
|
||||
>
|
||||
{t("next", { ns: "translation" })}
|
||||
<FaArrowRight className="text-xl" size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<PublicAccessConfirmationModal
|
||||
opened={isPublicAccessConfirmationModalOpened}
|
||||
onClose={onCancelPublicAccess}
|
||||
onCancel={onCancelPublicAccess}
|
||||
onConfirm={onConfirmPublicAccess}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainParamsStep = () => {
|
||||
const { watch } = useFormContext<CreateBrainProps>();
|
||||
const brainName = watch("name");
|
||||
const description = watch("description");
|
||||
|
||||
const isNextButtonDisabled = brainName === "" || description === "";
|
||||
|
||||
return {
|
||||
isNextButtonDisabled,
|
||||
};
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { BrainStatus } from "@/lib/types/brainConfig";
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainStatusOptions = () => {
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
|
||||
const brainStatusOptions: {
|
||||
label: string;
|
||||
value: BrainStatus;
|
||||
}[] = [
|
||||
{
|
||||
label: t("private_brain_label", { ns: "brain" }),
|
||||
value: "private",
|
||||
},
|
||||
{
|
||||
label: t("public_brain_label", { ns: "brain" }),
|
||||
value: "public",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
brainStatusOptions,
|
||||
};
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const usePublicAccessConfirmationModal = () => {
|
||||
const {
|
||||
watch,
|
||||
setValue,
|
||||
formState: { dirtyFields },
|
||||
} = useFormContext<CreateBrainProps>();
|
||||
|
||||
const [
|
||||
isPublicAccessConfirmationModalOpened,
|
||||
setIsPublicAccessConfirmationModalOpened,
|
||||
] = useState(false);
|
||||
|
||||
const status = watch("status");
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "public" && dirtyFields.status === true) {
|
||||
setIsPublicAccessConfirmationModalOpened(true);
|
||||
}
|
||||
}, [dirtyFields.status, status]);
|
||||
|
||||
const onConfirmPublicAccess = () => {
|
||||
setIsPublicAccessConfirmationModalOpened(false);
|
||||
};
|
||||
|
||||
const onCancelPublicAccess = () => {
|
||||
setValue("status", "private", {
|
||||
shouldDirty: true,
|
||||
});
|
||||
setIsPublicAccessConfirmationModalOpened(false);
|
||||
};
|
||||
|
||||
return {
|
||||
isPublicAccessConfirmationModalOpened,
|
||||
onConfirmPublicAccess,
|
||||
onCancelPublicAccess,
|
||||
};
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaArrowRight } from "react-icons/fa";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
@ -18,7 +19,7 @@ export const BrainTypeSelectionStep = ({
|
||||
const { knowledgeSourceOptions } = useKnowledgeSourceLabel();
|
||||
const { register } = useBrainTypeSelectionStep();
|
||||
const { goToNextStep, currentStep } = useBrainCreationSteps();
|
||||
|
||||
const { t } = useTranslation(["translation"]);
|
||||
if (currentStep !== "BRAIN_TYPE") {
|
||||
return <Fragment />;
|
||||
}
|
||||
@ -36,7 +37,7 @@ export const BrainTypeSelectionStep = ({
|
||||
variant="tertiary"
|
||||
onClick={onCancelBrainCreation}
|
||||
>
|
||||
Annuler
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -45,7 +46,7 @@ export const BrainTypeSelectionStep = ({
|
||||
data-testid="create-brain-submit-button"
|
||||
onClick={goToNextStep}
|
||||
>
|
||||
Suivant
|
||||
{t("next")}
|
||||
<FaArrowRight className="text-xl" size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -1,10 +1,19 @@
|
||||
import { useEffect } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainTypeSelectionStep = () => {
|
||||
const { register } = useFormContext<CreateBrainProps>();
|
||||
const { register, watch, reset, setValue } =
|
||||
useFormContext<CreateBrainProps>();
|
||||
const brainType = watch("brain_type");
|
||||
|
||||
useEffect(() => {
|
||||
const currentBrainType = brainType;
|
||||
reset();
|
||||
setValue("brain_type", currentBrainType);
|
||||
}, [brainType, reset, setValue]);
|
||||
|
||||
return {
|
||||
register,
|
||||
|
@ -2,6 +2,7 @@ import { Fragment } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Step } from "./components/Step";
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
export const Stepper = (): JSX.Element => {
|
||||
@ -11,24 +12,7 @@ export const Stepper = (): JSX.Element => {
|
||||
<div className="flex flex-row justify-between w-full px-12 mb-12">
|
||||
{steps.map((step, index) => (
|
||||
<Fragment key={step.value}>
|
||||
<div
|
||||
key={step.label}
|
||||
className="flex flex-row justify-center items-center flex-1"
|
||||
>
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
<div
|
||||
className={cn(
|
||||
"h-[40px] w-[40px] border-solid rounded-full flex flex-row items-center justify-center mb-2 border-primary border-2",
|
||||
step.value === currentStep ? "bg-primary text-white" : ""
|
||||
)}
|
||||
>
|
||||
{index + 1}
|
||||
</div>
|
||||
<span key={step.label} className="text-xs text-center">
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Step index={index} step={step} />
|
||||
{index < steps.length - 1 && ( // Add horizontal line for all but the last step
|
||||
<hr
|
||||
className={cn(
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { FaCheckCircle } from "react-icons/fa";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { useBrainCreationSteps } from "../../../hooks/useBrainCreationSteps";
|
||||
import { Step as StepType } from "../../../types";
|
||||
|
||||
type StepProps = {
|
||||
index: number;
|
||||
step: StepType;
|
||||
};
|
||||
|
||||
export const Step = ({ index, step }: StepProps): JSX.Element => {
|
||||
const { currentStep, currentStepIndex } = useBrainCreationSteps();
|
||||
const isStepDone = index < currentStepIndex;
|
||||
const stepContent = isStepDone ? <FaCheckCircle /> : index + 1;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={step.label}
|
||||
className="flex flex-row justify-center items-center flex-1"
|
||||
>
|
||||
<div className="flex flex-col justify-center items-center">
|
||||
<div
|
||||
className={cn(
|
||||
"h-[40px] w-[40px] border-solid rounded-full flex flex-row items-center justify-center mb-2 border-primary border-2 text-primary",
|
||||
isStepDone ? "bg-primary text-white" : "",
|
||||
step.value === currentStep ? "bg-primary text-white" : ""
|
||||
)}
|
||||
>
|
||||
{stepContent}
|
||||
</div>
|
||||
<span key={step.label} className="text-xs text-center">
|
||||
{step.label}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -3,13 +3,13 @@ import { useTranslation } from "react-i18next";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
|
||||
import { StepperStep } from "../types";
|
||||
import { Step } from "../types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainCreationSteps = () => {
|
||||
const { t } = useTranslation("brain");
|
||||
|
||||
const steps: StepperStep[] = [
|
||||
const steps: Step[] = [
|
||||
{
|
||||
label: t("brain_type"),
|
||||
value: "BRAIN_TYPE",
|
||||
@ -25,11 +25,11 @@ export const useBrainCreationSteps = () => {
|
||||
];
|
||||
const { watch, setValue } = useFormContext<CreateBrainProps>();
|
||||
const currentStep = watch("brainCreationStep");
|
||||
const currentStepIndex = steps.findIndex(
|
||||
(step) => step.value === currentStep
|
||||
);
|
||||
|
||||
const goToNextStep = () => {
|
||||
const currentStepIndex = steps.findIndex(
|
||||
(step) => step.value === currentStep
|
||||
);
|
||||
if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) {
|
||||
return;
|
||||
}
|
||||
@ -39,9 +39,6 @@ export const useBrainCreationSteps = () => {
|
||||
};
|
||||
|
||||
const goToPreviousStep = () => {
|
||||
const currentStepIndex = steps.findIndex(
|
||||
(step) => step.value === currentStep
|
||||
);
|
||||
if (currentStepIndex === -1 || currentStepIndex === 0) {
|
||||
return;
|
||||
}
|
||||
@ -55,5 +52,6 @@ export const useBrainCreationSteps = () => {
|
||||
steps,
|
||||
goToNextStep,
|
||||
goToPreviousStep,
|
||||
currentStepIndex,
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { BrainCreationStep } from "../../types";
|
||||
|
||||
export type StepperStep = {
|
||||
export type Step = {
|
||||
label: string;
|
||||
value: BrainCreationStep;
|
||||
};
|
||||
|
@ -30,5 +30,8 @@
|
||||
"invalidUrl": "Invalid URL",
|
||||
"crawlButton": "Crawl",
|
||||
"themeSelect": "Interface theme",
|
||||
"languageSelect": "Preferred language"
|
||||
"languageSelect": "Preferred language",
|
||||
"cancel": "Cancel",
|
||||
"next": "Next",
|
||||
"previous": "Previous"
|
||||
}
|
||||
|
@ -30,5 +30,8 @@
|
||||
"invalidUrl": "URL inválida",
|
||||
"crawlButton": "Rastrear",
|
||||
"themeSelect": "Tema de interfaz",
|
||||
"languageSelect": "Idioma preferido"
|
||||
"languageSelect": "Idioma preferido",
|
||||
"cancel": "Cancelar",
|
||||
"next": "Siguiente",
|
||||
"previous": "Anterior"
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
"email": "Email",
|
||||
"or": "ou",
|
||||
"and": "et",
|
||||
|
||||
"logoutButton": "Déconnexion",
|
||||
"updateButton": "Mettre à jour",
|
||||
"uploadButton": "Télécharger",
|
||||
@ -31,5 +30,8 @@
|
||||
"invalidUrl": "URL invalide",
|
||||
"crawlButton": "Crawler",
|
||||
"themeSelect": "Thème de l'interface",
|
||||
"languageSelect": "Langue préférée"
|
||||
"languageSelect": "Langue préférée",
|
||||
"cancel": "Annuler",
|
||||
"next": "Suivant",
|
||||
"previous": "Précédent"
|
||||
}
|
||||
|
@ -30,5 +30,8 @@
|
||||
"invalidUrl": "URL inválida",
|
||||
"crawlButton": "Rastrear",
|
||||
"themeSelect": "Tema de interface",
|
||||
"languageSelect": "Língua preferida"
|
||||
"languageSelect": "Língua preferida",
|
||||
"cancel": "Cancelar",
|
||||
"next": "Próximo",
|
||||
"previous": "Anterior"
|
||||
}
|
||||
|
@ -30,5 +30,8 @@
|
||||
"invalidUrl": "Неверный URL",
|
||||
"crawlButton": "Поиск",
|
||||
"themeSelect": "Тема интерфейса",
|
||||
"languageSelect": "предпочтительный язык"
|
||||
"languageSelect": "предпочтительный язык",
|
||||
"cancel": "Отмена",
|
||||
"next": "Далее",
|
||||
"previous": "Назад"
|
||||
}
|
||||
|
@ -30,5 +30,8 @@
|
||||
"invalidUrl": "无效的 URL",
|
||||
"crawlButton": "爬取",
|
||||
"themeSelect": "主题",
|
||||
"languageSelect": "首选语言"
|
||||
"languageSelect": "首选语言",
|
||||
"cancel": "取消",
|
||||
"next": "下一个",
|
||||
"previous": "上一个"
|
||||
}
|
Loading…
Reference in New Issue
Block a user