diff --git a/frontend/app/brains-management/page.tsx b/frontend/app/brains-management/page.tsx index 9d2604104049..f01996234c15 100644 --- a/frontend/app/brains-management/page.tsx +++ b/frontend/app/brains-management/page.tsx @@ -11,7 +11,7 @@ const BrainsManagement = (): JSX.Element => { const { t } = useTranslation("chat"); return ( -
+
diff --git a/frontend/app/chat/[chatId]/components/DataPanel/DataPanel.module.scss b/frontend/app/chat/[chatId]/components/DataPanel/DataPanel.module.scss new file mode 100644 index 000000000000..1032bdff878f --- /dev/null +++ b/frontend/app/chat/[chatId]/components/DataPanel/DataPanel.module.scss @@ -0,0 +1,7 @@ +@use "@/styles/Spacings.module.scss"; + +.data_panel_wrapper { + display: flex; + flex-direction: column; + row-gap: Spacings.$spacing05; +} diff --git a/frontend/app/chat/[chatId]/components/DataPanel/DataPanel.tsx b/frontend/app/chat/[chatId]/components/DataPanel/DataPanel.tsx new file mode 100644 index 000000000000..2408f0c76452 --- /dev/null +++ b/frontend/app/chat/[chatId]/components/DataPanel/DataPanel.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { useChatContext } from "@/lib/context"; +import { CloseBrain } from "@/lib/types/MessageMetadata"; + +import styles from "./DataPanel.module.scss"; +import RelatedBrains from "./components/RelatedBrains/RelatedBrains"; + +const DataPanel = (): JSX.Element => { + const { messages } = useChatContext(); + const [lastMessageRelatedBrain, setLastMessageRelatedBrain] = useState< + CloseBrain[] + >([]); + + useEffect(() => { + if (messages.length > 0) { + const lastMessage = messages[messages.length - 1]; + if (lastMessage?.metadata?.close_brains) { + setLastMessageRelatedBrain(lastMessage.metadata.close_brains); + } + } + }, [lastMessageRelatedBrain, messages]); + + return ( +
+ +
+ ); +}; + +export default DataPanel; diff --git a/frontend/app/chat/[chatId]/components/DataPanel/components/RelatedBrains/RelatedBrains.module.scss b/frontend/app/chat/[chatId]/components/DataPanel/components/RelatedBrains/RelatedBrains.module.scss new file mode 100644 index 000000000000..d897eb80be4e --- /dev/null +++ b/frontend/app/chat/[chatId]/components/DataPanel/components/RelatedBrains/RelatedBrains.module.scss @@ -0,0 +1,50 @@ +@use "@/styles/Colors.module.scss"; +@use "@/styles/Spacings.module.scss"; + +.close_brains_wrapper { + display: flex; + flex-direction: column; + padding-bottom: Spacings.$spacing03; + gap: Spacings.$spacing01; + + .brain_line { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + padding-left: Spacings.$spacing03; + padding-right: Spacings.$spacing05; + overflow: hidden; + + .left { + display: flex; + align-items: center; + gap: Spacings.$spacing03; + + .copy_icon { + visibility: hidden; + } + + .brain_name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &.current { + color: Colors.$primary; + } + } + } + + .similarity_score { + cursor: help; + } + + &:hover { + .left .copy_icon { + visibility: visible; + cursor: pointer; + } + } + } +} diff --git a/frontend/app/chat/[chatId]/components/DataPanel/components/RelatedBrains/RelatedBrains.tsx b/frontend/app/chat/[chatId]/components/DataPanel/components/RelatedBrains/RelatedBrains.tsx new file mode 100644 index 000000000000..4d380f9ac4e5 --- /dev/null +++ b/frontend/app/chat/[chatId]/components/DataPanel/components/RelatedBrains/RelatedBrains.tsx @@ -0,0 +1,94 @@ +import { useEffect, useState } from "react"; + +import { FoldableSection } from "@/lib/components/ui/FoldableSection/FoldableSection"; +import Icon from "@/lib/components/ui/Icon/Icon"; +import { useChatContext } from "@/lib/context"; +import { CloseBrain } from "@/lib/types/MessageMetadata"; + +import styles from "./RelatedBrains.module.scss"; + +interface RelatedBrainsProps { + closeBrains: CloseBrain[]; +} + +interface CloseBrainProps { + color: string; + isCurrentBrain: boolean; +} + +const RelatedBrains = ({ closeBrains }: RelatedBrainsProps): JSX.Element => { + const [closeBrainsProps, setCloseBrainProps] = useState( + [] + ); + const { messages } = useChatContext(); + const lerp = (start: number, end: number, t: number): number => { + return start * (1 - t) + end * t; + }; + + useEffect(() => { + const newProps = closeBrains.map((brain) => { + const t = Math.pow(brain.similarity, 2); + const r = Math.round(lerp(211, 138, t)); + const g = Math.round(lerp(211, 43, t)); + const b = Math.round(lerp(211, 226, t)); + const isCurrentBrain = + brain.name === messages[messages.length - 1].brain_name; + + return { color: `rgb(${r}, ${g}, ${b})`, isCurrentBrain: isCurrentBrain }; + }); + setCloseBrainProps(newProps); + }, [closeBrains, messages]); + + if (closeBrains.length === 0) { + return <>; + } + + return ( + +
+ {closeBrains.map((brain, index) => ( +
+
+
+ + void navigator.clipboard.writeText("@" + brain.name) + } + > +
+

+ @{brain.name} +

+
+
+ {Math.round(brain.similarity * 100)} +
+
+ ))} +
+
+ ); +}; + +export default RelatedBrains; diff --git a/frontend/app/chat/[chatId]/hooks/useChat.ts b/frontend/app/chat/[chatId]/hooks/useChat.ts index f5c21e96d64c..9e4a3b56ab99 100644 --- a/frontend/app/chat/[chatId]/hooks/useChat.ts +++ b/frontend/app/chat/[chatId]/hooks/useChat.ts @@ -83,7 +83,7 @@ export const useChat = () => { } const chatQuestion: ChatQuestion = { - model, + model, // eslint-disable-line @typescript-eslint/no-unsafe-assignment question, temperature: temperature, max_tokens: maxTokens, @@ -93,7 +93,6 @@ export const useChat = () => { callback?.(); await addStreamQuestion(currentChatId, chatQuestion); - } catch (error) { console.error({ error }); diff --git a/frontend/app/chat/[chatId]/page.module.scss b/frontend/app/chat/[chatId]/page.module.scss new file mode 100644 index 000000000000..7a0024595530 --- /dev/null +++ b/frontend/app/chat/[chatId]/page.module.scss @@ -0,0 +1,20 @@ +@use "@/styles/Colors.module.scss"; +@use "@/styles/Spacings.module.scss"; + +.chat_page_container { + display: flex; + flex: 1 1 0%; + background-color: Colors.$white; + padding: Spacings.$spacing06; + display: flex; + gap: Spacings.$spacing09; + + &.feeding { + background-color: Colors.$chat-bg-gray; + } + + .data_panel_wrapper { + height: 100%; + width: 30%; + } +} diff --git a/frontend/app/chat/[chatId]/page.tsx b/frontend/app/chat/[chatId]/page.tsx index d11fa5df66f7..9f6f01eafe24 100644 --- a/frontend/app/chat/[chatId]/page.tsx +++ b/frontend/app/chat/[chatId]/page.tsx @@ -1,41 +1,54 @@ "use client"; import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; +import { useDevice } from "@/lib/hooks/useDevice"; import { useCustomDropzone } from "@/lib/hooks/useDropzone"; import { cn } from "@/lib/utils"; import { ActionsBar } from "./components/ActionsBar"; import { ChatDialogueArea } from "./components/ChatDialogueArea/ChatDialogue"; +import DataPanel from "./components/DataPanel/DataPanel"; import { useChatNotificationsSync } from "./hooks/useChatNotificationsSync"; +import styles from "./page.module.scss"; const SelectedChatPage = (): JSX.Element => { - const { getRootProps } = useCustomDropzone(); - const { shouldDisplayFeedCard } = useKnowledgeToFeedContext(); + const { getRootProps } = useCustomDropzone(); + const { shouldDisplayFeedCard } = useKnowledgeToFeedContext(); + const { isMobile } = useDevice(); - useChatNotificationsSync(); + useChatNotificationsSync(); - return ( -
-
-
-
- -
- -
-
+ return ( +
+
+
+
+ +
+
- ); +
+ {!isMobile && ( +
+ +
+ )} +
+ ); }; export default SelectedChatPage; diff --git a/frontend/app/chat/[chatId]/types/index.ts b/frontend/app/chat/[chatId]/types/index.ts index a478c898af65..d6dad3b85619 100644 --- a/frontend/app/chat/[chatId]/types/index.ts +++ b/frontend/app/chat/[chatId]/types/index.ts @@ -1,5 +1,7 @@ import { UUID } from "crypto"; +import { CloseBrain } from "@/lib/types/MessageMetadata"; + export type ChatQuestion = { model?: string; question?: string; @@ -18,6 +20,7 @@ export type ChatMessage = { brain_name?: string; metadata?: { sources?: [string]; + close_brains?: CloseBrain[]; }; }; diff --git a/frontend/app/search/page.module.scss b/frontend/app/search/page.module.scss index cdfd2ad99d72..2fdef462a2b4 100644 --- a/frontend/app/search/page.module.scss +++ b/frontend/app/search/page.module.scss @@ -6,7 +6,7 @@ @use "@/styles/Variables.module.scss"; .search_page_container { - background-color: Colors.$ivory; + background-color: Colors.$white; width: 100%; height: 100%; display: flex; diff --git a/frontend/lib/components/Menu/Menu.tsx b/frontend/lib/components/Menu/Menu.tsx index 14a996d673ae..c63793f21df8 100644 --- a/frontend/lib/components/Menu/Menu.tsx +++ b/frontend/lib/components/Menu/Menu.tsx @@ -5,7 +5,7 @@ import { MenuControlButton } from "@/app/chat/[chatId]/components/ActionsBar/com import { nonProtectedPaths } from "@/lib/config/routesConfig"; import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext"; -import styles from './Menu.module.scss' +import styles from "./Menu.module.scss"; import { AnimatedDiv } from "./components/AnimationDiv"; import { BrainsManagementButton } from "./components/BrainsManagementButton"; import { DiscussionButton } from "./components/DiscussionButton"; @@ -16,51 +16,58 @@ import { ProfileButton } from "./components/ProfileButton"; import { UpgradeToPlus } from "./components/UpgradeToPlus"; export const Menu = (): JSX.Element => { - const { isOpened } = useMenuContext(); - const pathname = usePathname() ?? ""; + const { isOpened } = useMenuContext(); + const pathname = usePathname() ?? ""; - if (nonProtectedPaths.includes(pathname)) { - return <>; - } + if (nonProtectedPaths.includes(pathname)) { + return <>; + } - const displayedOnPages = ["/chat", "/library", "/brains-management", "/search"]; + const displayedOnPages = [ + "/chat", + "/library", + "/brains-management", + "/search", + ]; - const isMenuDisplayed = displayedOnPages.some((page) => - pathname.includes(page) - ); + const isMenuDisplayed = displayedOnPages.some((page) => + pathname.includes(page) + ); - if (!isMenuDisplayed) { - return <>; - } + if (!isMenuDisplayed) { + return <>; + } - /* eslint-disable @typescript-eslint/restrict-template-expressions */ + /* eslint-disable @typescript-eslint/restrict-template-expressions */ - return ( - -
- -
- -
-
- - - - -
-
-
- - -
-
-
+ return ( + +
+ +
+ +
+
+ + + + +
-
- +
+ +
- - ); +
+ +
+
+ +
+ + ); }; diff --git a/frontend/lib/components/Menu/components/AnimationDiv.tsx b/frontend/lib/components/Menu/components/AnimationDiv.tsx index 2d8cdbeaedbd..6d5cb89ac564 100644 --- a/frontend/lib/components/Menu/components/AnimationDiv.tsx +++ b/frontend/lib/components/Menu/components/AnimationDiv.tsx @@ -21,7 +21,7 @@ export const AnimatedDiv = ({ children }: AnimatedDivProps): JSX.Element => { ? "10px 10px 16px rgba(0, 0, 0, 0)" : "10px 10px 16px rgba(0, 0, 0, 0.5)", }} - className={"overflow-hidden flex flex-col flex-1 bg-white"} + className={"overflow-hidden flex flex-col flex-1 bg-grey"} > {children} diff --git a/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss b/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss new file mode 100644 index 000000000000..c4180c3b3f00 --- /dev/null +++ b/frontend/lib/components/ui/FoldableSection/FoldableSection.module.scss @@ -0,0 +1,36 @@ +@use "@/styles/Colors.module.scss"; +@use "@/styles/Spacings.module.scss"; + +.foldable_section_wrapper { + display: flex; + flex-direction: column; + border-radius: 5px; + border: 1px dashed Colors.$light-grey; + overflow: hidden; + + .header_wrapper { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + padding: Spacings.$spacing03; + + .header_left { + display: flex; + align-items: center; + gap: Spacings.$spacing03; + overflow: hidden; + + .header_title { + font-weight: bold; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + &:hover { + background-color: Colors.$lightest-grey; + } + } +} diff --git a/frontend/lib/components/ui/FoldableSection/FoldableSection.tsx b/frontend/lib/components/ui/FoldableSection/FoldableSection.tsx new file mode 100644 index 000000000000..549fa226c9dc --- /dev/null +++ b/frontend/lib/components/ui/FoldableSection/FoldableSection.tsx @@ -0,0 +1,39 @@ +import { useEffect, useState } from "react"; + +import { iconList } from "@/lib/helpers/iconList"; + +import styles from "./FoldableSection.module.scss"; + +import { Icon } from "../Icon/Icon"; + +interface FoldableSectionProps { + label: string; + icon: keyof typeof iconList; + children: React.ReactNode; + foldedByDefault?: boolean; +} + +export const FoldableSection = (props: FoldableSectionProps): JSX.Element => { + const [folded, setFolded] = useState(false); + + useEffect(() => { + setFolded(props.foldedByDefault ?? false); + }, [props.foldedByDefault]); + + return ( +
+
setFolded(!folded)}> +
+ +

{props.label}

+
+ +
+ {!folded &&
{props.children}
} +
+ ); +}; diff --git a/frontend/lib/components/ui/Icon/Icon.module.scss b/frontend/lib/components/ui/Icon/Icon.module.scss index 43e854d7d36c..46cd8a093488 100644 --- a/frontend/lib/components/ui/Icon/Icon.module.scss +++ b/frontend/lib/components/ui/Icon/Icon.module.scss @@ -25,7 +25,7 @@ color: Colors.$black; &.hovered { - color: Colors.$accent; + color: Colors.$primary; } } diff --git a/frontend/lib/components/ui/Icon/Icon.tsx b/frontend/lib/components/ui/Icon/Icon.tsx index e940965728ad..c4d7050ac89b 100644 --- a/frontend/lib/components/ui/Icon/Icon.tsx +++ b/frontend/lib/components/ui/Icon/Icon.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; import { IconType } from "react-icons/lib"; import { iconList } from "@/lib/helpers/iconList"; @@ -13,6 +14,8 @@ interface IconProps { disabled?: boolean; classname?: string; hovered?: boolean; + handleHover?: boolean; + onClick?: () => void; } export const Icon = ({ @@ -22,9 +25,18 @@ export const Icon = ({ disabled, classname, hovered, + handleHover, + onClick, }: IconProps): JSX.Element => { + const [iconHovered, setIconHovered] = useState(false); const IconComponent: IconType = iconList[name]; + useEffect(() => { + if (!handleHover) { + setIconHovered(!!hovered); + } + }, [hovered, handleHover]); + return ( handleHover && setIconHovered(true)} + onMouseLeave={() => handleHover && setIconHovered(false)} + onClick={onClick} /> ); }; diff --git a/frontend/lib/components/ui/SearchBar/SearchBar.module.scss b/frontend/lib/components/ui/SearchBar/SearchBar.module.scss index 1c81396b2b26..156065aaaea1 100644 --- a/frontend/lib/components/ui/SearchBar/SearchBar.module.scss +++ b/frontend/lib/components/ui/SearchBar/SearchBar.module.scss @@ -10,12 +10,7 @@ background-color: Colors.$white; padding: Spacings.$spacing05; border-radius: 12px; - box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - - &:hover { - box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), - 0 8px 10px -6px rgb(0 0 0 / 0.1); - } + border: 1px solid Colors.$light-grey; .search_icon { width: IconSizes.$big; diff --git a/frontend/lib/components/ui/TextButton/TextButton.module.scss b/frontend/lib/components/ui/TextButton/TextButton.module.scss index 158a18935f43..c7535d0f38cf 100644 --- a/frontend/lib/components/ui/TextButton/TextButton.module.scss +++ b/frontend/lib/components/ui/TextButton/TextButton.module.scss @@ -13,6 +13,6 @@ color: Colors.$black; &.hovered { - color: Colors.$accent; + color: Colors.$primary; } } diff --git a/frontend/lib/helpers/iconList.ts b/frontend/lib/helpers/iconList.ts index 666288fad6df..0a04ccfbc054 100644 --- a/frontend/lib/helpers/iconList.ts +++ b/frontend/lib/helpers/iconList.ts @@ -1,9 +1,20 @@ import { AiOutlineLoading3Quarters } from "react-icons/ai"; import { IconType } from "react-icons/lib"; -import { LuPlusCircle, LuSearch } from "react-icons/lu"; +import { + LuBrain, + LuChevronDown, + LuChevronRight, + LuCopy, + LuPlusCircle, + LuSearch, +} from "react-icons/lu"; export const iconList: { [name: string]: IconType } = { add: LuPlusCircle, + brain: LuBrain, + chevronDown: LuChevronDown, + chevronRight: LuChevronRight, + copy: LuCopy, loader: AiOutlineLoading3Quarters, search: LuSearch, }; diff --git a/frontend/lib/types/MessageMetadata.ts b/frontend/lib/types/MessageMetadata.ts new file mode 100644 index 000000000000..550dafc4db0b --- /dev/null +++ b/frontend/lib/types/MessageMetadata.ts @@ -0,0 +1,9 @@ +export interface CloseBrain { + id: string; + similarity: number; + name: string; +} + +export interface MessageMetadata { + closeBrains: CloseBrain[]; +} diff --git a/frontend/styles/_Colors.module.scss b/frontend/styles/_Colors.module.scss index f2fa73dea507..df110d9bf791 100644 --- a/frontend/styles/_Colors.module.scss +++ b/frontend/styles/_Colors.module.scss @@ -1,8 +1,12 @@ -$white: #FFFFFF; -$black: #11243E; -$primary: #6142D4; -$secondary: #F3ECFF; -$tertiary: #F6F4FF; -$accent: #13ABBA; -$highlight: #FAFAFA; -$ivory: #FCFAF6, \ No newline at end of file +$white: #ffffff; +$lightest-grey: #f5f5f5; +$light-grey: #d3d3d3; +$normal-grey: #c8c8c8; +$black: #11243e; +$primary: #6142d4; +$secondary: #f3ecff; +$tertiary: #f6f4ff; +$accent: #13abba; +$highlight: #fafafa; +$ivory: #fcfaf6; +$chat-bg-gray: #d9d9d9;