-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(chat): add brain selection through mention input
- Loading branch information
1 parent
85ae06c
commit 18305ad
Showing
29 changed files
with
791 additions
and
340 deletions.
There are no files selected for viewing
2 changes: 1 addition & 1 deletion
2
frontend/app/chat/[chatId]/components/ActionsBar/ActionsBar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
7 changes: 7 additions & 0 deletions
7
...p/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/ChatBar.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
"use client"; | ||
|
||
import { CustomComponentMentionEditor } from "./components/MentionInput/MentionInput"; | ||
|
||
export const ChatBar = (): JSX.Element => { | ||
return <CustomComponentMentionEditor />; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
...tionsBar/components/ChatInput/components/ChatBar/components/MentionInput/MentionInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import Editor from "@draft-js-plugins/editor"; | ||
import { Popover } from "@draft-js-plugins/mention"; | ||
import { ReactElement } from "react"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
import "draft-js/dist/Draft.css"; | ||
import { AddNewBrainButton } from "./components/AddNewBrainButton"; | ||
import { useMentionInput } from "./hooks/useMentionInput"; | ||
|
||
export const CustomComponentMentionEditor = (): ReactElement => { | ||
const { | ||
mentionInputRef, | ||
MentionSuggestions, | ||
editorState, | ||
onOpenChange, | ||
onSearchChange, | ||
open, | ||
plugins, | ||
setEditorState, | ||
suggestions, | ||
onAddMention, | ||
} = useMentionInput(); | ||
const { t } = useTranslation(["chat"]); | ||
|
||
return ( | ||
<div | ||
className="w-full" | ||
onClick={() => { | ||
mentionInputRef.current?.focus(); | ||
}} | ||
> | ||
<Editor | ||
editorKey={"editor"} | ||
editorState={editorState} | ||
onChange={setEditorState} | ||
plugins={plugins} | ||
ref={mentionInputRef} | ||
placeholder={t("actions_bar_placeholder")} | ||
/> | ||
<MentionSuggestions | ||
open={open} | ||
onOpenChange={onOpenChange} | ||
suggestions={suggestions} | ||
onSearchChange={onSearchChange} | ||
popoverContainer={({ children, ...otherProps }) => ( | ||
<Popover {...otherProps}> | ||
<div className="z-50 bg-white dark:bg-black border border-black/10 dark:border-white/25 rounded-md shadow-md overflow-y-auto"> | ||
{children} | ||
<AddNewBrainButton /> | ||
</div> | ||
</Popover> | ||
)} | ||
onAddMention={onAddMention} | ||
entryComponent={({ mention, ...otherProps }) => ( | ||
<p | ||
{...otherProps} | ||
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer" | ||
> | ||
{mention.name} | ||
</p> | ||
)} | ||
/> | ||
</div> | ||
); | ||
}; |
9 changes: 9 additions & 0 deletions
9
...nts/ChatInput/components/ChatBar/components/MentionInput/components/AddNewBrainButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import Link from "next/link"; | ||
|
||
import Button from "@/lib/components/ui/Button"; | ||
|
||
export const AddNewBrainButton = (): JSX.Element => ( | ||
<Link href={"/brains-management"}> | ||
<Button variant={"tertiary"}>Add new brain</Button> | ||
</Link> | ||
); |
1 change: 1 addition & 0 deletions
1
...nsBar/components/ChatInput/components/ChatBar/components/MentionInput/components/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./AddNewBrainButton"; |
188 changes: 188 additions & 0 deletions
188
...components/ChatInput/components/ChatBar/components/MentionInput/hooks/useMentionInput.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
/* eslint-disable max-lines */ | ||
import Editor from "@draft-js-plugins/editor"; | ||
import createMentionPlugin, { | ||
defaultSuggestionsFilter, | ||
MentionData, | ||
} from "@draft-js-plugins/mention"; | ||
import { UUID } from "crypto"; | ||
import { EditorState, Modifier } from "draft-js"; | ||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
|
||
import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; | ||
|
||
import { BrainMentionsList } from "../../../types"; | ||
import { BrainMentionItem } from "../../BrainMentionItem"; | ||
import { mapToMentionData } from "../utils/mapToMentionData"; | ||
|
||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
export const useMentionInput = () => { | ||
const { allBrains } = useBrainContext(); | ||
|
||
const [selectedBrainAddedOnload, setSelectedBrainAddedOnload] = | ||
useState(false); | ||
|
||
const { setCurrentBrainId, currentBrainId } = useBrainContext(); | ||
const [editorState, setEditorState] = useState(() => | ||
EditorState.createEmpty() | ||
); | ||
|
||
const [open, setOpen] = useState(false); | ||
|
||
const [mentionItems, setMentionItems] = useState<BrainMentionsList>({ | ||
"@": allBrains.map((brain) => ({ ...brain, value: brain.name })), | ||
}); | ||
|
||
const [suggestions, setSuggestions] = useState( | ||
mapToMentionData(mentionItems["@"]) | ||
); | ||
|
||
const mentionInputRef = useRef<Editor>(null); | ||
|
||
const onAddMention = (mention: MentionData) => { | ||
setCurrentBrainId(mention.id as UUID); | ||
}; | ||
|
||
const removeMention = (entityKeyToRemove: string): void => { | ||
const contentState = editorState.getCurrentContent(); | ||
const entity = contentState.getEntity(entityKeyToRemove); | ||
|
||
if (entity.getType() === "mention") { | ||
const newContentState = contentState.replaceEntityData( | ||
entityKeyToRemove, | ||
{} | ||
); | ||
|
||
const newEditorState = EditorState.push( | ||
editorState, | ||
newContentState, | ||
"apply-entity" | ||
); | ||
|
||
setEditorState(newEditorState); | ||
setCurrentBrainId(null); | ||
} | ||
}; | ||
|
||
const { MentionSuggestions, plugins } = useMemo(() => { | ||
const mentionPlugin = createMentionPlugin({ | ||
mentionComponent: ({ entityKey, mention: { name } }) => ( | ||
<BrainMentionItem | ||
text={name + "" + entityKey} | ||
onRemove={() => removeMention(entityKey)} | ||
/> | ||
), | ||
|
||
popperOptions: { | ||
placement: "top-end", | ||
}, | ||
}); | ||
const { MentionSuggestions: coreMentionSuggestions } = mentionPlugin; | ||
const corePlugins = [mentionPlugin]; | ||
|
||
return { plugins: corePlugins, MentionSuggestions: coreMentionSuggestions }; | ||
}, []); | ||
|
||
const onOpenChange = useCallback((_open: boolean) => { | ||
setOpen(_open); | ||
}, []); | ||
|
||
const onSearchChange = useCallback( | ||
({ trigger, value }: { trigger: string; value: string }) => { | ||
setSuggestions( | ||
defaultSuggestionsFilter( | ||
value, | ||
currentBrainId !== null ? [] : mapToMentionData(mentionItems["@"]), | ||
trigger | ||
) | ||
); | ||
}, | ||
[mentionItems, currentBrainId] | ||
); | ||
|
||
useEffect(() => { | ||
setSuggestions(mapToMentionData(mentionItems["@"])); | ||
}, [mentionItems]); | ||
|
||
useEffect(() => { | ||
setMentionItems({ | ||
...mentionItems, | ||
"@": [ | ||
...allBrains.map((brain) => ({ | ||
...brain, | ||
value: brain.name, | ||
})), | ||
], | ||
}); | ||
}, [allBrains]); | ||
useEffect(() => { | ||
if (selectedBrainAddedOnload) { | ||
return; | ||
} | ||
|
||
if (currentBrainId === null || mentionItems["@"].length === 0) { | ||
return; | ||
} | ||
|
||
const mention = mentionItems["@"].find( | ||
(item) => item.id === currentBrainId | ||
); | ||
|
||
if (mention !== undefined) { | ||
const mentionText = `@${mention.name}`; | ||
const mentionWithSpace = `${mentionText} `; // Add white space after the mention | ||
|
||
// Check if the mention with white space is already in the editor's content | ||
const contentState = editorState.getCurrentContent(); | ||
const plainText = contentState.getPlainText(); | ||
|
||
if (plainText.includes(mentionWithSpace)) { | ||
return; // Mention with white space already in content, no need to add it again | ||
} | ||
|
||
const stateWithEntity = contentState.createEntity( | ||
"mention", | ||
"IMMUTABLE", | ||
{ | ||
mention, | ||
} | ||
); | ||
const entityKey = stateWithEntity.getLastCreatedEntityKey(); | ||
|
||
const selectionState = editorState.getSelection(); | ||
const updatedContentState = Modifier.insertText( | ||
contentState, | ||
selectionState, | ||
mentionWithSpace, | ||
undefined, | ||
entityKey | ||
); | ||
|
||
// Calculate the new selection position after inserting the mention with white space | ||
const newSelection = selectionState.merge({ | ||
anchorOffset: selectionState.getStartOffset() + mentionWithSpace.length, | ||
focusOffset: selectionState.getStartOffset() + mentionWithSpace.length, | ||
}); | ||
|
||
const newEditorState = EditorState.forceSelection( | ||
EditorState.push(editorState, updatedContentState, "insert-characters"), | ||
newSelection | ||
); | ||
|
||
setEditorState(newEditorState); | ||
} | ||
setSelectedBrainAddedOnload(true); | ||
}, [currentBrainId, mentionItems]); | ||
|
||
return { | ||
mentionInputRef, | ||
editorState, | ||
setEditorState, | ||
plugins, | ||
MentionSuggestions, | ||
onOpenChange, | ||
onSearchChange, | ||
open, | ||
suggestions, | ||
onAddMention, | ||
}; | ||
}; |
1 change: 1 addition & 0 deletions
1
...nents/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./MentionInput"; |
11 changes: 11 additions & 0 deletions
11
...components/ChatInput/components/ChatBar/components/MentionInput/utils/mapToMentionData.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { MentionData } from "@draft-js-plugins/mention"; | ||
|
||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; | ||
|
||
export const mapToMentionData = ( | ||
brains: MinimalBrainForUser[] | ||
): MentionData[] => | ||
brains.map((brain) => ({ | ||
name: brain.name, | ||
id: brain.id as string, | ||
})); |
1 change: 1 addition & 0 deletions
1
...chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./MentionInput"; |
11 changes: 11 additions & 0 deletions
11
...chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/hooks/useChatBar.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
import { useFeature } from "@growthbook/growthbook-react"; | ||
|
||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types | ||
export const useChatBar = () => { | ||
const shouldUseNewUX = useFeature("new-ux").on; | ||
|
||
return { | ||
shouldUseNewUX, | ||
}; | ||
}; |
1 change: 1 addition & 0 deletions
1
.../app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./ChatBar"; |
9 changes: 9 additions & 0 deletions
9
.../app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; | ||
|
||
export type BrainMentionType = MinimalBrainForUser & { value: string }; | ||
|
||
export type Trigger = "@" | "#"; | ||
|
||
export type BrainMentionsList = { | ||
"@": MinimalBrainForUser[]; | ||
}; |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
1 change: 1 addition & 0 deletions
1
frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./ChatBar"; |
2 changes: 1 addition & 1 deletion
2
...omponents/ChatInput/hooks/useChatInput.ts → ...omponents/ChatInput/hooks/useChatInput.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.