feat(frontend): helpbox (#3007)

# 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-08-14 19:31:30 +02:00 committed by GitHub
parent b5f31a83d4
commit 2812ea4f35
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 409 additions and 41 deletions

View File

@ -18,6 +18,11 @@
}
}
&.blur {
filter: opacity(0.1);
transition: filter 0.2s ease-in-out;
}
.content_container {
overflow: scroll;
flex: 1 1 0%;

View File

@ -6,6 +6,7 @@ import { PostHogProvider } from "posthog-js/react";
import { PropsWithChildren, useEffect } from "react";
import { BrainCreationProvider } from "@/lib/components/AddBrainModal/brainCreation-provider";
import { HelpWindow } from "@/lib/components/HelpWindow/HelpWindow";
import { Menu } from "@/lib/components/Menu/Menu";
import { useOutsideClickListener } from "@/lib/components/Menu/hooks/useOutsideClickListener";
import { SearchModal } from "@/lib/components/SearchModal/SearchModal";
@ -16,6 +17,8 @@ import {
} from "@/lib/context";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { ChatsProvider } from "@/lib/context/ChatsProvider";
import { HelpProvider } from "@/lib/context/HelpProvider/help-provider";
import { useHelpContext } from "@/lib/context/HelpProvider/hooks/useHelpContext";
import { MenuProvider } from "@/lib/context/MenuProvider/Menu-provider";
import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext";
import { NotificationsProvider } from "@/lib/context/NotificationsProvider/notifications-provider";
@ -48,6 +51,7 @@ const App = ({ children }: PropsWithChildren): JSX.Element => {
const { onClickOutside } = useOutsideClickListener();
const { session } = useSupabase();
const { isOpened } = useMenuContext();
const { isVisible } = useHelpContext();
usePageTracking();
@ -66,8 +70,13 @@ const App = ({ children }: PropsWithChildren): JSX.Element => {
<IntercomProvider>
<div className="flex flex-1 flex-col overflow-auto">
<SearchModalProvider>
<HelpWindow />
<SearchModal />
<div className={styles.app_container}>
<div
className={`${styles.app_container} ${
isVisible ? styles.blur : ""
}`}
>
<div className={styles.menu_container}>
<Menu />
</div>
@ -95,25 +104,27 @@ const AppWithQueryClient = ({ children }: PropsWithChildren): JSX.Element => {
return (
<QueryClientProvider client={queryClient}>
<UserSettingsProvider>
<BrainProvider>
<KnowledgeToFeedProvider>
<BrainCreationProvider>
<NotificationsProvider>
<MenuProvider>
<OnboardingProvider>
<FromConnectionsProvider>
<ChatsProvider>
<ChatProvider>
<App>{children}</App>
</ChatProvider>
</ChatsProvider>
</FromConnectionsProvider>
</OnboardingProvider>
</MenuProvider>
</NotificationsProvider>
</BrainCreationProvider>
</KnowledgeToFeedProvider>
</BrainProvider>
<HelpProvider>
<BrainProvider>
<KnowledgeToFeedProvider>
<BrainCreationProvider>
<NotificationsProvider>
<MenuProvider>
<OnboardingProvider>
<FromConnectionsProvider>
<ChatsProvider>
<ChatProvider>
<App>{children}</App>
</ChatProvider>
</ChatsProvider>
</FromConnectionsProvider>
</OnboardingProvider>
</MenuProvider>
</NotificationsProvider>
</BrainCreationProvider>
</KnowledgeToFeedProvider>
</BrainProvider>
</HelpProvider>
</UserSettingsProvider>
</QueryClientProvider>
);

View File

@ -0,0 +1,94 @@
@use "styles/BoxShadow.module.scss";
@use "styles/ScreenSizes.module.scss";
@use "styles/Spacings.module.scss";
@use "styles/Typography.module.scss";
@use "styles/ZIndexes.module.scss";
.help_wrapper {
position: absolute;
height: 100vh;
right: 0;
width: 0;
z-index: ZIndexes.$modal + 1;
box-shadow: BoxShadow.$medium;
background-color: var(--background-0);
overflow: hidden;
transition: width 0.2s ease-in-out;
overflow-y: auto;
&.visible {
width: 600px;
.header {
display: block;
display: flex;
align-items: center;
justify-content: space-between;
padding: Spacings.$spacing05;
border-bottom: 1px solid var(--border-1);
.title {
@include Typography.H1;
}
}
.content {
padding: Spacings.$spacing05;
display: block;
.section {
display: flex;
flex-direction: column;
.title {
@include Typography.H2;
}
.section_content {
padding: Spacings.$spacing05;
padding-inline: Spacings.$spacing06;
font-size: Typography.$small;
ul {
padding: Spacings.$spacing05;
li {
padding-block: Spacings.$spacing03;
.connection {
display: inline-flex;
align-items: center;
gap: Spacings.$spacing02;
padding-right: Spacings.$spacing02;
.pre {
white-space: pre;
}
}
}
}
}
.image {
display: flex;
justify-content: center;
width: 100%;
padding-top: Spacings.$spacing05;
}
}
}
}
.header {
display: none;
}
.content {
display: none;
}
@media screen and (max-width: ScreenSizes.$small) {
&.visible {
width: 100%;
}
}
}

View File

@ -0,0 +1,188 @@
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { useUserApi } from "@/lib/api/user/useUserApi";
import { useHelpContext } from "@/lib/context/HelpProvider/hooks/useHelpContext";
import { useUserData } from "@/lib/hooks/useUserData";
import styles from "./HelpWindow.module.scss";
import { Icon } from "../ui/Icon/Icon";
export const HelpWindow = (): JSX.Element => {
const { isVisible, setIsVisible } = useHelpContext();
const [loadingOnboarded, setLoadingOnboarded] = useState(false);
const helpWindowRef = useRef<HTMLDivElement>(null);
const { userIdentityData } = useUserData();
const { updateUserIdentity } = useUserApi();
const closeHelpWindow = async () => {
setIsVisible(false);
if (!userIdentityData?.onboarded) {
setLoadingOnboarded(true);
await updateUserIdentity({
...userIdentityData,
username: userIdentityData?.username ?? "",
onboarded: true,
});
}
};
useEffect(() => {
if (
userIdentityData?.username &&
!userIdentityData.onboarded &&
!loadingOnboarded
) {
setIsVisible(true);
}
});
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
helpWindowRef.current &&
!helpWindowRef.current.contains(event.target as Node)
) {
void (async () => {
try {
await closeHelpWindow();
} catch (error) {
console.error("Error while closing help window", error);
}
})();
} else {
setIsVisible(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
});
return (
<div
className={`${styles.help_wrapper} ${isVisible ? styles.visible : ""}`}
ref={helpWindowRef}
>
<div className={styles.header}>
<span className={styles.title}>🧠 What is Quivr ?</span>
<Icon
name="close"
size="normal"
color="black"
handleHover={true}
onClick={() => closeHelpWindow()}
/>
</div>
<div className={styles.content}>
<div className={styles.section}>
<span className={styles.title}>🧱 Build your second brains</span>
<span className={styles.section_content}>
A <strong>Brain</strong> in Quivr is an advanced knowledge system
designed to integrate and leverage information from various sources.
<ul>
<li>
<strong>📁 Knowledge Integration</strong>
<br /> Connect to and pull data from various platforms like{" "}
<span className={styles.connection}>
<strong>Google Drive</strong>{" "}
<Image
src="https://quivr-cms.s3.eu-west-3.amazonaws.com/gdrive_8316d080fd.png"
alt="Google Drive"
width={16}
height={16}
/>
</span>
,
<span className={styles.connection}>
<strong>SharePoint</strong>{" "}
<Image
src="https://quivr-cms.s3.eu-west-3.amazonaws.com/sharepoint_8c41cfdb09.png"
alt="SharePoint"
width={16}
height={16}
/>
</span>
, and{""}
<span className={styles.connection}>
<strong className={styles.pre}> Dropbox</strong>{" "}
<Image
src="https://quivr-cms.s3.eu-west-3.amazonaws.com/dropbox_dce4f3d753.png"
alt="Dropbox"
width={16}
height={16}
/>
</span>
. You can also incorporate data from <strong>URLs</strong> and{" "}
<strong>files</strong>.
</li>
<li>
<strong>🤖 AI Models</strong>
<br /> Utilize powerful models such as{" "}
<strong>
<em>GPT</em>
</strong>{" "}
and{" "}
<strong>
<em>Mistral</em>
</strong>{" "}
to process and understand the integrated knowledge.
</li>
<li>
<strong>🔧 Customization</strong>
<br /> Tailor the behavior of your Brain with{" "}
<em>custom prompts</em> and <em>settings</em>, such as{" "}
<strong>max tokens</strong>, to better suit your needs.
</li>
</ul>
<p>
You can also <strong>share</strong> your brains with other Quivr
users, allowing them to access and benefit from your knowledge
systems. 🤝
</p>
</span>
</div>
<div className={styles.section}>
<span className={styles.title}>🤖 Talk to AI Models</span>
<span className={styles.section_content}>
<p>
Quivr allows you to <strong>interact directly</strong> with AI
models such as{" "}
<strong>
<em>GPT-4</em>
</strong>{" "}
and{" "}
<strong>
<em>Mistral</em>
</strong>
. Simply start a conversation with the AI to get answers and
support based on a broad range of data and knowledge. 🤖
</p>
</span>
</div>
<div className={styles.section}>
<div className={styles.title}>🎯 Select assistant</div>
<span className={styles.section_content}>
<p>
Press
<strong> @</strong> to choose the AI model or the Brain you want
to interact with.
</p>
</span>
<div className={styles.image}>
<Image
src="https://quivr-cms.s3.eu-west-3.amazonaws.com/Screen_82ac3783aa.png"
width={500}
height={100}
alt="Quivr"
/>
</div>
</div>
</div>
</div>
);
};

View File

@ -1,3 +1,4 @@
@use "styles/Radius.module.scss";
@use "styles/ScreenSizes.module.scss";
@use "styles/Spacings.module.scss";
@use "styles/Typography.module.scss";
@ -40,3 +41,18 @@
}
}
}
.help_button {
display: flex;
padding: Spacings.$spacing02;
position: absolute;
top: Spacings.$spacing04;
right: Spacings.$spacing03;
border-radius: Radius.$circle;
cursor: pointer;
background-color: var(--background-2);
&:hover {
background-color: var(--background-3);
}
}

View File

@ -1,3 +1,4 @@
import { useHelpContext } from "@/lib/context/HelpProvider/hooks/useHelpContext";
import { useMenuContext } from "@/lib/context/MenuProvider/hooks/useMenuContext";
import { ButtonType } from "@/lib/types/QuivrButton";
@ -18,28 +19,39 @@ export const PageHeader = ({
buttons,
}: Props): JSX.Element => {
const { isOpened } = useMenuContext();
const { isVisible, setIsVisible } = useHelpContext();
return (
<div className={styles.page_header_wrapper}>
<div className={`${styles.left} ${!isOpened ? styles.menu_closed : ""}`}>
<Icon name={iconName} size="large" color="primary" />
<span>{label}</span>
<>
<div className={styles.page_header_wrapper}>
<div
className={`${styles.left} ${!isOpened ? styles.menu_closed : ""}`}
>
<Icon name={iconName} size="large" color="primary" />
<span>{label}</span>
</div>
<div className={styles.buttons_wrapper}>
{buttons.map((button, index) => (
<QuivrButton
key={index}
label={button.label}
onClick={button.onClick}
color={button.color}
iconName={button.iconName}
hidden={button.hidden}
disabled={button.disabled}
tooltip={button.tooltip}
/>
))}
</div>
</div>
<div className={styles.buttons_wrapper}>
{buttons.map((button, index) => (
<QuivrButton
key={index}
label={button.label}
onClick={button.onClick}
color={button.color}
iconName={button.iconName}
hidden={button.hidden}
disabled={button.disabled}
tooltip={button.tooltip}
/>
))}
<div
className={styles.help_button}
onClick={() => setIsVisible(!isVisible)}
>
<Icon name="help" size="normal" color="black" />
</div>
</div>
</>
);
};

View File

@ -0,0 +1,29 @@
import { createContext, useState } from "react";
type HelpContextType = {
isVisible: boolean;
setIsVisible: React.Dispatch<React.SetStateAction<boolean>>;
};
export const HelpContext = createContext<HelpContextType | undefined>(
undefined
);
export const HelpProvider = ({
children,
}: {
children: React.ReactNode;
}): JSX.Element => {
const [isVisible, setIsVisible] = useState(false);
return (
<HelpContext.Provider
value={{
isVisible,
setIsVisible,
}}
>
{children}
</HelpContext.Provider>
);
};

View File

@ -0,0 +1,13 @@
import { useContext } from "react";
import { HelpContext } from "../help-provider";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useHelpContext = () => {
const context = useContext(HelpContext);
if (context === undefined) {
throw new Error("useHelpContext must be used within a HelpProvider");
}
return context;
};

View File

@ -7,7 +7,7 @@ export const useNotificationsContext = () => {
const context = useContext(NotificationsContext);
if (context === undefined) {
throw new Error(
"useNotificationsContext must be used within a MenuProvider"
"useNotificationsContext must be used within a NotificationsProvider"
);
}

View File

@ -47,7 +47,6 @@ import { GoLightBulb } from "react-icons/go";
import { HiBuildingOffice } from "react-icons/hi2";
import {
IoIosAdd,
IoIosHelpCircleOutline,
IoIosRadio,
IoMdClose,
IoMdLogOut,
@ -59,6 +58,7 @@ import {
IoBookOutline,
IoCloudDownloadOutline,
IoFootsteps,
IoHelp,
IoHomeOutline,
IoShareSocial,
IoWarningOutline,
@ -146,7 +146,7 @@ export const iconList: { [name: string]: IconType } = {
goal: LuGoal,
graph: VscGraph,
hashtag: RiHashtag,
help: IoIosHelpCircleOutline,
help: IoHelp,
hide: LuArrowLeftFromLine,
history: MdHistory,
home: IoHomeOutline,