feat(frontend): manage current brain (#2165)

# 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:
Antoine Dewez 2024-02-06 23:34:50 -08:00 committed by GitHub
parent 9d948b33d5
commit 9517b01d9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 95 additions and 22 deletions

View File

@ -33,7 +33,7 @@ export const ChatInput = (): JSX.Element => {
}} }}
> >
<div className={styles.chat_container}> <div className={styles.chat_container}>
<CurrentBrain /> <CurrentBrain allowingRemoveBrain={false} />
<div <div
className={` className={`
${styles.chat_wrapper} ${styles.chat_wrapper}

View File

@ -1,9 +1,14 @@
"use client"; "use client";
import { UUID } from "crypto";
import { useEffect } from "react";
import { AddBrainModal } from "@/lib/components/AddBrainModal"; import { AddBrainModal } from "@/lib/components/AddBrainModal";
import { useBrainCreationContext } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider"; import { useBrainCreationContext } from "@/lib/components/AddBrainModal/components/AddBrainSteps/brainCreation-provider";
import PageHeader from "@/lib/components/PageHeader/PageHeader"; import PageHeader from "@/lib/components/PageHeader/PageHeader";
import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal"; import { UploadDocumentModal } from "@/lib/components/UploadDocumentModal/UploadDocumentModal";
import { useChatContext } from "@/lib/context";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext"; import { useKnowledgeToFeedContext } from "@/lib/context/KnowledgeToFeedProvider/hooks/useKnowledgeToFeedContext";
import { useDevice } from "@/lib/hooks/useDevice"; import { useDevice } from "@/lib/hooks/useDevice";
import { useCustomDropzone } from "@/lib/hooks/useDropzone"; import { useCustomDropzone } from "@/lib/hooks/useDropzone";
@ -23,6 +28,9 @@ const SelectedChatPage = (): JSX.Element => {
const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext(); const { setShouldDisplayFeedCard } = useKnowledgeToFeedContext();
const { setIsBrainCreationModalOpened } = useBrainCreationContext(); const { setIsBrainCreationModalOpened } = useBrainCreationContext();
const { currentBrain, setCurrentBrainId } = useBrainContext();
const { messages } = useChatContext();
useChatNotificationsSync(); useChatNotificationsSync();
const buttons: ButtonType[] = [ const buttons: ButtonType[] = [
@ -32,6 +40,7 @@ const SelectedChatPage = (): JSX.Element => {
onClick: () => { onClick: () => {
setIsBrainCreationModalOpened(true); setIsBrainCreationModalOpened(true);
}, },
iconName: "brain",
}, },
{ {
label: "Add knowledge", label: "Add knowledge",
@ -39,9 +48,24 @@ const SelectedChatPage = (): JSX.Element => {
onClick: () => { onClick: () => {
setShouldDisplayFeedCard(true); setShouldDisplayFeedCard(true);
}, },
iconName: "upload",
},
{
label: "Manage current brain",
color: "primary",
onClick: () => {
window.location.href = `/studio/${currentBrain?.id}`;
},
iconName: "edit",
}, },
]; ];
useEffect(() => {
if (!currentBrain && messages.length > 0) {
setCurrentBrainId(messages[messages.length - 1].brain_id as UUID);
}
}, [messages]);
return ( return (
<div className={styles.main_container}> <div className={styles.main_container}>
<div className={styles.page_header}> <div className={styles.page_header}>

View File

@ -34,6 +34,7 @@ const Search = (): JSX.Element => {
onClick: () => { onClick: () => {
setIsBrainCreationModalOpened(true); setIsBrainCreationModalOpened(true);
}, },
iconName: "brain",
}, },
{ {
label: "Add knowledge", label: "Add knowledge",
@ -41,6 +42,7 @@ const Search = (): JSX.Element => {
onClick: () => { onClick: () => {
setShouldDisplayFeedCard(true); setShouldDisplayFeedCard(true);
}, },
iconName: "upload",
}, },
]; ];

View File

@ -42,6 +42,7 @@ const Studio = (): JSX.Element => {
onClick: () => { onClick: () => {
setIsBrainCreationModalOpened(true); setIsBrainCreationModalOpened(true);
}, },
iconName: "brain",
}, },
{ {
label: "Add knowledge", label: "Add knowledge",
@ -49,6 +50,7 @@ const Studio = (): JSX.Element => {
onClick: () => { onClick: () => {
setShouldDisplayFeedCard(true); setShouldDisplayFeedCard(true);
}, },
iconName: "upload",
}, },
]; ];

View File

@ -38,6 +38,7 @@ const UserPage = (): JSX.Element => {
onClick: () => { onClick: () => {
setIsLogoutModalOpened(true); setIsLogoutModalOpened(true);
}, },
iconName: "logout",
}; };
const userTabs: Tab[] = [ const userTabs: Tab[] = [
{ {
@ -91,12 +92,14 @@ const UserPage = (): JSX.Element => {
onClick={() => setIsLogoutModalOpened(false)} onClick={() => setIsLogoutModalOpened(false)}
color="primary" color="primary"
label={t("cancel", { ns: "logout" })} label={t("cancel", { ns: "logout" })}
iconName="close"
></QuivrButton> ></QuivrButton>
<QuivrButton <QuivrButton
isLoading={isLoggingOut} isLoading={isLoggingOut}
color="dangerous" color="dangerous"
onClick={() => void handleLogout()} onClick={() => void handleLogout()}
label={t("logoutButton")} label={t("logoutButton")}
iconName="logout"
></QuivrButton> ></QuivrButton>
</div> </div>
</div> </div>

View File

@ -4,7 +4,13 @@ import styles from "./CurrentBrain.module.scss";
import { Icon } from "../ui/Icon/Icon"; import { Icon } from "../ui/Icon/Icon";
export const CurrentBrain = (): JSX.Element => { interface CurrentBrainProps {
allowingRemoveBrain: boolean;
}
export const CurrentBrain = ({
allowingRemoveBrain,
}: CurrentBrainProps): JSX.Element => {
const { currentBrain, setCurrentBrainId } = useBrainContext(); const { currentBrain, setCurrentBrainId } = useBrainContext();
const removeCurrentBrain = (): void => { const removeCurrentBrain = (): void => {
@ -25,6 +31,7 @@ export const CurrentBrain = (): JSX.Element => {
<span className={styles.brain_name}>{currentBrain.name}</span> <span className={styles.brain_name}>{currentBrain.name}</span>
</div> </div>
</div> </div>
{allowingRemoveBrain && (
<div <div
onClick={(event) => { onClick={(event) => {
event.nativeEvent.stopImmediatePropagation(); event.nativeEvent.stopImmediatePropagation();
@ -33,6 +40,7 @@ export const CurrentBrain = (): JSX.Element => {
> >
<Icon size="normal" name="close" color="black" handleHover={true} /> <Icon size="normal" name="close" color="black" handleHover={true} />
</div> </div>
)}
</div> </div>
</div> </div>
); );

View File

@ -32,6 +32,7 @@ export const PageHeader = ({
label={button.label} label={button.label}
onClick={button.onClick} onClick={button.onClick}
color={button.color} color={button.color}
iconName={button.iconName}
/> />
))} ))}
</div> </div>

View File

@ -51,10 +51,6 @@
.white { .white {
color: Colors.$white; color: Colors.$white;
&:hover {
color: Colors.$accent;
}
} }
.dangerous { .dangerous {

View File

@ -1,5 +1,6 @@
@use "@/styles/Colors.module.scss"; @use "@/styles/Colors.module.scss";
@use "@/styles/Radius.module.scss"; @use "@/styles/Radius.module.scss";
@use "@/styles/ScreenSizes.module.scss";
@use "@/styles/Spacings.module.scss"; @use "@/styles/Spacings.module.scss";
@use "@/styles/Typography.module.scss"; @use "@/styles/Typography.module.scss";
@ -11,6 +12,7 @@
border-radius: Radius.$normal; border-radius: Radius.$normal;
border: 1.5px solid transparent; border: 1.5px solid transparent;
cursor: pointer; cursor: pointer;
display: flex;
&.primary { &.primary {
border-color: Colors.$primary; border-color: Colors.$primary;
@ -32,3 +34,16 @@
} }
} }
} }
.icon_label {
display: flex;
flex-direction: row;
gap: Spacings.$spacing02;
align-items: center;
@media (max-width: ScreenSizes.$small) {
.label {
display: none;
}
}
}

View File

@ -1,7 +1,10 @@
import { useState } from "react";
import { ButtonType } from "@/lib/types/QuivrButton"; import { ButtonType } from "@/lib/types/QuivrButton";
import styles from "./QuivrButton.module.scss"; import styles from "./QuivrButton.module.scss";
import { Icon } from "../Icon/Icon";
import { LoaderIcon } from "../LoaderIcon/LoaderIcon"; import { LoaderIcon } from "../LoaderIcon/LoaderIcon";
export const QuivrButton = ({ export const QuivrButton = ({
@ -9,14 +12,27 @@ export const QuivrButton = ({
label, label,
color, color,
isLoading, isLoading,
iconName,
}: ButtonType): JSX.Element => { }: ButtonType): JSX.Element => {
const [hovered, setHovered] = useState<boolean>(false);
return ( return (
<div <div
className={`${styles.button_wrapper} ${styles[color]}`} className={`${styles.button_wrapper} ${styles[color]}`}
onClick={onClick} onClick={onClick}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
> >
{!isLoading ? ( {!isLoading ? (
<span>{label}</span> <div className={styles.icon_label}>
<Icon
name={iconName}
size="normal"
color={hovered ? "white" : color}
handleHover={false}
/>
<span className={styles.label}>{label}</span>
</div>
) : ( ) : (
<LoaderIcon color="black" size="small" /> <LoaderIcon color="black" size="small" />
)} )}

View File

@ -56,7 +56,7 @@ export const SearchBar = ({
${currentBrain ? styles.with_brain : ""} ${currentBrain ? styles.with_brain : ""}
`} `}
> >
<CurrentBrain /> <CurrentBrain allowingRemoveBrain={true} />
<div <div
className={` className={`
${styles.editor_wrapper} ${styles.editor_wrapper}

View File

@ -1,6 +1,6 @@
import { AiOutlineLoading3Quarters } from "react-icons/ai"; import { AiOutlineLoading3Quarters } from "react-icons/ai";
import { BsArrowRightShort } from "react-icons/bs"; import { BsArrowRightShort, BsChatLeftText } from "react-icons/bs";
import { CiChat1, CiFlag1 } from "react-icons/ci"; import { CiFlag1 } from "react-icons/ci";
import { import {
FaCheck, FaCheck,
FaCheckCircle, FaCheckCircle,
@ -9,9 +9,12 @@ import {
FaRegUserCircle, FaRegUserCircle,
FaUnlock, FaUnlock,
} from "react-icons/fa"; } from "react-icons/fa";
import { FaArrowUpFromBracket } from "react-icons/fa6";
import { IoIosAdd, IoMdClose, IoMdLogOut } from "react-icons/io"; import { IoIosAdd, IoMdClose, IoMdLogOut } from "react-icons/io";
import { IoHomeOutline, IoSettingsSharp } from "react-icons/io5"; import {
IoArrowUpCircleOutline,
IoHomeOutline,
IoSettingsSharp,
} from "react-icons/io5";
import { IconType } from "react-icons/lib"; import { IconType } from "react-icons/lib";
import { import {
LuBrain, LuBrain,
@ -38,7 +41,7 @@ export const iconList: { [name: string]: IconType } = {
addWithoutCircle: IoIosAdd, addWithoutCircle: IoIosAdd,
brain: LuBrain, brain: LuBrain,
brainCircuit: LuBrainCircuit, brainCircuit: LuBrainCircuit,
chat: CiChat1, chat: BsChatLeftText,
check: FaCheck, check: FaCheck,
checkCircle: FaCheckCircle, checkCircle: FaCheckCircle,
chevronDown: LuChevronDown, chevronDown: LuChevronDown,
@ -50,7 +53,7 @@ export const iconList: { [name: string]: IconType } = {
email: MdAlternateEmail, email: MdAlternateEmail,
file: LuFile, file: LuFile,
flag: CiFlag1, flag: CiFlag1,
followUp: FaArrowUpFromBracket, followUp: IoArrowUpCircleOutline,
graph: VscGraph, graph: VscGraph,
hastag: RiHashtag, hastag: RiHashtag,
history: MdHistory, history: MdHistory,

View File

@ -1,8 +1,11 @@
import { Color } from "./Colors"; import { Color } from "./Colors";
import { iconList } from "../helpers/iconList";
export interface ButtonType { export interface ButtonType {
label: string; label: string;
color: Color; color: Color;
isLoading?: boolean; isLoading?: boolean;
iconName: keyof typeof iconList;
onClick: () => void; onClick: () => void;
} }