mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-10-05 18:38:06 +03:00
feat(frontend): new brain creation modal (#2192)
# Description Please include a summary of the changes and the related issue. Please also include relevant motivation and context. ## Checklist before requesting a review Please delete options that are not relevant. - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented hard-to-understand areas - [ ] I have ideally added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged ## Screenshots (if appropriate):
This commit is contained in:
parent
08e015af6c
commit
6383918f7b
@ -5,7 +5,7 @@ import { posthog } from "posthog-js";
|
||||
import { PostHogProvider } from "posthog-js/react";
|
||||
import { PropsWithChildren, useEffect } from "react";
|
||||
|
||||
import { BrainCreationProvider } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
|
||||
import { BrainCreationProvider } from "@/lib/components/AddBrainModal/brainCreation-provider";
|
||||
import { Menu } from "@/lib/components/Menu/Menu";
|
||||
import { useOutsideClickListener } from "@/lib/components/Menu/hooks/useOutsideClickListener";
|
||||
import SearchModal from "@/lib/components/SearchModal/SearchModal";
|
||||
|
@ -2,7 +2,7 @@ import { SuggestionKeyDownProps } from "@tiptap/suggestion";
|
||||
import { forwardRef } from "react";
|
||||
import { FaAngleDoubleDown } from "react-icons/fa";
|
||||
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider";
|
||||
import TextButton from "@/lib/components/ui/TextButton/TextButton";
|
||||
|
||||
import { AddNewPromptButton } from "./components/AddNewPromptButton";
|
||||
|
@ -9,6 +9,7 @@
|
||||
padding-block: Spacings.$spacing05;
|
||||
width: 100%;
|
||||
gap: Spacings.$spacing05;
|
||||
overflow: hidden;
|
||||
|
||||
.single_selector_wrapper {
|
||||
width: 30%;
|
||||
@ -37,6 +38,7 @@
|
||||
overflow: scroll;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing02;
|
||||
flex-grow: 1;
|
||||
overflow: scroll;
|
||||
|
||||
.uploaded_knowledge {
|
||||
@ -46,6 +48,7 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
font-size: Typography.$small;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
|
@ -13,7 +13,11 @@ import { FromDocuments } from "./components/FromDocuments/FromDocuments";
|
||||
import { FromWebsites } from "./components/FromWebsites/FromWebsites";
|
||||
import { formatMinimalBrainsToSelectComponentInput } from "./utils/formatMinimalBrainsToSelectComponentInput";
|
||||
|
||||
export const KnowledgeToFeed = (): JSX.Element => {
|
||||
export const KnowledgeToFeed = ({
|
||||
hideBrainSelector,
|
||||
}: {
|
||||
hideBrainSelector?: boolean;
|
||||
}): JSX.Element => {
|
||||
const { allBrains, setCurrentBrainId, currentBrain } = useBrainContext();
|
||||
const [selectedTab, setSelectedTab] = useState("From documents");
|
||||
const { knowledgeToFeed, removeKnowledgeToFeed } =
|
||||
@ -46,18 +50,20 @@ export const KnowledgeToFeed = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className={styles.knowledge_to_feed_wrapper}>
|
||||
<div className={styles.single_selector_wrapper}>
|
||||
<SingleSelector
|
||||
options={brainsWithUploadRights}
|
||||
onChange={setCurrentBrainId}
|
||||
selectedOption={
|
||||
currentBrain
|
||||
? { label: currentBrain.name, value: currentBrain.id }
|
||||
: undefined
|
||||
}
|
||||
placeholder="Select a brain"
|
||||
/>
|
||||
</div>
|
||||
{!hideBrainSelector && (
|
||||
<div className={styles.single_selector_wrapper}>
|
||||
<SingleSelector
|
||||
options={brainsWithUploadRights}
|
||||
onChange={setCurrentBrainId}
|
||||
selectedOption={
|
||||
currentBrain
|
||||
? { label: currentBrain.name, value: currentBrain.id }
|
||||
: undefined
|
||||
}
|
||||
placeholder="Select a brain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Tabs tabList={knowledgesTabs} />
|
||||
<div className={styles.tabs_content_wrapper}>
|
||||
{selectedTab === "From documents" && <FromDocuments />}
|
||||
|
@ -1,5 +1,6 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/ScreenSizes.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
|
||||
.from_document_wrapper {
|
||||
@ -24,6 +25,10 @@
|
||||
display: flex;
|
||||
gap: Spacings.$spacing02;
|
||||
|
||||
@media (max-width: ScreenSizes.$small) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
|
@ -4,7 +4,7 @@ import { UUID } from "crypto";
|
||||
import { useEffect } from "react";
|
||||
|
||||
import { AddBrainModal } from "@/lib/components/AddBrainModal";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider";
|
||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
||||
import { useChatContext } from "@/lib/context";
|
||||
|
@ -4,7 +4,7 @@ import { useEffect } from "react";
|
||||
|
||||
import { QuivrLogo } from "@/lib/assets/QuivrLogo";
|
||||
import { AddBrainModal } from "@/lib/components/AddBrainModal";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider";
|
||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
||||
import { SearchBar } from "@/lib/components/ui/SearchBar/SearchBar";
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { AddBrainModal } from "@/lib/components/AddBrainModal";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
|
||||
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/brainCreation-provider";
|
||||
import PageHeader from "@/lib/components/PageHeader/PageHeader";
|
||||
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
|
||||
import { Tabs } from "@/lib/components/ui/Tabs/Tabs";
|
||||
|
@ -0,0 +1,21 @@
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
|
||||
.add_brain_modal_container {
|
||||
display: flex;
|
||||
padding-block: Spacings.$spacing05;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
gap: Spacings.$spacing08;
|
||||
|
||||
.stepper_container {
|
||||
width: 100%;
|
||||
padding-inline: Spacings.$spacing08;
|
||||
}
|
||||
|
||||
.content_wrapper {
|
||||
flex-grow: 1;
|
||||
overflow: scroll;
|
||||
}
|
||||
}
|
@ -1,11 +1,23 @@
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||
import { addBrainDefaultValues } from "@/lib/config/defaultBrainConfig";
|
||||
|
||||
import { AddBrainSteps } from "./components/AddBrainSteps/AddBrainSteps";
|
||||
import { CreateBrainProps } from "./types";
|
||||
import styles from "./AddBrainModal.module.scss";
|
||||
import { useBrainCreationContext } from "./brainCreation-provider";
|
||||
import { BrainKnowledgeStep } from "./components/BrainKnowledgeStep/BrainKnowledgeStep";
|
||||
import { BrainMainInfosStep } from "./components/BrainMainInfosStep/BrainMainInfosStep";
|
||||
import { BrainTypeSelectionStep } from "./components/BrainTypeSelectionStep/BrainTypeSelectionStep";
|
||||
import { Stepper } from "./components/Stepper/Stepper";
|
||||
import { CreateBrainProps } from "./types/types";
|
||||
|
||||
export const AddBrainModal = (): JSX.Element => {
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
|
||||
const { isBrainCreationModalOpened, setIsBrainCreationModalOpened } =
|
||||
useBrainCreationContext();
|
||||
|
||||
const defaultValues: CreateBrainProps = {
|
||||
...addBrainDefaultValues,
|
||||
setDefault: true,
|
||||
@ -18,7 +30,25 @@ export const AddBrainModal = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<AddBrainSteps />
|
||||
<Modal
|
||||
title={t("newBrainTitle", { ns: "brain" })}
|
||||
desc={t("newBrainSubtitle", { ns: "brain" })}
|
||||
isOpen={isBrainCreationModalOpened}
|
||||
setOpen={setIsBrainCreationModalOpened}
|
||||
bigModal={true}
|
||||
CloseTrigger={<div />}
|
||||
>
|
||||
<div className={styles.add_brain_modal_container}>
|
||||
<div className={styles.stepper_container}>
|
||||
<Stepper />
|
||||
</div>
|
||||
<div className={styles.content_wrapper}>
|
||||
<BrainTypeSelectionStep />
|
||||
<BrainMainInfosStep />
|
||||
<BrainKnowledgeStep />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
|
@ -3,6 +3,8 @@ import { createContext, useContext, useState } from "react";
|
||||
interface BrainCreationContextProps {
|
||||
isBrainCreationModalOpened: boolean;
|
||||
setIsBrainCreationModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
creating: boolean;
|
||||
setCreating: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
export const BrainCreationContext = createContext<
|
||||
@ -15,11 +17,17 @@ export const BrainCreationProvider = ({
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element => {
|
||||
const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] =
|
||||
useState(false);
|
||||
useState<boolean>(false);
|
||||
const [creating, setCreating] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<BrainCreationContext.Provider
|
||||
value={{ isBrainCreationModalOpened, setIsBrainCreationModalOpened }}
|
||||
value={{
|
||||
isBrainCreationModalOpened,
|
||||
setIsBrainCreationModalOpened,
|
||||
creating,
|
||||
setCreating,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BrainCreationContext.Provider>
|
@ -1,44 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||
|
||||
import { useBrainCreationContext } from "./brainCreation-provider";
|
||||
import { BrainKnowledgeStep } from "./components/BrainKnowledgeStep/BrainKnowledgeStep";
|
||||
import { BrainParamsStep } from "./components/BrainParamsStep/BrainParamsStep";
|
||||
import { BrainTypeSelectionStep } from "./components/BrainTypeSelectionStep/BrainTypeSelectionStep";
|
||||
import { Stepper } from "./components/Stepper/Stepper";
|
||||
|
||||
export const AddBrainSteps = (): JSX.Element => {
|
||||
const { t } = useTranslation(["translation", "brain", "config"]);
|
||||
|
||||
const { isBrainCreationModalOpened, setIsBrainCreationModalOpened } =
|
||||
useBrainCreationContext();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
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)}
|
||||
/>
|
||||
<BrainKnowledgeStep
|
||||
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
|
||||
/>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -1,80 +0,0 @@
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaArrowLeft } from "react-icons/fa";
|
||||
|
||||
import { ApiRequestDefinition } from "@/lib/components/ApiRequestDefinition";
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { BrainType } from "@/lib/types/BrainConfig";
|
||||
|
||||
import { CompositeBrainConnections } from "./components/CompositeBrainConnections/CompositeBrainConnections";
|
||||
import { KnowledgeToFeedInput } from "./components/KnowledgeToFeedInput";
|
||||
import { useBrainCreationHandler } from "./hooks/useBrainCreationHandler";
|
||||
import { useBrainKnowledgeStep } from "./hooks/useBrainKnowledgeStep";
|
||||
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
type BrainKnowledgeStepProps = {
|
||||
onCancelBrainCreation: () => void;
|
||||
};
|
||||
|
||||
export const BrainKnowledgeStep = ({
|
||||
onCancelBrainCreation,
|
||||
}: BrainKnowledgeStepProps): JSX.Element => {
|
||||
const { brainType, isSubmitButtonDisabled } = useBrainKnowledgeStep();
|
||||
const { t } = useTranslation(["translation"]);
|
||||
const { goToPreviousStep, currentStep } = useBrainCreationSteps();
|
||||
const { handleCreateBrain, isBrainCreationPending } = useBrainCreationHandler(
|
||||
{
|
||||
closeBrainCreationModal: onCancelBrainCreation,
|
||||
}
|
||||
);
|
||||
|
||||
const brainTypeToKnowledgeComponent: Record<BrainType, JSX.Element> = {
|
||||
doc: <KnowledgeToFeedInput />,
|
||||
api: <ApiRequestDefinition />,
|
||||
composite: <CompositeBrainConnections />,
|
||||
};
|
||||
|
||||
if (currentStep !== "KNOWLEDGE" || brainType === undefined) {
|
||||
return <Fragment />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{brainTypeToKnowledgeComponent[brainType]}
|
||||
<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"
|
||||
disabled={isBrainCreationPending}
|
||||
>
|
||||
{t("cancel", { ns: "translation" })}
|
||||
</Button>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={goToPreviousStep}
|
||||
className="py-2 border-primary text-primary"
|
||||
disabled={isBrainCreationPending}
|
||||
>
|
||||
<FaArrowLeft className="text-xl" size={16} />
|
||||
{t("previous", { ns: "translation" })}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="bg-primary text-white py-2 border-none"
|
||||
type="button"
|
||||
onClick={() => void handleCreateBrain()}
|
||||
disabled={isSubmitButtonDisabled || isBrainCreationPending}
|
||||
isLoading={isBrainCreationPending}
|
||||
>
|
||||
{t("createButton", { ns: "translation" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
Crawler,
|
||||
FeedItems,
|
||||
FileUploader,
|
||||
} from "@/lib/components/KnowledgeToFeedInput/components";
|
||||
|
||||
export const KnowledgeToFeedInput = (): JSX.Element => {
|
||||
const { t } = useTranslation(["translation", "upload"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-row gap-10 justify-between items-center mt-5">
|
||||
<FileUploader />
|
||||
<span className="whitespace-nowrap ">
|
||||
{`${t("and", { ns: "translation" })} / ${t("or", {
|
||||
ns: "translation",
|
||||
})}`}
|
||||
</span>
|
||||
<Crawler />
|
||||
</div>
|
||||
<FeedItems />
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
|
||||
import { useBrainCreationApi } from "./useBrainCreationApi";
|
||||
|
||||
type UseBrainCreationHandler = {
|
||||
closeBrainCreationModal: () => void;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainCreationHandler = ({
|
||||
closeBrainCreationModal,
|
||||
}: UseBrainCreationHandler) => {
|
||||
const { getValues } = useFormContext<CreateBrainProps>();
|
||||
const { publish } = useToast();
|
||||
const { t } = useTranslation(["brain", "config"]);
|
||||
|
||||
const { isBrainCreationPending, createBrain } = useBrainCreationApi({
|
||||
closeBrainCreationModal,
|
||||
});
|
||||
|
||||
const handleCreateBrain = () => {
|
||||
const { name, description } = getValues();
|
||||
|
||||
if (name.trim() === "" || isBrainCreationPending) {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("nameRequired", { ns: "config" }),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (description.trim() === "") {
|
||||
publish({
|
||||
variant: "danger",
|
||||
text: t("descriptionRequired", { ns: "config" }),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
createBrain();
|
||||
};
|
||||
|
||||
return {
|
||||
handleCreateBrain,
|
||||
isBrainCreationPending,
|
||||
};
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
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 useBrainKnowledgeStep = () => {
|
||||
const { watch } = useFormContext<CreateBrainProps>();
|
||||
const brainType = watch("brain_type");
|
||||
const url = watch("brain_definition.url");
|
||||
const compositeBrainConnections = watch("connected_brains_ids") ?? [];
|
||||
const isApiBrain = brainType === "api";
|
||||
const isCompositeBrain = brainType === "composite";
|
||||
|
||||
const isApiBrainDefinitionsFilled = url !== "";
|
||||
|
||||
const isSubmitButtonDisabled =
|
||||
(isCompositeBrain && compositeBrainConnections.length === 0) ||
|
||||
(isApiBrain && !isApiBrainDefinitionsFilled);
|
||||
|
||||
return {
|
||||
brainType,
|
||||
isSubmitButtonDisabled,
|
||||
};
|
||||
};
|
@ -1,96 +0,0 @@
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaArrowLeft, FaArrowRight } from "react-icons/fa";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import Field from "@/lib/components/ui/Field";
|
||||
import { TextArea } from "@/lib/components/ui/TextArea";
|
||||
|
||||
import { PublicAccessConfirmationModal } from "./components/PublicAccessConfirmationModal";
|
||||
import { useBrainParamsStep } from "./hooks/useBrainParamsStep";
|
||||
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 { 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"
|
||||
required
|
||||
{...register("description")}
|
||||
/>
|
||||
<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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,42 +0,0 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Button from "@/lib/components/ui/Button";
|
||||
import { Modal } from "@/lib/components/ui/Modal/Modal";
|
||||
|
||||
type PublicAccessConfirmationModalProps = {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
export const PublicAccessConfirmationModal = ({
|
||||
opened,
|
||||
onClose,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
}: PublicAccessConfirmationModalProps): JSX.Element => {
|
||||
const { t } = useTranslation(["brain"]);
|
||||
|
||||
return (
|
||||
<Modal isOpen={opened} setOpen={onClose} CloseTrigger={<div />}>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t("set_brain_status_to_public_modal_title"),
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t("set_brain_status_to_public_modal_description"),
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-row justify-between pt-10 px-10 items-center">
|
||||
<Button type="button" onClick={onConfirm} variant="secondary">
|
||||
{t("confirm_set_brain_status_to_public")}
|
||||
</Button>
|
||||
<Button type="button" onClick={onCancel}>
|
||||
{t("cancel_set_brain_status_to_public")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
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,
|
||||
};
|
||||
};
|
@ -1,25 +0,0 @@
|
||||
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,
|
||||
};
|
||||
};
|
@ -1,43 +0,0 @@
|
||||
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,56 +0,0 @@
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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();
|
||||
const { t } = useTranslation(["translation"]);
|
||||
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}
|
||||
>
|
||||
{t("cancel")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="bg-primary text-white py-2 border-none"
|
||||
type="button"
|
||||
data-testid="create-brain-submit-button"
|
||||
onClick={goToNextStep}
|
||||
>
|
||||
{t("next")}
|
||||
<FaArrowRight className="text-xl" size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
@ -1,21 +0,0 @@
|
||||
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, watch, reset, setValue } =
|
||||
useFormContext<CreateBrainProps>();
|
||||
const brainType = watch("brain_type");
|
||||
|
||||
useEffect(() => {
|
||||
const currentBrainType = brainType;
|
||||
reset();
|
||||
setValue("brain_type", currentBrainType);
|
||||
}, [brainType, reset, setValue]);
|
||||
|
||||
return {
|
||||
register,
|
||||
};
|
||||
};
|
@ -1,35 +0,0 @@
|
||||
import { useFeatureIsOn } from "@growthbook/growthbook-react";
|
||||
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 isCompositeBrainActivated = useFeatureIsOn("agent-brain");
|
||||
|
||||
const knowledgeSourceOptions: {
|
||||
label: string;
|
||||
value: BrainType;
|
||||
}[] = [
|
||||
{
|
||||
label: t("knowledge_source_doc", { ns: "brain" }),
|
||||
value: "doc",
|
||||
},
|
||||
{
|
||||
label: t("knowledge_source_api", { ns: "brain" }),
|
||||
value: "api",
|
||||
},
|
||||
];
|
||||
|
||||
if (isCompositeBrainActivated) {
|
||||
knowledgeSourceOptions.push({
|
||||
label: t("knowledge_source_composite_brain", { ns: "brain" }),
|
||||
value: "composite",
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
knowledgeSourceOptions,
|
||||
};
|
||||
};
|
@ -1,31 +0,0 @@
|
||||
import { Fragment } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { Step } from "./components/Step";
|
||||
|
||||
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}>
|
||||
<Step index={index} step={step} />
|
||||
{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>
|
||||
);
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from "./AddBrainSteps";
|
@ -1,6 +0,0 @@
|
||||
import { BrainCreationStep } from "../../types";
|
||||
|
||||
export type Step = {
|
||||
label: string;
|
||||
value: BrainCreationStep;
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.brain_knowledge_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-inline: Spacings.$spacing08;
|
||||
height: 100%;
|
||||
|
||||
.title {
|
||||
@include Typography.H2;
|
||||
}
|
||||
|
||||
.buttons_wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
|
||||
import styles from "./BrainKnowledgeStep.module.scss";
|
||||
import { useBrainCreationApi } from "./hooks/useBrainCreationApi";
|
||||
|
||||
import { useBrainCreationContext } from "../../brainCreation-provider";
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
export const BrainKnowledgeStep = (): JSX.Element => {
|
||||
const { currentStepIndex, goToPreviousStep } = useBrainCreationSteps();
|
||||
const { createBrain } = useBrainCreationApi();
|
||||
const { creating, setCreating } = useBrainCreationContext();
|
||||
|
||||
const previous = (): void => {
|
||||
goToPreviousStep();
|
||||
};
|
||||
|
||||
const feed = (): void => {
|
||||
setCreating(true);
|
||||
createBrain();
|
||||
};
|
||||
|
||||
if (currentStepIndex !== 2) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.brain_knowledge_wrapper}>
|
||||
<div>
|
||||
<span className={styles.title}>Feed your brain</span>
|
||||
<KnowledgeToFeed hideBrainSelector={true} />
|
||||
</div>
|
||||
<div className={styles.buttons_wrapper}>
|
||||
<QuivrButton
|
||||
label="Previous step"
|
||||
color="primary"
|
||||
iconName="chevronLeft"
|
||||
onClick={previous}
|
||||
/>
|
||||
<QuivrButton
|
||||
label="Create brain"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -2,7 +2,7 @@ import { CheckedState } from "@radix-ui/react-checkbox";
|
||||
import { UUID } from "crypto";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useConnectableBrain = () => {
|
@ -4,22 +4,17 @@ import { useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { PUBLIC_BRAINS_KEY } from "@/lib/api/brain/config";
|
||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types";
|
||||
import { useKnowledgeToFeedInput } from "@/lib/components/KnowledgeToFeedInput/hooks/useKnowledgeToFeedInput.ts";
|
||||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
|
||||
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
import { useToast } from "@/lib/hooks";
|
||||
import { useKnowledgeToFeedFilesAndUrls } from "@/lib/hooks/useKnowledgeToFeed";
|
||||
|
||||
type UseBrainCreationHandler = {
|
||||
closeBrainCreationModal: () => void;
|
||||
};
|
||||
import { useBrainCreationContext } from "../../../brainCreation-provider";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainCreationApi = ({
|
||||
closeBrainCreationModal,
|
||||
}: UseBrainCreationHandler) => {
|
||||
export const useBrainCreationApi = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { publish } = useToast();
|
||||
const { t } = useTranslation(["brain", "config"]);
|
||||
@ -27,8 +22,9 @@ export const useBrainCreationApi = ({
|
||||
const { getValues, reset } = useFormContext<CreateBrainProps>();
|
||||
const { setKnowledgeToFeed } = useKnowledgeToFeedContext();
|
||||
const { createBrain: createBrainApi, setCurrentBrainId } = useBrainContext();
|
||||
const { setAsDefaultBrain } = useBrainApi();
|
||||
const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput();
|
||||
const { setIsBrainCreationModalOpened, setCreating } =
|
||||
useBrainCreationContext();
|
||||
|
||||
const handleFeedBrain = async (brainId: UUID): Promise<void> => {
|
||||
const uploadPromises = files.map((file) =>
|
||||
@ -44,7 +40,6 @@ export const useBrainCreationApi = ({
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
setDefault,
|
||||
brain_definition,
|
||||
brain_secrets_values,
|
||||
status,
|
||||
@ -76,12 +71,11 @@ export const useBrainCreationApi = ({
|
||||
void handleFeedBrain(createdBrainId);
|
||||
}
|
||||
|
||||
if (setDefault) {
|
||||
await setAsDefaultBrain(createdBrainId);
|
||||
}
|
||||
setCurrentBrainId(createdBrainId);
|
||||
closeBrainCreationModal();
|
||||
setIsBrainCreationModalOpened(false);
|
||||
setCreating(false);
|
||||
reset();
|
||||
|
||||
void queryClient.invalidateQueries({
|
||||
queryKey: [PUBLIC_BRAINS_KEY],
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.brain_main_infos_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
padding-inline: Spacings.$spacing08;
|
||||
|
||||
.title {
|
||||
@include Typography.H2;
|
||||
}
|
||||
|
||||
.inputs_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
}
|
||||
|
||||
.buttons_wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
import { TextAreaInput } from "@/lib/components/ui/TextAreaInput/TextAreaInput";
|
||||
import { TextInput } from "@/lib/components/ui/TextInput/TextInput";
|
||||
|
||||
import styles from "./BrainMainInfosStep.module.scss";
|
||||
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
export const BrainMainInfosStep = (): JSX.Element => {
|
||||
const { currentStepIndex, goToNextStep, goToPreviousStep } =
|
||||
useBrainCreationSteps();
|
||||
|
||||
const { watch } = useFormContext<CreateBrainProps>();
|
||||
const name = watch("name");
|
||||
const description = watch("description");
|
||||
|
||||
const isDisabled = !name || !description;
|
||||
|
||||
const next = (): void => {
|
||||
goToNextStep();
|
||||
};
|
||||
|
||||
const previous = (): void => {
|
||||
goToPreviousStep();
|
||||
};
|
||||
|
||||
if (currentStepIndex !== 1) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.brain_main_infos_wrapper}>
|
||||
<div className={styles.inputs_wrapper}>
|
||||
<span className={styles.title}>Define brain identity</span>
|
||||
<Controller
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<TextInput
|
||||
label="Name"
|
||||
inputValue={field.value as string} // Explicitly specify the type as string
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="description"
|
||||
render={({ field }) => (
|
||||
<TextAreaInput
|
||||
label="Description"
|
||||
inputValue={field.value as string}
|
||||
setInputValue={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.buttons_wrapper}>
|
||||
<QuivrButton
|
||||
color="primary"
|
||||
label="Previous Step"
|
||||
onClick={() => previous()}
|
||||
iconName="chevronLeft"
|
||||
/>
|
||||
<QuivrButton
|
||||
color="primary"
|
||||
label="Next Step"
|
||||
onClick={() => next()}
|
||||
iconName="chevronRight"
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.brain_type_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
padding: Spacings.$spacing05;
|
||||
border-radius: Radius.$big;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
background-color: Colors.$lightest-grey;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.first_line_wrapper {
|
||||
display: flex;
|
||||
gap: Spacings.$spacing03;
|
||||
align-items: center;
|
||||
|
||||
.name {
|
||||
@include Typography.H3;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
color: Colors.$dark-grey;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
border-color: Colors.$primary;
|
||||
|
||||
.name {
|
||||
color: Colors.$primary;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: Colors.$black;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { BrainType } from "@/lib/components/AddBrainModal/types/types";
|
||||
import Icon from "@/lib/components/ui/Icon/Icon";
|
||||
|
||||
import styles from "./BrainTypeSelection.module.scss";
|
||||
|
||||
export const BrainTypeSelection = ({
|
||||
brainType,
|
||||
onClick,
|
||||
selected,
|
||||
}: {
|
||||
brainType: BrainType;
|
||||
onClick: () => void;
|
||||
selected: boolean;
|
||||
}): JSX.Element => {
|
||||
const [isHovered, setIsHovered] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
${styles.brain_type_wrapper}
|
||||
${brainType.disabled && styles.disabled}
|
||||
${selected && styles.selected}
|
||||
`}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={styles.first_line_wrapper}>
|
||||
<Icon
|
||||
name={brainType.iconName}
|
||||
size="normal"
|
||||
color={isHovered || selected ? "primary" : "black"}
|
||||
/>
|
||||
<span className={styles.name}>{brainType.name}</span>
|
||||
</div>
|
||||
<span className={styles.description}>{brainType.description}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,31 @@
|
||||
@use "@/styles/ScreenSizes.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.brain_types_wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding-inline: Spacings.$spacing08;
|
||||
height: 100%;
|
||||
gap: Spacings.$spacing05;
|
||||
|
||||
@media (max-width: ScreenSizes.$small) {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.main_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
|
||||
.title {
|
||||
@include Typography.H2;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { BrainType } from "@/lib/components/AddBrainModal/types/types";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
|
||||
import { BrainTypeSelection } from "./BrainTypeSelection/BrainTypeSelection";
|
||||
import styles from "./BrainTypeSelectionStep.module.scss";
|
||||
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
export const BrainTypeSelectionStep = (): JSX.Element => {
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(0);
|
||||
const { goToNextStep, currentStepIndex } = useBrainCreationSteps();
|
||||
|
||||
const brainTypes: BrainType[] = [
|
||||
{
|
||||
name: "Core Brain",
|
||||
description: "Upload documents or website links to feed your brain.",
|
||||
iconName: "feed",
|
||||
},
|
||||
{
|
||||
name: "Sync Brain - Coming soon!",
|
||||
description:
|
||||
"Connect to your tools and applications to interact with your data.",
|
||||
iconName: "software",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
name: "Custom Brain - Coming soon!",
|
||||
description:
|
||||
"Explore your databases, converse with your APIs, and much more!",
|
||||
iconName: "custom",
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const next = (): void => {
|
||||
goToNextStep();
|
||||
};
|
||||
|
||||
if (currentStepIndex !== 0) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.brain_types_wrapper}>
|
||||
<div className={styles.main_wrapper}>
|
||||
<span className={styles.title}>Choose a type of brain</span>
|
||||
{brainTypes.map((brainType, index) => (
|
||||
<div key={index}>
|
||||
<BrainTypeSelection
|
||||
brainType={brainType}
|
||||
selected={index === selectedIndex}
|
||||
onClick={() => setSelectedIndex(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.button}>
|
||||
<QuivrButton
|
||||
label="Next Step"
|
||||
iconName="chevronRight"
|
||||
color="primary"
|
||||
onClick={() => next()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,105 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.stepper_wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: Radius.$circle;
|
||||
position: relative;
|
||||
|
||||
.circle {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
background-color: Colors.$primary;
|
||||
border-radius: Radius.$circle;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.inside_circle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: Radius.$circle;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.done_step {
|
||||
.circle {
|
||||
background-color: Colors.$success;
|
||||
}
|
||||
|
||||
.step_info {
|
||||
.step_status {
|
||||
color: Colors.$success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.current_step {
|
||||
.circle {
|
||||
background-color: Colors.$white;
|
||||
border: 1px solid Colors.$primary;
|
||||
}
|
||||
|
||||
.inside_circle {
|
||||
background-color: Colors.$primary;
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
}
|
||||
|
||||
.step_info {
|
||||
.step_status {
|
||||
color: Colors.$primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.pending_step {
|
||||
.circle {
|
||||
background-color: Colors.$primary-light;
|
||||
}
|
||||
|
||||
.step_info {
|
||||
.step_status {
|
||||
color: Colors.$normal-grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.step_info {
|
||||
margin-top: Spacings.$spacing03;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: Typography.$tiny;
|
||||
width: 2.5rem;
|
||||
|
||||
.step_index {
|
||||
white-space: nowrap;
|
||||
color: Colors.$normal-grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
flex-grow: 1;
|
||||
height: 4px;
|
||||
border-radius: Radius.$big;
|
||||
background-color: Colors.$primary-light;
|
||||
margin: 0 8px;
|
||||
margin-top: Spacings.$spacing05;
|
||||
|
||||
&.done {
|
||||
background-color: Colors.$success;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
import { Icon } from "@/lib/components/ui/Icon/Icon";
|
||||
|
||||
import styles from "./Stepper.module.scss";
|
||||
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
export const Stepper = (): JSX.Element => {
|
||||
const { currentStep, steps } = useBrainCreationSteps();
|
||||
|
||||
const currentStepIndex = steps.findIndex(
|
||||
(step) => step.value === currentStep
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.stepper_wrapper}>
|
||||
{steps.map((step, index) => (
|
||||
<>
|
||||
<div
|
||||
className={`${styles.step} ${
|
||||
index === currentStepIndex
|
||||
? styles.current_step
|
||||
: index < currentStepIndex
|
||||
? styles.done_step
|
||||
: styles.pending_step
|
||||
}`}
|
||||
key={step.value}
|
||||
>
|
||||
<div className={styles.circle}>
|
||||
<div className={styles.inside_circle}>
|
||||
{index < currentStepIndex && (
|
||||
<Icon name="check" size="normal" color="white" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.step_info}>
|
||||
<span className={styles.step_index}>STEP {index + 1}</span>
|
||||
<span className={styles.step_status}>
|
||||
{index === currentStepIndex
|
||||
? "Progress"
|
||||
: index < currentStepIndex
|
||||
? "Completed"
|
||||
: "Pending"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{index < steps.length - 1 && (
|
||||
<div
|
||||
className={`
|
||||
${styles.bar}
|
||||
${index < currentStepIndex ? styles.done : ""}
|
||||
`}
|
||||
></div>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1 +0,0 @@
|
||||
export * from "./AddBrainSteps";
|
@ -1,9 +1,10 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
|
||||
import { Step } from "../types";
|
||||
import {
|
||||
CreateBrainProps,
|
||||
Step,
|
||||
} from "@/lib/components/AddBrainModal/types/types";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainCreationSteps = () => {
|
@ -1,4 +1,5 @@
|
||||
import { CreateBrainInput } from "@/lib/api/brain/types";
|
||||
import { iconList } from "@/lib/helpers/iconList";
|
||||
|
||||
const brainCreationSteps = ["BRAIN_TYPE", "BRAIN_PARAMS", "KNOWLEDGE"] as const;
|
||||
|
||||
@ -8,3 +9,16 @@ export type CreateBrainProps = CreateBrainInput & {
|
||||
setDefault: boolean;
|
||||
brainCreationStep: BrainCreationStep;
|
||||
};
|
||||
|
||||
export interface BrainType {
|
||||
name: string;
|
||||
description: string;
|
||||
iconName: keyof typeof iconList;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export type Step = {
|
||||
label: string;
|
||||
value: BrainCreationStep;
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types";
|
||||
|
||||
import { defaultParamDefinitionRow } from "../config";
|
||||
import { mapApiBrainDefinitionSchemaToParameterDefinition } from "../utils/mapApiBrainDefinitionSchemaToParameterDefinition";
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
|
||||
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types";
|
||||
|
||||
import {
|
||||
brainSecretsValueKeyInForm,
|
||||
|
@ -26,7 +26,7 @@
|
||||
overflow: scroll;
|
||||
|
||||
&.big_modal {
|
||||
width: 50vw;
|
||||
width: 40vw;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,35 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
|
||||
.text_area_input_container {
|
||||
display: flex;
|
||||
border: 1px solid Colors.$lighter-grey;
|
||||
gap: Spacings.$spacing03;
|
||||
padding-block: Spacings.$spacing02;
|
||||
padding-inline: Spacings.$spacing03;
|
||||
border-radius: Radius.$big;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&.simple {
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
.text_input {
|
||||
background-color: transparent;
|
||||
width: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.text_area_input {
|
||||
caret-color: Colors.$accent;
|
||||
border: none;
|
||||
flex: 1;
|
||||
resize: none;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
32
frontend/lib/components/ui/TextAreaInput/TextAreaInput.tsx
Normal file
32
frontend/lib/components/ui/TextAreaInput/TextAreaInput.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import styles from "./TextAreaInput.module.scss";
|
||||
|
||||
type TextAreaInputProps = {
|
||||
label: string;
|
||||
inputValue: string;
|
||||
setInputValue: (value: string) => void;
|
||||
onSubmit?: () => void;
|
||||
};
|
||||
|
||||
export const TextAreaInput = ({
|
||||
label,
|
||||
inputValue,
|
||||
setInputValue,
|
||||
onSubmit,
|
||||
}: TextAreaInputProps): JSX.Element => {
|
||||
return (
|
||||
<div className={styles.text_area_input_container}>
|
||||
<textarea
|
||||
className={styles.text_area_input}
|
||||
value={inputValue}
|
||||
rows={5}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
placeholder={label}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && onSubmit) {
|
||||
onSubmit();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,5 +1,6 @@
|
||||
import { AiOutlineLoading3Quarters } from "react-icons/ai";
|
||||
import { BsArrowRightShort, BsChatLeftText } from "react-icons/bs";
|
||||
import { CgSoftwareDownload } from "react-icons/cg";
|
||||
import { CiFlag1 } from "react-icons/ci";
|
||||
import {
|
||||
FaCheck,
|
||||
@ -21,6 +22,7 @@ import {
|
||||
LuBrain,
|
||||
LuBrainCircuit,
|
||||
LuChevronDown,
|
||||
LuChevronLeft,
|
||||
LuChevronRight,
|
||||
LuCopy,
|
||||
LuFile,
|
||||
@ -29,7 +31,9 @@ import {
|
||||
} from "react-icons/lu";
|
||||
import {
|
||||
MdAlternateEmail,
|
||||
MdDashboardCustomize,
|
||||
MdDeleteOutline,
|
||||
MdDynamicFeed,
|
||||
MdHistory,
|
||||
MdOutlineModeEditOutline,
|
||||
MdUploadFile,
|
||||
@ -47,12 +51,15 @@ export const iconList: { [name: string]: IconType } = {
|
||||
check: FaCheck,
|
||||
checkCircle: FaCheckCircle,
|
||||
chevronDown: LuChevronDown,
|
||||
chevronLeft: LuChevronLeft,
|
||||
chevronRight: LuChevronRight,
|
||||
close: IoMdClose,
|
||||
copy: LuCopy,
|
||||
custom: MdDashboardCustomize,
|
||||
delete: MdDeleteOutline,
|
||||
edit: MdOutlineModeEditOutline,
|
||||
email: MdAlternateEmail,
|
||||
feed: MdDynamicFeed,
|
||||
file: LuFile,
|
||||
flag: CiFlag1,
|
||||
followUp: IoArrowUpCircleOutline,
|
||||
@ -66,6 +73,7 @@ export const iconList: { [name: string]: IconType } = {
|
||||
redirection: BsArrowRightShort,
|
||||
search: LuSearch,
|
||||
settings: IoSettingsSharp,
|
||||
software: CgSoftwareDownload,
|
||||
star: FaRegStar,
|
||||
unlock: FaUnlock,
|
||||
upload: FiUpload,
|
||||
|
@ -8,7 +8,7 @@ import { useOnboarding } from "./useOnboarding";
|
||||
import { useOnboardingTracker } from "./useOnboardingTracker";
|
||||
import { useToast } from "./useToast";
|
||||
|
||||
import { useBrainCreationContext } from "../components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
|
||||
import { useBrainCreationContext } from "../components/AddBrainModal/brainCreation-provider";
|
||||
import { useKnowledgeToFeedContext } from "../context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
|
||||
import { acceptedFormats } from "../helpers/acceptedFormats";
|
||||
import { cloneFileWithSanitizedName } from "../helpers/cloneFileWithSanitizedName";
|
||||
|
@ -30,3 +30,6 @@ $gold: #b8860b;
|
||||
// ERROR
|
||||
$dangerous-dark: #e30c17;
|
||||
$dangerous: #9b373c;
|
||||
|
||||
// SUCCESS
|
||||
$success: #47a455;
|
||||
|
@ -1,3 +1,4 @@
|
||||
$circle: 50%;
|
||||
$big: 12px;
|
||||
$normal: 5px;
|
||||
$small: 2px;
|
||||
|
@ -24,6 +24,7 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
$tiny: 12px;
|
||||
$small: 14px;
|
||||
$medium: 16px;
|
||||
$large: 18px;
|
||||
|
Loading…
Reference in New Issue
Block a user