feat: add brain creation steps system (#1814)

Issue: https://github.com/StanGirard/quivr/issues/1769

- Turn brain creation into steps
- Add first step content
- Add feature flag

Demo:


https://github.com/StanGirard/quivr/assets/63923024/1ce33a3a-86ea-4051-ba02-f95b7478411e
This commit is contained in:
Mamadou DICKO 2023-12-05 11:52:56 +01:00 committed by GitHub
parent 5f23bcfcb8
commit 77410f04b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 400 additions and 34 deletions

View File

@ -2,7 +2,7 @@
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { AddBrainModal } from "@/lib/components/AddBrainModal/AddBrainModal";
import { AddBrainModal } from "@/lib/components/AddBrainModal";
import { Sidebar } from "@/lib/components/Sidebar/Sidebar";
import Button from "@/lib/components/ui/Button";

View File

@ -2,8 +2,8 @@ import { FormProvider, useForm } from "react-hook-form";
import { addBrainDefaultValues } from "@/lib/config/defaultBrainConfig";
import { AddBrainConfig } from "./components/AddBrainConfig/AddBrainConfig";
import { CreateBrainProps } from "./components/AddBrainConfig/types";
import { AddBrainSteps } from "./components/AddBrainSteps/AddBrainSteps";
import { CreateBrainProps } from "./types";
type AddBrainModalProps = {
triggerClassName?: string;
@ -14,11 +14,8 @@ export const AddBrainModal = ({
}: AddBrainModalProps): JSX.Element => {
const defaultValues: CreateBrainProps = {
...addBrainDefaultValues,
prompt: {
title: "",
content: "",
},
setDefault: true,
brainCreationStep: "BRAIN_TYPE",
};
const methods = useForm<CreateBrainProps>({
@ -27,7 +24,7 @@ export const AddBrainModal = ({
return (
<FormProvider {...methods}>
<AddBrainConfig triggerClassName={triggerClassName} />
<AddBrainSteps triggerClassName={triggerClassName} />
</FormProvider>
);
};

View File

@ -0,0 +1,20 @@
import { useFeatureIsOn } from "@growthbook/growthbook-react";
import { AddBrainModal as AddBrainModalNew } from "./AddBrainModal";
import { AddBrainModal as AddBrainModalOld } from "../AddBrainModalOld";
type AddBrainModalSelectorProps = {
triggerClassName?: string;
};
export const AddBrainModalSelector = ({
triggerClassName,
}: AddBrainModalSelectorProps): JSX.Element => {
const isNewBrainCreationOn = useFeatureIsOn("new-brain-creation");
if (isNewBrainCreationOn) {
return <AddBrainModalNew triggerClassName={triggerClassName} />;
}
return <AddBrainModalOld triggerClassName={triggerClassName} />;
};

View File

@ -0,0 +1,58 @@
import { useTranslation } from "react-i18next";
import { MdAdd } from "react-icons/md";
import Button from "@/lib/components/ui/Button";
import { Modal } from "@/lib/components/ui/Modal";
import { cn } from "@/lib/utils";
import { BrainTypeSelectionStep } from "./components/BrainTypeSelectionStep/BrainTypeSelectionStep";
import { Stepper } from "./components/Stepper/Stepper";
import { useAddBrainConfig } from "./hooks/useAddBrainConfig";
type AddBrainConfigProps = {
triggerClassName?: string;
};
export const AddBrainSteps = ({
triggerClassName,
}: AddBrainConfigProps): JSX.Element => {
const { t } = useTranslation(["translation", "brain", "config"]);
const { isBrainCreationModalOpened, setIsBrainCreationModalOpened } =
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"
>
<Stepper />
<BrainTypeSelectionStep
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
/>
</form>
</Modal>
</>
);
};

View File

@ -0,0 +1,54 @@
import { Fragment } from "react";
import { FaArrowRight } from "react-icons/fa";
import Button from "@/lib/components/ui/Button";
import { Radio } from "@/lib/components/ui/Radio";
import { useBrainTypeSelectionStep } from "./hooks/useBrainTypeSelectionStep";
import { useKnowledgeSourceLabel } from "./hooks/useKnowledgeSourceLabel";
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
type BrainTypeSelectionStepProps = {
onCancelBrainCreation: () => void;
};
export const BrainTypeSelectionStep = ({
onCancelBrainCreation,
}: BrainTypeSelectionStepProps): JSX.Element => {
const { knowledgeSourceOptions } = useKnowledgeSourceLabel();
const { register } = useBrainTypeSelectionStep();
const { goToNextStep, currentStep } = useBrainCreationSteps();
if (currentStep !== "BRAIN_TYPE") {
return <Fragment />;
}
return (
<>
<Radio
items={knowledgeSourceOptions}
className="flex-1 justify-between"
{...register("brain_type")}
/>
<div className="flex flex-row flex-1 justify-center w-full gap-48 mt-10">
<Button
type="button"
variant="tertiary"
onClick={onCancelBrainCreation}
>
Annuler
</Button>
<Button
className="bg-primary text-white py-2 border-none"
type="button"
data-testid="create-brain-submit-button"
onClick={goToNextStep}
>
Suivant
<FaArrowRight className="text-xl" size={16} />
</Button>
</div>
</>
);
};

View File

@ -0,0 +1,12 @@
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>();
return {
register,
};
};

View File

@ -0,0 +1,30 @@
import { useTranslation } from "react-i18next";
import { BrainType } from "@/lib/types/brainConfig";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useKnowledgeSourceLabel = () => {
const { t } = useTranslation(["translation", "brain", "config"]);
const knowledgeSourceOptions: {
label: string;
value: BrainType;
}[] = [
{
label: t("knowledge_source_doc", { ns: "brain" }),
value: "doc",
},
{
label: t("knowledge_source_api", { ns: "brain" }),
value: "api",
},
{
label: t("knowledge_source_chatflow", { ns: "brain" }),
value: "chatflow",
},
];
return {
knowledgeSourceOptions,
};
};

View File

@ -0,0 +1,46 @@
import { Fragment } from "react";
import { cn } from "@/lib/utils";
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
export const Stepper = (): JSX.Element => {
const { currentStep, steps } = useBrainCreationSteps();
return (
<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>
{index < steps.length - 1 && ( // Add horizontal line for all but the last step
<hr
className={cn(
"flex-grow border-t-2 border-primary m-4",
step.value === currentStep
? "border-primary"
: "border-gray-300"
)}
/>
)}
</Fragment>
))}
</div>
);
};

View File

@ -0,0 +1,12 @@
import { useState } from "react";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useAddBrainConfig = () => {
const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] =
useState(false);
return {
isBrainCreationModalOpened,
setIsBrainCreationModalOpened,
};
};

View File

@ -0,0 +1,59 @@
import { useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
import { StepperStep } from "../types";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useBrainCreationSteps = () => {
const { t } = useTranslation("brain");
const steps: StepperStep[] = [
{
label: t("brain_type"),
value: "BRAIN_TYPE",
},
{
label: t("brain_params"),
value: "BRAIN_PARAMS",
},
{
label: t("brain_data"),
value: "KNOWLEDGE",
},
];
const { watch, setValue } = useFormContext<CreateBrainProps>();
const currentStep = watch("brainCreationStep");
const goToNextStep = () => {
const currentStepIndex = steps.findIndex(
(step) => step.value === currentStep
);
if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) {
return;
}
const nextStep = steps[currentStepIndex + 1];
return setValue("brainCreationStep", nextStep.value);
};
const goToPreviousStep = () => {
const currentStepIndex = steps.findIndex(
(step) => step.value === currentStep
);
if (currentStepIndex === -1 || currentStepIndex === 0) {
return;
}
const previousStep = steps[currentStepIndex - 1];
return setValue("brainCreationStep", previousStep.value);
};
return {
currentStep,
steps,
goToNextStep,
goToPreviousStep,
};
};

View File

@ -0,0 +1 @@
export * from "./AddBrainSteps";

View File

@ -0,0 +1,6 @@
import { BrainCreationStep } from "../../types";
export type StepperStep = {
label: string;
value: BrainCreationStep;
};

View File

@ -1 +1 @@
export * from "./AddBrainConfig";
export * from "./AddBrainSteps";

View File

@ -1 +1 @@
export * from "./AddBrainModal";
export { AddBrainModalSelector as AddBrainModal } from "./AddBrainModalSelector";

View File

@ -0,0 +1,10 @@
import { CreateBrainInput } from "@/lib/api/brain/types";
const brainCreationSteps = ["BRAIN_TYPE", "BRAIN_PARAMS", "KNOWLEDGE"] as const;
export type BrainCreationStep = (typeof brainCreationSteps)[number];
export type CreateBrainProps = CreateBrainInput & {
setDefault: boolean;
brainCreationStep: BrainCreationStep;
};

View File

@ -0,0 +1,33 @@
import { FormProvider, useForm } from "react-hook-form";
import { addBrainDefaultValues } from "@/lib/config/defaultBrainConfig";
import { AddBrainConfig } from "./components/AddBrainConfig/AddBrainConfig";
import { CreateBrainProps } from "./components/AddBrainConfig/types";
type AddBrainModalProps = {
triggerClassName?: string;
};
export const AddBrainModal = ({
triggerClassName,
}: AddBrainModalProps): JSX.Element => {
const defaultValues: CreateBrainProps = {
...addBrainDefaultValues,
prompt: {
title: "",
content: "",
},
setDefault: true,
};
const methods = useForm<CreateBrainProps>({
defaultValues,
});
return (
<FormProvider {...methods}>
<AddBrainConfig triggerClassName={triggerClassName} />
</FormProvider>
);
};

View File

@ -111,7 +111,6 @@ export const AddBrainConfig = ({
<ApiRequestDefinition />
</>
<fieldset className="w-full flex flex-col">
<label className="flex-1 text-sm" htmlFor="model">
{t("modelLabel", { ns: "config" })}
@ -129,7 +128,6 @@ export const AddBrainConfig = ({
</select>
</fieldset>
<fieldset className="w-full flex mt-4">
<label className="flex-1" htmlFor="tokens">
{t("maxTokens", { ns: "config" })}: {maxTokens}

View File

@ -0,0 +1 @@
export * from "./AddBrainConfig";

View File

@ -0,0 +1 @@
export * from "./AddBrainModal";

View File

@ -1,6 +1,6 @@
import { useFormContext } from "react-hook-form";
import { CreateBrainProps } from "../../../../AddBrainModal/components/AddBrainConfig/types";
import { CreateBrainProps } from "../../../../AddBrainModalOld/components/AddBrainConfig/types";
import { defaultParamDefinitionRow } from "../config";
import { mapApiBrainDefinitionSchemaToParameterDefinition } from "../utils/mapApiBrainDefinitionSchemaToParameterDefinition";

View File

@ -1,6 +1,6 @@
import { useFormContext } from "react-hook-form";
import { CreateBrainProps } from "../../../../AddBrainModal/components/AddBrainConfig/types";
import { CreateBrainProps } from "../../../../AddBrainModalOld/components/AddBrainConfig/types";
import {
brainSecretsValueKeyInForm,
defaultSecretDefinitionRow,

View File

@ -6,7 +6,7 @@ export const brainStatuses = ["private", "public"] as const;
export type BrainStatus = (typeof brainStatuses)[number];
export const brainTypes = ["doc", "api"] as const;
export const brainTypes = ["doc", "api", "chatflow"] as const;
export type BrainType = (typeof brainTypes)[number];
@ -37,7 +37,11 @@ export type BrainConfig = {
brain_definition?: ApiBrainDefinition;
};
export const openAiFreeModels = ["gpt-3.5-turbo","gpt-3.5-turbo-1106", "gpt-3.5-turbo-16k"] as const;
export const openAiFreeModels = [
"gpt-3.5-turbo",
"gpt-3.5-turbo-1106",
"gpt-3.5-turbo-16k",
] as const;
export const openAiPaidModels = [...openAiFreeModels, "gpt-4"] as const;

View File

@ -50,6 +50,7 @@
"knowledge_source_doc": "Documents",
"knowledge_source_api": "App (Through API)",
"knowledge_source_label": "Knowledge source",
"knowledge_source_chatflow":"Brain network",
"api_brain": {
"name": "Name",
"description": "Description",
@ -62,5 +63,8 @@
"update_secrets_button":"Update secrets",
"secrets_updated":"Secrets updated",
"secrets_update_error":"Error while updating secrets",
"manage_brain": "Manage brain"
"manage_brain": "Manage brain",
"brain_type":"Type of brain",
"brain_params":"Brain params",
"brain_data":"Brain data"
}

View File

@ -49,6 +49,7 @@
"myBrains": "Mis cerebros",
"knowledge_source_doc": "Documentos",
"knowledge_source_api": "API",
"knowledge_source_chatflow":"Red de cerebro",
"knowledge_source_label": "Fuente de conocimiento",
"api_brain":{
"name": "Nombre",
@ -62,5 +63,8 @@
"update_secrets_button": "Actualizar secretos",
"secrets_updated": "Secretos actualizados",
"secrets_update_error": "Error al actualizar secretos",
"manage_brain": "Gestionar cerebro"
"manage_brain": "Gestionar cerebro",
"brain_type": "Tipo de cerebro",
"brain_params": "Parámetros del cerebro",
"brain_data": "Datos del cerebro"
}

View File

@ -49,6 +49,7 @@
"myBrains": "Mes cerveaux",
"knowledge_source_doc": "Documents",
"knowledge_source_api": "API",
"knowledge_source_chatflow":"Réseau cérébral",
"knowledge_source_label": "Source de connaissance",
"api_brain": {
"name": "Nom",
@ -62,5 +63,8 @@
"update_secrets_button": "Mettre à jour les secrets",
"secrets_updated": "Secrets mis à jour",
"secrets_update_error": "Erreur lors de la mise à jour des secrets",
"manage_brain": "Gérer le cerveau"
"manage_brain": "Gérer le cerveau",
"brain_type": "Type de cerveau",
"brain_params": "Paramètres du cerveau",
"brain_data": "Données du cerveau"
}

View File

@ -49,6 +49,7 @@
"myBrains": "Meus cérebros",
"knowledge_source_doc": "Documentos",
"knowledge_source_api": "API",
"knowledge_source_chatflow":"Rede de cérebro",
"knowledge_source_label": "Fonte de conhecimento",
"api_brain":{
"name": "Name",
@ -62,5 +63,8 @@
"update_secrets_button": "Atualizar segredos",
"secrets_updated": "Segredos atualizados",
"secrets_update_error": "Erro ao atualizar segredos",
"manage_brain": "Gerenciar cérebro"
"manage_brain": "Gerenciar cérebro",
"brain_type": "Tipo de cérebro",
"brain_params": "Parâmetros do cérebro",
"brain_data": "Dados do cérebro"
}

View File

@ -49,6 +49,7 @@
"myBrains": "Мои мозги",
"knowledge_source_doc": "Документы",
"knowledge_source_api": "API",
"knowledge_source_chatflow":"Сеть мозга",
"knowledge_source_label": "Источник знаний",
"api_brain":{
"name": "Название",
@ -62,5 +63,8 @@
"update_secrets_button": "Обновить секреты",
"secrets_updated": "Секреты обновлены",
"secrets_update_error": "Ошибка при обновлении секретов",
"manage_brain": "Управление мозгом"
"manage_brain": "Управление мозгом",
"brain_type": "Тип мозга",
"brain_params": "Параметры мозга",
"brain_data": "Данные мозга"
}

View File

@ -49,6 +49,7 @@
"myBrains": "我的大脑",
"knowledge_source_doc": "文档",
"knowledge_source_api": "API",
"knowledge_source_chatflow":"脑网络",
"knowledge_source_label": "知识来源",
"api_brain":{
"name": "名称",
@ -62,5 +63,8 @@
"update_secrets_button": "更新秘密",
"secrets_updated": "秘密已更新",
"secrets_update_error": "更新秘密时出错",
"manage_brain": "管理大脑"
"manage_brain": "管理大脑",
"brain_type": "脑类型",
"brain_params": "脑参数",
"brain_data": "脑数据"
}