feat(frontend): disabled searchBar if no remaining credits or no brain selected (#2788)

# 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-07-01 14:50:09 +02:00 committed by GitHub
parent 2e4b80138c
commit bfdc5c8cf8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 92 additions and 58 deletions

View File

@ -6,12 +6,15 @@ import { Text } from "@tiptap/extension-text";
import { Extension, useEditor } from "@tiptap/react"; import { Extension, useEditor } from "@tiptap/react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
import { useBrainMention } from "./useBrainMention"; import { useBrainMention } from "./useBrainMention";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useCreateEditorState = (placeholder?: string) => { export const useCreateEditorState = (placeholder?: string) => {
const { t } = useTranslation(["chat"]); const { t } = useTranslation(["chat"]);
const { BrainMention, items } = useBrainMention(); const { BrainMention, items } = useBrainMention();
const { remainingCredits } = useUserSettingsContext();
const PreventEnter = Extension.create({ const PreventEnter = Extension.create({
addKeyboardShortcuts: () => { addKeyboardShortcuts: () => {
@ -24,7 +27,7 @@ export const useCreateEditorState = (placeholder?: string) => {
const editor = useEditor( const editor = useEditor(
{ {
autofocus: true, autofocus: !!remainingCredits,
onFocus: () => { onFocus: () => {
editor?.commands.focus("end"); editor?.commands.focus("end");
}, },

View File

@ -13,9 +13,11 @@
.chat_wrapper { .chat_wrapper {
display: flex; display: flex;
padding: Spacings.$spacing05; padding: Spacings.$spacing05;
padding-top: 0;
&.with_brain { &.disabled {
padding-top: 0; pointer-events: none;
opacity: 0.4;
} }
} }
} }

View File

@ -4,6 +4,7 @@ import { CurrentBrain } from "@/lib/components/CurrentBrain/CurrentBrain";
import Icon from "@/lib/components/ui/Icon/Icon"; import Icon from "@/lib/components/ui/Icon/Icon";
import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon"; import { LoaderIcon } from "@/lib/components/ui/LoaderIcon/LoaderIcon";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
import { ChatEditor } from "./components/ChatEditor/ChatEditor"; import { ChatEditor } from "./components/ChatEditor/ChatEditor";
import { useChatInput } from "./hooks/useChatInput"; import { useChatInput } from "./hooks/useChatInput";
@ -12,10 +13,11 @@ import styles from "./index.module.scss";
export const ChatInput = (): JSX.Element => { export const ChatInput = (): JSX.Element => {
const { setMessage, submitQuestion, generatingAnswer, message } = const { setMessage, submitQuestion, generatingAnswer, message } =
useChatInput(); useChatInput();
const { remainingCredits } = useUserSettingsContext();
const { currentBrain } = useBrainContext(); const { currentBrain } = useBrainContext();
const handleSubmitQuestion = () => { const handleSubmitQuestion = () => {
if (message.trim() !== "") { if (message.trim() !== "" && remainingCredits && currentBrain) {
submitQuestion(); submitQuestion();
} }
}; };
@ -30,12 +32,14 @@ export const ChatInput = (): JSX.Element => {
}} }}
> >
<div className={styles.chat_container}> <div className={styles.chat_container}>
<CurrentBrain allowingRemoveBrain={false} /> <CurrentBrain
allowingRemoveBrain={false}
remainingCredits={remainingCredits}
/>
<div <div
className={` className={`${styles.chat_wrapper} ${
${styles.chat_wrapper} !remainingCredits ? styles.disabled : ""
${currentBrain ? styles.with_brain : ""} }`}
`}
> >
<ChatEditor <ChatEditor
message={message} message={message}
@ -49,7 +53,7 @@ export const ChatInput = (): JSX.Element => {
name="followUp" name="followUp"
size="large" size="large"
color="accent" color="accent"
disabled={!message} disabled={!message || !remainingCredits || !currentBrain}
handleHover={true} handleHover={true}
onClick={handleSubmitQuestion} onClick={handleSubmitQuestion}
/> />

View File

@ -53,23 +53,6 @@
} }
} }
} }
.shortcuts_card_wrapper {
background-color: var(--background-2);
padding: Spacings.$spacing05;
gap: Spacings.$spacing03;
border-radius: Radius.$big;
.shortcut_wrapper {
display: flex;
align-items: center;
gap: Spacings.$spacing02;
.shortcut {
color: var(--primary-0);
}
}
}
} }
} }
@ -105,4 +88,4 @@
display: flex; display: flex;
gap: Spacings.$spacing05; gap: Spacings.$spacing05;
} }
} }

View File

@ -72,13 +72,6 @@ const Search = (): JSX.Element => {
<SearchBar /> <SearchBar />
</div> </div>
</div> </div>
<div className={styles.shortcuts_card_wrapper}>
<div className={styles.shortcut_wrapper}>
<span>Press</span>
<span className={styles.shortcut}>@</span>
<span>to select a brain</span>
</div>
</div>
</div> </div>
<UploadDocumentModal /> <UploadDocumentModal />
<AddBrainModal /> <AddBrainModal />

View File

@ -1,12 +1,20 @@
@use "styles/Spacings.module.scss"; @use "styles/Spacings.module.scss";
@use "styles/Typography.module.scss"; @use "styles/Typography.module.scss";
.current_brain_wrapper { %header_style {
background-color: var(--background-2); background-color: var(--background-2);
padding-inline: Spacings.$spacing05; padding-inline: Spacings.$spacing05;
padding-block: Spacings.$spacing01; padding-block: Spacings.$spacing01;
font-size: Typography.$small; font-size: Typography.$small;
color: var(--text-1); }
@mixin textColor($color) {
color: var(--#{$color});
}
.current_brain_wrapper {
@extend %header_style;
@include textColor(text-1);
.brain_infos { .brain_infos {
display: flex; display: flex;
@ -20,7 +28,8 @@
align-items: center; align-items: center;
@include Typography.EllipsisOverflow; @include Typography.EllipsisOverflow;
.title { .title,
.brain_name {
white-space: nowrap; white-space: nowrap;
} }
@ -35,10 +44,27 @@
} }
.brain_name { .brain_name {
color: var(--text-3); @include textColor(text-3);
@include Typography.EllipsisOverflow; @include Typography.EllipsisOverflow;
} }
} }
} }
} }
} }
.no_brain_selected,
.no_credits_left {
@extend %header_style;
}
.no_brain_selected {
@include textColor(warning);
.strong {
font-weight: 800;
}
}
.no_credits_left {
@include textColor(dangerous);
}

View File

@ -9,10 +9,12 @@ import { Icon } from "../ui/Icon/Icon";
interface CurrentBrainProps { interface CurrentBrainProps {
allowingRemoveBrain: boolean; allowingRemoveBrain: boolean;
remainingCredits: number | null;
} }
export const CurrentBrain = ({ export const CurrentBrain = ({
allowingRemoveBrain, allowingRemoveBrain,
remainingCredits,
}: CurrentBrainProps): JSX.Element => { }: CurrentBrainProps): JSX.Element => {
const { currentBrain, setCurrentBrainId } = useBrainContext(); const { currentBrain, setCurrentBrainId } = useBrainContext();
const { isDarkMode } = useUserSettingsContext(); const { isDarkMode } = useUserSettingsContext();
@ -20,8 +22,24 @@ export const CurrentBrain = ({
setCurrentBrainId(null); setCurrentBrainId(null);
}; };
if (!remainingCredits) {
return (
<div className={styles.no_credits_left}>
<span>
Youve run out of credits! Upgrade your plan now to continue chatting.
</span>
</div>
);
}
if (!currentBrain) { if (!currentBrain) {
return <></>; return (
<div className={styles.no_brain_selected}>
<span>
Press <strong className={styles.strong}>@</strong> to select a Brain
</span>
</div>
);
} }
return ( return (

View File

@ -18,9 +18,11 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: Spacings.$spacing05; padding: Spacings.$spacing05;
padding-top: 0;
&.with_brain { &.disabled {
padding-top: 0; pointer-events: none;
opacity: 0.3;
} }
.search_icon { .search_icon {
@ -36,4 +38,4 @@
} }
} }
} }
} }

View File

@ -6,6 +6,7 @@ import { useChatInput } from "@/app/chat/[chatId]/components/ActionsBar/componen
import { useChat } from "@/app/chat/[chatId]/hooks/useChat"; import { useChat } from "@/app/chat/[chatId]/hooks/useChat";
import { useChatContext } from "@/lib/context"; import { useChatContext } from "@/lib/context";
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext";
import { useUserSettingsContext } from "@/lib/context/UserSettingsProvider/hooks/useUserSettingsContext";
import styles from "./SearchBar.module.scss"; import styles from "./SearchBar.module.scss";
@ -23,6 +24,7 @@ export const SearchBar = ({
const { setMessages } = useChatContext(); const { setMessages } = useChatContext();
const { addQuestion } = useChat(); const { addQuestion } = useChat();
const { currentBrain, setCurrentBrainId } = useBrainContext(); const { currentBrain, setCurrentBrainId } = useBrainContext();
const { remainingCredits } = useUserSettingsContext();
useEffect(() => { useEffect(() => {
setCurrentBrainId(null); setCurrentBrainId(null);
@ -33,7 +35,7 @@ export const SearchBar = ({
}, [message]); }, [message]);
const submit = async (): Promise<void> => { const submit = async (): Promise<void> => {
if (!searching) { if (!!remainingCredits && !!currentBrain && !searching) {
setSearching(true); setSearching(true);
setMessages([]); setMessages([]);
try { try {
@ -50,18 +52,15 @@ export const SearchBar = ({
}; };
return ( return (
<div <div className={styles.search_bar_wrapper}>
className={` <CurrentBrain
${styles.search_bar_wrapper} allowingRemoveBrain={true}
${currentBrain ? styles.with_brain : ""} remainingCredits={remainingCredits}
`} />
>
<CurrentBrain allowingRemoveBrain={true} />
<div <div
className={` className={`${styles.editor_wrapper} ${
${styles.editor_wrapper} !remainingCredits ? styles.disabled : ""
${currentBrain ? styles.with_brain : ""} }`}
`}
> >
<Editor <Editor
message={message} message={message}
@ -75,7 +74,11 @@ export const SearchBar = ({
<LuSearch <LuSearch
className={` className={`
${styles.search_icon} ${styles.search_icon}
${isDisabled ? styles.disabled : ""} ${
isDisabled || !remainingCredits || !currentBrain
? styles.disabled
: ""
}
`} `}
onClick={() => void submit()} onClick={() => void submit()}
/> />