From 1fb39dfeb67ca1a1886a81d2796864df0890aace Mon Sep 17 00:00:00 2001 From: mamadoudicko Date: Tue, 22 Aug 2023 17:57:57 +0200 Subject: [PATCH] feat: add prompt trigger to mention input --- frontend/app/App.tsx | 6 +- .../hooks/helpers/MentionPlugin.tsx | 19 +++-- .../hooks/helpers/MentionState.ts | 59 +++++++------ .../hooks/helpers/MentionUtils.ts | 15 +++- .../MentionInput/hooks/useMentionInput.tsx | 84 +++++++++++-------- .../utils/mapMinimalBrainToMentionData.ts | 12 +-- .../utils/mapPromptToMentionData.ts | 9 ++ .../{BrainMentionItem.tsx => MentionItem.tsx} | 8 +- .../ChatInput/components/ChatBar/types.ts | 10 ++- .../[chatId]/components/ActionsBar/types.ts | 4 +- frontend/app/chat/[chatId]/hooks/useChat.ts | 3 +- .../BrainProvider/hooks/useBrainProvider.ts | 18 ++++ 12 files changed, 159 insertions(+), 88 deletions(-) create mode 100644 frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapPromptToMentionData.ts rename frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/{BrainMentionItem.tsx => MentionItem.tsx} (71%) diff --git a/frontend/app/App.tsx b/frontend/app/App.tsx index bb40172b645c..5283a3b47708 100644 --- a/frontend/app/App.tsx +++ b/frontend/app/App.tsx @@ -7,17 +7,19 @@ import { NavBar } from "@/lib/components/NavBar"; import { TrackingWrapper } from "@/lib/components/TrackingWrapper"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { useSupabase } from "@/lib/context/SupabaseProvider"; -import '../lib/config/LocaleConfig/i18n' import { UpdateMetadata } from "@/lib/helpers/updateMetadata"; +import "../lib/config/LocaleConfig/i18n"; // This wrapper is used to make effect calls at a high level in app rendering. export const App = ({ children }: PropsWithChildren): JSX.Element => { - const { fetchAllBrains, fetchAndSetActiveBrain } = useBrainContext(); + const { fetchAllBrains, fetchAndSetActiveBrain, fetchPublicPrompts } = + useBrainContext(); const { session } = useSupabase(); useEffect(() => { void fetchAllBrains(); void fetchAndSetActiveBrain(); + void fetchPublicPrompts(); }, [session?.user]); return ( diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionPlugin.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionPlugin.tsx index 981b7a1b5004..105947abbee1 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionPlugin.tsx +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionPlugin.tsx @@ -1,9 +1,10 @@ import createMentionPlugin from "@draft-js-plugins/mention"; import { useMemo } from "react"; +import { MentionTriggerType } from "@/app/chat/[chatId]/components/ActionsBar/types"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; -import { BrainMentionItem } from "../../../BrainMentionItem"; +import { MentionItem } from "../../../MentionItem"; interface MentionPluginProps { removeMention: (entityKeyToRemove: string) => void; @@ -12,20 +13,26 @@ interface MentionPluginProps { // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useMentionPlugin = (props: MentionPluginProps) => { const { removeMention } = props; - const { setCurrentBrainId } = useBrainContext(); + const { setCurrentBrainId, setCurrentPromptId } = useBrainContext(); const { MentionSuggestions, plugins } = useMemo(() => { const mentionPlugin = createMentionPlugin({ - mentionComponent: ({ entityKey, mention: { name } }) => ( - ( + { - setCurrentBrainId(null); + if (trigger === "@") { + setCurrentBrainId(null); + } + if (trigger === "#") { + setCurrentPromptId(null); + } removeMention(entityKey); }} + trigger={trigger as MentionTriggerType} /> ), - + mentionTrigger: ["@", "#"], popperOptions: { placement: "top-end", modifiers: [ diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionState.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionState.ts index 991ac774e042..3d603a0aa7c9 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionState.ts +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionState.ts @@ -1,26 +1,32 @@ +/* eslint-disable max-lines */ +import { MentionData } from "@draft-js-plugins/mention"; import { EditorState } from "draft-js"; import { useEffect, useState } from "react"; -import { MentionTriggerType } from "@/app/chat/[chatId]/components/ActionsBar/types"; +import { + mentionTriggers, + MentionTriggerType, +} from "@/app/chat/[chatId]/components/ActionsBar/types"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; import { MentionInputMentionsType, TriggerMap } from "../../../../types"; import { mapMinimalBrainToMentionData } from "../../utils/mapMinimalBrainToMentionData"; +import { mapPromptToMentionData } from "../../utils/mapPromptToMentionData"; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useMentionState = () => { - const { allBrains } = useBrainContext(); + const { allBrains, publicPrompts } = useBrainContext(); + const [editorState, legacySetEditorState] = useState(() => EditorState.createEmpty() ); const [mentionItems, setMentionItems] = useState({ - "@": allBrains.map((brain) => ({ ...brain, value: brain.name })), + "@": allBrains.map(mapMinimalBrainToMentionData), + "#": publicPrompts.map(mapPromptToMentionData), }); - const [suggestions, setSuggestions] = useState( - mapMinimalBrainToMentionData(mentionItems["@"]) - ); + const [suggestions, setSuggestions] = useState([]); const setEditorState = (newState: EditorState) => { const currentSelection = newState.getSelection(); @@ -35,22 +41,20 @@ export const useMentionState = () => { const getEditorCurrentMentions = (): TriggerMap[] => { const contentState = editorState.getCurrentContent(); const plainText = contentState.getPlainText(); - const mentionTriggers = Object.keys(mentionItems); + console.log({ plainText }); const mentionTexts: TriggerMap[] = []; mentionTriggers.forEach((trigger) => { - if (trigger === "@") { - mentionItems["@"].forEach((item) => { - const mentionText = `${trigger}${item.name}`; - if (plainText.includes(mentionText)) { - mentionTexts.push({ - trigger: trigger as MentionTriggerType, - content: item.name, - }); - } - }); - } + mentionItems[trigger].forEach((item) => { + const mentionText = `${trigger}${item.name}`; + if (plainText.includes(mentionText)) { + mentionTexts.push({ + trigger: trigger, + content: item.name, + }); + } + }); }); return mentionTexts; @@ -61,8 +65,8 @@ export const useMentionState = () => { ): string => { const contentState = editorCurrentState.getCurrentContent(); let plainText = contentState.getPlainText(); - Object.keys(mentionItems).forEach((trigger) => { - if (trigger === "@") { + (Object.keys(mentionItems) as MentionTriggerType[]).forEach((trigger) => { + if (mentionTriggers.includes(trigger)) { mentionItems[trigger].forEach((item) => { const regex = new RegExp(`${trigger}${item.name}`, "g"); plainText = plainText.replace(regex, ""); @@ -76,15 +80,17 @@ export const useMentionState = () => { useEffect(() => { setMentionItems({ ...mentionItems, - "@": [ - ...allBrains.map((brain) => ({ - ...brain, - value: brain.name, - })), - ], + "@": allBrains.map(mapMinimalBrainToMentionData), }); }, [allBrains]); + useEffect(() => { + setMentionItems({ + ...mentionItems, + "#": publicPrompts.map(mapPromptToMentionData), + }); + }, [publicPrompts]); + return { editorState, setEditorState, @@ -94,5 +100,6 @@ export const useMentionState = () => { suggestions, getEditorCurrentMentions, getEditorTextWithoutMentions, + publicPrompts, }; }; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionUtils.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionUtils.ts index 789eb64fcee7..6f0557290ba6 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionUtils.ts +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/helpers/MentionUtils.ts @@ -1,7 +1,10 @@ import { addMention, MentionData } from "@draft-js-plugins/mention"; import { EditorState } from "draft-js"; -import { MentionTriggerType } from "@/app/chat/[chatId]/components/ActionsBar/types"; +import { + mentionTriggers, + MentionTriggerType, +} from "@/app/chat/[chatId]/components/ActionsBar/types"; import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainContext"; type MentionUtilsProps = { @@ -9,6 +12,11 @@ type MentionUtilsProps = { setEditorState: (editorState: EditorState) => void; }; +const mentionsTags = [ + "mention", + ...mentionTriggers.map((trigger) => `${trigger}mention`), +]; + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const useMentionUtils = (props: MentionUtilsProps) => { const { editorState, setEditorState } = props; @@ -18,7 +26,7 @@ export const useMentionUtils = (props: MentionUtilsProps) => { const contentState = editorState.getCurrentContent(); const entity = contentState.getEntity(entityKeyToRemove); - if (entity.getType() === "mention") { + if (mentionsTags.includes(entity.getType())) { const newContentState = contentState.replaceEntityData( entityKeyToRemove, {} @@ -37,9 +45,10 @@ export const useMentionUtils = (props: MentionUtilsProps) => { const insertMention = ( mention: MentionData, - trigger: MentionTriggerType, customEditorState?: EditorState ): EditorState => { + const trigger = mention.trigger as MentionTriggerType; + const editorStateWithMention = addMention( customEditorState ?? editorState, mention, diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/useMentionInput.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/useMentionInput.tsx index e4ea9dc2037d..c1c15074debc 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/useMentionInput.tsx +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/hooks/useMentionInput.tsx @@ -13,10 +13,9 @@ import { useBrainContext } from "@/lib/context/BrainProvider/hooks/useBrainConte import { useMentionPlugin } from "./helpers/MentionPlugin"; import { useMentionState } from "./helpers/MentionState"; import { useMentionUtils } from "./helpers/MentionUtils"; -import { mapMinimalBrainToMentionData } from "../utils/mapMinimalBrainToMentionData"; - import "@draft-js-plugins/mention/lib/plugin.css"; import "draft-js/dist/Draft.css"; +import { mapMinimalBrainToMentionData } from "../utils/mapMinimalBrainToMentionData"; type UseMentionInputProps = { message: string; @@ -30,7 +29,13 @@ export const useMentionInput = ({ onSubmit, setMessage, }: UseMentionInputProps) => { - const { allBrains, currentBrainId, setCurrentBrainId } = useBrainContext(); + const { + allBrains, + currentBrainId, + currentPromptId, + setCurrentBrainId, + setCurrentPromptId, + } = useBrainContext(); const { editorState, @@ -41,6 +46,7 @@ export const useMentionInput = ({ suggestions, getEditorCurrentMentions, getEditorTextWithoutMentions, + publicPrompts, } = useMentionState(); const { removeMention, insertMention } = useMentionUtils({ @@ -64,7 +70,13 @@ export const useMentionInput = ({ }, []); const onAddMention = (mention: MentionData) => { - setCurrentBrainId(mention.id as UUID); + if (mention.trigger === "#") { + setCurrentPromptId(mention.id as UUID); + } + + if (mention.trigger === "@") { + setCurrentBrainId(mention.id as UUID); + } }; const onSearchChange = ({ @@ -74,18 +86,18 @@ export const useMentionInput = ({ trigger: string; value: string; }) => { - if (currentBrainId !== null) { + if (currentBrainId !== null && trigger === "@") { setSuggestions([]); return; } - setSuggestions( - defaultSuggestionsFilter( - value, - mapMinimalBrainToMentionData(mentionItems["@"]), - trigger - ) - ); + if (currentPromptId !== null && trigger === "#") { + setSuggestions([]); + + return; + } + + setSuggestions(defaultSuggestionsFilter(value, mentionItems, trigger)); }; const insertCurrentBrainAsMention = (): void => { @@ -94,7 +106,16 @@ export const useMentionInput = ({ ); if (mention !== undefined) { - insertMention(mention, "@"); + insertMention(mention); + } + }; + const insertCurrentPromptAsMention = (): void => { + const mention = mentionItems["#"].find( + (item) => item.id === currentPromptId + ); + + if (mention !== undefined) { + insertMention(mention); } }; @@ -102,17 +123,11 @@ export const useMentionInput = ({ const currentMentions = getEditorCurrentMentions(); let newEditorState = EditorState.createEmpty(); currentMentions.forEach((mention) => { - if (mention.trigger === "@") { - const correspondingMention = mentionItems["@"].find( - (item) => item.name === mention.content - ); - if (correspondingMention !== undefined) { - newEditorState = insertMention( - correspondingMention, - mention.trigger, - newEditorState - ); - } + const correspondingMention = mentionItems[mention.trigger].find( + (item) => item.name === mention.content + ); + if (correspondingMention !== undefined) { + newEditorState = insertMention(correspondingMention, newEditorState); } }); setEditorState(newEditorState); @@ -144,19 +159,10 @@ export const useMentionInput = ({ } }, [message]); - useEffect(() => { - setSuggestions(mapMinimalBrainToMentionData(mentionItems["@"])); - }, [mentionItems]); - useEffect(() => { setMentionItems({ ...mentionItems, - "@": [ - ...allBrains.map((brain) => ({ - ...brain, - value: brain.name, - })), - ], + "@": allBrains.map(mapMinimalBrainToMentionData), }); }, [allBrains]); @@ -178,8 +184,13 @@ export const useMentionInput = ({ const contentState = editorState.getCurrentContent(); const plainText = contentState.getPlainText(); - if (plainText === "" && currentBrainId !== null) { - insertCurrentBrainAsMention(); + if (plainText === "") { + if (currentBrainId !== null) { + insertCurrentBrainAsMention(); + } + if (currentPromptId !== null) { + insertCurrentPromptAsMention(); + } } }, [editorState]); @@ -196,5 +207,6 @@ export const useMentionInput = ({ insertCurrentBrainAsMention, handleEditorChange, keyBindingFn, + publicPrompts, }; }; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapMinimalBrainToMentionData.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapMinimalBrainToMentionData.ts index 0eb02e66730e..1dd96272db38 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapMinimalBrainToMentionData.ts +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapMinimalBrainToMentionData.ts @@ -3,9 +3,9 @@ import { MentionData } from "@draft-js-plugins/mention"; import { MinimalBrainForUser } from "@/lib/context/BrainProvider/types"; export const mapMinimalBrainToMentionData = ( - brains: MinimalBrainForUser[] -): MentionData[] => - brains.map((brain) => ({ - name: brain.name, - id: brain.id as string, - })); + brain: MinimalBrainForUser +): MentionData => ({ + name: brain.name, + id: brain.id as string, + trigger: "@", +}); diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapPromptToMentionData.ts b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapPromptToMentionData.ts new file mode 100644 index 000000000000..4010908df922 --- /dev/null +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionInput/utils/mapPromptToMentionData.ts @@ -0,0 +1,9 @@ +import { MentionData } from "@draft-js-plugins/mention"; + +import { Prompt } from "@/lib/types/Prompt"; + +export const mapPromptToMentionData = (prompt: Prompt): MentionData => ({ + name: prompt.title, + id: prompt.id, + trigger: "#", +}); diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/BrainMentionItem.tsx b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionItem.tsx similarity index 71% rename from frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/BrainMentionItem.tsx rename to frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionItem.tsx index 3068c22fad7a..56cfbb3c4518 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/BrainMentionItem.tsx +++ b/frontend/app/chat/[chatId]/components/ActionsBar/components/ChatInput/components/ChatBar/components/MentionItem.tsx @@ -1,18 +1,22 @@ import { MdRemoveCircleOutline } from "react-icons/md"; +import { MentionTriggerType } from "../../../../../types"; + type MentionItemProps = { text: string; onRemove: () => void; + trigger?: MentionTriggerType; }; -export const BrainMentionItem = ({ +export const MentionItem = ({ text, onRemove, + trigger, }: MentionItemProps): JSX.Element => { return (
- @{text} + {`${trigger ?? ""}${text}`} ; + export type TriggerMap = { trigger: MentionTriggerType; content: string; diff --git a/frontend/app/chat/[chatId]/components/ActionsBar/types.ts b/frontend/app/chat/[chatId]/components/ActionsBar/types.ts index 81138cfa0219..a7fafa4bbbd7 100644 --- a/frontend/app/chat/[chatId]/components/ActionsBar/types.ts +++ b/frontend/app/chat/[chatId]/components/ActionsBar/types.ts @@ -1,3 +1,3 @@ -export type MentionType = { id: string; display: string }; +export const mentionTriggers = ["@", "#"] as const; -export type MentionTriggerType = "@" | "#"; +export type MentionTriggerType = (typeof mentionTriggers)[number]; diff --git a/frontend/app/chat/[chatId]/hooks/useChat.ts b/frontend/app/chat/[chatId]/hooks/useChat.ts index 660f7118a660..98cc2c663230 100644 --- a/frontend/app/chat/[chatId]/hooks/useChat.ts +++ b/frontend/app/chat/[chatId]/hooks/useChat.ts @@ -24,7 +24,7 @@ export const useChat = () => { const [generatingAnswer, setGeneratingAnswer] = useState(false); const { history } = useChatContext(); - const { currentBrain } = useBrainContext(); + const { currentBrain, currentPromptId } = useBrainContext(); const { publish } = useToast(); const { createChat } = useChatApi(); @@ -64,6 +64,7 @@ export const useChat = () => { temperature: chatConfig?.temperature, max_tokens: chatConfig?.maxTokens, brain_id: currentBrain?.id, + prompt_id: currentPromptId ?? undefined, }; await addStreamQuestion(currentChatId, chatQuestion); diff --git a/frontend/lib/context/BrainProvider/hooks/useBrainProvider.ts b/frontend/lib/context/BrainProvider/hooks/useBrainProvider.ts index d433afec9e07..43cd044c07d9 100644 --- a/frontend/lib/context/BrainProvider/hooks/useBrainProvider.ts +++ b/frontend/lib/context/BrainProvider/hooks/useBrainProvider.ts @@ -4,7 +4,9 @@ import { useCallback, useEffect, useState } from "react"; import { CreateBrainInput } from "@/lib/api/brain/types"; import { useBrainApi } from "@/lib/api/brain/useBrainApi"; +import { usePromptApi } from "@/lib/api/prompt/usePromptApi"; import { useToast } from "@/lib/hooks"; +import { Prompt } from "@/lib/types/Prompt"; import { useEventTracking } from "@/services/analytics/useEventTracking"; import { @@ -21,12 +23,18 @@ export const useBrainProvider = () => { const { track } = useEventTracking(); const { createBrain, deleteBrain, getBrains, getDefaultBrain } = useBrainApi(); + const { getPublicPrompts } = usePromptApi(); const [allBrains, setAllBrains] = useState([]); const [currentBrainId, setCurrentBrainId] = useState(null); const [defaultBrainId, setDefaultBrainId] = useState(); const [isFetchingBrains, setIsFetchingBrains] = useState(false); + const [publicPrompts, setPublicPrompts] = useState([]); + const [currentPromptId, setCurrentPromptId] = useState(null); + const currentPrompt = publicPrompts.find( + (prompt) => prompt.id === currentPromptId + ); const currentBrain = allBrains.find((brain) => brain.id === currentBrainId); const createBrainHandler = async ( brain: CreateBrainInput @@ -100,6 +108,11 @@ export const useBrainProvider = () => { const fetchDefaultBrain = async () => { setDefaultBrainId((await getDefaultBrain())?.id); }; + + const fetchPublicPrompts = async () => { + setPublicPrompts(await getPublicPrompts()); + }; + useEffect(() => { void fetchDefaultBrain(); }, []); @@ -118,5 +131,10 @@ export const useBrainProvider = () => { isFetchingBrains, defaultBrainId, fetchDefaultBrain, + fetchPublicPrompts, + publicPrompts, + currentPrompt, + setCurrentPromptId, + currentPromptId, }; };