mirror of
https://github.com/QuivrHQ/quivr.git
synced 2024-12-15 01:21:48 +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"
|
SYNC = "sync"
|
||||||
DOC = "doc"
|
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):
|
class IntegrationDescriptionEntity(BaseModel):
|
||||||
id: UUID
|
id: UUID
|
||||||
@ -17,8 +26,11 @@ class IntegrationDescriptionEntity(BaseModel):
|
|||||||
integration_logo_url: Optional[str] = None
|
integration_logo_url: Optional[str] = None
|
||||||
connection_settings: Optional[dict] = None
|
connection_settings: Optional[dict] = None
|
||||||
integration_type: IntegrationType
|
integration_type: IntegrationType
|
||||||
|
tags: Optional[list[IntegrationBrainTag]] = []
|
||||||
|
information: Optional[str] = None
|
||||||
description: str
|
description: str
|
||||||
max_files: int
|
max_files: int
|
||||||
|
allow_model_change: bool
|
||||||
|
|
||||||
|
|
||||||
class IntegrationEntity(BaseModel):
|
class IntegrationEntity(BaseModel):
|
||||||
|
@ -59,7 +59,7 @@ export const SettingsTabContent = ({
|
|||||||
<div className={styles.general_information}>
|
<div className={styles.general_information}>
|
||||||
<GeneralInformation hasEditRights={hasEditRights} />
|
<GeneralInformation hasEditRights={hasEditRights} />
|
||||||
</div>
|
</div>
|
||||||
{brain?.brain_type === "doc" && (
|
{brain?.integration_description?.allow_model_change && (
|
||||||
<div className={styles.model_information}>
|
<div className={styles.model_information}>
|
||||||
<ModelSelection
|
<ModelSelection
|
||||||
accessibleModels={accessibleModels}
|
accessibleModels={accessibleModels}
|
||||||
|
@ -53,7 +53,7 @@ export type ApiBrainDefinition = {
|
|||||||
|
|
||||||
export type IntegrationSettings = {
|
export type IntegrationSettings = {
|
||||||
integration_id?: string;
|
integration_id?: string;
|
||||||
settings?: { [x: string]: object | undefined };
|
settings?: { [x: string]: string | undefined };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CreateBrainInput = {
|
export type CreateBrainInput = {
|
||||||
@ -71,14 +71,26 @@ export type CreateBrainInput = {
|
|||||||
integration?: IntegrationSettings;
|
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 = {
|
export type IntegrationBrains = {
|
||||||
id: UUID;
|
id: UUID;
|
||||||
integration_name: string;
|
integration_name: string;
|
||||||
integration_logo_url: string;
|
integration_logo_url: string;
|
||||||
connections_settings: Record<string, unknown>;
|
connection_settings: string;
|
||||||
integration_type: "custom" | "sync";
|
integration_type: "custom" | "sync";
|
||||||
description: string;
|
description: string;
|
||||||
max_files: number;
|
max_files: number;
|
||||||
|
tags: IntegrationBrainTag[];
|
||||||
|
information: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateBrainInput = Partial<CreateBrainInput>;
|
export type UpdateBrainInput = Partial<CreateBrainInput>;
|
||||||
|
@ -14,4 +14,5 @@ export const mapBackendMinimalBrainToMinimalBrain = (
|
|||||||
description: backendMinimalBrain.description,
|
description: backendMinimalBrain.description,
|
||||||
integration_logo_url: backendMinimalBrain.integration_logo_url,
|
integration_logo_url: backendMinimalBrain.integration_logo_url,
|
||||||
max_files: backendMinimalBrain.max_files,
|
max_files: backendMinimalBrain.max_files,
|
||||||
|
allow_model_change: backendMinimalBrain.allow_model_change,
|
||||||
});
|
});
|
||||||
|
@ -19,7 +19,7 @@ export const AddBrainModal = (): JSX.Element => {
|
|||||||
const {
|
const {
|
||||||
isBrainCreationModalOpened,
|
isBrainCreationModalOpened,
|
||||||
setIsBrainCreationModalOpened,
|
setIsBrainCreationModalOpened,
|
||||||
setCurrentIntegrationBrain,
|
setCurrentSelectedBrain,
|
||||||
} = useBrainCreationContext();
|
} = useBrainCreationContext();
|
||||||
|
|
||||||
const defaultValues: CreateBrainProps = {
|
const defaultValues: CreateBrainProps = {
|
||||||
@ -33,7 +33,7 @@ export const AddBrainModal = (): JSX.Element => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentIntegrationBrain(undefined);
|
setCurrentSelectedBrain(undefined);
|
||||||
}, [isBrainCreationModalOpened]);
|
}, [isBrainCreationModalOpened]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,8 +7,8 @@ interface BrainCreationContextProps {
|
|||||||
setIsBrainCreationModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsBrainCreationModalOpened: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
creating: boolean;
|
creating: boolean;
|
||||||
setCreating: React.Dispatch<React.SetStateAction<boolean>>;
|
setCreating: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
currentIntegrationBrain: IntegrationBrains | undefined;
|
currentSelectedBrain: IntegrationBrains | undefined;
|
||||||
setCurrentIntegrationBrain: React.Dispatch<
|
setCurrentSelectedBrain: React.Dispatch<
|
||||||
React.SetStateAction<IntegrationBrains | undefined>
|
React.SetStateAction<IntegrationBrains | undefined>
|
||||||
>;
|
>;
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ export const BrainCreationProvider = ({
|
|||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] =
|
const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [currentIntegrationBrain, setCurrentIntegrationBrain] =
|
const [currentSelectedBrain, setCurrentSelectedBrain] =
|
||||||
useState<IntegrationBrains>();
|
useState<IntegrationBrains>();
|
||||||
const [creating, setCreating] = useState<boolean>(false);
|
const [creating, setCreating] = useState<boolean>(false);
|
||||||
|
|
||||||
@ -35,8 +35,8 @@ export const BrainCreationProvider = ({
|
|||||||
setIsBrainCreationModalOpened,
|
setIsBrainCreationModalOpened,
|
||||||
creating,
|
creating,
|
||||||
setCreating,
|
setCreating,
|
||||||
currentIntegrationBrain,
|
currentSelectedBrain,
|
||||||
setCurrentIntegrationBrain,
|
setCurrentSelectedBrain,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{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 Image from "next/image";
|
||||||
|
|
||||||
import { IntegrationBrains } from "@/lib/api/brain/types";
|
import { IntegrationBrains } from "@/lib/api/brain/types";
|
||||||
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
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 Tooltip from "@/lib/components/ui/Tooltip/Tooltip";
|
||||||
|
|
||||||
import styles from "./CustomBrainList.module.scss";
|
import styles from "./BrainCatalogue.module.scss";
|
||||||
|
|
||||||
import { useBrainCreationContext } from "../../../brainCreation-provider";
|
import { useBrainCreationContext } from "../../../brainCreation-provider";
|
||||||
|
|
||||||
export const CustomBrainList = ({
|
export const BrainCatalogue = ({
|
||||||
customBrainList,
|
brains,
|
||||||
|
next,
|
||||||
}: {
|
}: {
|
||||||
customBrainList: IntegrationBrains[];
|
brains: IntegrationBrains[];
|
||||||
|
next: () => void;
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const { setCurrentIntegrationBrain, currentIntegrationBrain } =
|
const { setCurrentSelectedBrain, currentSelectedBrain } =
|
||||||
useBrainCreationContext();
|
useBrainCreationContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.cards_wrapper}>
|
<div className={styles.cards_wrapper}>
|
||||||
<MessageInfoBox type="info">
|
<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>
|
</MessageInfoBox>
|
||||||
<span className={styles.title}>Choose a custom brain</span>
|
<span className={styles.title}>Choose a brain type</span>
|
||||||
<div>
|
<div className={styles.brains_grid}>
|
||||||
{customBrainList.map((brain) => {
|
{brains.map((brain) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={brain.id}
|
key={brain.id}
|
||||||
onClick={() => setCurrentIntegrationBrain(brain)}
|
className={styles.brain_card_container}
|
||||||
|
onClick={() => {
|
||||||
|
next();
|
||||||
|
setCurrentSelectedBrain(brain);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip tooltip={brain.description}>
|
<Tooltip tooltip={brain.description}>
|
||||||
<div
|
<div
|
||||||
className={`${styles.brain_card_wrapper} ${
|
className={`${styles.brain_card_wrapper} ${
|
||||||
currentIntegrationBrain === brain ? styles.selected : ""
|
currentSelectedBrain === brain ? styles.selected : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
@ -44,6 +55,11 @@ export const CustomBrainList = ({
|
|||||||
<span className={styles.brain_title}>
|
<span className={styles.brain_title}>
|
||||||
{brain.integration_name}
|
{brain.integration_name}
|
||||||
</span>
|
</span>
|
||||||
|
<div className={styles.tag_wrapper}>
|
||||||
|
{brain.tags[0] && (
|
||||||
|
<Tag color="primary" name={capitalCase(brain.tags[0])} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</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 { useEffect, useState } from "react";
|
||||||
|
import { useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
import { IntegrationBrains } from "@/lib/api/brain/types";
|
import { IntegrationBrains } from "@/lib/api/brain/types";
|
||||||
import { useBrainApi } from "@/lib/api/brain/useBrainApi";
|
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 styles from "./BrainTypeSelectionStep.module.scss";
|
||||||
import { CustomBrainList } from "./CustomBrainList/CustomBrainList";
|
|
||||||
|
|
||||||
import { useBrainCreationContext } from "../../brainCreation-provider";
|
|
||||||
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
||||||
|
import { CreateBrainProps } from "../../types/types";
|
||||||
|
|
||||||
export const BrainTypeSelectionStep = (): JSX.Element => {
|
export const BrainTypeSelectionStep = (): JSX.Element => {
|
||||||
const [selectedIndex, setSelectedIndex] = useState<number>(-1);
|
const [brains, setBrains] = useState<IntegrationBrains[]>([]);
|
||||||
const [customBrainsCatalogueOpened, setCustomBrainsCatalogueOpened] =
|
|
||||||
useState<boolean>(false);
|
|
||||||
const [customBrains, setCustomBrains] = useState<IntegrationBrains[]>([]);
|
|
||||||
const { goToNextStep, currentStepIndex } = useBrainCreationSteps();
|
const { goToNextStep, currentStepIndex } = useBrainCreationSteps();
|
||||||
const { getIntegrationBrains } = useBrainApi();
|
const { getIntegrationBrains } = useBrainApi();
|
||||||
const { currentIntegrationBrain } = useBrainCreationContext();
|
const { setValue } = useFormContext<CreateBrainProps>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getIntegrationBrains()
|
getIntegrationBrains()
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
setCustomBrains(
|
setBrains(response);
|
||||||
response.filter((brain) => brain.integration_type === "custom")
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(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) {
|
if (currentStepIndex !== 0) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
@ -68,54 +36,7 @@ export const BrainTypeSelectionStep = (): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.brain_types_wrapper}>
|
<div className={styles.brain_types_wrapper}>
|
||||||
<div className={styles.main_wrapper}>
|
<div className={styles.main_wrapper}>
|
||||||
{customBrainsCatalogueOpened ? (
|
<BrainCatalogue brains={brains} next={goToNextStep} />
|
||||||
<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)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</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;
|
@include Typography.H2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: Spacings.$spacing05;
|
||||||
|
}
|
||||||
|
|
||||||
.message_info_box_wrapper {
|
.message_info_box_wrapper {
|
||||||
align-self: center;
|
align-self: center;
|
||||||
display: flex;
|
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 { KnowledgeToFeed } from "@/app/chat/[chatId]/components/ActionsBar/components";
|
||||||
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox";
|
||||||
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
import QuivrButton from "@/lib/components/ui/QuivrButton/QuivrButton";
|
||||||
|
import { TextInput } from "@/lib/components/ui/TextInput/TextInput";
|
||||||
|
|
||||||
import styles from "./CreateBrainStep.module.scss";
|
import styles from "./CreateBrainStep.module.scss";
|
||||||
import { useBrainCreationApi } from "./hooks/useBrainCreationApi";
|
import { useBrainCreationApi } from "./hooks/useBrainCreationApi";
|
||||||
@ -10,9 +14,29 @@ import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
|
|||||||
|
|
||||||
export const CreateBrainStep = (): JSX.Element => {
|
export const CreateBrainStep = (): JSX.Element => {
|
||||||
const { currentStepIndex, goToPreviousStep } = useBrainCreationSteps();
|
const { currentStepIndex, goToPreviousStep } = useBrainCreationSteps();
|
||||||
const { createBrain } = useBrainCreationApi();
|
const { createBrain, fields, setFields } = useBrainCreationApi();
|
||||||
const { creating, setCreating, currentIntegrationBrain } =
|
const { creating, setCreating, currentSelectedBrain } =
|
||||||
useBrainCreationContext();
|
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 => {
|
const previous = (): void => {
|
||||||
goToPreviousStep();
|
goToPreviousStep();
|
||||||
@ -29,28 +53,48 @@ export const CreateBrainStep = (): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.brain_knowledge_wrapper}>
|
<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>
|
<div>
|
||||||
<span className={styles.title}>Feed your brain</span>
|
<span className={styles.title}>Feed your brain</span>
|
||||||
<KnowledgeToFeed hideBrainSelector={true} />
|
<KnowledgeToFeed hideBrainSelector={true} />
|
||||||
</div>
|
</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}>
|
<div className={styles.buttons_wrapper}>
|
||||||
<QuivrButton
|
<QuivrButton
|
||||||
label="Previous step"
|
label="Previous step"
|
||||||
@ -58,13 +102,24 @@ export const CreateBrainStep = (): JSX.Element => {
|
|||||||
iconName="chevronLeft"
|
iconName="chevronLeft"
|
||||||
onClick={previous}
|
onClick={previous}
|
||||||
/>
|
/>
|
||||||
<QuivrButton
|
{(!currentSelectedBrain?.max_files && !createBrainStepIndex) ||
|
||||||
label="Create"
|
createBrainStepIndex ? (
|
||||||
color="primary"
|
<QuivrButton
|
||||||
iconName="add"
|
label="Create"
|
||||||
onClick={feed}
|
color="primary"
|
||||||
isLoading={creating}
|
iconName="add"
|
||||||
/>
|
onClick={feed}
|
||||||
|
isLoading={creating}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<QuivrButton
|
||||||
|
label="Feed your brain"
|
||||||
|
color="primary"
|
||||||
|
iconName="add"
|
||||||
|
onClick={() => setCreateBrainStepIndex(1)}
|
||||||
|
isLoading={creating}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import { UUID } from "crypto";
|
import { UUID } from "crypto";
|
||||||
|
import { useState } from "react";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -24,11 +25,11 @@ export const useBrainCreationApi = () => {
|
|||||||
const { setKnowledgeToFeed } = useKnowledgeToFeedContext();
|
const { setKnowledgeToFeed } = useKnowledgeToFeedContext();
|
||||||
const { createBrain: createBrainApi, setCurrentBrainId } = useBrainContext();
|
const { createBrain: createBrainApi, setCurrentBrainId } = useBrainContext();
|
||||||
const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput();
|
const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput();
|
||||||
const {
|
const { setIsBrainCreationModalOpened, setCreating, currentSelectedBrain } =
|
||||||
setIsBrainCreationModalOpened,
|
useBrainCreationContext();
|
||||||
setCreating,
|
const [fields, setFields] = useState<
|
||||||
currentIntegrationBrain,
|
{ name: string; type: string; value: string }[]
|
||||||
} = useBrainCreationContext();
|
>([]);
|
||||||
|
|
||||||
const handleFeedBrain = async (brainId: UUID): Promise<void> => {
|
const handleFeedBrain = async (brainId: UUID): Promise<void> => {
|
||||||
const uploadPromises = files.map((file) =>
|
const uploadPromises = files.map((file) =>
|
||||||
@ -44,15 +45,19 @@ export const useBrainCreationApi = () => {
|
|||||||
const { name, description } = getValues();
|
const { name, description } = getValues();
|
||||||
let integrationSettings: IntegrationSettings | undefined = undefined;
|
let integrationSettings: IntegrationSettings | undefined = undefined;
|
||||||
|
|
||||||
if (currentIntegrationBrain) {
|
if (currentSelectedBrain) {
|
||||||
integrationSettings = {
|
integrationSettings = {
|
||||||
integration_id: currentIntegrationBrain.id,
|
integration_id: currentSelectedBrain.id,
|
||||||
settings: {},
|
settings: fields.reduce((acc, field) => {
|
||||||
|
acc[field.name] = field.value;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {} as { [key: string]: string }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdBrainId = await createBrainApi({
|
const createdBrainId = await createBrainApi({
|
||||||
brain_type: currentIntegrationBrain ? "integration" : "doc",
|
brain_type: currentSelectedBrain ? "integration" : "doc",
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
integration: integrationSettings,
|
integration: integrationSettings,
|
||||||
@ -98,5 +103,7 @@ export const useBrainCreationApi = () => {
|
|||||||
return {
|
return {
|
||||||
createBrain: mutate,
|
createBrain: mutate,
|
||||||
isBrainCreationPending,
|
isBrainCreationPending,
|
||||||
|
fields,
|
||||||
|
setFields,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
import { useFormContext } from "react-hook-form";
|
import { useFormContext } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -6,9 +7,12 @@ import {
|
|||||||
Step,
|
Step,
|
||||||
} from "@/lib/components/AddBrainModal/types/types";
|
} from "@/lib/components/AddBrainModal/types/types";
|
||||||
|
|
||||||
|
import { useBrainCreationContext } from "../brainCreation-provider";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export const useBrainCreationSteps = () => {
|
export const useBrainCreationSteps = () => {
|
||||||
const { t } = useTranslation("brain");
|
const { t } = useTranslation("brain");
|
||||||
|
const { isBrainCreationModalOpened } = useBrainCreationContext();
|
||||||
|
|
||||||
const steps: Step[] = [
|
const steps: Step[] = [
|
||||||
{
|
{
|
||||||
@ -30,6 +34,10 @@ export const useBrainCreationSteps = () => {
|
|||||||
(step) => step.value === currentStep
|
(step) => step.value === currentStep
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
goToFirstStep();
|
||||||
|
}, [isBrainCreationModalOpened]);
|
||||||
|
|
||||||
const goToNextStep = () => {
|
const goToNextStep = () => {
|
||||||
if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) {
|
if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) {
|
||||||
return;
|
return;
|
||||||
@ -48,6 +56,10 @@ export const useBrainCreationSteps = () => {
|
|||||||
return setValue("brainCreationStep", previousStep.value);
|
return setValue("brainCreationStep", previousStep.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goToFirstStep = () => {
|
||||||
|
return setValue("brainCreationStep", steps[0].value);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentStep,
|
currentStep,
|
||||||
steps,
|
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_name: string;
|
||||||
integration_type: "custom" | "sync";
|
integration_type: "custom" | "sync";
|
||||||
max_files: number;
|
max_files: number;
|
||||||
|
allow_model_change: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Brain = {
|
export type Brain = {
|
||||||
@ -45,6 +46,7 @@ export type MinimalBrainForUser = {
|
|||||||
description: string;
|
description: string;
|
||||||
integration_logo_url?: string;
|
integration_logo_url?: string;
|
||||||
max_files: number;
|
max_files: number;
|
||||||
|
allow_model_change: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: rename rights to role in Backend and use MinimalBrainForUser instead of BackendMinimalBrainForUser
|
//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