From b7a081e94f73a181d7b425b4194af1b44f9df6fd Mon Sep 17 00:00:00 2001 From: Antoine Dewez <44063631+Zewed@users.noreply.github.com> Date: Wed, 28 Aug 2024 02:30:48 +0200 Subject: [PATCH] feat(frontend): interaction with brain items (#3106) # 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): --- .../MentionItem/MentionItem.module.scss | 6 +- .../BrainButton/BrainButton.module.scss | 53 ++++++++++++------ .../BrainsList/BrainButton/BrainButton.tsx | 56 +++++++++++++------ .../BrainItem/BrainItem.module.scss | 14 +++-- .../BrainsList/BrainItem/BrainItem.tsx | 41 +++++++++++--- .../KnowledgeTab/KnowledgeTab.module.scss | 7 +++ .../components/KnowledgeTab/KnowledgeTab.tsx | 18 +++--- frontend/app/studio/[brainId]/page.tsx | 13 +++++ .../CurrentBrain/CurrentBrain.module.scss | 6 +- .../PageHeader/PageHeader.module.scss | 2 +- frontend/lib/components/ui/Icon/Icon.tsx | 30 +++++++--- .../ui/OptionsModal/OptionsModal.tsx | 1 + .../ui/QuivrButton/QuivrButton.module.scss | 6 ++ .../components/ui/QuivrButton/QuivrButton.tsx | 3 +- frontend/lib/helpers/iconList.ts | 8 +-- frontend/lib/types/QuivrButton.ts | 2 +- 16 files changed, 191 insertions(+), 75 deletions(-) diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss index 96b5b326c7cc..1b6b98ad95da 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatEditor/Editor/MentionsList/MentionItem/MentionItem.module.scss @@ -25,8 +25,10 @@ } .brain_snippet { - width: 18px; - height: 18px; + min-width: 18px; + min-height: 18px; + max-width: 18px; + max-height: 18px; border-radius: Radius.$normal; display: flex; justify-content: center; diff --git a/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss b/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss index e368cee65b6f..151b7aa382c6 100644 --- a/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss +++ b/frontend/app/search/BrainsList/BrainButton/BrainButton.module.scss @@ -5,7 +5,7 @@ .brain_button_container { display: flex; - padding: Spacings.$spacing03; + padding: Spacings.$spacing04; border: 1px solid var(--border-0); border-radius: Radius.$normal; cursor: pointer; @@ -19,30 +19,47 @@ border-color: var(--primary-0); } + &.not_hovered { + &:hover { + border-color: var(--border-0); + } + } + .header { display: flex; - gap: Spacings.$spacing03; + gap: Spacings.$spacing05; width: 100%; align-items: center; + overflow: hidden; + justify-content: space-between; - .brain_image { - border-radius: Radius.$normal; - } - - .name { - @include Typography.EllipsisOverflow; - font-size: Typography.$small; - font-weight: 500; - } - - .brain_snippet { - width: 24px; - height: 24px; - border-radius: Radius.$normal; + .left { display: flex; - justify-content: center; + gap: Spacings.$spacing03; align-items: center; - font-size: Typography.$tiny; + overflow: hidden; + + .brain_image { + border-radius: Radius.$normal; + } + + .name { + @include Typography.EllipsisOverflow; + font-size: Typography.$small; + font-weight: 500; + } + + .brain_snippet { + min-width: 24px; + min-height: 24px; + max-width: 24px; + max-height: 24px; + border-radius: Radius.$normal; + display: flex; + justify-content: center; + align-items: center; + font-size: Typography.$tiny; + } } } diff --git a/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx b/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx index e0024bfa969b..6e4bccb9163b 100644 --- a/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx +++ b/frontend/app/search/BrainsList/BrainButton/BrainButton.tsx @@ -2,7 +2,9 @@ import { UUID } from "crypto"; import Image from "next/image"; +import { useState } from "react"; +import { Icon } from "@/lib/components/ui/Icon/Icon"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { BrainType } from "@/lib/types/BrainConfig"; @@ -28,36 +30,58 @@ const BrainButton = ({ brainOrModel, newBrain, }: BrainButtonProps): JSX.Element => { + const [optionsHovered, setOptionsHovered] = useState(false); const { setCurrentBrainId } = useBrainContext(); return (
{ setCurrentBrainId(brainOrModel.id); newBrain(); }} >
- {brainOrModel.image_url ? ( - Brain or Model - ) : ( +
+ {brainOrModel.image_url ? ( + Brain or Model + ) : ( +
+ {brainOrModel.snippet_emoji} +
+ )} + + {brainOrModel.display_name ?? brainOrModel.name} + +
+ {brainOrModel.brain_type === "doc" && (
{ + event.stopPropagation(); + window.location.href = `/studio/${brainOrModel.id}`; + }} + onMouseEnter={() => setOptionsHovered(true)} + onMouseLeave={() => setOptionsHovered(false)} > - {brainOrModel.snippet_emoji} +
)} - - {brainOrModel.display_name ?? brainOrModel.name} -
{brainOrModel.description}
diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss index 96b072a46bdb..4638607ae9d9 100644 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss +++ b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.module.scss @@ -7,7 +7,6 @@ .brain_item_wrapper { padding-inline: Spacings.$spacing05; - overflow: hidden; display: flex; flex-direction: column; gap: Spacings.$spacing03; @@ -18,7 +17,6 @@ padding-block: Spacings.$spacing04; position: relative; overflow: visible; - overflow: hidden; height: 100%; box-shadow: BoxShadow.$small; @@ -27,6 +25,12 @@ color: var(--text-3); } + &.not_hovered { + &:hover { + border-color: var(--border-1); + } + } + .brain_header { display: flex; justify-content: space-between; @@ -43,8 +47,10 @@ overflow: hidden; .brain_snippet { - width: 24px; - height: 24px; + min-width: 24px; + min-height: 24px; + max-width: 24px; + max-height: 24px; border-radius: Radius.$normal; display: flex; justify-content: center; diff --git a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx index 04cfdfd6d1bd..984cf78785ca 100644 --- a/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx +++ b/frontend/app/studio/BrainsTabs/components/ManageBrains/BrainsList/BrainItem/BrainItem.tsx @@ -8,6 +8,7 @@ import { Icon } from "@/lib/components/ui/Icon/Icon"; import { OptionsModal } from "@/lib/components/ui/OptionsModal/OptionsModal"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; +import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext"; import { Option } from "@/lib/types/Options"; @@ -20,6 +21,7 @@ type BrainItemProps = { export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { const [optionsOpened, setOptionsOpened] = useState(false); + const [optionsHovered, setOptionsHovered] = useState(false); const { handleUnsubscribeOrDeleteBrain, @@ -27,13 +29,14 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { setIsDeleteOrUnsubscribeModalOpened, isDeleteOrUnsubscribeRequestPending, } = useBrainManagementTabs(brain.id); - const { allBrains } = useBrainContext(); + const { allBrains, setCurrentBrainId } = useBrainContext(); const { isOwnedByCurrentUser } = getBrainPermissions({ brainId: brain.id, userAccessibleBrains: allBrains, }); const { brain: fetchedBrain } = useBrainFetcher({ brainId: brain.id }); const { isDarkMode } = useUserSettingsContext(); + const { setIsVisible } = useSearchModalContext(); const iconRef = useRef(null); const optionsRef = useRef(null); @@ -45,6 +48,16 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { iconName: "edit", iconColor: "primary", }, + { + label: "Talk to Brain", + onClick: () => { + setOptionsOpened(false); + setIsVisible(true); + setTimeout(() => setCurrentBrainId(brain.id)); + }, + iconName: "chat", + iconColor: "primary", + }, { label: "Delete", onClick: () => void setIsDeleteOrUnsubscribeModalOpened(true), @@ -76,7 +89,7 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { @@ -100,12 +113,17 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { }} className={styles.icon} > - +
setOptionsHovered(true)} + onMouseLeave={() => setOptionsHovered(false)} + > + +
@@ -127,7 +145,12 @@ export const BrainItem = ({ brain, even }: BrainItemProps): JSX.Element => { } />
-
+
setOptionsHovered(true)} + onMouseLeave={() => setOptionsHovered(false)} + > {optionsOpened && }
diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss index f8f436593383..3d1f588a6666 100644 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss +++ b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.module.scss @@ -11,5 +11,12 @@ width: 100%; gap: Spacings.$spacing05; padding-block: Spacings.$spacing05; + + .message { + display: inline-flex; + flex-wrap: wrap; + align-items: center; + gap: Spacings.$spacing03; + } } } diff --git a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx index 801ef8cbbd26..1cf5653ad831 100644 --- a/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx +++ b/frontend/app/studio/[brainId]/BrainManagementTabs/components/KnowledgeTab/KnowledgeTab.tsx @@ -49,14 +49,16 @@ export const KnowledgeTab = ({
- This brain is empty! You can add knowledge by clicking on - setShouldDisplayFeedCard(true)} - /> - . +
+ This brain is empty! You can add knowledge by clicking on + setShouldDisplayFeedCard(true)} + /> + . +
diff --git a/frontend/app/studio/[brainId]/page.tsx b/frontend/app/studio/[brainId]/page.tsx index 5572234e1487..93d28e3531be 100644 --- a/frontend/app/studio/[brainId]/page.tsx +++ b/frontend/app/studio/[brainId]/page.tsx @@ -6,6 +6,7 @@ import { PageHeader } from "@/lib/components/PageHeader/PageHeader"; import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; +import { useSearchModalContext } from "@/lib/context/SearchModalProvider/hooks/useSearchModalContext"; import { ButtonType } from "@/lib/types/QuivrButton"; import { BrainManagementTabs } from "./BrainManagementTabs/BrainManagementTabs"; @@ -17,6 +18,7 @@ import styles from "./page.module.scss"; const BrainsManagement = (): JSX.Element => { const { brain } = useBrainManagement(); + const { setIsVisible } = useSearchModalContext(); const { handleUnsubscribeOrDeleteBrain, isDeleteOrUnsubscribeModalOpened, @@ -32,6 +34,17 @@ const BrainsManagement = (): JSX.Element => { const { setCurrentBrainId } = useBrainContext(); const buttons: ButtonType[] = [ + { + label: "Talk to Brain", + color: "primary", + onClick: () => { + if (brain) { + setIsVisible(true); + setTimeout(() => setCurrentBrainId(brain.id)); + } + }, + iconName: "chat", + }, { label: "Add knowledge", color: "primary", diff --git a/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss b/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss index d0dabc4d1a22..ed409dd19671 100644 --- a/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss +++ b/frontend/lib/components/CurrentBrain/CurrentBrain.module.scss @@ -58,8 +58,10 @@ } .brain_snippet { - width: 18px; - height: 18px; + min-width: 18px; + min-height: 18px; + max-width: 18px; + max-height: 18px; border-radius: Radius.$normal; display: flex; justify-content: center; diff --git a/frontend/lib/components/PageHeader/PageHeader.module.scss b/frontend/lib/components/PageHeader/PageHeader.module.scss index 3d686a8cd39f..10e7cd119a67 100644 --- a/frontend/lib/components/PageHeader/PageHeader.module.scss +++ b/frontend/lib/components/PageHeader/PageHeader.module.scss @@ -56,7 +56,7 @@ display: flex; padding: Spacings.$spacing02; position: absolute; - top: Spacings.$spacing04; + top: calc(Spacings.$spacing03 + Spacings.$spacing01); right: Spacings.$spacing03; border-radius: Radius.$circle; cursor: pointer; diff --git a/frontend/lib/components/ui/Icon/Icon.tsx b/frontend/lib/components/ui/Icon/Icon.tsx index 9b0a9d28c90b..077b4822ad4a 100644 --- a/frontend/lib/components/ui/Icon/Icon.tsx +++ b/frontend/lib/components/ui/Icon/Icon.tsx @@ -37,20 +37,32 @@ export const Icon = ({ } }, [hovered, handleHover]); + const handleMouseEnter = (event: React.MouseEvent) => { + if (handleHover) { + event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); + setIconHovered(true); + } + }; + + const handleMouseLeave = () => { + if (handleHover) { + setIconHovered(false); + } + }; + return ( handleHover && setIconHovered(true)} - onMouseLeave={() => handleHover && setIconHovered(false)} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} onClick={onClick} /> ); }; - -export default Icon; diff --git a/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx b/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx index 0a2959691a52..ed0b1221e267 100644 --- a/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx +++ b/frontend/lib/components/ui/OptionsModal/OptionsModal.tsx @@ -44,6 +44,7 @@ export const OptionsModal = ({ options }: OptionsModalProps): JSX.Element => { onClick={(event) => { event.preventDefault(); event.stopPropagation(); + event.nativeEvent.stopImmediatePropagation(); option.onClick(); }} onMouseEnter={() => handleMouseEnter(index)} diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss index 56f228485c3e..17f938639f60 100644 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss +++ b/frontend/lib/components/ui/QuivrButton/QuivrButton.module.scss @@ -35,6 +35,12 @@ display: none; } + @media (max-width: ScreenSizes.$small) { + padding-inline: Spacings.$spacing03; + padding-block: Spacings.$spacing01; + font-size: Typography.$tiny; + } + &.small { padding-inline: Spacings.$spacing03; padding-block: Spacings.$spacing01; diff --git a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx index eea79bcfb883..c1e1fd9f6f01 100644 --- a/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx +++ b/frontend/lib/components/ui/QuivrButton/QuivrButton.tsx @@ -27,7 +27,8 @@ export const QuivrButton = ({ const handleMouseEnter = () => setHovered(true); const handleMouseLeave = () => setHovered(false); - const handleClick = () => { + const handleClick = (event: React.MouseEvent) => { + event.nativeEvent.stopImmediatePropagation(); if (!disabled) { void onClick?.(); } diff --git a/frontend/lib/helpers/iconList.ts b/frontend/lib/helpers/iconList.ts index 6666568ccc58..440aa879daa2 100644 --- a/frontend/lib/helpers/iconList.ts +++ b/frontend/lib/helpers/iconList.ts @@ -2,7 +2,6 @@ import { AiOutlineLoading3Quarters } from "react-icons/ai"; import { BiCoin } from "react-icons/bi"; import { BsArrowRightShort, - BsChatLeftText, BsFiletypeCsv, BsFiletypeDocx, BsFiletypeHtml, @@ -57,6 +56,7 @@ import { import { IoArrowUpCircleOutline, IoBookOutline, + IoChatbubbleEllipsesOutline, IoCloudDownloadOutline, IoFootsteps, IoHelp, @@ -104,7 +104,7 @@ import { RiHashtag, RiNotification2Line, } from "react-icons/ri"; -import { SlOptions } from "react-icons/sl"; +import { SlOptionsVertical } from "react-icons/sl"; import { TbNetwork, TbRobot } from "react-icons/tb"; import { VscGraph } from "react-icons/vsc"; @@ -118,7 +118,7 @@ export const iconList: { [name: string]: IconType } = { brainCircuit: LuBrainCircuit, calendar: FaCalendar, chair: PiOfficeChairFill, - chat: BsChatLeftText, + chat: IoChatbubbleEllipsesOutline, check: FaCheck, checkCircle: FaCheckCircle, chevronDown: LuChevronDown, @@ -175,7 +175,7 @@ export const iconList: { [name: string]: IconType } = { notifications: RiNotification2Line, office: HiBuildingOffice, odt: BsFiletypeDocx, - options: SlOptions, + options: SlOptionsVertical, paragraph: BsTextParagraph, pptx: BsFiletypePptx, prompt: FaRegKeyboard, diff --git a/frontend/lib/types/QuivrButton.ts b/frontend/lib/types/QuivrButton.ts index d27b735fea4b..07d5569c888f 100644 --- a/frontend/lib/types/QuivrButton.ts +++ b/frontend/lib/types/QuivrButton.ts @@ -7,7 +7,7 @@ export interface ButtonType { color: Color; isLoading?: boolean; iconName: keyof typeof iconList; - onClick?: () => void | Promise; + onClick?: (event?: MouseEvent) => void | Promise; disabled?: boolean; hidden?: boolean; important?: boolean;