diff --git a/backend/routes/subscription_routes.py b/backend/routes/subscription_routes.py index 581a12d1d..ace0eef35 100644 --- a/backend/routes/subscription_routes.py +++ b/backend/routes/subscription_routes.py @@ -205,7 +205,7 @@ def get_user_invitation( detail="You have not been invited to this brain", ) - brain_details = brain_service.get_brain_details(brain_id) + brain_details = brain_service.get_brain_details(brain_id, current_user.id) if brain_details is None: raise HTTPException( diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx index 37477dbd0..cf5f46017 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/KnowledgeToFeed/KnowledgeToFeed.tsx @@ -26,8 +26,9 @@ export const KnowledgeToFeed = ({ const brainsWithUploadRights = formatMinimalBrainsToSelectComponentInput( useMemo( () => - allBrains.filter((brain) => - requiredRolesForUpload.includes(brain.role) + allBrains.filter( + (brain) => + requiredRolesForUpload.includes(brain.role) && !!brain.max_files ), [allBrains] ) diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/QADisplay.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/QADisplay.tsx index 2079c87f0..b5a25a56c 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/QADisplay.tsx +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/QADisplay.tsx @@ -14,6 +14,7 @@ export const QADisplay = ({ content }: QADisplayProps): JSX.Element => { brain_name, prompt_title, metadata, + brain_id, } = content; return ( @@ -31,6 +32,7 @@ export const QADisplay = ({ content }: QADisplayProps): JSX.Element => { text={assistant} brainName={brain_name} promptName={prompt_title} + brainId={brain_id} metadata={metadata} // eslint-disable-line @typescript-eslint/no-unsafe-assignment /> diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.tsx index 81c7d9ac5..eacc9f5a9 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.tsx +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/MessageRow.tsx @@ -19,11 +19,19 @@ type MessageRowProps = { metadata?: { sources?: Source[]; }; + brainId?: string; }; export const MessageRow = React.forwardRef( ( - { speaker, text, brainName, promptName, children }: MessageRowProps, + { + speaker, + text, + brainName, + promptName, + children, + brainId, + }: MessageRowProps, ref: React.Ref ) => { const { handleCopy, isUserSpeaker } = useMessageRow({ @@ -42,7 +50,7 @@ export const MessageRow = React.forwardRef( > {!isUserSpeaker ? (
- +
) : ( diff --git a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx index 58d8217e0..84b7f52c2 100644 --- a/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx +++ b/frontend/app/chat/[chatId]/components/ChatDialogueArea/components/ChatDialogue/components/QADisplay/components/MessageRow/components/QuestionBrain/QuestionBrain.tsx @@ -1,22 +1,51 @@ -import { Fragment } from "react"; +import Image from "next/image"; +import { Fragment, useEffect, useState } from "react"; +import { useBrainApi } from "@/lib/api/brain/useBrainApi"; import Icon from "@/lib/components/ui/Icon/Icon"; import styles from "./QuestionBrain.module.scss"; type QuestionBrainProps = { brainName?: string | null; + brainId?: string; }; export const QuestionBrain = ({ brainName, + brainId, }: QuestionBrainProps): JSX.Element => { + const [brainLogoUrl, setBrainLogoUrl] = useState( + undefined + ); + + const { getBrain } = useBrainApi(); + + const getBrainLogoUrl = async () => { + if (brainId) { + try { + const brain = await getBrain(brainId.toString()); + setBrainLogoUrl(brain?.integration_description?.integration_logo_url); + } catch (error) { + console.error(error); + } + } + }; + + useEffect(() => { + void getBrainLogoUrl(); + }, [brainId]); + if (brainName === undefined || brainName === null) { return ; } return (
- + {brainLogoUrl ? ( + brainLogo + ) : ( + + )} {brainName}
); diff --git a/frontend/app/chat/[chatId]/page.tsx b/frontend/app/chat/[chatId]/page.tsx index 3c7d1ce38..303343952 100644 --- a/frontend/app/chat/[chatId]/page.tsx +++ b/frontend/app/chat/[chatId]/page.tsx @@ -49,6 +49,7 @@ const SelectedChatPage = (): JSX.Element => { setShouldDisplayFeedCard(true); }, iconName: "uploadFile", + hidden: !currentBrain?.max_files, }, { label: "Manage current brain", diff --git a/frontend/app/studio/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx b/frontend/app/studio/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx index 56cf1ed66..3d3de0019 100644 --- a/frontend/app/studio/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx +++ b/frontend/app/studio/[brainId]/components/BrainManagementTabs/BrainManagementTabs.tsx @@ -25,7 +25,6 @@ export const BrainManagementTabs = (): JSX.Element => { isDeleteOrUnsubscribeModalOpened, setIsDeleteOrUnsubscribeModalOpened, hasEditRights, - isPublicBrain, isOwnedByCurrentUser, isDeleteOrUnsubscribeRequestPending, } = useBrainManagementTabs(); @@ -57,14 +56,16 @@ export const BrainManagementTabs = (): JSX.Element => { {t("settings", { ns: "config" })} - {(!isPublicBrain || hasEditRights) && ( + {hasEditRights && ( <> {t("people", { ns: "config" })} - - {knowledgeOrSecretsTabLabel} - + {brain?.brain_type === "doc" && ( + + {knowledgeOrSecretsTabLabel} + + )} )} @@ -76,12 +77,14 @@ export const BrainManagementTabs = (): JSX.Element => { - - - + {brain?.brain_type === "doc" && ( + + + + )}
diff --git a/frontend/app/studio/[brainId]/components/BrainManagementTabs/components/SettingsTab/SettingsTab.tsx b/frontend/app/studio/[brainId]/components/BrainManagementTabs/components/SettingsTab/SettingsTab.tsx index f6ef3f46a..a916d0389 100644 --- a/frontend/app/studio/[brainId]/components/BrainManagementTabs/components/SettingsTab/SettingsTab.tsx +++ b/frontend/app/studio/[brainId]/components/BrainManagementTabs/components/SettingsTab/SettingsTab.tsx @@ -15,6 +15,8 @@ import { usePermissionsController } from "./hooks/usePermissionsController"; import { UsePromptProps } from "./hooks/usePrompt"; import { useSettingsTab } from "./hooks/useSettingsTab"; +import { useBrainFetcher } from "../../hooks/useBrainFetcher"; + type SettingsTabProps = { brainId: UUID; }; @@ -47,6 +49,10 @@ export const SettingsTabContent = ({ brainId, }); + const { brain } = useBrainFetcher({ + brainId, + }); + return ( <>
- - + {brain?.brain_type === "doc" && ( + <> + + + + + )} { const { t } = useTranslation(["translation", "brain", "config"]); - const { - hasEditRights, - isPublicBrain, - isOwnedByCurrentUser, - isDefaultBrain, - isSettingAsDefault, - setAsDefaultBrainHandler, - } = props; + const { hasEditRights, isPublicBrain, isOwnedByCurrentUser } = props; const { register } = useBrainFormState(); - const { brainTypeOptions } = useGeneralInformation(); - return ( <>
@@ -61,21 +47,6 @@ export const GeneralInformation = ( {t("brain:public_brain_label")} )} -
- {hasEditRights && ( - - )} -
@@ -89,15 +60,6 @@ export const GeneralInformation = ( {...register("description")} /> -
- -
); diff --git a/frontend/lib/api/brain/brain.ts b/frontend/lib/api/brain/brain.ts index 833331b41..65bdf1d58 100644 --- a/frontend/lib/api/brain/brain.ts +++ b/frontend/lib/api/brain/brain.ts @@ -11,6 +11,7 @@ import { import { CreateBrainInput, + IntegrationBrains, ListFilesProps, SubscriptionUpdatableProperties, UpdateBrainInput, @@ -155,3 +156,10 @@ export const getDocsFromQuestion = async ( ) ).data.docs; }; + +export const getIntegrationBrains = async ( + axiosInstance: AxiosInstance +): Promise => { + return (await axiosInstance.get(`/brains/integrations`)) + .data; +}; diff --git a/frontend/lib/api/brain/types.ts b/frontend/lib/api/brain/types.ts index c38115ff9..1f80e0d50 100644 --- a/frontend/lib/api/brain/types.ts +++ b/frontend/lib/api/brain/types.ts @@ -51,6 +51,11 @@ export type ApiBrainDefinition = { jq_instructions: string; }; +export type IntegrationSettings = { + integration_id?: string; + settings?: { [x: string]: object | undefined }; +}; + export type CreateBrainInput = { name: string; description: string; @@ -63,6 +68,17 @@ export type CreateBrainInput = { brain_definition?: Omit; brain_secrets_values?: Record; connected_brains_ids?: UUID[]; + integration?: IntegrationSettings; +}; + +export type IntegrationBrains = { + id: UUID; + integration_name: string; + integration_logo_url: string; + connections_settings: Record; + integration_type: "custom" | "sync"; + description: string; + max_files: number; }; export type UpdateBrainInput = Partial; diff --git a/frontend/lib/api/brain/useBrainApi.ts b/frontend/lib/api/brain/useBrainApi.ts index 0951d21a2..455bbf062 100644 --- a/frontend/lib/api/brain/useBrainApi.ts +++ b/frontend/lib/api/brain/useBrainApi.ts @@ -9,6 +9,7 @@ import { getBrainUsers, getDefaultBrain, getDocsFromQuestion, + getIntegrationBrains, getPublicBrains, setAsDefaultBrain, Subscription, @@ -55,5 +56,6 @@ export const useBrainApi = () => { brainId: string, secrets: Record ) => updateBrainSecrets(brainId, secrets, axiosInstance), + getIntegrationBrains: async () => getIntegrationBrains(axiosInstance), }; }; diff --git a/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts b/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts index fcdef125a..7f809b848 100644 --- a/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts +++ b/frontend/lib/api/brain/utils/mapBackendMinimalBrainToMinimalBrain.ts @@ -12,4 +12,6 @@ export const mapBackendMinimalBrainToMinimalBrain = ( status: backendMinimalBrain.status, brain_type: backendMinimalBrain.brain_type, description: backendMinimalBrain.description, + integration_logo_url: backendMinimalBrain.integration_logo_url, + max_files: backendMinimalBrain.max_files, }); diff --git a/frontend/lib/components/AddBrainModal/AddBrainModal.tsx b/frontend/lib/components/AddBrainModal/AddBrainModal.tsx index 183321e83..354fa4080 100644 --- a/frontend/lib/components/AddBrainModal/AddBrainModal.tsx +++ b/frontend/lib/components/AddBrainModal/AddBrainModal.tsx @@ -1,3 +1,4 @@ +import { useEffect } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -6,17 +7,20 @@ import { addBrainDefaultValues } from "@/lib/config/defaultBrainConfig"; 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 { CreateBrainStep } from "./components/CreateBrainStep/CreateBrainStep"; 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 { + isBrainCreationModalOpened, + setIsBrainCreationModalOpened, + setCurrentIntegrationBrain, + } = useBrainCreationContext(); const defaultValues: CreateBrainProps = { ...addBrainDefaultValues, @@ -28,6 +32,10 @@ export const AddBrainModal = (): JSX.Element => { defaultValues, }); + useEffect(() => { + setCurrentIntegrationBrain(undefined); + }, [isBrainCreationModalOpened]); + return ( {
- +
diff --git a/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx b/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx index d19c2b5e5..c32e2e8c0 100644 --- a/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx +++ b/frontend/lib/components/AddBrainModal/brainCreation-provider.tsx @@ -1,10 +1,16 @@ import { createContext, useContext, useState } from "react"; +import { IntegrationBrains } from "@/lib/api/brain/types"; + interface BrainCreationContextProps { isBrainCreationModalOpened: boolean; setIsBrainCreationModalOpened: React.Dispatch>; creating: boolean; setCreating: React.Dispatch>; + currentIntegrationBrain: IntegrationBrains | undefined; + setCurrentIntegrationBrain: React.Dispatch< + React.SetStateAction + >; } export const BrainCreationContext = createContext< @@ -18,6 +24,8 @@ export const BrainCreationProvider = ({ }): JSX.Element => { const [isBrainCreationModalOpened, setIsBrainCreationModalOpened] = useState(false); + const [currentIntegrationBrain, setCurrentIntegrationBrain] = + useState(); const [creating, setCreating] = useState(false); return ( @@ -27,6 +35,8 @@ export const BrainCreationProvider = ({ setIsBrainCreationModalOpened, creating, setCreating, + currentIntegrationBrain, + setCurrentIntegrationBrain, }} > {children} diff --git a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/Components/ConnectableBrain/ConnectableBrain.tsx b/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/Components/ConnectableBrain/ConnectableBrain.tsx deleted file mode 100644 index fc97513bc..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/Components/ConnectableBrain/ConnectableBrain.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Checkbox } from "@/lib/components/ui/Checkbox"; -import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; - -import { useConnectableBrain } from "./hooks/useConnectableBrain"; - -type ConnectableBrainProps = { - brain: MinimalBrainForUser; -}; - -export const ConnectableBrain = ({ - brain, -}: ConnectableBrainProps): JSX.Element => { - const { onCheckedChange } = useConnectableBrain(); - - return ( -
- - onCheckedChange({ - brainId: brain.id, - checked, - }) - } - id={`connected_brains_ids-${brain.id}`} - /> - -
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/Components/ConnectableBrain/hooks/useConnectableBrain.ts b/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/Components/ConnectableBrain/hooks/useConnectableBrain.ts deleted file mode 100644 index 9127038f1..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/Components/ConnectableBrain/hooks/useConnectableBrain.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CheckedState } from "@radix-ui/react-checkbox"; -import { UUID } from "crypto"; -import { useFormContext } from "react-hook-form"; - -import { CreateBrainProps } from "@/lib/components/AddBrainModal/types/types"; - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const useConnectableBrain = () => { - const { setValue, getValues } = useFormContext(); - - const onCheckedChange = ({ - checked, - brainId, - }: { - checked: CheckedState; - brainId: UUID; - }) => { - if (checked === "indeterminate") { - return; - } - const connected_brains_ids = getValues("connected_brains_ids") ?? []; - if (checked) { - setValue("connected_brains_ids", [...connected_brains_ids, brainId]); - } else { - setValue( - "connected_brains_ids", - connected_brains_ids.filter((id) => id !== brainId) - ); - } - }; - - return { - onCheckedChange, - }; -}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/CompositeBrainConnections.tsx b/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/CompositeBrainConnections.tsx deleted file mode 100644 index 91bffefe0..000000000 --- a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/components/CompositeBrainConnections/CompositeBrainConnections.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useTranslation } from "react-i18next"; - -import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; - -import { ConnectableBrain } from "./Components/ConnectableBrain/ConnectableBrain"; - -export const CompositeBrainConnections = (): JSX.Element => { - const { allBrains } = useBrainContext(); - const sortedBrains = allBrains.sort((a, b) => a.name.localeCompare(b.name)); - const { t } = useTranslation("brain"); - - return ( -
-

- {t("composite_brain_composition_invitation")} -

-
- {sortedBrains.map((brain) => ( - - ))} -
-
- ); -}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelection/BrainTypeSelection.module.scss b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelection/BrainTypeSelection.module.scss index f9bd8143d..35b16ed5d 100644 --- a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelection/BrainTypeSelection.module.scss +++ b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelection/BrainTypeSelection.module.scss @@ -10,7 +10,7 @@ padding: Spacings.$spacing05; border-radius: Radius.$big; cursor: pointer; - border: 2px solid transparent; + border: 1px solid Colors.$lightest-black; &.disabled { pointer-events: none; @@ -35,6 +35,7 @@ &:hover, &.selected { border-color: Colors.$primary; + background-color: Colors.$primary-lightest; .name { color: Colors.$primary; diff --git a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.module.scss b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.module.scss index 90cc0bdf1..0f0f20629 100644 --- a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.module.scss +++ b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.module.scss @@ -10,6 +10,8 @@ padding-inline: Spacings.$spacing08; height: 100%; gap: Spacings.$spacing05; + overflow-y: hidden; + overflow-x: visible; @media (max-width: ScreenSizes.$small) { padding-inline: 0; @@ -19,13 +21,22 @@ display: flex; flex-direction: column; gap: Spacings.$spacing05; + padding-top: Spacings.$spacing03; + overflow-y: scroll; + overflow-x: visible; .title { @include Typography.H2; } } - .button { + .buttons_wrapper { align-self: flex-end; + + &.two_buttons { + display: flex; + justify-content: space-between; + align-self: normal; + } } } diff --git a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.tsx b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.tsx index 114d8dbde..48e7da18d 100644 --- a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.tsx +++ b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/BrainTypeSelectionStep.tsx @@ -1,16 +1,37 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; +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 styles from "./BrainTypeSelectionStep.module.scss"; +import { CustomBrainList } from "./CustomBrainList/CustomBrainList"; +import { useBrainCreationContext } from "../../brainCreation-provider"; import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps"; export const BrainTypeSelectionStep = (): JSX.Element => { const [selectedIndex, setSelectedIndex] = useState(0); + const [customBrainsCatalogueOpened, setCustomBrainsCatalogueOpened] = + useState(false); + const [customBrains, setCustomBrains] = useState([]); const { goToNextStep, currentStepIndex } = useBrainCreationSteps(); + const { getIntegrationBrains } = useBrainApi(); + const { currentIntegrationBrain } = useBrainCreationContext(); + + useEffect(() => { + getIntegrationBrains() + .then((response) => { + setCustomBrains( + response.filter((brain) => brain.integration_type === "custom") + ); + }) + .catch((error) => { + console.error(error); + }); + }, []); const brainTypes: BrainType[] = [ { @@ -18,6 +39,15 @@ export const BrainTypeSelectionStep = (): JSX.Element => { 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: @@ -25,13 +55,6 @@ export const BrainTypeSelectionStep = (): JSX.Element => { 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 => { @@ -45,23 +68,53 @@ export const BrainTypeSelectionStep = (): JSX.Element => { return (
- Choose a type of brain - {brainTypes.map((brainType, index) => ( -
- setSelectedIndex(index)} - /> -
- ))} + {customBrainsCatalogueOpened ? ( + + ) : ( + <> + Choose a type of brain + {brainTypes.map((brainType, index) => ( +
+ { + setSelectedIndex(index); + if (brainType.onClick) { + brainType.onClick(); + } + }} + /> +
+ ))} + + )}
-
+
+ {customBrainsCatalogueOpened && ( + { + setCustomBrainsCatalogueOpened(false); + setSelectedIndex(-1); + }} + /> + )} next()} + disabled={ + selectedIndex === -1 || + (!!customBrainsCatalogueOpened && !currentIntegrationBrain) + } />
diff --git a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/CustomBrainList/CustomBrainList.module.scss b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/CustomBrainList/CustomBrainList.module.scss new file mode 100644 index 000000000..e5b697d43 --- /dev/null +++ b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/CustomBrainList/CustomBrainList.module.scss @@ -0,0 +1,44 @@ +@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; + } + } + } +} diff --git a/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/CustomBrainList/CustomBrainList.tsx b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/CustomBrainList/CustomBrainList.tsx new file mode 100644 index 000000000..997f5d6ff --- /dev/null +++ b/frontend/lib/components/AddBrainModal/components/BrainTypeSelectionStep/CustomBrainList/CustomBrainList.tsx @@ -0,0 +1,53 @@ +import Image from "next/image"; + +import { IntegrationBrains } from "@/lib/api/brain/types"; +import { MessageInfoBox } from "@/lib/components/ui/MessageInfoBox/MessageInfoBox"; +import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; + +import styles from "./CustomBrainList.module.scss"; + +import { useBrainCreationContext } from "../../../brainCreation-provider"; + +export const CustomBrainList = ({ + customBrainList, +}: { + customBrainList: IntegrationBrains[]; +}): JSX.Element => { + const { setCurrentIntegrationBrain, currentIntegrationBrain } = + useBrainCreationContext(); + + return ( +
+ + Choose a custom brain +
+ {customBrainList.map((brain) => { + return ( +
setCurrentIntegrationBrain(brain)} + > + +
+ {brain.integration_name} + + {brain.integration_name} + +
+
+
+ ); + })} +
+
+ ); +}; diff --git a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/BrainKnowledgeStep.module.scss b/frontend/lib/components/AddBrainModal/components/CreateBrainStep/CreateBrainStep.module.scss similarity index 100% rename from frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/BrainKnowledgeStep.module.scss rename to frontend/lib/components/AddBrainModal/components/CreateBrainStep/CreateBrainStep.module.scss diff --git a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/BrainKnowledgeStep.tsx b/frontend/lib/components/AddBrainModal/components/CreateBrainStep/CreateBrainStep.tsx similarity index 65% rename from frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/BrainKnowledgeStep.tsx rename to frontend/lib/components/AddBrainModal/components/CreateBrainStep/CreateBrainStep.tsx index 81c4e01e7..04cd7b5c1 100644 --- a/frontend/lib/components/AddBrainModal/components/BrainKnowledgeStep/BrainKnowledgeStep.tsx +++ b/frontend/lib/components/AddBrainModal/components/CreateBrainStep/CreateBrainStep.tsx @@ -1,16 +1,18 @@ 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 styles from "./BrainKnowledgeStep.module.scss"; +import styles from "./CreateBrainStep.module.scss"; import { useBrainCreationApi } from "./hooks/useBrainCreationApi"; import { useBrainCreationContext } from "../../brainCreation-provider"; import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps"; -export const BrainKnowledgeStep = (): JSX.Element => { +export const CreateBrainStep = (): JSX.Element => { const { currentStepIndex, goToPreviousStep } = useBrainCreationSteps(); const { createBrain } = useBrainCreationApi(); - const { creating, setCreating } = useBrainCreationContext(); + const { creating, setCreating, currentIntegrationBrain } = + useBrainCreationContext(); const previous = (): void => { goToPreviousStep(); @@ -27,10 +29,17 @@ export const BrainKnowledgeStep = (): JSX.Element => { return (
-
- Feed your brain - -
+ {!currentIntegrationBrain ? ( +
+ Feed your brain + +
+ ) : ( + + )}
{ const { setKnowledgeToFeed } = useKnowledgeToFeedContext(); const { createBrain: createBrainApi, setCurrentBrainId } = useBrainContext(); const { crawlWebsiteHandler, uploadFileHandler } = useKnowledgeToFeedInput(); - const { setIsBrainCreationModalOpened, setCreating } = - useBrainCreationContext(); + const { + setIsBrainCreationModalOpened, + setCreating, + currentIntegrationBrain, + } = useBrainCreationContext(); const handleFeedBrain = async (brainId: UUID): Promise => { const uploadPromises = files.map((file) => @@ -37,26 +41,21 @@ export const useBrainCreationApi = () => { }; const createBrain = async (): Promise => { - const { - name, - description, - brain_definition, - brain_secrets_values, - status, - brain_type, - connected_brains_ids, - } = getValues(); + const { name, description } = getValues(); + let integrationSettings: IntegrationSettings | undefined = undefined; + + if (currentIntegrationBrain) { + integrationSettings = { + integration_id: currentIntegrationBrain.id, + settings: {}, + }; + } const createdBrainId = await createBrainApi({ + brain_type: currentIntegrationBrain ? "integration" : "doc", name, description, - status, - brain_type, - brain_definition: brain_type === "api" ? brain_definition : undefined, - brain_secrets_values: - brain_type === "api" ? brain_secrets_values : undefined, - connected_brains_ids: - brain_type === "composite" ? connected_brains_ids : undefined, + integration: integrationSettings, }); if (createdBrainId === undefined) { @@ -67,9 +66,8 @@ export const useBrainCreationApi = () => { return; } - if (brain_type === "doc") { - void handleFeedBrain(createdBrainId); - } + + void handleFeedBrain(createdBrainId); setCurrentBrainId(createdBrainId); setIsBrainCreationModalOpened(false); diff --git a/frontend/lib/components/CurrentBrain/CurrentBrain.tsx b/frontend/lib/components/CurrentBrain/CurrentBrain.tsx index bcd5cc1d8..052752732 100644 --- a/frontend/lib/components/CurrentBrain/CurrentBrain.tsx +++ b/frontend/lib/components/CurrentBrain/CurrentBrain.tsx @@ -1,3 +1,5 @@ +import Image from "next/image"; + import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import styles from "./CurrentBrain.module.scss"; @@ -27,7 +29,16 @@ export const CurrentBrain = ({
Talking to
- + {currentBrain.integration_logo_url ? ( + brain + ) : ( + + )} {currentBrain.name}
diff --git a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx index db193e825..702077c65 100644 --- a/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx +++ b/frontend/lib/components/KnowledgeToFeedInput/components/FeedItems/components/FeedTitleDisplayer/FeedTitleDisplayer.tsx @@ -1,4 +1,4 @@ -import Tooltip from "@/lib/components/ui/Tooltip"; +import Tooltip from "@/lib/components/ui/Tooltip/Tooltip"; import { enhanceUrlDisplay } from "./utils/enhanceUrlDisplay"; import { removeFileExtension } from "./utils/removeFileExtension"; diff --git a/frontend/lib/components/PageHeader/PageHeader.tsx b/frontend/lib/components/PageHeader/PageHeader.tsx index 8632c464e..fe78447a7 100644 --- a/frontend/lib/components/PageHeader/PageHeader.tsx +++ b/frontend/lib/components/PageHeader/PageHeader.tsx @@ -33,6 +33,7 @@ export const PageHeader = ({ onClick={button.onClick} color={button.color} iconName={button.iconName} + hidden={button.hidden} /> ))}
diff --git a/frontend/lib/components/ui/Ellipsis.tsx b/frontend/lib/components/ui/Ellipsis.tsx index e5474d3f8..256831095 100644 --- a/frontend/lib/components/ui/Ellipsis.tsx +++ b/frontend/lib/components/ui/Ellipsis.tsx @@ -4,7 +4,7 @@ import { HTMLAttributes } from "react"; import { cn } from "@/lib/utils"; -import Tooltip from "./Tooltip"; +import Tooltip from "./Tooltip/Tooltip"; interface EllipsisProps extends HTMLAttributes { children: string; diff --git a/frontend/lib/components/ui/Icon/Icon.module.scss b/frontend/lib/components/ui/Icon/Icon.module.scss index 6419708c7..95dabdf29 100644 --- a/frontend/lib/components/ui/Icon/Icon.module.scss +++ b/frontend/lib/components/ui/Icon/Icon.module.scss @@ -69,6 +69,10 @@ } } +.success { + color: Colors.$success; +} + .disabled { color: Colors.$black; pointer-events: none; diff --git a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss new file mode 100644 index 000000000..a10fa2705 --- /dev/null +++ b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.module.scss @@ -0,0 +1,18 @@ +@use "@/styles/Colors.module.scss"; +@use "@/styles/Radius.module.scss"; +@use "@/styles/Spacings.module.scss"; + +.message_info_box_wrapper { + padding: Spacings.$spacing03; + display: flex; + align-items: center; + gap: Spacings.$spacing03; + width: 100%; + border: 1px solid Colors.$normal-grey; + color: Colors.$black; + border-radius: Radius.$normal; + + &.success { + border-color: Colors.$success; + } +} diff --git a/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx new file mode 100644 index 000000000..099aff55a --- /dev/null +++ b/frontend/lib/components/ui/MessageInfoBox/MessageInfoBox.tsx @@ -0,0 +1,41 @@ +import { iconList } from "@/lib/helpers/iconList"; +import { Color } from "@/lib/types/Colors"; + +import styles from "./MessageInfoBox.module.scss"; + +import { Icon } from "../Icon/Icon"; + +export type MessageInfoBoxProps = { + content: string; + type: "info" | "success" | "warning" | "error"; +}; + +export const MessageInfoBox = ({ + content, + type, +}: MessageInfoBoxProps): JSX.Element => { + const getIconProps = (): { + iconName: keyof typeof iconList; + iconColor: Color; + } => { + switch (type) { + case "info": + return { iconName: "info", iconColor: "grey" }; + case "success": + return { iconName: "check", iconColor: "success" }; + default: + return { iconName: "info", iconColor: "grey" }; + } + }; + + return ( +
+ + {content} +
+ ); +}; diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss index 574a17263..32068fbe9 100644 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss +++ b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss @@ -15,6 +15,10 @@ display: flex; width: fit-content; + &.hidden { + display: none; + } + &.primary { border-color: Colors.$primary; color: Colors.$primary; diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx index da058bd43..763f8d78f 100644 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx +++ b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx @@ -14,6 +14,7 @@ export const QuivrButton = ({ isLoading, iconName, disabled, + hidden, }: ButtonType): JSX.Element => { const [hovered, setHovered] = useState(false); @@ -23,6 +24,7 @@ export const QuivrButton = ({ ${styles.button_wrapper} ${styles[color]} ${disabled ? styles.disabled : ""} + ${hidden ? styles.hidden : ""} `} // eslint-disable-next-line @typescript-eslint/no-misused-promises onClick={() => onClick()} diff --git a/frontend/lib/components/ui/Tooltip/Tooltip.module.scss b/frontend/lib/components/ui/Tooltip/Tooltip.module.scss new file mode 100644 index 000000000..de95790f2 --- /dev/null +++ b/frontend/lib/components/ui/Tooltip/Tooltip.module.scss @@ -0,0 +1,13 @@ +@use "@/styles/Colors.module.scss"; +@use "@/styles/Radius.module.scss"; +@use "@/styles/Spacings.module.scss"; +@use "@/styles/Typography.module.scss"; +@use "@/styles/ZIndexes.module.scss"; + +.tooltip_content_wrapper { + z-index: ZIndexes.$tooltip; + background-color: Colors.$lightest-black; + padding: Spacings.$spacing03; + border-radius: Radius.$normal; + font-size: Typography.$small; +} diff --git a/frontend/lib/components/ui/Tooltip.tsx b/frontend/lib/components/ui/Tooltip/Tooltip.tsx similarity index 80% rename from frontend/lib/components/ui/Tooltip.tsx rename to frontend/lib/components/ui/Tooltip/Tooltip.tsx index 43c4206e2..6d14c7883 100644 --- a/frontend/lib/components/ui/Tooltip.tsx +++ b/frontend/lib/components/ui/Tooltip/Tooltip.tsx @@ -3,6 +3,8 @@ import * as TooltipPrimitive from "@radix-ui/react-tooltip"; import { AnimatePresence, motion } from "framer-motion"; import { ReactNode, useState } from "react"; +import styles from "./Tooltip.module.scss"; + interface TooltipProps { children?: ReactNode; tooltip?: ReactNode; @@ -31,11 +33,9 @@ const Tooltip = ({ children, tooltip }: TooltipProps): JSX.Element => { opacity: 0, transition: { ease: "easeIn", duration: 0.1 }, }} - // transition={{ duration: 0.2, ease: "circInOut" }} - className="select-none rounded-md border border-black/10 dark:border-white/25 bg-white dark:bg-gray-800 px-5 py-3 text-sm leading-none shadow-lg dark:shadow-primary/25" + className={styles.tooltip_content_wrapper} > {tooltip} - diff --git a/frontend/lib/config/defaultBrainConfig.ts b/frontend/lib/config/defaultBrainConfig.ts index b46e1da8c..260bdb48f 100644 --- a/frontend/lib/config/defaultBrainConfig.ts +++ b/frontend/lib/config/defaultBrainConfig.ts @@ -26,6 +26,7 @@ export const addBrainDefaultValues: CreateBrainInput = { jq_instructions: "", }, connected_brains_ids: [], + integration: undefined, }; export const defaultModel: Model = "gpt-3.5-turbo"; diff --git a/frontend/lib/context/BrainProvider/types.ts b/frontend/lib/context/BrainProvider/types.ts index 84bd83624..80c91bbea 100644 --- a/frontend/lib/context/BrainProvider/types.ts +++ b/frontend/lib/context/BrainProvider/types.ts @@ -10,6 +10,16 @@ import { BrainType, Model } from "../../types/BrainConfig"; export type BrainAccessStatus = "private" | "public"; +export type IntegrationDescription = { + connection_settings?: object; + description: string; + id: UUID; + integration_logo_url: string; + integration_name: string; + integration_type: "custom" | "sync"; + max_files: number; +}; + export type Brain = { id: UUID; name: string; @@ -22,6 +32,7 @@ export type Brain = { prompt_id?: string | null; brain_type?: BrainType; brain_definition?: ApiBrainDefinition; + integration_description?: IntegrationDescription; }; export type MinimalBrainForUser = { @@ -31,6 +42,8 @@ export type MinimalBrainForUser = { status: BrainAccessStatus; brain_type: BrainType; description: string; + integration_logo_url?: string; + max_files: number; }; //TODO: rename rights to role in Backend and use MinimalBrainForUser instead of BackendMinimalBrainForUser @@ -44,7 +57,7 @@ export type PublicBrain = { description?: string; number_of_subscribers: number; last_update: string; - brain_type: BrainType; + brain_type?: BrainType; brain_definition?: ApiBrainDefinition; }; diff --git a/frontend/lib/helpers/iconList.ts b/frontend/lib/helpers/iconList.ts index 7d510198f..2908dd422 100644 --- a/frontend/lib/helpers/iconList.ts +++ b/frontend/lib/helpers/iconList.ts @@ -10,8 +10,14 @@ import { FaRegUserCircle, FaUnlock, } from "react-icons/fa"; +import { FaInfo } from "react-icons/fa6"; import { FiUpload } from "react-icons/fi"; -import { IoIosAdd, IoMdClose, IoMdLogOut } from "react-icons/io"; +import { + IoIosAdd, + IoIosHelpCircleOutline, + IoMdClose, + IoMdLogOut, +} from "react-icons/io"; import { IoArrowUpCircleOutline, IoHomeOutline, @@ -66,8 +72,10 @@ export const iconList: { [name: string]: IconType } = { followUp: IoArrowUpCircleOutline, graph: VscGraph, hastag: RiHashtag, + help: IoIosHelpCircleOutline, history: MdHistory, home: IoHomeOutline, + info: FaInfo, key: FaKey, loader: AiOutlineLoading3Quarters, logout: IoMdLogOut, diff --git a/frontend/lib/types/BrainConfig.ts b/frontend/lib/types/BrainConfig.ts index 80668a71e..84231d8e5 100644 --- a/frontend/lib/types/BrainConfig.ts +++ b/frontend/lib/types/BrainConfig.ts @@ -4,12 +4,12 @@ import { ApiBrainDefinition } from "../api/brain/types"; export const brainStatuses = ["private", "public"] as const; -export type BrainStatus = (typeof brainStatuses)[number]; - -export const brainTypes = ["doc", "api", "composite"] as const; +export const brainTypes = ["doc", "api", "composite", "integration"] as const; export type BrainType = (typeof brainTypes)[number]; +export type BrainStatus = (typeof brainStatuses)[number]; + export type Model = (typeof freeModels)[number]; // TODO: update this type to match the backend (antropic, openai and some other keys should be removed) diff --git a/frontend/lib/types/Colors.ts b/frontend/lib/types/Colors.ts index 25b75d01c..b0450d985 100644 --- a/frontend/lib/types/Colors.ts +++ b/frontend/lib/types/Colors.ts @@ -6,4 +6,5 @@ export type Color = | "gold" | "accent" | "white" - | "dangerous"; + | "dangerous" + | "success"; diff --git a/frontend/lib/types/QuivrButton.ts b/frontend/lib/types/QuivrButton.ts index 1b858f160..10cdd9522 100644 --- a/frontend/lib/types/QuivrButton.ts +++ b/frontend/lib/types/QuivrButton.ts @@ -9,4 +9,5 @@ export interface ButtonType { iconName: keyof typeof iconList; onClick: () => void | Promise; disabled?: boolean; + hidden?: boolean; } diff --git a/frontend/styles/_ZIndexes.module.scss b/frontend/styles/_ZIndexes.module.scss index 62590654a..08f5ab7ac 100644 --- a/frontend/styles/_ZIndexes.module.scss +++ b/frontend/styles/_ZIndexes.module.scss @@ -1,3 +1,4 @@ $base: 1000; $overlay: 1010; $modal: 1020; +$tooltip: 1030;