mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-14 17:03:29 +03:00
feat(frontend): brain Catalogue (#2303)
# 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
c19f443e7f
commit
4efa346a66
@ -10,6 +10,15 @@ class IntegrationType(str, Enum):
|
||||
SYNC = "sync"
|
||||
DOC = "doc"
|
||||
|
||||
class IntegrationBrainTag(str, Enum):
|
||||
NEW = "new"
|
||||
RECOMMENDED = "recommended"
|
||||
MOST_POPULAR = "most_popular"
|
||||
PREMIUM = "premium"
|
||||
COMING_SOON = "coming_soon"
|
||||
COMMUNITY = "community"
|
||||
DEPRECATED = "deprecated"
|
||||
|
||||
|
||||
class IntegrationDescriptionEntity(BaseModel):
|
||||
id: UUID
|
||||
@ -17,8 +26,11 @@ class IntegrationDescriptionEntity(BaseModel):
|
||||
integration_logo_url: Optional[str] = None
|
||||
connection_settings: Optional[dict] = None
|
||||
integration_type: IntegrationType
|
||||
tags: Optional[list[IntegrationBrainTag]] = []
|
||||
information: Optional[str] = None
|
||||
description: str
|
||||
max_files: int
|
||||
allow_model_change: bool
|
||||
|
||||
|
||||
class IntegrationEntity(BaseModel):
|
||||
|
@ -59,7 +59,7 @@ export const SettingsTabContent = ({
|
||||
<div className={styles.general_information}>
|
||||
<GeneralInformation hasEditRights={hasEditRights} />
|
||||
</div>
|
||||
{brain?.brain_type === "doc" && (
|
||||
{brain?.integration_description?.allow_model_change && (
|
||||
<div className={styles.model_information}>
|
||||
<ModelSelection
|
||||
accessibleModels={accessibleModels}
|
||||
|
@ -53,7 +53,7 @@ export type ApiBrainDefinition = {
|
||||
|
||||
export type IntegrationSettings = {
|
||||
integration_id?: string;
|
||||
settings?: { [x: string]: object | undefined };
|
||||
settings?: { [x: string]: string | undefined };
|
||||
};
|
||||
|
||||
export type CreateBrainInput = {
|
||||
@ -71,14 +71,26 @@ export type CreateBrainInput = {
|
||||
integration?: IntegrationSettings;
|
||||
};
|
||||
|
||||
enum IntegrationBrainTag {
|
||||
NEW = "new",
|
||||
RECOMMENDED = "recommended",
|
||||
MOST_POPULAR = "most_popular",
|
||||
PREMIUM = "premium",
|
||||
COMING_SOON = "coming_soon",
|
||||
COMMUNITY = "community",
|
||||
DEPRECATED = "deprecated",
|
||||
}
|
||||
|
||||
export type IntegrationBrains = {
|
||||
id: UUID;
|
||||
integration_name: string;
|
||||
integration_logo_url: string;
|
||||
connections_settings: Record<string, unknown>;
|
||||
connection_settings: string;
|
||||
integration_type: "custom" | "sync";
|
||||
description: string;
|
||||
max_files: number;
|
||||
tags: IntegrationBrainTag[];
|
||||
information: string;
|
||||
};
|
||||
|
||||
export type UpdateBrainInput = Partial<CreateBrainInput>;
|
||||
|
@ -14,4 +14,5 @@ export const mapBackendMinimalBrainToMinimalBrain = (
|
||||
description: backendMinimalBrain.description,
|
||||
integration_logo_url: backendMinimalBrain.integration_logo_url,
|
||||
max_files: backendMinimalBrain.max_files,
|
||||
allow_model_change: backendMinimalBrain.allow_model_change,
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ export const AddBrainModal = (): JSX.Element => {
|
||||
const {
|
||||
isBrainCreationModalOpened,
|
||||
setIsBrainCreationModalOpened,
|
||||
setCurrentIntegrationBrain,
|
||||
setCurrentSelectedBrain,
|
||||
} = useBrainCreationContext();
|
||||
|
||||
const defaultValues: CreateBrainProps = {
|
||||
@ -33,7 +33,7 @@ export const AddBrainModal = (): JSX.Element => {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentIntegrationBrain(undefined);
|
||||
setCurrentSelectedBrain(undefined);
|
||||
}, [isBrainCreationModalOpened]);
|
||||
|
||||
return (
|
||||
|
@ -7,8 +7,8 @@ interface BrainCreationContextProps {
|
||||
setIsBrainCreationModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
creating: boolean;
|
||||
setCreating: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
currentIntegrationBrain: IntegrationBrains | undefined;
|
||||
setCurrentIntegrationBrain: React.Dispatch<
|
||||
currentSelectedBrain: IntegrationBrains | undefined;
|
||||
setCurrentSelectedBrain: React.Dispatch<
|
||||
React.SetStateAction<IntegrationBrains | undefined>
|
||||
>;
|
||||
}
|
||||
@ -24,7 +24,7 @@ export const BrainCreationProvider = ({
|
||||
}): JSX.Element => {
|
||||
const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] =
|
||||
useState<boolean>(false);
|
||||
const [currentIntegrationBrain, setCurrentIntegrationBrain] =
|
||||
const [currentSelectedBrain, setCurrentSelectedBrain] =
|
||||
useState<IntegrationBrains>();
|
||||
const [creating, setCreating] = useState<boolean>(false);
|
||||
|
||||
@ -35,8 +35,8 @@ export const BrainCreationProvider = ({
|
||||
setIsBrainCreationModalOpened,
|
||||
creating,
|
||||
setCreating,
|
||||
currentIntegrationBrain,
|
||||
setCurrentIntegrationBrain,
|
||||
currentSelectedBrain,
|
||||
setCurrentSelectedBrain,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@ -0,0 +1,60 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/ScreenSizes.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.cards_wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
|
||||
.title {
|
||||
@include Typography.H2;
|
||||
}
|
||||
|
||||
.brains_grid {
|
||||
display: flex;
|
||||
gap: Spacings.$spacing03;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.brain_card_container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing02;
|
||||
|
||||
.tag_wrapper {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.brain_card_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: Radius.$normal;
|
||||
gap: Spacings.$spacing03;
|
||||
padding: Spacings.$spacing04;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
width: 120px;
|
||||
|
||||
.brain_title {
|
||||
font-size: Typography.$medium;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
border-color: Colors.$primary;
|
||||
background-color: Colors.$primary-lightest;
|
||||
|
||||
.brain_title {
|
||||
color: Colors.$primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +1,49 @@
|
||||
import { capitalCase } from "change-case";
|
||||
import Image from "next/image";
|
||||
|
||||
import { IntegrationBrains } from "@/lib/api/brain/types";
|
||||
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
||||
import { Tag } from "@/lib/components/ui/Tag/Tag";
|
||||
import Tooltip from "@/lib/components/ui/Tooltip/Tooltip";
|
||||
|
||||
import styles from "./CustomBrainList.module.scss";
|
||||
import styles from "./BrainCatalogue.module.scss";
|
||||
|
||||
import { useBrainCreationContext } from "../../../brainCreation-provider";
|
||||
|
||||
export const CustomBrainList = ({
|
||||
customBrainList,
|
||||
export const BrainCatalogue = ({
|
||||
brains,
|
||||
next,
|
||||
}: {
|
||||
customBrainList: IntegrationBrains[];
|
||||
brains: IntegrationBrains[];
|
||||
next: () => void;
|
||||
}): JSX.Element => {
|
||||
const { setCurrentIntegrationBrain, currentIntegrationBrain } =
|
||||
const { setCurrentSelectedBrain, currentSelectedBrain } =
|
||||
useBrainCreationContext();
|
||||
|
||||
return (
|
||||
<div className={styles.cards_wrapper}>
|
||||
<MessageInfoBox type="info">
|
||||
More custom brains are coming!
|
||||
<span>
|
||||
A Brain is a specialized AI tool designed to interact with specific
|
||||
use cases or data sources.
|
||||
</span>
|
||||
</MessageInfoBox>
|
||||
<span className={styles.title}>Choose a custom brain</span>
|
||||
<div>
|
||||
{customBrainList.map((brain) => {
|
||||
<span className={styles.title}>Choose a brain type</span>
|
||||
<div className={styles.brains_grid}>
|
||||
{brains.map((brain) => {
|
||||
return (
|
||||
<div
|
||||
key={brain.id}
|
||||
onClick={() => setCurrentIntegrationBrain(brain)}
|
||||
className={styles.brain_card_container}
|
||||
onClick={() => {
|
||||
next();
|
||||
setCurrentSelectedBrain(brain);
|
||||
}}
|
||||
>
|
||||
<Tooltip tooltip={brain.description}>
|
||||
<div
|
||||
className={`${styles.brain_card_wrapper} ${
|
||||
currentIntegrationBrain === brain ? styles.selected : ""
|
||||
currentSelectedBrain === brain ? styles.selected : ""
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
@ -44,6 +55,11 @@ export const CustomBrainList = ({
|
||||
<span className={styles.brain_title}>
|
||||
{brain.integration_name}
|
||||
</span>
|
||||
<div className={styles.tag_wrapper}>
|
||||
{brain.tags[0] && (
|
||||
<Tag color="primary" name={capitalCase(brain.tags[0])} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
@ -1,48 +0,0 @@
|
||||
@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: 1px solid Colors.$lightest-black;
|
||||
|
||||
&.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;
|
||||
background-color: Colors.$primary-lightest;
|
||||
|
||||
.name {
|
||||
color: Colors.$primary;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: Colors.$black;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
@ -1,66 +1,34 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { IntegrationBrains } from "@/lib/api/brain/types";
|
||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
||||
import { BrainType } from "@/lib/components/AddBrainModal/types/types";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
|
||||
import { BrainTypeSelection } from "./BrainTypeSelection/BrainTypeSelection";
|
||||
import { BrainCatalogue } from "./BrainCatalogue/BrainCatalogue";
|
||||
import styles from "./BrainTypeSelectionStep.module.scss";
|
||||
import { CustomBrainList } from "./CustomBrainList/CustomBrainList";
|
||||
|
||||
import { useBrainCreationContext } from "../../brainCreation-provider";
|
||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
import { CreateBrainProps } from "../../types/types";
|
||||
|
||||
export const BrainTypeSelectionStep = (): JSX.Element => {
|
||||
const [selectedIndex, setSelectedIndex] = useState<number>(-1);
|
||||
const [customBrainsCatalogueOpened, setCustomBrainsCatalogueOpened] =
|
||||
useState<boolean>(false);
|
||||
const [customBrains, setCustomBrains] = useState<IntegrationBrains[]>([]);
|
||||
const [brains, setBrains] = useState<IntegrationBrains[]>([]);
|
||||
const { goToNextStep, currentStepIndex } = useBrainCreationSteps();
|
||||
const { getIntegrationBrains } = useBrainApi();
|
||||
const { currentIntegrationBrain } = useBrainCreationContext();
|
||||
const { setValue } = useFormContext<CreateBrainProps>();
|
||||
|
||||
useEffect(() => {
|
||||
getIntegrationBrains()
|
||||
.then((response) => {
|
||||
setCustomBrains(
|
||||
response.filter((brain) => brain.integration_type === "custom")
|
||||
);
|
||||
setBrains(response);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
|
||||
setValue("name", "");
|
||||
setValue("description", "");
|
||||
}, []);
|
||||
|
||||
const brainTypes: BrainType[] = [
|
||||
{
|
||||
name: "Core Brain",
|
||||
description: "Upload documents or website links to feed your brain.",
|
||||
iconName: "feed",
|
||||
},
|
||||
{
|
||||
name: "Custom Brain",
|
||||
description:
|
||||
"Explore your databases, converse with your APIs, and much more!",
|
||||
iconName: "custom",
|
||||
onClick: () => {
|
||||
setCustomBrainsCatalogueOpened(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Sync Brain - Coming soon!",
|
||||
description:
|
||||
"Connect to your tools and applications to interact with your data.",
|
||||
iconName: "software",
|
||||
disabled: true,
|
||||
},
|
||||
];
|
||||
|
||||
const next = (): void => {
|
||||
goToNextStep();
|
||||
};
|
||||
|
||||
if (currentStepIndex !== 0) {
|
||||
return <></>;
|
||||
}
|
||||
@ -68,54 +36,7 @@ export const BrainTypeSelectionStep = (): JSX.Element => {
|
||||
return (
|
||||
<div className={styles.brain_types_wrapper}>
|
||||
<div className={styles.main_wrapper}>
|
||||
{customBrainsCatalogueOpened ? (
|
||||
<CustomBrainList customBrainList={customBrains} />
|
||||
) : (
|
||||
<>
|
||||
<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);
|
||||
if (brainType.onClick) {
|
||||
brainType.onClick();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.buttons_wrapper} ${
|
||||
customBrainsCatalogueOpened ? styles.two_buttons : ""
|
||||
}`}
|
||||
>
|
||||
{customBrainsCatalogueOpened && (
|
||||
<QuivrButton
|
||||
label="Type of brain"
|
||||
iconName="chevronLeft"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
setCustomBrainsCatalogueOpened(false);
|
||||
setSelectedIndex(-1);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<QuivrButton
|
||||
label="Next Step"
|
||||
iconName="chevronRight"
|
||||
color="primary"
|
||||
onClick={() => next()}
|
||||
disabled={
|
||||
selectedIndex === -1 ||
|
||||
(!!customBrainsCatalogueOpened && !currentIntegrationBrain)
|
||||
}
|
||||
/>
|
||||
<BrainCatalogue brains={brains} next={goToNextStep} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,44 +0,0 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/ScreenSizes.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.cards_wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
|
||||
.title {
|
||||
@include Typography.H2;
|
||||
}
|
||||
|
||||
.brain_card_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: Radius.$normal;
|
||||
gap: Spacings.$spacing03;
|
||||
padding: Spacings.$spacing04;
|
||||
border: 1px solid Colors.$lightest-black;
|
||||
width: fit-content;
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
|
||||
.brain_title {
|
||||
font-size: Typography.$small;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.selected {
|
||||
border-color: Colors.$primary;
|
||||
background-color: Colors.$primary-lightest;
|
||||
|
||||
.brain_title {
|
||||
color: Colors.$primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,12 @@
|
||||
@include Typography.H2;
|
||||
}
|
||||
|
||||
.settings_wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: Spacings.$spacing05;
|
||||
}
|
||||
|
||||
.message_info_box_wrapper {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { capitalCase } from "change-case";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components";
|
||||
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||
import { TextInput } from "@/lib/components/ui/TextInput/TextInput";
|
||||
|
||||
import styles from "./CreateBrainStep.module.scss";
|
||||
import { useBrainCreationApi } from "./hooks/useBrainCreationApi";
|
||||
@ -10,9 +14,29 @@ import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||
|
||||
export const CreateBrainStep = (): JSX.Element => {
|
||||
const { currentStepIndex, goToPreviousStep } = useBrainCreationSteps();
|
||||
const { createBrain } = useBrainCreationApi();
|
||||
const { creating, setCreating, currentIntegrationBrain } =
|
||||
const { createBrain, fields, setFields } = useBrainCreationApi();
|
||||
const { creating, setCreating, currentSelectedBrain } =
|
||||
useBrainCreationContext();
|
||||
const [createBrainStepIndex, setCreateBrainStepIndex] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSelectedBrain?.connection_settings) {
|
||||
const newFields = Object.entries(
|
||||
currentSelectedBrain.connection_settings
|
||||
).map(([key, type]) => {
|
||||
return { name: key, type, value: "" };
|
||||
});
|
||||
setFields(newFields);
|
||||
}
|
||||
|
||||
setCreateBrainStepIndex(Number(!currentSelectedBrain?.connection_settings));
|
||||
}, [currentSelectedBrain?.connection_settings]);
|
||||
|
||||
const handleInputChange = (name: string, value: string) => {
|
||||
setFields(
|
||||
fields.map((field) => (field.name === name ? { ...field, value } : field))
|
||||
);
|
||||
};
|
||||
|
||||
const previous = (): void => {
|
||||
goToPreviousStep();
|
||||
@ -29,28 +53,48 @@ export const CreateBrainStep = (): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className={styles.brain_knowledge_wrapper}>
|
||||
{!currentIntegrationBrain ? (
|
||||
{!createBrainStepIndex && (
|
||||
<div className={styles.settings_wrapper}>
|
||||
<MessageInfoBox type="warning">
|
||||
{currentSelectedBrain?.information}
|
||||
</MessageInfoBox>
|
||||
{fields.map(({ name, value }) => (
|
||||
<TextInput
|
||||
key={name}
|
||||
inputValue={value}
|
||||
setInputValue={(inputValue) =>
|
||||
handleInputChange(name, inputValue)
|
||||
}
|
||||
label={capitalCase(name)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!!currentSelectedBrain?.max_files && !!createBrainStepIndex && (
|
||||
<div>
|
||||
<span className={styles.title}>Feed your brain</span>
|
||||
<KnowledgeToFeed hideBrainSelector={true} />
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.message_info_box_wrapper}>
|
||||
<MessageInfoBox type="info">
|
||||
<div className={styles.message_content}>
|
||||
Click on
|
||||
<QuivrButton
|
||||
label="Create"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
to finish your brain creation.
|
||||
</div>
|
||||
</MessageInfoBox>
|
||||
</div>
|
||||
)}
|
||||
{!currentSelectedBrain?.max_files &&
|
||||
!currentSelectedBrain?.connection_settings && (
|
||||
<div className={styles.message_info_box_wrapper}>
|
||||
<MessageInfoBox type="info">
|
||||
<div className={styles.message_content}>
|
||||
Click on
|
||||
<QuivrButton
|
||||
label="Create"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
to finish your brain creation.
|
||||
</div>
|
||||
</MessageInfoBox>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={styles.buttons_wrapper}>
|
||||
<QuivrButton
|
||||
label="Previous step"
|
||||
@ -58,13 +102,24 @@ export const CreateBrainStep = (): JSX.Element => {
|
||||
iconName="chevronLeft"
|
||||
onClick={previous}
|
||||
/>
|
||||
<QuivrButton
|
||||
label="Create"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
{(!currentSelectedBrain?.max_files && !createBrainStepIndex) ||
|
||||
createBrainStepIndex ? (
|
||||
<QuivrButton
|
||||
label="Create"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={feed}
|
||||
isLoading={creating}
|
||||
/>
|
||||
) : (
|
||||
<QuivrButton
|
||||
label="Feed your brain"
|
||||
color="primary"
|
||||
iconName="add"
|
||||
onClick={() => setCreateBrainStepIndex(1)}
|
||||
isLoading={creating}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { UUID } from "crypto";
|
||||
import { useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@ -24,11 +25,11 @@ export const useBrainCreationApi = () => {
|
||||
const { setKnowledgeToFeed } = useKnowledgeToFeedContext();
|
||||
const { createBrain: createBrainApi, setCurrentBrainId } = useBrainContext();
|
||||
const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput();
|
||||
const {
|
||||
setIsBrainCreationModalOpened,
|
||||
setCreating,
|
||||
currentIntegrationBrain,
|
||||
} = useBrainCreationContext();
|
||||
const { setIsBrainCreationModalOpened, setCreating, currentSelectedBrain } =
|
||||
useBrainCreationContext();
|
||||
const [fields, setFields] = useState<
|
||||
{ name: string; type: string; value: string }[]
|
||||
>([]);
|
||||
|
||||
const handleFeedBrain = async (brainId: UUID): Promise<void> => {
|
||||
const uploadPromises = files.map((file) =>
|
||||
@ -44,15 +45,19 @@ export const useBrainCreationApi = () => {
|
||||
const { name, description } = getValues();
|
||||
let integrationSettings: IntegrationSettings | undefined = undefined;
|
||||
|
||||
if (currentIntegrationBrain) {
|
||||
if (currentSelectedBrain) {
|
||||
integrationSettings = {
|
||||
integration_id: currentIntegrationBrain.id,
|
||||
settings: {},
|
||||
integration_id: currentSelectedBrain.id,
|
||||
settings: fields.reduce((acc, field) => {
|
||||
acc[field.name] = field.value;
|
||||
|
||||
return acc;
|
||||
}, {} as { [key: string]: string }),
|
||||
};
|
||||
}
|
||||
|
||||
const createdBrainId = await createBrainApi({
|
||||
brain_type: currentIntegrationBrain ? "integration" : "doc",
|
||||
brain_type: currentSelectedBrain ? "integration" : "doc",
|
||||
name,
|
||||
description,
|
||||
integration: integrationSettings,
|
||||
@ -98,5 +103,7 @@ export const useBrainCreationApi = () => {
|
||||
return {
|
||||
createBrain: mutate,
|
||||
isBrainCreationPending,
|
||||
fields,
|
||||
setFields,
|
||||
};
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useEffect } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@ -6,9 +7,12 @@ import {
|
||||
Step,
|
||||
} from "@/lib/components/AddBrainModal/types/types";
|
||||
|
||||
import { useBrainCreationContext } from "../brainCreation-provider";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export const useBrainCreationSteps = () => {
|
||||
const { t } = useTranslation("brain");
|
||||
const { isBrainCreationModalOpened } = useBrainCreationContext();
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
@ -30,6 +34,10 @@ export const useBrainCreationSteps = () => {
|
||||
(step) => step.value === currentStep
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
goToFirstStep();
|
||||
}, [isBrainCreationModalOpened]);
|
||||
|
||||
const goToNextStep = () => {
|
||||
if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) {
|
||||
return;
|
||||
@ -48,6 +56,10 @@ export const useBrainCreationSteps = () => {
|
||||
return setValue("brainCreationStep", previousStep.value);
|
||||
};
|
||||
|
||||
const goToFirstStep = () => {
|
||||
return setValue("brainCreationStep", steps[0].value);
|
||||
};
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
steps,
|
||||
|
20
frontend/lib/components/ui/Tag/Tag.module.scss
Normal file
20
frontend/lib/components/ui/Tag/Tag.module.scss
Normal file
@ -0,0 +1,20 @@
|
||||
@use "@/styles/Colors.module.scss";
|
||||
@use "@/styles/Radius.module.scss";
|
||||
@use "@/styles/Spacings.module.scss";
|
||||
@use "@/styles/Typography.module.scss";
|
||||
|
||||
.tag_wrapper {
|
||||
padding: Spacings.$spacing01;
|
||||
padding-inline: Spacings.$spacing03;
|
||||
border-radius: Radius.$big;
|
||||
width: fit-content;
|
||||
font-size: Typography.$tiny;
|
||||
|
||||
&.primary {
|
||||
background-color: Colors.$primary-light;
|
||||
}
|
||||
|
||||
&.gold {
|
||||
background-color: Colors.$gold;
|
||||
}
|
||||
}
|
16
frontend/lib/components/ui/Tag/Tag.tsx
Normal file
16
frontend/lib/components/ui/Tag/Tag.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Color } from "@/lib/types/Colors";
|
||||
|
||||
import styles from "./Tag.module.scss";
|
||||
|
||||
interface TagProps {
|
||||
name: string;
|
||||
color: Color;
|
||||
}
|
||||
|
||||
export const Tag = (props: TagProps): JSX.Element => {
|
||||
return (
|
||||
<div className={`${styles.tag_wrapper} ${styles[props.color]} `}>
|
||||
{props.name}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -18,6 +18,7 @@ export type IntegrationDescription = {
|
||||
integration_name: string;
|
||||
integration_type: "custom" | "sync";
|
||||
max_files: number;
|
||||
allow_model_change: boolean;
|
||||
};
|
||||
|
||||
export type Brain = {
|
||||
@ -45,6 +46,7 @@ export type MinimalBrainForUser = {
|
||||
description: string;
|
||||
integration_logo_url?: string;
|
||||
max_files: number;
|
||||
allow_model_change: boolean;
|
||||
};
|
||||
|
||||
//TODO: rename rights to role in Backend and use MinimalBrainForUser instead of BackendMinimalBrainForUser
|
||||
|
7
supabase/migrations/20240305225452_tags-integration.sql
Normal file
7
supabase/migrations/20240305225452_tags-integration.sql
Normal file
@ -0,0 +1,7 @@
|
||||
create type "public"."brain_tags" as enum ('new', 'recommended', 'most_popular', 'premium', 'coming_soon', 'community', 'deprecated');
|
||||
|
||||
alter table "public"."integrations" add column "information" text;
|
||||
|
||||
alter table "public"."integrations" add column "tags" brain_tags[];
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
alter table "public"."integrations" add column "allow_model_change" boolean not null default true;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user