From f73e9d7ba5d02df279ec052ff22afe1b8271e23f Mon Sep 17 00:00:00 2001 From: Flavien David Date: Mon, 18 Dec 2023 09:19:36 +0100 Subject: [PATCH 01/29] tmp --- .../assistant/conversation/InputBar.tsx | 430 ++-------- .../assistant/conversation/InputBarTipTap.tsx | 60 ++ .../assistant/conversation/MentionList.jsx | 95 +++ .../assistant/conversation/suggestion.js | 71 ++ front/package-lock.json | 789 +++++++++++++++++- front/package.json | 5 + 6 files changed, 1069 insertions(+), 381 deletions(-) create mode 100644 front/components/assistant/conversation/InputBarTipTap.tsx create mode 100644 front/components/assistant/conversation/MentionList.jsx create mode 100644 front/components/assistant/conversation/suggestion.js diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 399868b3400a..870f30497ea8 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -35,6 +35,8 @@ import { handleFileUploadToText } from "@app/lib/client/handle_file_upload"; import { useAgentConfigurations } from "@app/lib/swr"; import { classNames, filterAndSortAgents } from "@app/lib/utils"; +import Tiptap from "./InputBarTipTap"; + // AGENT MENTION function AgentMention({ @@ -193,6 +195,8 @@ function AgentListImpl( const AgentList = forwardRef(AgentListImpl); +import { useCurrentEditor } from "@tiptap/react"; + function moveCursorToEnd(el: HTMLElement) { const range = document.createRange(); const sel = window.getSelection(); @@ -295,35 +299,44 @@ export function AssistantInputBar({ const [isExpanded, setIsExpanded] = useState(false); + const { editor } = useCurrentEditor(); + const handleSubmit = async () => { - if (empty) { - return; - } - const contentEditable = document.getElementById("dust-input-bar"); + // if (empty) { + // return; + // } + + console.log(">> result:", JSON.stringify(editor?.getJSON(), null, 2)); + + const contentEditable = document.getElementsByClassName("dust-input-bar"); + console.log("> contentEditable:", contentEditable); if (contentEditable) { const mentions: MentionType[] = []; let content = ""; - Array.from(contentEditable.childNodes).forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - const agentConfigurationId = node.getAttribute( - "data-agent-configuration-id" - ); - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - const agentName = node.getAttribute("data-agent-name"); - - if (agentConfigurationId && agentName) { - mentions.push({ - configurationId: agentConfigurationId, - }); - // Internal format for mentions is `:mention[agentName]{sId=agentConfigurationId}`. - content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; - } - } - if (node.nodeType === Node.TEXT_NODE) { - content += node.textContent; - } - }); + + // Array.from(contentEditable.childNodes).forEach((node) => { + // if (node.nodeType === Node.ELEMENT_NODE) { + // // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. + // const agentConfigurationId = node.getAttribute( + // "data-agent-configuration-id" + // ); + // // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. + // const agentName = node.getAttribute("data-agent-name"); + + // if (agentConfigurationId && agentName) { + // mentions.push({ + // configurationId: agentConfigurationId, + // }); + // // Internal format for mentions is `:mention[agentName]{sId=agentConfigurationId}`. + // content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; + // } + // } + // if (node.nodeType === Node.TEXT_NODE) { + // content += node.textContent; + // } + // }); + + // content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; content = content.trim(); content = content.replace(/\u200B/g, ""); @@ -345,7 +358,7 @@ export function AssistantInputBar({ } setIsExpanded(false); onSubmit(content, mentions, contentFragment); - contentEditable.innerHTML = ""; + // contentEditable.innerHTML = ""; setContentFragmentFilename(undefined); setContentFragmentBody(undefined); } @@ -464,21 +477,17 @@ export function AssistantInputBar({ stickyMentionsTextContent, selectedAssistant, ]); - const contentEditableClasses = classNames( - "inline-block w-full", - "border-0 pr-1 py-3.5 pl-2 sm:pl-0 outline-none ring-0 focus:border-0 focus:outline-none focus:ring-0", - "whitespace-pre-wrap font-normal" - ); + return ( <> - + /> */} {generationContext.generatingMessageIds.length > 0 && (
@@ -505,349 +514,7 @@ export function AssistantInputBar({ : "" )} > -
- {/* Placeholder */} -
-
- Ask a question or get some @help -
-
Ask a question
-
- - {contentFragmentFilename && contentFragmentBody && ( -
- { - setContentFragmentBody(undefined); - setContentFragmentFilename(undefined); - }} - /> -
- )} - -
{ - e.preventDefault(); - - // Get the plain text. - const text = e.clipboardData.getData("text/plain"); - - const selection = window.getSelection(); - if (!selection) { - return; - } - const range = selection.getRangeAt(0); - let node = range.endContainer; - let offset = range.endOffset; - - if ( - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - node.getAttribute && - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - node.getAttribute("id") === "dust-input-bar" - ) { - const textNode = document.createTextNode(""); - node.appendChild(textNode); - node = textNode; - offset = 0; - } - - if ( - node.parentNode && - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - node.parentNode.getAttribute && - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - node.parentNode.getAttribute("id") === "dust-input-bar" - ) { - // Inject the text at the cursor position. - node.textContent = - node.textContent?.slice(0, offset) + - text + - node.textContent?.slice(offset); - } - - // Scroll to the end of the input - if (inputRef.current) { - setTimeout(() => { - const element = inputRef.current; - if (element) { - element.scrollTop = element.scrollHeight; - } - }, 0); - } - - // Move the cursor to the end of the paste. - const newRange = document.createRange(); - newRange.setStart(node, offset + text.length); - newRange.setEnd(node, offset + text.length); - selection.removeAllRanges(); - selection.addRange(newRange); - }} - onKeyDown={(e) => { - // We prevent the content editable from creating italics, bold and underline. - if (e.ctrlKey || e.metaKey) { - if (e.key === "u" || e.key === "b" || e.key === "i") { - e.preventDefault(); - } - } - if (!e.shiftKey && e.key === "Enter") { - e.preventDefault(); - e.stopPropagation(); - void handleSubmit(); - } - }} - onInput={() => { - const selection = window.getSelection(); - if ( - selection && - selection.rangeCount !== 0 && - selection.isCollapsed - ) { - const range = selection.getRangeAt(0); - const node = range.endContainer; - const offset = range.endOffset; - - const lastOne = node.textContent - ? node.textContent.slice(offset - 1, offset) - : null; - const preLastOne = node.textContent - ? node.textContent.slice(offset - 2, offset - 1) - : null; - - // Mention selection logic. - - if ( - lastOne === "@" && - (preLastOne === " " || preLastOne === "") && - node.textContent && - node.parentNode && - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - node.parentNode.getAttribute && - // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - node.parentNode.getAttribute("id") === "dust-input-bar" - ) { - const mentionSelectNode = document.createElement("div"); - - mentionSelectNode.style.display = "inline-block"; - mentionSelectNode.setAttribute("key", "mentionSelect"); - mentionSelectNode.className = "text-brand font-medium"; - mentionSelectNode.textContent = "@"; - mentionSelectNode.contentEditable = "false"; - - const inputNode = document.createElement("span"); - inputNode.setAttribute("ignore", "none"); - inputNode.className = classNames( - "min-w-0 px-0 py-0", - "border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0", - "text-brand font-medium" - ); - inputNode.contentEditable = "true"; - - mentionSelectNode.appendChild(inputNode); - - const beforeTextNode = document.createTextNode( - node.textContent.slice(0, offset - 1) - ); - const afterTextNode = document.createTextNode( - node.textContent.slice(offset) - ); - - node.parentNode.replaceChild(beforeTextNode, node); - - beforeTextNode.parentNode?.insertBefore( - afterTextNode, - beforeTextNode.nextSibling - ); - beforeTextNode.parentNode?.insertBefore( - mentionSelectNode, - afterTextNode - ); - - const rect = mentionSelectNode.getBoundingClientRect(); - const position = { - left: Math.floor(rect.left) - 24, - bottom: - Math.floor(window.innerHeight - rect.bottom) + 32, - }; - if (!isNaN(position.left) && !isNaN(position.bottom)) { - setAgentListPosition(position); - } - - setAgentListVisible(true); - inputNode.focus(); - - inputNode.onblur = () => { - let selected = agentListRef.current?.selected(); - setAgentListVisible(false); - setTimeout(() => { - setAgentListFilter(""); - agentListRef.current?.reset(); - }); - - if (inputNode.getAttribute("ignore") !== "none") { - selected = null; - } - - // console.log("SELECTED", selected); - - // We received a selected agent configration, recover the state of the - // contenteditable and inject an AgentMention component. - if (selected) { - // Construct an AgentMention component and inject it as HTML. - const mentionNode = getAgentMentionNode(selected); - - // This is mainly to please TypeScript. - if (!mentionNode || !mentionSelectNode.parentNode) { - return; - } - - // Replace mentionSelectNode with mentionNode. - mentionSelectNode.parentNode.replaceChild( - mentionNode, - mentionSelectNode - ); - - // Prepend a space to afterTextNode (this will be the space that comes after - // the mention). - afterTextNode.textContent = ` ${afterTextNode.textContent}`; - - // If afterTextNode is the last node add an invisible character to prevent a - // Chrome bugish behaviour ¯\_(ツ)_/¯ - if (afterTextNode.nextSibling === null) { - afterTextNode.textContent = `${afterTextNode.textContent}\u200B`; - } - - // Restore the cursor, taking into account the added space. - range.setStart(afterTextNode, 1); - range.setEnd(afterTextNode, 1); - selection.removeAllRanges(); - selection.addRange(range); - } - - // We didn't receive a selected agent configuration, restore the state of the - // contenteditable and re-inject the content that was created during the - // selection process into the contenteditable. - if (!selected && mentionSelectNode.parentNode) { - mentionSelectNode.parentNode.removeChild( - mentionSelectNode - ); - - range.setStart(afterTextNode, 0); - range.setEnd(afterTextNode, 0); - selection.removeAllRanges(); - selection.addRange(range); - - // Insert the content of mentionSelectNode after beforeTextNode only if - // we're not in ignore mode unless we are in ingnore mode (the user - // backspaced into the @) - if ( - inputNode.getAttribute("ignore") === "none" || - inputNode.getAttribute("ignore") === "space" - ) { - const newTextNode = document.createTextNode( - (mentionSelectNode.textContent || "") + - (inputNode.getAttribute("ignore") === "space" - ? " " - : "") - ); - beforeTextNode.parentNode?.insertBefore( - newTextNode, - beforeTextNode.nextSibling - ); - } - } - }; - - // These are events on the small contentEditable that receives the user input - // and drives the agent list selection. - inputNode.onkeydown = (e) => { - // console.log("KEYDOWN", e.key); - if (e.key === "Escape") { - agentListRef.current?.reset(); - inputNode.setAttribute("ignore", "escape"); - inputNode.blur(); - e.preventDefault(); - } - if (e.key === "ArrowDown") { - agentListRef.current?.next(); - e.preventDefault(); - } - if (e.key === "ArrowUp") { - agentListRef.current?.prev(); - e.preventDefault(); - } - if (e.key === "Backspace") { - if (inputNode.textContent === "") { - agentListRef.current?.reset(); - inputNode.setAttribute("ignore", "backspace"); - inputNode.blur(); - e.preventDefault(); - } - } - if (e.key === " ") { - if (agentListRef.current?.perfectMatch()) { - inputNode.blur(); - e.preventDefault(); - } else { - agentListRef.current?.reset(); - inputNode.setAttribute("ignore", "space"); - inputNode.blur(); - e.preventDefault(); - } - } - if (e.key === "Enter") { - inputNode.blur(); - e.preventDefault(); - } - }; - - // These are the event that drive the selection of the the agent list, if we - // have no more match we just blur to exit the selection process. - inputNode.oninput = (e) => { - const target = e.target as HTMLInputElement; - // console.log("INPUT", target.textContent); - setAgentListFilter(target.textContent || ""); - e.stopPropagation(); - setTimeout(() => { - if (agentListRef.current?.noMatch()) { - agentListRef.current?.reset(); - inputNode.blur(); - } - }); - }; - } - } - }} - // We aboslutely don't want any content in that div just below here. Do not add - // anyting to it. - >
-
- {/* This div is a spacer to avoid having the text clash with the buttons */} -
-
+
@@ -971,6 +638,17 @@ export function FixedAssistantInputBar({ stickyMentions?: AgentMention[]; conversationId: string | null; }) { + const { agentConfigurations } = useAgentConfigurations({ + workspaceId: owner.sId, + agentsGetView: conversationId ? { conversationId } : "list", + }); + + const activeAgents = agentConfigurations.filter((a) => a.status === "active"); + activeAgents.sort(compareAgentsForSort); + + const filtered = filterAndSortAgents(activeAgents, ""); + console.log(">> filtered:", filtered); + return (
diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx new file mode 100644 index 000000000000..17cd71281deb --- /dev/null +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -0,0 +1,60 @@ +// import "./styles.scss"; + +import Mention from "@tiptap/extension-mention"; +import Placeholder from "@tiptap/extension-placeholder"; +import { EditorContent, useEditor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import React from "react"; + +import { classNames } from "@app/lib/utils"; + +import { makeGetAssistantSuggestions } from "./suggestion.js"; + +// const PreventEnter = Extension.create({ +// addKeyboardShortcuts(this) { +// return { +// 'Enter': () => true +// } +// }, +// }) + +const Tiptap = (props: any) => { + // Consider: + // StarterKit.configure({ + // history: false, + // }), + const editor = useEditor({ + extensions: [ + StarterKit, + Mention.configure({ + HTMLAttributes: { + class: + "min-w-0 px-0 py-0 border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0 text-brand font-medium", + }, + suggestion: makeGetAssistantSuggestions(props.assistants), + }), + Placeholder.configure({ + placeholder: "Ask a question or get some @help", + emptyNodeClass: + "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none", + }), + ], + editorProps: { + attributes: { + class: + "w-full border-0 pr-1 py-3.5 pl-2 sm:pl-0 outline-none ring-0 focus:border-0 focus:outline-none focus:ring-0 whitespace-pre-wrap font-normal scrollbar-hide overflow-y-auto max-h-64 h-24", + }, + }, + }); + + return ( + + ); +}; + +export default Tiptap; diff --git a/front/components/assistant/conversation/MentionList.jsx b/front/components/assistant/conversation/MentionList.jsx new file mode 100644 index 000000000000..543098a72819 --- /dev/null +++ b/front/components/assistant/conversation/MentionList.jsx @@ -0,0 +1,95 @@ +// import './MentionList.scss' + +import { + Avatar, +} from "@dust-tt/sparkle"; +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from 'react' + +import { classNames } from "@app/lib/utils"; + +export const MentionList = forwardRef(function mentionList(props, ref) { + const [selectedIndex, setSelectedIndex] = useState(0) + + const selectItem = index => { + const item = props.items[index] + console.log('>> item:', item); + + if (item) { + props.command({ id: item.name }) + } + } + + const upHandler = () => { + setSelectedIndex(((selectedIndex + props.items.length) - 1) % props.items.length) + } + + const downHandler = () => { + setSelectedIndex((selectedIndex + 1) % props.items.length) + } + + const enterHandler = () => { + selectItem(selectedIndex) + } + + useEffect(() => setSelectedIndex(0), [props.items]) + + useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }) => { + if (event.key === 'ArrowUp') { + upHandler() + return true + } + + if (event.key === 'ArrowDown') { + downHandler() + return true + } + + if (event.key === 'Enter') { + enterHandler() + return true + } + + return false + }, + })) + + return ( +
+ {props.items.length + ? props.items.map((item, index) => ( + <>
+ +
+ + + // <> + )) + :
No result
+ } +
+ ) +}) \ No newline at end of file diff --git a/front/components/assistant/conversation/suggestion.js b/front/components/assistant/conversation/suggestion.js new file mode 100644 index 000000000000..67841422c55b --- /dev/null +++ b/front/components/assistant/conversation/suggestion.js @@ -0,0 +1,71 @@ +import { ReactRenderer } from "@tiptap/react"; +import tippy from "tippy.js"; + +import { MentionList } from "./MentionList.jsx"; + +export function makeGetAssistantSuggestions(assistants) { + return { + items: ({ query }) => { + return assistants + .filter((item) => + item.name.toLowerCase().startsWith(query.toLowerCase()) + ) + .slice(0, 5); + }, + + render: () => { + let reactRenderer; + let popup; + + return { + onStart: (props) => { + if (!props.clientRect) { + return; + } + + reactRenderer = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }); + + popup = tippy("body", { + getReferenceClientRect: props.clientRect, + appendTo: () => document.body, + content: reactRenderer.element, + showOnCreate: true, + interactive: true, + trigger: "manual", + placement: "bottom-start", + }); + }, + + onUpdate(props) { + reactRenderer.updateProps(props); + + if (!props.clientRect) { + return; + } + + popup[0].setProps({ + getReferenceClientRect: props.clientRect, + }); + }, + + onKeyDown(props) { + if (props.event.key === "Escape") { + popup[0].hide(); + + return true; + } + + return reactRenderer.ref?.onKeyDown(props); + }, + + onExit() { + popup[0].destroy(); + reactRenderer.destroy(); + }, + }; + }, + }; +} diff --git a/front/package-lock.json b/front/package-lock.json index 98bf90fcbf18..5b8652553bb8 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -22,6 +22,11 @@ "@temporalio/worker": "^1.7.4", "@temporalio/workflow": "^1.7.4", "@textea/json-viewer": "^3.1.1", + "@tiptap/extension-mention": "^2.1.13", + "@tiptap/extension-placeholder": "^2.1.13", + "@tiptap/pm": "^2.1.13", + "@tiptap/react": "^2.1.13", + "@tiptap/starter-kit": "^2.1.13", "@types/emoji-mart": "^3.0.10", "@uiw/react-textarea-code-editor": "^2.1.7", "ajv": "^8.12.0", @@ -2332,7 +2337,6 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -2445,6 +2449,50 @@ "@redis/client": "^1.0.0" } }, + "node_modules/@remirror/core-constants": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", + "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==" + }, + "node_modules/@remirror/core-helpers": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-helpers/-/core-helpers-3.0.0.tgz", + "integrity": "sha512-tusEgQJIqg4qKj6HSBUFcyRnWnziw3neh4T9wOmsPGHFC3w9kl5KSrDb9UAgE8uX6y32FnS7vJ955mWOl3n50A==", + "dependencies": { + "@remirror/core-constants": "^2.0.2", + "@remirror/types": "^1.0.1", + "@types/object.omit": "^3.0.0", + "@types/object.pick": "^1.3.2", + "@types/throttle-debounce": "^2.1.0", + "case-anything": "^2.1.13", + "dash-get": "^1.0.2", + "deepmerge": "^4.3.1", + "fast-deep-equal": "^3.1.3", + "make-error": "^1.3.6", + "object.omit": "^3.0.0", + "object.pick": "^1.3.0", + "throttle-debounce": "^3.0.1" + } + }, + "node_modules/@remirror/types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@remirror/types/-/types-1.0.1.tgz", + "integrity": "sha512-VlZQxwGnt1jtQ18D6JqdIF+uFZo525WEqrfp9BOc3COPpK4+AWCgdnAWL+ho6imWcoINlGjR/+3b6y5C1vBVEA==", + "dependencies": { + "type-fest": "^2.19.0" + } + }, + "node_modules/@remirror/types/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.5.1.tgz", @@ -2782,6 +2830,390 @@ "react-dom": "^17 || ^18" } }, + "node_modules/@tiptap/core": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.1.13.tgz", + "integrity": "sha512-cMC8bgTN63dj1Mv82iDeeLl6sa9kY0Pug8LSalxVEptRmyFVsVxGgu2/6Y3T+9aCYScxfS06EkA8SdzFMAwYTQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.1.13.tgz", + "integrity": "sha512-oe6wSQACmODugoP9XH3Ouffjy4BsOBWfTC+dETHNCG6ZED6ShHN3CB9Vr7EwwRgmm2WLaKAjMO1sVumwH+Z1rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.1.13.tgz", + "integrity": "sha512-6cHsQTh/rUiG4jkbJer3vk7g60I5tBwEBSGpdxmEHh83RsvevD8+n92PjA24hYYte5RNlATB011E1wu8PVhSvw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.1.13.tgz", + "integrity": "sha512-Hm7e1GX3AI6lfaUmr6WqsS9MMyXIzCkhh+VQi6K8jj4Q4s8kY4KPoAyD/c3v9pZ/dieUtm2TfqrOCkbHzsJQBg==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.1.13.tgz", + "integrity": "sha512-NkWlQ5bLPUlcROj6G/d4oqAxMf3j3wfndGOPp0z8OoXJtVbVoXl/aMSlLbVgE6n8r6CS8MYxKhXNxrb7Ll2foA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.1.13.tgz", + "integrity": "sha512-f5fLYlSgliVVa44vd7lQGvo49+peC+Z2H0Fn84TKNCH7tkNZzouoJsHYn0/enLaQ9Sq+24YPfqulfiwlxyiT8w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.1.13.tgz", + "integrity": "sha512-E3tweNExPOV+t1ODKX0MDVsS0aeHGWc1ECt+uyp6XwzsN0bdF2A5+pttQqM7sTcMnQkVACGFbn9wDeLRRcfyQg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.1.13.tgz", + "integrity": "sha512-wLwiTWsVmZTGIE5duTcHRmW4ulVxNW4nmgfpk95+mPn1iKyNGtrVhGWleLhBlTj+DWXDtcfNWZgqZkZNzhkqYQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.1.13.tgz", + "integrity": "sha512-NAyJi4BJxH7vl/2LNS1X0ndwFKjEtX+cRgshXCnMyh7qNpIRW6Plczapc/W1OiMncOEhZJfpZfkRSfwG01FWFg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.1.13.tgz", + "integrity": "sha512-9Oz7pk1Nts2+EyY+rYfnREGbLzQ5UFazAvRhF6zAJdvyuDmAYm0Jp6s0GoTrpV0/dJEISoFaNpPdMJOb9EBNRw==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.1.13.tgz", + "integrity": "sha512-Cl5apsoTcyPPCgE3ThufxQxZ1wyqqh+9uxUN9VF9AbeTkid6oPZvKXwaILf6AFnkSy+SuKrb9kZD2iaezxpzXw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.1.13.tgz", + "integrity": "sha512-TGkMzMQayuKg+vN4du0x1ahEItBLcCT1jdWeRsjdM8gHfzbPLdo4PQhVsvm1I0xaZmbJZelhnVsUwRZcIu1WNA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.1.13.tgz", + "integrity": "sha512-PEmc19QLmlVUTiHWoF0hpgNTNPNU0nlaFmMKskzO+cx5Df4xvHmv/UqoIwp7/UFbPMkfVJT1ozQU7oD1IWn9Hg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.1.13.tgz", + "integrity": "sha512-1ouitThGTBUObqw250aDwGLMNESBH5PRXIGybsCFO1bktdmWtEw7m72WY41EuX2BH8iKJpcYPerl3HfY1vmCNw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.1.13.tgz", + "integrity": "sha512-7OgjgNqZXvBejgULNdMSma2M1nzv4bbZG+FT5XMFZmEOxR9IB1x/RzChjPdeicff2ZK2sfhMBc4Y9femF5XkUg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.1.13.tgz", + "integrity": "sha512-HyDJfuDn5hzwGKZiANcvgz6wcum6bEgb4wmJnfej8XanTMJatNVv63TVxCJ10dSc9KGpPVcIkg6W8/joNXIEbw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.1.13.tgz", + "integrity": "sha512-6e8iiCWXOiJTl1XOwVW2tc0YG18h70HUtEHFCx2m5HspOGFKsFEaSS3qYxOheM9HxlmQeDt8mTtqftRjEFRxPQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-mention": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.1.13.tgz", + "integrity": "sha512-OYqaucyBiCN/CmDYjpOVX74RJcIEKmAqiZxUi8Gfaq7ryEO5a8Gk93nK+8uZ0onaqHE+mHpoLFFbcAFbOPgkUQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0", + "@tiptap/suggestion": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.1.13.tgz", + "integrity": "sha512-UO4ZAL5Vrr1WwER5VjgmeNIWHpqy9cnIRo1En07gZ0OWTjs1eITPcu+4TCn1ZG6DhoFvAQzE5DTxxdhIotg+qw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.1.13.tgz", + "integrity": "sha512-cEoZBJrsQn69FPpUMePXG/ltGXtqKISgypj70PEHXt5meKDjpmMVSY4/8cXvFYEYsI9GvIwyAK0OrfAHiSoROA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-placeholder": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-2.1.13.tgz", + "integrity": "sha512-vIY7y7UbqsrAW/y8bDE9eRenbQEU16kNHB5Wri8RU1YiUZpkPgdXP/pLqyjIIq95SwP/vdTIHjHoQ77VLRl1hA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.1.13.tgz", + "integrity": "sha512-VN6zlaCNCbyJUCDyBFxavw19XmQ4LkCh8n20M8huNqW77lDGXA2A7UcWLHaNBpqAijBRu9mWI8l4Bftyf2fcAw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.1.13.tgz", + "integrity": "sha512-zzsTTvu5U67a8WjImi6DrmpX2Q/onLSaj+LRWPh36A1Pz2WaxW5asZgaS+xWCnR+UrozlCALWa01r7uv69jq0w==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.1.13.tgz", + "integrity": "sha512-zNbA7muWsHuVg12GrTgN/j119rLePPq5M8dZgkKxUwdw8VmU3eUyBp1SihPEXJ2U0MGdZhNhFX7Y74g11u66sg==", + "dependencies": { + "prosemirror-changeset": "^2.2.0", + "prosemirror-collab": "^1.3.0", + "prosemirror-commands": "^1.3.1", + "prosemirror-dropcursor": "^1.5.0", + "prosemirror-gapcursor": "^1.3.1", + "prosemirror-history": "^1.3.0", + "prosemirror-inputrules": "^1.2.0", + "prosemirror-keymap": "^1.2.0", + "prosemirror-markdown": "^1.10.1", + "prosemirror-menu": "^1.2.1", + "prosemirror-model": "^1.18.1", + "prosemirror-schema-basic": "^1.2.0", + "prosemirror-schema-list": "^1.2.2", + "prosemirror-state": "^1.4.1", + "prosemirror-tables": "^1.3.0", + "prosemirror-trailing-node": "^2.0.2", + "prosemirror-transform": "^1.7.0", + "prosemirror-view": "^1.28.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.1.13.tgz", + "integrity": "sha512-Dq3f8EtJnpImP3iDtJo+7bulnN9SJZRZcVVzxHXccLcC2MxtmDdlPGZjP+wxO800nd8toSIOd5734fPNf/YcfA==", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.1.13", + "@tiptap/extension-floating-menu": "^2.1.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.1.13.tgz", + "integrity": "sha512-ph/mUR/OwPtPkZ5rNHINxubpABn8fHnvJSdhXFrY/q6SKoaO11NZXgegRaiG4aL7O6Sz4LsZVw6Sm0Ae+GJmrg==", + "dependencies": { + "@tiptap/core": "^2.1.13", + "@tiptap/extension-blockquote": "^2.1.13", + "@tiptap/extension-bold": "^2.1.13", + "@tiptap/extension-bullet-list": "^2.1.13", + "@tiptap/extension-code": "^2.1.13", + "@tiptap/extension-code-block": "^2.1.13", + "@tiptap/extension-document": "^2.1.13", + "@tiptap/extension-dropcursor": "^2.1.13", + "@tiptap/extension-gapcursor": "^2.1.13", + "@tiptap/extension-hard-break": "^2.1.13", + "@tiptap/extension-heading": "^2.1.13", + "@tiptap/extension-history": "^2.1.13", + "@tiptap/extension-horizontal-rule": "^2.1.13", + "@tiptap/extension-italic": "^2.1.13", + "@tiptap/extension-list-item": "^2.1.13", + "@tiptap/extension-ordered-list": "^2.1.13", + "@tiptap/extension-paragraph": "^2.1.13", + "@tiptap/extension-strike": "^2.1.13", + "@tiptap/extension-text": "^2.1.13" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/suggestion": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.1.13.tgz", + "integrity": "sha512-Y05TsiXTFAJ5SrfoV+21MAxig5UNbY0AVa03lQlh/yicTRPpIc6hgZzblB0uxDSYoj6+kaHE4MIZvPvhUD8BJQ==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3022,6 +3454,16 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.5.tgz", "integrity": "sha512-4slmbtwV59ZxitY4ixUZdy1uRLf9eSIvBWPQxNjhHYWEtn0FryfKpyS2cvADYXTayWdKEIsJengncrVvkI4I6A==" }, + "node_modules/@types/object.omit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.3.tgz", + "integrity": "sha512-xrq4bQTBGYY2cw+gV4PzoG2Lv3L0pjZ1uXStRRDQoATOYW1lCsFQHhQ+OkPhIcQoqLjAq7gYif7D14Qaa6Zbew==" + }, + "node_modules/@types/object.pick": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.4.tgz", + "integrity": "sha512-5PjwB0uP2XDp3nt5u5NJAG2DORHIRClPzWT/TTZhJ2Ekwe8M5bA9tvPdi9NO/n2uvu2/ictat8kgqvLfcIE1SA==" + }, "node_modules/@types/p5": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@types/p5/-/p5-1.7.0.tgz", @@ -3148,6 +3590,11 @@ "meshoptimizer": "~0.18.1" } }, + "node_modules/@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" + }, "node_modules/@types/unist": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", @@ -3747,8 +4194,7 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "5.3.0", @@ -4494,6 +4940,17 @@ "cargo-cp-artifact": "bin/cargo-cp-artifact.js" } }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -4844,6 +5301,11 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cropperjs": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.6.1.tgz", @@ -4895,6 +5357,11 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/dash-get": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dash-get/-/dash-get-1.0.2.tgz", + "integrity": "sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==" + }, "node_modules/dateformat": { "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", @@ -5278,6 +5745,17 @@ "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -7678,6 +8156,17 @@ "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7820,6 +8309,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -7954,6 +8454,14 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -8883,6 +9391,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -9052,8 +9568,7 @@ "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, "node_modules/make-fetch-happen": { "version": "9.1.0", @@ -9103,6 +9618,22 @@ "tmpl": "1.0.5" } }, + "node_modules/markdown-it": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.0.0.tgz", + "integrity": "sha512-seFjF0FIcPt4P9U39Bq1JYblX0KZCjDLFFQPHpL5AzHpqPEKtosxmdq/LTVZnjfH7tjt9BxStm+wXcDBNuYmzw==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.0.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -9430,6 +9961,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/memfs": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", @@ -10815,6 +11351,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "dependencies": { + "is-extendable": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object.values": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", @@ -10929,6 +11487,11 @@ "node": ">= 0.8.0" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -11856,6 +12419,183 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/prosemirror-changeset": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", + "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz", + "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", + "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.2.tgz", + "integrity": "sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.3.0.tgz", + "integrity": "sha512-z1GRP2vhh5CihYMQYsJSa1cOwXb3SYxALXOIfAkX8nZserARtl9LiL+CEl+T+OFIsXc3mJIHKhbsmRzC0HDAXA==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", + "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.12.0.tgz", + "integrity": "sha512-6F5HS8Z0HDYiS2VQDZzfZP6A0s/I0gbkJy8NCzzDMtcsz3qrfqyroMMeoSjAmOhDITyon11NbXSzztfKi+frSQ==", + "dependencies": { + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", + "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.4.tgz", + "integrity": "sha512-RPmVXxUfOhyFdayHawjuZCxiROsm9L4FCUA6pWI+l7n2yCBsWy9VpdE1hpDHUS8Vad661YLY9AzqfjLhAKQ4iQ==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz", + "integrity": "sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==", + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.3.0.tgz", + "integrity": "sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.5.tgz", + "integrity": "sha512-JSZ2cCNlApu/ObAhdPyotrjBe2cimniniTpz60YXzbL0kZ+47nEYk2LWbfKU2lKpBkUNquta2PjteoNi4YCluQ==", + "dependencies": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.7.tgz", + "integrity": "sha512-8zcZORYj/8WEwsGo6yVCRXFMOfBo0Ub3hCUvmoWIZYfMP26WqENU0mpEP27w7mt8buZWuGrydBewr0tOArPb1Q==", + "dependencies": { + "@remirror/core-constants": "^2.0.2", + "@remirror/core-helpers": "^3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.19.0", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.31.2" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.8.0.tgz", + "integrity": "sha512-BaSBsIMv52F1BVVMvOmp1yzD3u65uC3HTzCBQV1WDPqJRQ2LuHKcyfn0jwqodo8sR9vVzMzZyI+Dal5W9E6a9A==", + "dependencies": { + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.32.6", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.32.6.tgz", + "integrity": "sha512-26r5LvyDlPgUNVf7ZdNdGrMJnylwjJtUJTfDuYOANIVx9lqWD1WCBlGg283weYQGKUC64DXR25LeAmliB9CrFQ==", + "dependencies": { + "prosemirror-model": "^1.16.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proto3-json-serializer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", @@ -11912,6 +12652,14 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", @@ -12669,6 +13417,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -13900,6 +14653,22 @@ "resolved": "https://registry.npmjs.org/three/-/three-0.155.0.tgz", "integrity": "sha512-sNgCYmDijnIqkD/bMfk+1pHg3YzsxW7V2ChpuP6HCQ8NiZr3RufsXQr8M3SSUMjW4hG+sUk7YbyuY0DncaDTJQ==" }, + "node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -14212,6 +14981,11 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.0.0.tgz", + "integrity": "sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -14582,6 +15356,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/front/package.json b/front/package.json index d4cb7f468a6d..5b10f1187c29 100644 --- a/front/package.json +++ b/front/package.json @@ -30,6 +30,11 @@ "@temporalio/worker": "^1.7.4", "@temporalio/workflow": "^1.7.4", "@textea/json-viewer": "^3.1.1", + "@tiptap/extension-mention": "^2.1.13", + "@tiptap/extension-placeholder": "^2.1.13", + "@tiptap/pm": "^2.1.13", + "@tiptap/react": "^2.1.13", + "@tiptap/starter-kit": "^2.1.13", "@types/emoji-mart": "^3.0.10", "@uiw/react-textarea-code-editor": "^2.1.7", "ajv": "^8.12.0", From 8389b6119ba0a288cfcae8e2ffe7a2f7f13497a9 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Wed, 20 Dec 2023 17:19:58 +0100 Subject: [PATCH 02/29] Tmp --- .../assistant/conversation/InputBar.tsx | 449 +++++++----------- .../assistant/conversation/InputBarTipTap.tsx | 159 ++++++- .../assistant/conversation/MentionList.jsx | 13 +- .../assistant/conversation/suggestion.js | 2 + front/lib/swr.ts | 6 +- 5 files changed, 307 insertions(+), 322 deletions(-) diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 870f30497ea8..67dbd05d8dec 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -27,7 +27,6 @@ import { import * as ReactDOMServer from "react-dom/server"; import { mutate } from "swr"; -import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; import { GenerationContext } from "@app/components/assistant/conversation/GenerationContextProvider"; import { SendNotificationsContext } from "@app/components/sparkle/Notification"; import { compareAgentsForSort } from "@app/lib/assistant"; @@ -58,167 +57,6 @@ function AgentMention({ // AGENT LIST -function AgentListImpl( - { - owner, - visible, - filter, - position, - conversationId, - }: { - owner: WorkspaceType; - visible: boolean; - filter: string; - position: { - bottom: number; - left: number; - }; - conversationId: string | null; - }, - ref: ForwardedRef<{ - prev: () => void; - next: () => void; - reset: () => void; - selected: () => AgentConfigurationType | null; - noMatch: () => boolean; - perfectMatch: () => boolean; - }> -) { - const [focus, setFocus] = useState(0); - - const focusRef = useRef(null); - - const { agentConfigurations } = useAgentConfigurations({ - workspaceId: owner.sId, - agentsGetView: conversationId ? { conversationId } : "list", - }); - - const activeAgents = agentConfigurations.filter((a) => a.status === "active"); - activeAgents.sort(compareAgentsForSort); - - const filtered = filterAndSortAgents(activeAgents, filter); - - useImperativeHandle(ref, () => ({ - prev: () => { - setFocus((f) => (f > 0 ? f - 1 : 0)); - }, - next: () => { - setFocus((f) => (f < filtered.length - 1 ? f + 1 : filtered.length - 1)); - }, - reset: () => { - setFocus(0); - }, - selected: () => { - if (focus < filtered.length) { - return filtered[focus]; - } - return null; - }, - noMatch: () => { - return filtered.length === 0; - }, - perfectMatch: () => { - return !!filtered.find( - (a) => a.name.toLowerCase() === filter.toLowerCase() - ); - }, - })); - - useEffect(() => { - if (focus > filtered.length - 1) { - if (filtered.length === 0) { - setFocus(0); - } else { - setFocus(filtered.length - 1); - } - } - if (focusRef.current) { - focusRef.current.scrollIntoView({ - // behavior: "smooth", - block: "nearest", - inline: "start", - }); - } - }, [focus, visible, filter, filtered]); - - return ( - -
-
- {filtered.map((c, i) => ( -
-
{ - setFocus(i); - }} - > -
- -
- {"@"} - {c.name} -
-
-
-
- ))} -
-
-
- ); -} - -const AgentList = forwardRef(AgentListImpl); - -import { useCurrentEditor } from "@tiptap/react"; - -function moveCursorToEnd(el: HTMLElement) { - const range = document.createRange(); - const sel = window.getSelection(); - if (sel) { - range.selectNodeContents(el); - range.collapse(false); - sel.removeAllRanges(); - sel.addRange(range); - } -} - -function getAgentMentionNode( - agentConfiguration: AgentConfigurationType -): ChildNode | null { - const htmlString = ReactDOMServer.renderToStaticMarkup( - - ); - const wrapper = document.createElement("div"); - wrapper.innerHTML = htmlString.trim(); - return wrapper.firstChild; -} - export function AssistantInputBar({ owner, onSubmit, @@ -249,7 +87,6 @@ export function AssistantInputBar({ const [contentFragmentFilename, setContentFragmentFilename] = useState< string | undefined >(undefined); - const fileInputRef = useRef(null); const inputRef = useRef(null); const agentListRef = useRef<{ prev: () => void; @@ -264,30 +101,6 @@ export function AssistantInputBar({ inputRef.current?.focus(); }, []); - // Empty bar detection logic - const [empty, setEmpty] = useState( - !inputRef.current?.textContent || - inputRef.current.textContent.replace(/[\u200B\n]/g, "").length === 0 - ); - // MutationObserver is only defined after window is defined so observer cannot - // be defined in the useRef below - const observer = useRef(null); - useEffect(() => { - if (!observer.current && inputRef.current) { - observer.current = new MutationObserver(function () { - setEmpty( - !inputRef.current?.textContent || - inputRef.current.textContent.replace(/[\u200B\n]/g, "").length === 0 - ); - }); - observer.current.observe(inputRef.current, { - childList: true, - characterData: true, - subtree: true, - }); - } - }, []); - const { agentConfigurations } = useAgentConfigurations({ workspaceId: owner.sId, agentsGetView: conversationId ? { conversationId } : "list", @@ -297,71 +110,101 @@ export function AssistantInputBar({ const activeAgents = agentConfigurations.filter((a) => a.status === "active"); activeAgents.sort(compareAgentsForSort); - const [isExpanded, setIsExpanded] = useState(false); - - const { editor } = useCurrentEditor(); + // const [isExpanded, setIsExpanded] = useState(false); - const handleSubmit = async () => { + const handleSubmit = async (jsonPayload, resetEditorText) => { + // TODO: Only display in blue the CTA once the editor is not empty. // if (empty) { // return; // } - console.log(">> result:", JSON.stringify(editor?.getJSON(), null, 2)); - - const contentEditable = document.getElementsByClassName("dust-input-bar"); - console.log("> contentEditable:", contentEditable); - if (contentEditable) { - const mentions: MentionType[] = []; - let content = ""; - - // Array.from(contentEditable.childNodes).forEach((node) => { - // if (node.nodeType === Node.ELEMENT_NODE) { - // // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - // const agentConfigurationId = node.getAttribute( - // "data-agent-configuration-id" - // ); - // // @ts-expect-error - parentNode is the contenteditable, it has a getAttribute. - // const agentName = node.getAttribute("data-agent-name"); - - // if (agentConfigurationId && agentName) { - // mentions.push({ - // configurationId: agentConfigurationId, - // }); - // // Internal format for mentions is `:mention[agentName]{sId=agentConfigurationId}`. - // content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; - // } - // } - // if (node.nodeType === Node.TEXT_NODE) { - // content += node.textContent; - // } - // }); - - // content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; - - content = content.trim(); - content = content.replace(/\u200B/g, ""); - let contentFragment: - | { - title: string; - content: string; - url: string | null; - contentType: string; - } - | undefined = undefined; - if (contentFragmentBody && contentFragmentFilename) { - contentFragment = { - title: contentFragmentFilename, - content: contentFragmentBody, - url: null, - contentType: "file_attachment", - }; + console.log(">>jsonPayload:", JSON.stringify(jsonPayload, null, 2)); + + const mentions: MentionType[] = []; + let content = ""; + + const [firstParagraph] = jsonPayload.content; + + for (const node of firstParagraph.content) { + if (node.type === "mention") { + const { id: agentConfigurationId, label: agentName } = node.attrs; + if (agentConfigurationId && agentName) { + mentions.push({ + configurationId: agentConfigurationId, + }); + // Internal format for mentions is `:mention[agentName]{sId=agentConfigurationId}`. + content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; + } + } + if (node.type === "text") { + content += node.text; } - setIsExpanded(false); - onSubmit(content, mentions, contentFragment); - // contentEditable.innerHTML = ""; - setContentFragmentFilename(undefined); - setContentFragmentBody(undefined); } + + console.log(">> content:", content); + + content = content.trim(); + // Still needed? + content = content.replace(/\u200B/g, ""); + let contentFragment: + | { + title: string; + content: string; + url: string | null; + contentType: string; + } + | undefined = undefined; + if (contentFragmentBody && contentFragmentFilename) { + contentFragment = { + title: contentFragmentFilename, + content: contentFragmentBody, + url: null, + contentType: "file_attachment", + }; + } + // setIsExpanded(false); + onSubmit(content, mentions, contentFragment); + resetEditorText(); + setContentFragmentFilename(undefined); + setContentFragmentBody(undefined); + }; + + const onInputFileChange = async (event) => { + const file = event?.target?.files?.[0]; + console.log("> file:", file); + if (!file) return; + if (file.size > 10_000_000) { + sendNotification({ + type: "error", + title: "File too large.", + description: + "PDF uploads are limited to 10Mb per file. Please consider uploading a smaller file.", + }); + return; + } + const res = await handleFileUploadToText(file); + + if (res.isErr()) { + sendNotification({ + type: "error", + title: "Error uploading file.", + description: res.error.message, + }); + return; + } + if (res.value.content.length > 1_000_000) { + // This error should pretty much never be triggered but it is a possible case, so here it is. + sendNotification({ + type: "error", + title: "File too large.", + description: + "The extracted text from your PDF has more than 1 million characters. This will overflow the assistant context. Please consider uploading a smaller file.", + }); + return; + } + + setContentFragmentFilename(res.value.title); + setContentFragmentBody(res.value.content); }; const [isAnimating, setIsAnimating] = useState(false); @@ -374,9 +217,8 @@ export function AssistantInputBar({ } }, [animate, isAnimating]); - const stickyMentionsTextContent = useRef(null); - const [isProcessing, setIsProcessing] = useState(false); + const stickyMentionsTextContent = useRef(null); // GenerationContext: to know if we are generating or not const generationContext = useContext(GenerationContext); @@ -428,49 +270,48 @@ export function AssistantInputBar({ mentionsToInject?.map((m) => m.configurationId) ); - const contentEditable = document.getElementById("dust-input-bar"); - if (contentEditable) { - const textContent = contentEditable.textContent?.trim(); - - if (textContent?.length && !stickyMentionsTextContent.current) { - return; - } - - if ( - textContent?.length && - textContent !== stickyMentionsTextContent.current - ) { - // content has changed, we don't clear it (we preserve whatever the user typed) - return; - } + const editor = {}; + const isNotEmpty = !editor.isEmpty; + // TODO: Add mention to editor. + if (isNotEmpty && !stickyMentionsTextContent.current) { + return; + } - // we clear the content of the input bar -- at this point, it's either already empty, - // or contains only the sticky mentions added by this hook - contentEditable.innerHTML = ""; - let lastTextNode = null; - for (const configurationId of mentionedAgentConfigurationIds) { - const agentConfiguration = agentConfigurations.find( - (agent) => agent.sId === configurationId - ); - if (!agentConfiguration) { - continue; - } - const mentionNode = getAgentMentionNode(agentConfiguration); - if (!mentionNode) { - continue; - } - contentEditable.appendChild(mentionNode); - lastTextNode = document.createTextNode(" "); - contentEditable.appendChild(lastTextNode); + if ( + isNotEmpty && + !editor.hasOnlyMention(stickyMentionsTextContent.current) + ) { + // content has changed, we don't clear it (we preserve whatever the user typed) + return; + } - stickyMentionsTextContent.current = - contentEditable.textContent?.trim() || null; + // we clear the content of the input bar -- at this point, it's either already empty, + // or contains only the sticky mentions added by this hook + contentEditable.innerHTML = ""; + let lastTextNode = null; + for (const configurationId of mentionedAgentConfigurationIds) { + const agentConfiguration = agentConfigurations.find( + (agent) => agent.sId === configurationId + ); + if (!agentConfiguration) { + continue; } - // move the cursor to the end of the input bar - if (lastTextNode) { - moveCursorToEnd(contentEditable); + const mentionNode = getAgentMentionNode(agentConfiguration); + if (!mentionNode) { + continue; } + contentEditable.appendChild(mentionNode); + lastTextNode = document.createTextNode(" "); + contentEditable.appendChild(lastTextNode); + + stickyMentionsTextContent.current = + contentEditable.textContent?.trim() || null; } + // move the cursor to the end of the input bar + if (lastTextNode) { + moveCursorToEnd(contentEditable); + } + editor?.commands.focus("end"); }, [ stickyMentions, agentConfigurations, @@ -478,6 +319,11 @@ export function AssistantInputBar({ selectedAssistant, ]); + console.log("> isAnimating:", isAnimating); + + console.log(">> contentFragmentFilename:", contentFragmentFilename); + console.log(">> contentFragmentBody:", contentFragmentBody); + return ( <> {/* -
+
- +
+ {contentFragmentFilename && contentFragmentBody && ( +
+ { + setContentFragmentBody(undefined); + setContentFragmentFilename(undefined); + }} + /> +
+ )} + + +
-
+ {/*
-
+
*/}
diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx index 17cd71281deb..dde1a643dca6 100644 --- a/front/components/assistant/conversation/InputBarTipTap.tsx +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -1,31 +1,61 @@ // import "./styles.scss"; +import { + ArrowUpIcon, + AttachmentIcon, + Button, + FullscreenExitIcon, + FullscreenIcon, + IconButton, +} from "@dust-tt/sparkle"; import Mention from "@tiptap/extension-mention"; import Placeholder from "@tiptap/extension-placeholder"; -import { EditorContent, useEditor } from "@tiptap/react"; +import { EditorContent, Extension, useEditor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; -import React from "react"; +import React, { useRef, useState } from "react"; +import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; import { classNames } from "@app/lib/utils"; import { makeGetAssistantSuggestions } from "./suggestion.js"; -// const PreventEnter = Extension.create({ -// addKeyboardShortcuts(this) { -// return { -// 'Enter': () => true -// } -// }, -// }) - const Tiptap = (props: any) => { // Consider: // StarterKit.configure({ // history: false, // }), + + // const activeAgents = agentConfigurations.filter((a) => a.status === "active"); + // activeAgents.sort(compareAgentsForSort); + + // TODO: Should we keep this here? + const [isExpanded, setIsExpanded] = useState(false); + + const PreventEnter = Extension.create({ + addKeyboardShortcuts(this) { + const { editor } = this; + + return { + Enter: () => { + props.onCTAClick(editor.getJSON(), () => { + editor.commands.setContent(""); + setIsExpanded(false); + }); + + return true; + }, + }; + }, + }); + + // TODO: Update suggestion when assistants is loaded! + const editor = useEditor({ + enableInputRules: false, // Disable Markdown when typing. + enablePasteRules: false, // Disable Markdown when pasting. extensions: [ - StarterKit, + StarterKit.configure({}), + PreventEnter, Mention.configure({ HTMLAttributes: { class: @@ -41,19 +71,110 @@ const Tiptap = (props: any) => { ], editorProps: { attributes: { - class: - "w-full border-0 pr-1 py-3.5 pl-2 sm:pl-0 outline-none ring-0 focus:border-0 focus:outline-none focus:ring-0 whitespace-pre-wrap font-normal scrollbar-hide overflow-y-auto max-h-64 h-24", + class: "border-0 outline-none overflow-y-auto h-full", }, }, + // TODO: Should we consider using slotBefore/slotAfter? }); + // TODO: Reset after loading. + const fileInputRef = useRef(null); + + const contentEditableClasses = classNames( + "inline-block w-full", + "border-0 pr-1 pl-2 sm:pl-0 outline-none ring-0 focus:border-0 focus:outline-none focus:ring-0 py-1.5", + "whitespace-pre-wrap font-normal" + ); + + console.log(">> selectedAssistant:", props.selectedAssistant); + return ( - +
+ + +
+
+ { + props.onInputFileChange(event); + }} + /> + { + fileInputRef.current?.click(); + }} + /> + { + editor + ?.chain() + .focus() + .insertContent({ + type: "mention", + attrs: { + id: c.sId, + label: c.name, + }, + }) + .insertContent(" ") // add an extra space after the mention + .run(); + }} + assistants={props.assistants} + showBuilderButtons={true} + /> +
+ { + setIsExpanded((event) => !event); + // Focus on the end! + editor?.commands.focus("end"); + }} + /> +
+
+
+
); }; diff --git a/front/components/assistant/conversation/MentionList.jsx b/front/components/assistant/conversation/MentionList.jsx index 543098a72819..957d4fbc3d21 100644 --- a/front/components/assistant/conversation/MentionList.jsx +++ b/front/components/assistant/conversation/MentionList.jsx @@ -17,10 +17,9 @@ export const MentionList = forwardRef(function mentionList(props, ref) { const selectItem = index => { const item = props.items[index] - console.log('>> item:', item); if (item) { - props.command({ id: item.name }) + props.command({ id: item.sId, label: item.name }) } } @@ -77,16 +76,6 @@ export const MentionList = forwardRef(function mentionList(props, ref) {
- // <> )) :
No result
} diff --git a/front/components/assistant/conversation/suggestion.js b/front/components/assistant/conversation/suggestion.js index 67841422c55b..04c4759b1eb7 100644 --- a/front/components/assistant/conversation/suggestion.js +++ b/front/components/assistant/conversation/suggestion.js @@ -6,6 +6,8 @@ import { MentionList } from "./MentionList.jsx"; export function makeGetAssistantSuggestions(assistants) { return { items: ({ query }) => { + console.log(">> query:", query); + console.log(">> assistants:", assistants); return assistants .filter((item) => item.name.toLowerCase().startsWith(query.toLowerCase()) diff --git a/front/lib/swr.ts b/front/lib/swr.ts index 81678105b9c8..1b9f4ca8adab 100644 --- a/front/lib/swr.ts +++ b/front/lib/swr.ts @@ -453,11 +453,15 @@ export function useAgentConfigurations({ typeof agentsGetView === "string" ? `view=${agentsGetView}` : `conversationId=${agentsGetView.conversationId}`; - const { data, error, mutate } = useSWR( + const { data, error, mutate, isLoading } = useSWR( `/api/w/${workspaceId}/assistant/agent_configurations?${viewQueryString}`, agentConfigurationsFetcher ); + console.log(">> data:", data); + console.log(">> error:", error); + console.log(">> isLoading:", isLoading); + return { agentConfigurations: data ? data.agentConfigurations : [], isAgentConfigurationsLoading: !error && !data, From 318739ceee255a865f6f1f10c5f98ba985727b79 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Wed, 20 Dec 2023 20:23:45 +0100 Subject: [PATCH 03/29] Tmp --- .../assistant/conversation/InputBar.tsx | 91 +++----- .../assistant/conversation/InputBarTipTap.tsx | 194 +++++++++++++++--- .../{suggestion.js => suggestion.ts} | 10 +- 3 files changed, 205 insertions(+), 90 deletions(-) rename front/components/assistant/conversation/{suggestion.js => suggestion.ts} (85%) diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 67dbd05d8dec..2e01d9c2af17 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -257,72 +257,45 @@ export function AssistantInputBar({ } }, [isProcessing, generationContext.generatingMessageIds.length]); - useEffect(() => { - if (!stickyMentions?.length && !selectedAssistant) { - return; - } + console.log("> isAnimating:", isAnimating); - const mentionsToInject = stickyMentions?.length - ? stickyMentions - : ([selectedAssistant] as [AgentMention]); + console.log(">> contentFragmentFilename:", contentFragmentFilename); + console.log(">> contentFragmentBody:", contentFragmentBody); - const mentionedAgentConfigurationIds = new Set( - mentionsToInject?.map((m) => m.configurationId) - ); + let stickyMentionsConfigurationToInject: any[] = []; + if (stickyMentions?.length) { + stickyMentionsConfigurationToInject = stickyMentions; + } else if (selectedAssistant) { + stickyMentionsConfigurationToInject = [selectedAssistant] as [AgentMention]; + } - const editor = {}; - const isNotEmpty = !editor.isEmpty; - // TODO: Add mention to editor. - if (isNotEmpty && !stickyMentionsTextContent.current) { - return; - } + console.log(">> selectedAssistant:", selectedAssistant); - if ( - isNotEmpty && - !editor.hasOnlyMention(stickyMentionsTextContent.current) - ) { - // content has changed, we don't clear it (we preserve whatever the user typed) - return; - } + const stickyMentionsToInject: any[] = stickyMentionsConfigurationToInject + .map((configuration) => { + const agentConfig = agentConfigurations.find( + (agent) => agent.sId === configuration.configurationId + ); - // we clear the content of the input bar -- at this point, it's either already empty, - // or contains only the sticky mentions added by this hook - contentEditable.innerHTML = ""; - let lastTextNode = null; - for (const configurationId of mentionedAgentConfigurationIds) { - const agentConfiguration = agentConfigurations.find( - (agent) => agent.sId === configurationId + console.log( + ">> config:", + agentConfig, + "for", + configuration.configurationId ); - if (!agentConfiguration) { - continue; - } - const mentionNode = getAgentMentionNode(agentConfiguration); - if (!mentionNode) { - continue; - } - contentEditable.appendChild(mentionNode); - lastTextNode = document.createTextNode(" "); - contentEditable.appendChild(lastTextNode); - stickyMentionsTextContent.current = - contentEditable.textContent?.trim() || null; - } - // move the cursor to the end of the input bar - if (lastTextNode) { - moveCursorToEnd(contentEditable); - } - editor?.commands.focus("end"); - }, [ - stickyMentions, - agentConfigurations, - stickyMentionsTextContent, - selectedAssistant, - ]); + if (!agentConfig) { + return; + } - console.log("> isAnimating:", isAnimating); + return { id: agentConfig.sId, name: agentConfig.name }; + }) + .filter(Boolean); - console.log(">> contentFragmentFilename:", contentFragmentFilename); - console.log(">> contentFragmentBody:", contentFragmentBody); + console.log( + ">> stickyMentionsToInject:", + JSON.stringify(stickyMentionsToInject, null, 2) + ); return ( <> @@ -376,12 +349,14 @@ export function AssistantInputBar({
diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx index dde1a643dca6..2b904245553e 100644 --- a/front/components/assistant/conversation/InputBarTipTap.tsx +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -8,18 +8,79 @@ import { FullscreenIcon, IconButton, } from "@dust-tt/sparkle"; +import { AgentMention } from "@dust-tt/types"; import Mention from "@tiptap/extension-mention"; import Placeholder from "@tiptap/extension-placeholder"; -import { EditorContent, Extension, useEditor } from "@tiptap/react"; +import { Editor, EditorContent, Extension, useEditor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; -import React, { useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; +import { compareAgentsForSort } from "@app/lib/assistant"; +import { useAgentConfigurations } from "@app/lib/swr"; import { classNames } from "@app/lib/utils"; -import { makeGetAssistantSuggestions } from "./suggestion.js"; +import { makeGetAssistantSuggestions } from "./suggestion"; +import useAssistantSuggestions from "./useAssistantSuggestions"; + +interface RawMention { + id: string; + name: string; +} + +// function maybeInsertMentions(editor: Editor | null, rawMentions: RawMention[]) { +// if (!editor || rawMentions.length === 0) { +// return; +// } + +// console.log(">> rawMentions:", JSON.stringify(rawMentions, null, 2)); + +// const chainCommands = editor?.chain(); + +// for (const raw of rawMentions) { +// chainCommands +// .insertContent({ +// type: "mention", +// attrs: { +// id: raw.id, +// label: raw.name, +// }, +// }) +// .insertContent(" "); // Add an extra space after the mention. +// } + +// chainCommands.run(); +// } const Tiptap = (props: any) => { + console.log(">> TipTap <<"); + const stickyMentionsTextContent = useRef(null); + + // const suggestions = useAssistantSuggestions( + // props.owner, + // props.conversationId + // ); + + // console.log(">> suggestions:", suggestions); + + // REMOVE: + + const { owner, conversationId } = props; + const { agentConfigurations } = useAgentConfigurations({ + workspaceId: owner.sId, + agentsGetView: conversationId ? { conversationId } : "list", + }); + + const activeAgents = agentConfigurations.filter((a) => a.status === "active"); + activeAgents.sort(compareAgentsForSort); + + // Transform the assistants data into the format expected by Mention plugin + const suggestions = activeAgents.map((agent) => ({ + sId: agent.sId, + pictureUrl: agent.pictureUrl, + name: agent.name, + })); + // Consider: // StarterKit.configure({ // history: false, @@ -50,32 +111,111 @@ const Tiptap = (props: any) => { // TODO: Update suggestion when assistants is loaded! - const editor = useEditor({ - enableInputRules: false, // Disable Markdown when typing. - enablePasteRules: false, // Disable Markdown when pasting. - extensions: [ - StarterKit.configure({}), - PreventEnter, - Mention.configure({ - HTMLAttributes: { - class: - "min-w-0 px-0 py-0 border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0 text-brand font-medium", + const editor = useEditor( + { + enableInputRules: false, // Disable Markdown when typing. + enablePasteRules: false, // Disable Markdown when pasting. + extensions: [ + StarterKit.configure({}), + PreventEnter, + Mention.configure({ + HTMLAttributes: { + class: + "min-w-0 px-0 py-0 border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0 text-brand font-medium", + }, + suggestion: makeGetAssistantSuggestions(suggestions), + }), + Placeholder.configure({ + placeholder: "Ask a question or get some @help", + emptyNodeClass: + "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none", + }), + ], + editorProps: { + attributes: { + class: "border-0 outline-none overflow-y-auto h-full", }, - suggestion: makeGetAssistantSuggestions(props.assistants), - }), - Placeholder.configure({ - placeholder: "Ask a question or get some @help", - emptyNodeClass: - "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none", - }), - ], - editorProps: { - attributes: { - class: "border-0 outline-none overflow-y-auto h-full", }, + // TODO: Should we consider using slotBefore/slotAfter? }, - // TODO: Should we consider using slotBefore/slotAfter? - }); + [agentConfigurations] + ); + + const { stickyMentions, selectedAssistant } = props; + useEffect(() => { + if (!stickyMentions?.length && !selectedAssistant) { + return; + } + + const mentionsToInject = stickyMentions?.length + ? stickyMentions + : ([selectedAssistant] as [AgentMention]); + + const mentionedAgentConfigurationIds = new Set( + mentionsToInject?.map((m) => m.configurationId) + ); + + if (editor) { + // const textContent = editor.get.textContent?.trim(); + + const isNotEmpty = !editor.isEmpty; + if (isNotEmpty && !stickyMentionsTextContent.current) { + return; + } + + if ( + isNotEmpty && + editor.getText() !== stickyMentionsTextContent.current + ) { + // content has changed, we don't clear it (we preserve whatever the user typed) + return; + } + + // we clear the content of the input bar -- at this point, it's either already empty, + // or contains only the sticky mentions added by this hook + editor.commands.setContent(""); + const lastTextNode = null; + for (const configurationId of mentionedAgentConfigurationIds) { + const agentConfiguration = agentConfigurations.find( + (agent) => agent.sId === configurationId + ); + if (!agentConfiguration) { + continue; + } + + console.log(">> hello <<", agentConfiguration); + + editor + ?.chain() + .focus() + .insertContent({ + type: "mention", + attrs: { + id: agentConfiguration.sId, + label: agentConfiguration.name, + }, + }) + .insertContent(" ") // add an extra space after the mention + .run(); + // const mentionNode = getAgentMentionNode(agentConfiguration); + // if (!mentionNode) { + // continue; + // } + + stickyMentionsTextContent.current = editor.getText().trim() || null; + } + // move the cursor to the end of the input bar + if (lastTextNode) { + editor.commands.focus("end"); + } + } + }, [ + stickyMentions, + agentConfigurations, + stickyMentionsTextContent, + selectedAssistant, + editor, + ]); // TODO: Reset after loading. const fileInputRef = useRef(null); @@ -86,7 +226,7 @@ const Tiptap = (props: any) => { "whitespace-pre-wrap font-normal" ); - console.log(">> selectedAssistant:", props.selectedAssistant); + // maybeInsertMentions(editor, props.stickyMentionsToInject); return (
diff --git a/front/components/assistant/conversation/suggestion.js b/front/components/assistant/conversation/suggestion.ts similarity index 85% rename from front/components/assistant/conversation/suggestion.js rename to front/components/assistant/conversation/suggestion.ts index 04c4759b1eb7..410f153c741f 100644 --- a/front/components/assistant/conversation/suggestion.js +++ b/front/components/assistant/conversation/suggestion.ts @@ -3,12 +3,12 @@ import tippy from "tippy.js"; import { MentionList } from "./MentionList.jsx"; -export function makeGetAssistantSuggestions(assistants) { +export function makeGetAssistantSuggestions(suggestions: any[]) { + console.log(">> suggestions(!):", suggestions); return { - items: ({ query }) => { - console.log(">> query:", query); - console.log(">> assistants:", assistants); - return assistants + items: ({ query }: { query: string }) => { + console.log(">> makeGetAssistantSuggestions:", suggestions); + return suggestions .filter((item) => item.name.toLowerCase().startsWith(query.toLowerCase()) ) From 1ca9047bc44980e13f91cc08e682ef571f2789c8 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 09:49:44 +0100 Subject: [PATCH 04/29] Tmp --- .../assistant/conversation/InputBar.tsx | 6 +- .../assistant/conversation/InputBarTipTap.tsx | 190 +++--------------- .../conversation/inputBarEditorContent.tsx | 13 ++ .../conversation/useAssistantSuggestions.ts | 34 ++++ .../conversation/useCustomEditor.tsx | 62 ++++++ .../conversation/useHandleMentions.ts | 90 +++++++++ 6 files changed, 230 insertions(+), 165 deletions(-) create mode 100644 front/components/assistant/conversation/inputBarEditorContent.tsx create mode 100644 front/components/assistant/conversation/useAssistantSuggestions.ts create mode 100644 front/components/assistant/conversation/useCustomEditor.tsx create mode 100644 front/components/assistant/conversation/useHandleMentions.ts diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 2e01d9c2af17..092003597872 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -34,7 +34,7 @@ import { handleFileUploadToText } from "@app/lib/client/handle_file_upload"; import { useAgentConfigurations } from "@app/lib/swr"; import { classNames, filterAndSortAgents } from "@app/lib/utils"; -import Tiptap from "./InputBarTipTap"; +import InputBarContainer from "./InputBarTipTap"; // AGENT MENTION @@ -347,12 +347,12 @@ export function AssistantInputBar({
)} - { - console.log(">> TipTap <<"); - const stickyMentionsTextContent = useRef(null); - - // const suggestions = useAssistantSuggestions( - // props.owner, - // props.conversationId - // ); - - // console.log(">> suggestions:", suggestions); - +const InputBarContainer = (props: any) => { // REMOVE: - const { owner, conversationId } = props; - const { agentConfigurations } = useAgentConfigurations({ - workspaceId: owner.sId, - agentsGetView: conversationId ? { conversationId } : "list", - }); - - const activeAgents = agentConfigurations.filter((a) => a.status === "active"); - activeAgents.sort(compareAgentsForSort); - - // Transform the assistants data into the format expected by Mention plugin - const suggestions = activeAgents.map((agent) => ({ - sId: agent.sId, - pictureUrl: agent.pictureUrl, - name: agent.name, - })); + const suggestions = useAssistantSuggestions(owner, conversationId); // Consider: // StarterKit.configure({ // history: false, // }), - // const activeAgents = agentConfigurations.filter((a) => a.status === "active"); - // activeAgents.sort(compareAgentsForSort); - - // TODO: Should we keep this here? - const [isExpanded, setIsExpanded] = useState(false); - - const PreventEnter = Extension.create({ - addKeyboardShortcuts(this) { - const { editor } = this; - - return { - Enter: () => { - props.onCTAClick(editor.getJSON(), () => { - editor.commands.setContent(""); - setIsExpanded(false); - }); - - return true; - }, - }; - }, - }); - - // TODO: Update suggestion when assistants is loaded! - - const editor = useEditor( - { - enableInputRules: false, // Disable Markdown when typing. - enablePasteRules: false, // Disable Markdown when pasting. - extensions: [ - StarterKit.configure({}), - PreventEnter, - Mention.configure({ - HTMLAttributes: { - class: - "min-w-0 px-0 py-0 border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0 text-brand font-medium", - }, - suggestion: makeGetAssistantSuggestions(suggestions), - }), - Placeholder.configure({ - placeholder: "Ask a question or get some @help", - emptyNodeClass: - "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none", - }), - ], - editorProps: { - attributes: { - class: "border-0 outline-none overflow-y-auto h-full", - }, - }, - // TODO: Should we consider using slotBefore/slotAfter? - }, - [agentConfigurations] - ); + const { onEnterKeyDown } = props; + const editor = useCustomEditor(suggestions, onEnterKeyDown); + // Use the custom hook to handle mentions const { stickyMentions, selectedAssistant } = props; - useEffect(() => { - if (!stickyMentions?.length && !selectedAssistant) { - return; - } - - const mentionsToInject = stickyMentions?.length - ? stickyMentions - : ([selectedAssistant] as [AgentMention]); - - const mentionedAgentConfigurationIds = new Set( - mentionsToInject?.map((m) => m.configurationId) - ); - - if (editor) { - // const textContent = editor.get.textContent?.trim(); - - const isNotEmpty = !editor.isEmpty; - if (isNotEmpty && !stickyMentionsTextContent.current) { - return; - } - - if ( - isNotEmpty && - editor.getText() !== stickyMentionsTextContent.current - ) { - // content has changed, we don't clear it (we preserve whatever the user typed) - return; - } - - // we clear the content of the input bar -- at this point, it's either already empty, - // or contains only the sticky mentions added by this hook - editor.commands.setContent(""); - const lastTextNode = null; - for (const configurationId of mentionedAgentConfigurationIds) { - const agentConfiguration = agentConfigurations.find( - (agent) => agent.sId === configurationId - ); - if (!agentConfiguration) { - continue; - } - - console.log(">> hello <<", agentConfiguration); - - editor - ?.chain() - .focus() - .insertContent({ - type: "mention", - attrs: { - id: agentConfiguration.sId, - label: agentConfiguration.name, - }, - }) - .insertContent(" ") // add an extra space after the mention - .run(); - // const mentionNode = getAgentMentionNode(agentConfiguration); - // if (!mentionNode) { - // continue; - // } - - stickyMentionsTextContent.current = editor.getText().trim() || null; - } - // move the cursor to the end of the input bar - if (lastTextNode) { - editor.commands.focus("end"); - } - } - }, [ - stickyMentions, - agentConfigurations, - stickyMentionsTextContent, - selectedAssistant, + useHandleMentions( editor, - ]); + owner, + conversationId, + stickyMentions, + selectedAssistant + ); // TODO: Reset after loading. const fileInputRef = useRef(null); @@ -226,11 +87,20 @@ const Tiptap = (props: any) => { "whitespace-pre-wrap font-normal" ); - // maybeInsertMentions(editor, props.stickyMentionsToInject); + const [isExpanded, setIsExpanded] = useState(false); + + function handleExpansionToggle() { + setIsExpanded((currentExpanded) => !currentExpanded); + + // If the editor exists, focus at the end of the document when toggling expansion + if (editor) { + editor.commands.focus("end"); + } + } return (
- { icon={isExpanded ? FullscreenExitIcon : FullscreenIcon} size="sm" className="flex" - onClick={() => { - setIsExpanded((event) => !event); - // Focus on the end! - editor?.commands.focus("end"); - }} + onClick={handleExpansionToggle} />
@@ -307,7 +173,7 @@ const Tiptap = (props: any) => { labelVisible={false} disabledTooltip onClick={async () => { - await props.onCTAClick(editor?.getJSON(), () => { + await onEnterKeyDown(editor?.getJSON(), () => { editor?.commands.setContent(""); setIsExpanded(false); }); @@ -318,4 +184,4 @@ const Tiptap = (props: any) => { ); }; -export default Tiptap; +export default InputBarContainer; diff --git a/front/components/assistant/conversation/inputBarEditorContent.tsx b/front/components/assistant/conversation/inputBarEditorContent.tsx new file mode 100644 index 000000000000..cf85931ffc1d --- /dev/null +++ b/front/components/assistant/conversation/inputBarEditorContent.tsx @@ -0,0 +1,13 @@ +import { Editor, EditorContent } from "@tiptap/react"; + +interface InputBarEditorContentProps { + editor: Editor | null; + className: string; +} + +export default function InputBarEditorContent({ + editor, + className, +}: InputBarEditorContentProps) { + return ; +} diff --git a/front/components/assistant/conversation/useAssistantSuggestions.ts b/front/components/assistant/conversation/useAssistantSuggestions.ts new file mode 100644 index 000000000000..640bc1895b26 --- /dev/null +++ b/front/components/assistant/conversation/useAssistantSuggestions.ts @@ -0,0 +1,34 @@ +// useAssistantSuggestions.js +import { WorkspaceType } from "@dust-tt/types"; +import { useMemo } from "react"; + +import { compareAgentsForSort } from "@app/lib/assistant"; +import { useAgentConfigurations } from "@app/lib/swr"; + +const useAssistantSuggestions = ( + owner: WorkspaceType, + conversationId: string | null +) => { + const { agentConfigurations } = useAgentConfigurations({ + workspaceId: owner.sId, + agentsGetView: conversationId ? { conversationId } : "list", + }); + + // `useMemo` will ensure that suggestions is only recalculated when `agentConfigurations` changes. + const suggestions = useMemo(() => { + const activeAgents = agentConfigurations.filter( + (a) => a.status === "active" + ); + activeAgents.sort(compareAgentsForSort); + + return activeAgents.map((agent) => ({ + sId: agent.sId, + pictureUrl: agent.pictureUrl, + name: agent.name, + })); + }, [agentConfigurations]); + + return suggestions; +}; + +export default useAssistantSuggestions; diff --git a/front/components/assistant/conversation/useCustomEditor.tsx b/front/components/assistant/conversation/useCustomEditor.tsx new file mode 100644 index 000000000000..9db16da7b9cf --- /dev/null +++ b/front/components/assistant/conversation/useCustomEditor.tsx @@ -0,0 +1,62 @@ +import Mention from "@tiptap/extension-mention"; +import Placeholder from "@tiptap/extension-placeholder"; +import { Extension, useEditor } from "@tiptap/react"; +import { StarterKit } from "@tiptap/starter-kit"; + +import { makeGetAssistantSuggestions } from "./suggestion"; + +const useCustomEditor = (suggestions: any, onEnterKeyDown: any) => { + const PreventEnter = Extension.create({ + addKeyboardShortcuts(this) { + const { editor } = this; + + return { + Enter: () => { + // TODO: Move to a service. + const clearEditor = () => { + editor.commands.setContent(""); + // setIsExpanded(false); + }; + + // TODO: Parse JSON here, and pass the service. + onEnterKeyDown(editor.getJSON(), clearEditor); + + return true; + }, + }; + }, + }); + + const editor = useEditor( + { + enableInputRules: false, // Disable Markdown when typing. + enablePasteRules: false, // Disable Markdown when pasting. + extensions: [ + StarterKit.configure({}), + PreventEnter, + Mention.configure({ + HTMLAttributes: { + class: + "min-w-0 px-0 py-0 border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0 text-brand font-medium", + }, + suggestion: makeGetAssistantSuggestions(suggestions), + }), + Placeholder.configure({ + placeholder: "Ask a question or get some @help", + emptyNodeClass: + "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none", + }), + ], + editorProps: { + attributes: { + class: "border-0 outline-none overflow-y-auto h-full", + }, + }, + }, + [suggestions] + ); + + return editor; +}; + +export default useCustomEditor; diff --git a/front/components/assistant/conversation/useHandleMentions.ts b/front/components/assistant/conversation/useHandleMentions.ts new file mode 100644 index 000000000000..7146da0895f1 --- /dev/null +++ b/front/components/assistant/conversation/useHandleMentions.ts @@ -0,0 +1,90 @@ +// useHandleMentions.js +import { AgentMention, WorkspaceType } from "@dust-tt/types"; +import { Editor } from "@tiptap/react"; +import { useCallback, useEffect, useMemo, useRef } from "react"; + +import { useAgentConfigurations } from "@app/lib/swr"; + +const useHandleMentions = ( + editor: Editor | null, + owner: WorkspaceType, + conversationId: string | null, + stickyMentions: AgentMention[] | undefined, + selectedAssistant: AgentMention | null +) => { + const { agentConfigurations } = useAgentConfigurations({ + workspaceId: owner.sId, + agentsGetView: conversationId ? { conversationId } : "list", + }); + + // Memoize the mentioned agents to avoid unnecessary recalculations. + const mentionedAgentConfigurationIds = useMemo(() => { + const mentions = stickyMentions?.length + ? stickyMentions + : [selectedAssistant].filter(Boolean); + return new Set(mentions.map((m) => m.configurationId)); + }, [stickyMentions, selectedAssistant]); + + const stickyMentionsTextContent = useRef(null); + + // Function to insert mentions + // TODO: Move to a class/service. + const insertMentions = useCallback(() => { + if (editor) { + editor.commands.setContent(""); + for (const configurationId of mentionedAgentConfigurationIds) { + const agentConfiguration = agentConfigurations.find( + (agent) => agent.sId === configurationId + ); + + if (agentConfiguration) { + editor + .chain() + .focus() + .insertContent({ + type: "mention", + attrs: { + id: agentConfiguration.sId, + label: agentConfiguration.name, + }, + }) + .insertContent(" ") + .run(); // add an extra space after the mention + } + } + // Move the cursor to the end of the editor content + editor.commands.focus("end"); + } + }, [editor, mentionedAgentConfigurationIds, agentConfigurations]); + + useEffect(() => { + if (!stickyMentions?.length && !selectedAssistant) { + return; + } + + // TODO: Change logic to use service. + const editorIsEmpty = editor?.isEmpty; + const hasContent = editorIsEmpty === false; + const textContentHasChanged = + hasContent && + editor?.getText().trim() !== stickyMentionsTextContent.current; + + if (textContentHasChanged) { + // Content has changed, we don't clear it (we preserve whatever the user typed) + return; + } + + // Insert mentions if appropriate + insertMentions(); + + stickyMentionsTextContent.current = editor?.getText().trim() || null; + }, [ + stickyMentions, + selectedAssistant, + mentionedAgentConfigurationIds, + editor, + insertMentions, + ]); +}; + +export default useHandleMentions; From 549265f81f0ea8c234b3d33c878621c4ca7f6d4d Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 11:12:25 +0100 Subject: [PATCH 05/29] Fix each child should have a key warning --- .../assistant/conversation/MentionList.jsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/front/components/assistant/conversation/MentionList.jsx b/front/components/assistant/conversation/MentionList.jsx index 957d4fbc3d21..a41c45f61151 100644 --- a/front/components/assistant/conversation/MentionList.jsx +++ b/front/components/assistant/conversation/MentionList.jsx @@ -62,20 +62,19 @@ export const MentionList = forwardRef(function mentionList(props, ref) {
{props.items.length ? props.items.map((item, index) => ( - <>
- -
- - + +
)) :
No result
} From 6b1fe1d9edc32bad056e6ca7b1818d7c6b64e061 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 11:16:57 +0100 Subject: [PATCH 06/29] :scissors: --- .../components/assistant/conversation/useHandleMentions.ts | 1 + front/lib/swr.ts | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/front/components/assistant/conversation/useHandleMentions.ts b/front/components/assistant/conversation/useHandleMentions.ts index 7146da0895f1..64408c13938b 100644 --- a/front/components/assistant/conversation/useHandleMentions.ts +++ b/front/components/assistant/conversation/useHandleMentions.ts @@ -12,6 +12,7 @@ const useHandleMentions = ( stickyMentions: AgentMention[] | undefined, selectedAssistant: AgentMention | null ) => { + // TODO: Remove. const { agentConfigurations } = useAgentConfigurations({ workspaceId: owner.sId, agentsGetView: conversationId ? { conversationId } : "list", diff --git a/front/lib/swr.ts b/front/lib/swr.ts index 1b9f4ca8adab..81678105b9c8 100644 --- a/front/lib/swr.ts +++ b/front/lib/swr.ts @@ -453,15 +453,11 @@ export function useAgentConfigurations({ typeof agentsGetView === "string" ? `view=${agentsGetView}` : `conversationId=${agentsGetView.conversationId}`; - const { data, error, mutate, isLoading } = useSWR( + const { data, error, mutate } = useSWR( `/api/w/${workspaceId}/assistant/agent_configurations?${viewQueryString}`, agentConfigurationsFetcher ); - console.log(">> data:", data); - console.log(">> error:", error); - console.log(">> isLoading:", isLoading); - return { agentConfigurations: data ? data.agentConfigurations : [], isAgentConfigurationsLoading: !error && !data, From 9591d07ab6f49365166557dc562059e20ee4a2c2 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 11:18:21 +0100 Subject: [PATCH 07/29] :sparkles: --- .../assistant/conversation/InputBar.tsx | 175 +----------------- .../assistant/conversation/InputBarTipTap.tsx | 5 +- .../conversation/useAssistantSuggestions.ts | 12 +- 3 files changed, 6 insertions(+), 186 deletions(-) diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 092003597872..b95b710082c2 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -72,35 +72,12 @@ export function AssistantInputBar({ conversationId: string | null; stickyMentions?: AgentMention[]; }) { - const [agentListVisible, setAgentListVisible] = useState(false); - const [agentListFilter, setAgentListFilter] = useState(""); - const [agentListPosition, setAgentListPosition] = useState<{ - bottom: number; - left: number; - }>({ - bottom: 0, - left: 0, - }); const [contentFragmentBody, setContentFragmentBody] = useState< string | undefined >(undefined); const [contentFragmentFilename, setContentFragmentFilename] = useState< string | undefined >(undefined); - const inputRef = useRef(null); - const agentListRef = useRef<{ - prev: () => void; - next: () => void; - reset: () => void; - selected: () => AgentConfigurationType | null; - noMatch: () => boolean; - perfectMatch: () => boolean; - }>(null); - - useEffect(() => { - inputRef.current?.focus(); - }, []); - const { agentConfigurations } = useAgentConfigurations({ workspaceId: owner.sId, agentsGetView: conversationId ? { conversationId } : "list", @@ -218,7 +195,6 @@ export function AssistantInputBar({ }, [animate, isAnimating]); const [isProcessing, setIsProcessing] = useState(false); - const stickyMentionsTextContent = useRef(null); // GenerationContext: to know if we are generating or not const generationContext = useContext(GenerationContext); @@ -262,41 +238,6 @@ export function AssistantInputBar({ console.log(">> contentFragmentFilename:", contentFragmentFilename); console.log(">> contentFragmentBody:", contentFragmentBody); - let stickyMentionsConfigurationToInject: any[] = []; - if (stickyMentions?.length) { - stickyMentionsConfigurationToInject = stickyMentions; - } else if (selectedAssistant) { - stickyMentionsConfigurationToInject = [selectedAssistant] as [AgentMention]; - } - - console.log(">> selectedAssistant:", selectedAssistant); - - const stickyMentionsToInject: any[] = stickyMentionsConfigurationToInject - .map((configuration) => { - const agentConfig = agentConfigurations.find( - (agent) => agent.sId === configuration.configurationId - ); - - console.log( - ">> config:", - agentConfig, - "for", - configuration.configurationId - ); - - if (!agentConfig) { - return; - } - - return { id: agentConfig.sId, name: agentConfig.name }; - }) - .filter(Boolean); - - console.log( - ">> stickyMentionsToInject:", - JSON.stringify(stickyMentionsToInject, null, 2) - ); - return ( <> {/*
- - {/*
-
- { - // focus on the input text after the file selection interaction is over - inputRef.current?.focus(); - const file = e?.target?.files?.[0]; - if (!file) return; - if (file.size > 10_000_000) { - sendNotification({ - type: "error", - title: "File too large.", - description: - "PDF uploads are limited to 10Mb per file. Please consider uploading a smaller file.", - }); - return; - } - const res = await handleFileUploadToText(file); - - if (res.isErr()) { - sendNotification({ - type: "error", - title: "Error uploading file.", - description: res.error.message, - }); - return; - } - if (res.value.content.length > 1_000_000) { - // This error should pretty much never be triggered but it is a possible case, so here it is. - sendNotification({ - type: "error", - title: "File too large.", - description: - "The extracted text from your PDF has more than 1 million characters. This will overflow the assistant context. Please consider uploading a smaller file.", - }); - return; - } - setContentFragmentFilename(res.value.title); - setContentFragmentBody(res.value.content); - }} - /> - { - fileInputRef.current?.click(); - }} - /> - { - // We construct the HTML for an AgentMention and inject it in the content - // editable with an extra space after it. - const mentionNode = getAgentMentionNode(c); - const contentEditable = - document.getElementById("dust-input-bar"); - if (contentEditable && mentionNode) { - // Add mentionNode as last childe of contentEditable. - contentEditable.appendChild(mentionNode); - const afterTextNode = document.createTextNode(" "); - contentEditable.appendChild(afterTextNode); - contentEditable.focus(); - moveCursorToEnd(contentEditable); - } - }} - assistants={activeAgents} - showBuilderButtons={true} - /> -
- { - setIsExpanded((e) => !e); - }} - /> -
-
-
*/}
@@ -482,17 +322,6 @@ export function FixedAssistantInputBar({ stickyMentions?: AgentMention[]; conversationId: string | null; }) { - const { agentConfigurations } = useAgentConfigurations({ - workspaceId: owner.sId, - agentsGetView: conversationId ? { conversationId } : "list", - }); - - const activeAgents = agentConfigurations.filter((a) => a.status === "active"); - activeAgents.sort(compareAgentsForSort); - - const filtered = filterAndSortAgents(activeAgents, ""); - console.log(">> filtered:", filtered); - return (
diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx index 9d474c57adb7..fa4c01fb4299 100644 --- a/front/components/assistant/conversation/InputBarTipTap.tsx +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -58,8 +58,7 @@ interface RawMention { const InputBarContainer = (props: any) => { // REMOVE: const { owner, conversationId } = props; - const suggestions = useAssistantSuggestions(owner, conversationId); - + const suggestions = useAssistantSuggestions(props.agentConfigurations); // Consider: // StarterKit.configure({ // history: false, @@ -152,7 +151,7 @@ const InputBarContainer = (props: any) => { .insertContent(" ") // add an extra space after the mention .run(); }} - assistants={props.assistants} + assistants={props.allMentions} showBuilderButtons={true} />
diff --git a/front/components/assistant/conversation/useAssistantSuggestions.ts b/front/components/assistant/conversation/useAssistantSuggestions.ts index 640bc1895b26..ffaeb70b3ccd 100644 --- a/front/components/assistant/conversation/useAssistantSuggestions.ts +++ b/front/components/assistant/conversation/useAssistantSuggestions.ts @@ -1,19 +1,11 @@ -// useAssistantSuggestions.js -import { WorkspaceType } from "@dust-tt/types"; +import { AgentConfigurationType } from "@dust-tt/types"; import { useMemo } from "react"; import { compareAgentsForSort } from "@app/lib/assistant"; -import { useAgentConfigurations } from "@app/lib/swr"; const useAssistantSuggestions = ( - owner: WorkspaceType, - conversationId: string | null + agentConfigurations: AgentConfigurationType[] ) => { - const { agentConfigurations } = useAgentConfigurations({ - workspaceId: owner.sId, - agentsGetView: conversationId ? { conversationId } : "list", - }); - // `useMemo` will ensure that suggestions is only recalculated when `agentConfigurations` changes. const suggestions = useMemo(() => { const activeAgents = agentConfigurations.filter( From 829062a4f124bbe678073f554e35d0ad5db8c616 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 12:50:57 +0100 Subject: [PATCH 08/29] Clean up step 1 --- .../assistant/conversation/InputBar.tsx | 10 +- .../assistant/conversation/InputBarTipTap.tsx | 134 +++++++----------- .../conversation/useCustomEditor.tsx | 122 ++++++++++++++-- .../conversation/useHandleMentions.ts | 106 ++++++-------- 4 files changed, 210 insertions(+), 162 deletions(-) diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index b95b710082c2..6b6c30b6f172 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -89,11 +89,10 @@ export function AssistantInputBar({ // const [isExpanded, setIsExpanded] = useState(false); - const handleSubmit = async (jsonPayload, resetEditorText) => { - // TODO: Only display in blue the CTA once the editor is not empty. - // if (empty) { - // return; - // } + const handleSubmit = (isEmpty, jsonPayload, resetEditorText) => { + if (isEmpty) { + return; + } console.log(">>jsonPayload:", JSON.stringify(jsonPayload, null, 2)); @@ -292,7 +291,6 @@ export function AssistantInputBar({ allMentions={activeAgents} agentConfigurations={agentConfigurations} owner={owner} - conversationId={conversationId} selectedAssistant={selectedAssistant} onEnterKeyDown={handleSubmit} stickyMentions={stickyMentions} diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx index fa4c01fb4299..e262639fb0f7 100644 --- a/front/components/assistant/conversation/InputBarTipTap.tsx +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -8,71 +8,65 @@ import { FullscreenIcon, IconButton, } from "@dust-tt/sparkle"; -import { AgentMention } from "@dust-tt/types"; -import Mention from "@tiptap/extension-mention"; -import Placeholder from "@tiptap/extension-placeholder"; -import { Editor, EditorContent, Extension, useEditor } from "@tiptap/react"; -import StarterKit from "@tiptap/starter-kit"; -import React, { useEffect, useRef, useState } from "react"; +import { + AgentConfigurationType, + AgentMention, + WorkspaceType, +} from "@dust-tt/types"; +import React, { useRef, useState } from "react"; import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; -import { compareAgentsForSort } from "@app/lib/assistant"; -import { useAgentConfigurations } from "@app/lib/swr"; import { classNames } from "@app/lib/utils"; import InputBarEditorContent from "./inputBarEditorContent"; -import { makeGetAssistantSuggestions } from "./suggestion"; import useAssistantSuggestions from "./useAssistantSuggestions"; -import useCustomEditor from "./useCustomEditor"; +import useCustomEditor, { CustomEditorProps } from "./useCustomEditor"; import useHandleMentions from "./useHandleMentions"; -interface RawMention { - id: string; - name: string; +interface InputBarContainerProps { + allMentions: AgentConfigurationType[]; + agentConfigurations: AgentConfigurationType[]; + disableAttachment: boolean; + onEnterKeyDown: CustomEditorProps["onEnterKeyDown"]; + onInputFileChange: (e: React.ChangeEvent) => void; + owner: WorkspaceType; + selectedAssistant: AgentMention | null; + stickyMentions: AgentMention[] | undefined; } -// function maybeInsertMentions(editor: Editor | null, rawMentions: RawMention[]) { -// if (!editor || rawMentions.length === 0) { -// return; -// } - -// console.log(">> rawMentions:", JSON.stringify(rawMentions, null, 2)); +const InputBarContainer = ({ + allMentions, + agentConfigurations, + disableAttachment, + onEnterKeyDown, + onInputFileChange, + owner, + selectedAssistant, + stickyMentions, +}: InputBarContainerProps) => { + const suggestions = useAssistantSuggestions(agentConfigurations); -// const chainCommands = editor?.chain(); - -// for (const raw of rawMentions) { -// chainCommands -// .insertContent({ -// type: "mention", -// attrs: { -// id: raw.id, -// label: raw.name, -// }, -// }) -// .insertContent(" "); // Add an extra space after the mention. -// } + const [isExpanded, setIsExpanded] = useState(false); + function handleExpansionToggle() { + setIsExpanded((currentExpanded) => !currentExpanded); -// chainCommands.run(); -// } + // Focus at the end of the document when toggling expansion. + editorService.focusEnd(); + } -const InputBarContainer = (props: any) => { - // REMOVE: - const { owner, conversationId } = props; - const suggestions = useAssistantSuggestions(props.agentConfigurations); - // Consider: - // StarterKit.configure({ - // history: false, - // }), + function resetEditorContainerSize() { + setIsExpanded(false); + } - const { onEnterKeyDown } = props; - const editor = useCustomEditor(suggestions, onEnterKeyDown); + const { editor, editorService } = useCustomEditor({ + suggestions, + onEnterKeyDown, + resetEditorContainerSize, + }); - // Use the custom hook to handle mentions - const { stickyMentions, selectedAssistant } = props; useHandleMentions( - editor, - owner, - conversationId, + editorService, + agentConfigurations, stickyMentions, selectedAssistant ); @@ -86,17 +80,6 @@ const InputBarContainer = (props: any) => { "whitespace-pre-wrap font-normal" ); - const [isExpanded, setIsExpanded] = useState(false); - - function handleExpansionToggle() { - setIsExpanded((currentExpanded) => !currentExpanded); - - // If the editor exists, focus at the end of the document when toggling expansion - if (editor) { - editor.commands.focus("end"); - } - } - return (
{ type="file" ref={fileInputRef} style={{ display: "none" }} - // Move this code to the parent callsite. - onChange={(event) => { - props.onInputFileChange(event); - }} + onChange={onInputFileChange} /> { }} /> { - editor - ?.chain() - .focus() - .insertContent({ - type: "mention", - attrs: { - id: c.sId, - label: c.name, - }, - }) - .insertContent(" ") // add an extra space after the mention - .run(); + editorService.insertMention({ id: c.sId, label: c.name }); }} - assistants={props.allMentions} + assistants={allMentions} showBuilderButtons={true} />
@@ -168,12 +137,13 @@ const InputBarContainer = (props: any) => { size="sm" icon={ArrowUpIcon} label="Send" - disabled={editor?.isEmpty} + disabled={editorService.isEmpty()} labelVisible={false} disabledTooltip onClick={async () => { - await onEnterKeyDown(editor?.getJSON(), () => { - editor?.commands.setContent(""); + const jsonContent = editorService.getJSONContent(); + onEnterKeyDown(editorService.isEmpty(), jsonContent, () => { + editorService.clearEditor(); setIsExpanded(false); }); }} diff --git a/front/components/assistant/conversation/useCustomEditor.tsx b/front/components/assistant/conversation/useCustomEditor.tsx index 9db16da7b9cf..765f9a91e3c1 100644 --- a/front/components/assistant/conversation/useCustomEditor.tsx +++ b/front/components/assistant/conversation/useCustomEditor.tsx @@ -1,11 +1,106 @@ import Mention from "@tiptap/extension-mention"; import Placeholder from "@tiptap/extension-placeholder"; -import { Extension, useEditor } from "@tiptap/react"; +import { Editor, Extension, JSONContent, useEditor } from "@tiptap/react"; import { StarterKit } from "@tiptap/starter-kit"; +import { useMemo } from "react"; import { makeGetAssistantSuggestions } from "./suggestion"; -const useCustomEditor = (suggestions: any, onEnterKeyDown: any) => { +export interface EditorMention { + id: string; + label: string; +} + +const useEditorService = (editor: Editor | null) => { + const editorService = useMemo(() => { + // Return the service object with utility functions + return { + getMentions: () => { + // Implement parsing logic here + // TODO: + return editor?.getJSON(); + }, + + // Insert mention helper function + insertMention: ({ id, label }: { id: string; label: string }) => { + editor + ?.chain() + .focus() + .insertContent({ + type: "mention", + attrs: { id, label }, + }) + .insertContent(" ") // Add an extra space after the mention. + .run(); + }, + + resetWithMentions: (mentions: any[]) => { + editor?.commands.clearContent(); + const chainCommands = editor?.chain().focus(); + + mentions.forEach( + (m) => + chainCommands + ?.insertContent({ + type: "mention", + attrs: m, + }) + .insertContent(" ") // Add an extra space after the mention. + ); + + chainCommands?.run(); + }, + + focusEnd() { + editor?.commands.focus("end"); + }, + + isEmpty() { + return editor?.isEmpty ?? true; + }, + + getJSONContent() { + return editor?.getJSON(); + }, + + getTrimmedText() { + return editor?.getText().trim(); + }, + + clearEditor() { + return editor?.commands.clearContent(); + }, + + // Additional helper functions can be added here. + }; + }, [editor]); + + return editorService; +}; + +export type EditorService = ReturnType; + +export interface CustomEditorProps { + onEnterKeyDown: ( + isEmpty: boolean, + jsonPayload: JSONContent | undefined, + clearEditor: () => void + ) => void; + suggestions: any[]; + resetEditorContainerSize: () => void; +} + +const useCustomEditor = ({ + onEnterKeyDown, + resetEditorContainerSize, + suggestions, +}: CustomEditorProps) => { + // Memoize the suggestion configuration to avoid recreating the object on every render + const getSuggestions = useMemo( + () => makeGetAssistantSuggestions(suggestions), + [suggestions] + ); + const PreventEnter = Extension.create({ addKeyboardShortcuts(this) { const { editor } = this; @@ -14,12 +109,12 @@ const useCustomEditor = (suggestions: any, onEnterKeyDown: any) => { Enter: () => { // TODO: Move to a service. const clearEditor = () => { - editor.commands.setContent(""); - // setIsExpanded(false); + editor.commands.clearContent(); + resetEditorContainerSize(); }; // TODO: Parse JSON here, and pass the service. - onEnterKeyDown(editor.getJSON(), clearEditor); + onEnterKeyDown(editor.isEmpty, editor.getJSON(), clearEditor); return true; }, @@ -32,6 +127,10 @@ const useCustomEditor = (suggestions: any, onEnterKeyDown: any) => { enableInputRules: false, // Disable Markdown when typing. enablePasteRules: false, // Disable Markdown when pasting. extensions: [ + // TODO: Consider, + // StarterKit.configure({ + // history: false, + // }), StarterKit.configure({}), PreventEnter, Mention.configure({ @@ -39,7 +138,7 @@ const useCustomEditor = (suggestions: any, onEnterKeyDown: any) => { class: "min-w-0 px-0 py-0 border-none outline-none focus:outline-none focus:border-none ring-0 focus:ring-0 text-brand font-medium", }, - suggestion: makeGetAssistantSuggestions(suggestions), + suggestion: getSuggestions, }), Placeholder.configure({ placeholder: "Ask a question or get some @help", @@ -53,10 +152,17 @@ const useCustomEditor = (suggestions: any, onEnterKeyDown: any) => { }, }, }, - [suggestions] + [getSuggestions] ); - return editor; + // Use the custom hook to get the editor service + const editorService = useEditorService(editor); + + // Expose the editor instance and the editor service + return { + editor, + editorService, + }; }; export default useCustomEditor; diff --git a/front/components/assistant/conversation/useHandleMentions.ts b/front/components/assistant/conversation/useHandleMentions.ts index 64408c13938b..a7a2b4dcaae9 100644 --- a/front/components/assistant/conversation/useHandleMentions.ts +++ b/front/components/assistant/conversation/useHandleMentions.ts @@ -1,91 +1,65 @@ // useHandleMentions.js -import { AgentMention, WorkspaceType } from "@dust-tt/types"; -import { Editor } from "@tiptap/react"; -import { useCallback, useEffect, useMemo, useRef } from "react"; +import { AgentConfigurationType, AgentMention } from "@dust-tt/types"; +import { useEffect, useMemo, useRef } from "react"; -import { useAgentConfigurations } from "@app/lib/swr"; +import { EditorMention, EditorService } from "./useCustomEditor"; const useHandleMentions = ( - editor: Editor | null, - owner: WorkspaceType, - conversationId: string | null, + editorService: EditorService, + agentConfigurations: AgentConfigurationType[], stickyMentions: AgentMention[] | undefined, selectedAssistant: AgentMention | null ) => { - // TODO: Remove. - const { agentConfigurations } = useAgentConfigurations({ - workspaceId: owner.sId, - agentsGetView: conversationId ? { conversationId } : "list", - }); + const stickyMentionsTextContent = useRef(null); // Memoize the mentioned agents to avoid unnecessary recalculations. const mentionedAgentConfigurationIds = useMemo(() => { - const mentions = stickyMentions?.length - ? stickyMentions - : [selectedAssistant].filter(Boolean); - return new Set(mentions.map((m) => m.configurationId)); + let mentions: AgentMention[] = []; + if (stickyMentions?.length) { + mentions = stickyMentions; + } else if (selectedAssistant) { + mentions = [selectedAssistant]; + } + + return Array.from(new Set(mentions.map((m) => m.configurationId))); }, [stickyMentions, selectedAssistant]); - const stickyMentionsTextContent = useRef(null); + useEffect(() => { + if (mentionedAgentConfigurationIds.length === 0) { + return; + } + + const editorIsEmpty = editorService.isEmpty(); + const onlyContainsPreviousStickyMention = + !editorIsEmpty && + editorService.getTrimmedText() === stickyMentionsTextContent.current; + + // Insert sticky mentions under two conditions: + // 1. The editor is currently empty. + // 2. The editor contains only the sticky mention from a previously selected assistant. + // This ensures that sticky mentions are maintained but not duplicated. + if (editorIsEmpty || onlyContainsPreviousStickyMention) { + const mentionsToInsert: EditorMention[] = []; - // Function to insert mentions - // TODO: Move to a class/service. - const insertMentions = useCallback(() => { - if (editor) { - editor.commands.setContent(""); for (const configurationId of mentionedAgentConfigurationIds) { const agentConfiguration = agentConfigurations.find( (agent) => agent.sId === configurationId ); - if (agentConfiguration) { - editor - .chain() - .focus() - .insertContent({ - type: "mention", - attrs: { - id: agentConfiguration.sId, - label: agentConfiguration.name, - }, - }) - .insertContent(" ") - .run(); // add an extra space after the mention + mentionsToInsert.push({ + id: agentConfiguration.sId, + label: agentConfiguration.name, + }); } } - // Move the cursor to the end of the editor content - editor.commands.focus("end"); - } - }, [editor, mentionedAgentConfigurationIds, agentConfigurations]); - - useEffect(() => { - if (!stickyMentions?.length && !selectedAssistant) { - return; - } - // TODO: Change logic to use service. - const editorIsEmpty = editor?.isEmpty; - const hasContent = editorIsEmpty === false; - const textContentHasChanged = - hasContent && - editor?.getText().trim() !== stickyMentionsTextContent.current; - - if (textContentHasChanged) { - // Content has changed, we don't clear it (we preserve whatever the user typed) - return; + if (mentionsToInsert.length !== 0) { + editorService.resetWithMentions(mentionsToInsert); + stickyMentionsTextContent.current = + editorService.getTrimmedText() ?? null; + } } - - // Insert mentions if appropriate - insertMentions(); - - stickyMentionsTextContent.current = editor?.getText().trim() || null; - }, [ - stickyMentions, - selectedAssistant, - mentionedAgentConfigurationIds, - editor, - insertMentions, - ]); + }, [agentConfigurations, editorService, mentionedAgentConfigurationIds]); }; export default useHandleMentions; From 68e09a2688275056a49845e7d6899918a6fe3ec6 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 15:06:55 +0100 Subject: [PATCH 09/29] Final clean up --- .../assistant/conversation/InputBar.tsx | 106 +++++------------- .../assistant/conversation/InputBarTipTap.tsx | 12 +- .../assistant/conversation/MentionList.jsx | 83 -------------- .../assistant/conversation/MentionList.tsx | 97 ++++++++++++++++ .../assistant/conversation/suggestion.ts | 29 +++-- .../conversation/useAssistantSuggestions.ts | 4 +- .../conversation/useCustomEditor.tsx | 78 ++++++++++--- 7 files changed, 213 insertions(+), 196 deletions(-) delete mode 100644 front/components/assistant/conversation/MentionList.jsx create mode 100644 front/components/assistant/conversation/MentionList.tsx diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 6b6c30b6f172..77cc15be428d 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -1,30 +1,14 @@ -import { - ArrowUpIcon, - AttachmentIcon, - Avatar, - Button, - Citation, - FullscreenExitIcon, - FullscreenIcon, - IconButton, - StopIcon, -} from "@dust-tt/sparkle"; +import { Button, Citation, StopIcon } from "@dust-tt/sparkle"; import { WorkspaceType } from "@dust-tt/types"; import { AgentConfigurationType } from "@dust-tt/types"; import { AgentMention, MentionType } from "@dust-tt/types"; -import { Transition } from "@headlessui/react"; import { createContext, - ForwardedRef, - forwardRef, Fragment, useContext, useEffect, - useImperativeHandle, - useRef, useState, } from "react"; -import * as ReactDOMServer from "react-dom/server"; import { mutate } from "swr"; import { GenerationContext } from "@app/components/assistant/conversation/GenerationContextProvider"; @@ -32,9 +16,9 @@ import { SendNotificationsContext } from "@app/components/sparkle/Notification"; import { compareAgentsForSort } from "@app/lib/assistant"; import { handleFileUploadToText } from "@app/lib/client/handle_file_upload"; import { useAgentConfigurations } from "@app/lib/swr"; -import { classNames, filterAndSortAgents } from "@app/lib/utils"; +import { classNames } from "@app/lib/utils"; -import InputBarContainer from "./InputBarTipTap"; +import InputBarContainer, { InputBarContainerProps } from "./InputBarTipTap"; // AGENT MENTION @@ -84,44 +68,32 @@ export function AssistantInputBar({ }); const sendNotification = useContext(SendNotificationsContext); + const [isAnimating, setIsAnimating] = useState(false); + const { animate, selectedAssistant } = useContext(InputBarContext); + useEffect(() => { + if (animate && !isAnimating) { + setIsAnimating(true); + setTimeout(() => setIsAnimating(false), 1500); + } + }, [animate, isAnimating]); + const activeAgents = agentConfigurations.filter((a) => a.status === "active"); activeAgents.sort(compareAgentsForSort); - // const [isExpanded, setIsExpanded] = useState(false); - - const handleSubmit = (isEmpty, jsonPayload, resetEditorText) => { + const handleSubmit: InputBarContainerProps["onEnterKeyDown"] = ( + isEmpty, + textAndMentions, + resetEditorText + ) => { if (isEmpty) { return; } - console.log(">>jsonPayload:", JSON.stringify(jsonPayload, null, 2)); - - const mentions: MentionType[] = []; - let content = ""; - - const [firstParagraph] = jsonPayload.content; - - for (const node of firstParagraph.content) { - if (node.type === "mention") { - const { id: agentConfigurationId, label: agentName } = node.attrs; - if (agentConfigurationId && agentName) { - mentions.push({ - configurationId: agentConfigurationId, - }); - // Internal format for mentions is `:mention[agentName]{sId=agentConfigurationId}`. - content += `:mention[${agentName}]{sId=${agentConfigurationId}}`; - } - } - if (node.type === "text") { - content += node.text; - } - } - - console.log(">> content:", content); + const { mentions: rawMentions, text } = textAndMentions; + const mentions: MentionType[] = rawMentions.map((m) => ({ + configurationId: m.id, + })); - content = content.trim(); - // Still needed? - content = content.replace(/\u200B/g, ""); let contentFragment: | { title: string; @@ -138,16 +110,16 @@ export function AssistantInputBar({ contentType: "file_attachment", }; } - // setIsExpanded(false); - onSubmit(content, mentions, contentFragment); + onSubmit(text, mentions, contentFragment); resetEditorText(); setContentFragmentFilename(undefined); setContentFragmentBody(undefined); }; - const onInputFileChange = async (event) => { - const file = event?.target?.files?.[0]; - console.log("> file:", file); + const onInputFileChange: InputBarContainerProps["onInputFileChange"] = async ( + event + ) => { + const file = (event?.target as HTMLInputElement)?.files?.[0]; if (!file) return; if (file.size > 10_000_000) { sendNotification({ @@ -183,16 +155,6 @@ export function AssistantInputBar({ setContentFragmentBody(res.value.content); }; - const [isAnimating, setIsAnimating] = useState(false); - const { animate, selectedAssistant } = useContext(InputBarContext); - - useEffect(() => { - if (animate && !isAnimating) { - setIsAnimating(true); - setTimeout(() => setIsAnimating(false), 1500); - } - }, [animate, isAnimating]); - const [isProcessing, setIsProcessing] = useState(false); // GenerationContext: to know if we are generating or not @@ -232,22 +194,8 @@ export function AssistantInputBar({ } }, [isProcessing, generationContext.generatingMessageIds.length]); - console.log("> isAnimating:", isAnimating); - - console.log(">> contentFragmentFilename:", contentFragmentFilename); - console.log(">> contentFragmentBody:", contentFragmentBody); - return ( <> - {/* */} - {generationContext.generatingMessageIds.length > 0 && (
-
- )) - :
No result
- } -
- ) -}) \ No newline at end of file diff --git a/front/components/assistant/conversation/MentionList.tsx b/front/components/assistant/conversation/MentionList.tsx new file mode 100644 index 000000000000..010e50c605f2 --- /dev/null +++ b/front/components/assistant/conversation/MentionList.tsx @@ -0,0 +1,97 @@ +// import './MentionList.scss' + +import { Avatar } from "@dust-tt/sparkle"; +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from "react"; + +import { classNames } from "@app/lib/utils"; + +import { EditorSuggestion } from "./suggestion"; + +interface MentionListProps { + command: any; + items: EditorSuggestion[]; +} + +export const MentionList = forwardRef(function mentionList( + props: MentionListProps, + ref +) { + const [selectedIndex, setSelectedIndex] = useState(0); + + const selectItem = (index: number) => { + const item = props.items[index]; + + if (item) { + props.command({ id: item.id, label: item.label }); + } + }; + + const upHandler = () => { + setSelectedIndex( + (selectedIndex + props.items.length - 1) % props.items.length + ); + }; + + const downHandler = () => { + setSelectedIndex((selectedIndex + 1) % props.items.length); + }; + + const enterHandler = () => { + selectItem(selectedIndex); + }; + + useEffect(() => setSelectedIndex(0), [props.items]); + + useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }: { event: { key: string } }) => { + if (event.key === "ArrowUp") { + upHandler(); + return true; + } + + if (event.key === "ArrowDown") { + downHandler(); + return true; + } + + if (event.key === "Enter") { + enterHandler(); + return true; + } + + return false; + }, + })); + + return ( +
+ {props.items.length ? ( + props.items.map((item, index) => ( +
+ + +
+ )) + ) : ( +
No result
+ )} +
+ ); +}); diff --git a/front/components/assistant/conversation/suggestion.ts b/front/components/assistant/conversation/suggestion.ts index 410f153c741f..8bc7799a53c3 100644 --- a/front/components/assistant/conversation/suggestion.ts +++ b/front/components/assistant/conversation/suggestion.ts @@ -1,26 +1,33 @@ import { ReactRenderer } from "@tiptap/react"; import tippy from "tippy.js"; -import { MentionList } from "./MentionList.jsx"; +import { MentionList } from "./MentionList"; -export function makeGetAssistantSuggestions(suggestions: any[]) { - console.log(">> suggestions(!):", suggestions); +export interface EditorSuggestion { + id: string; + label: string; + pictureUrl: string; +} + +const SUGGESTION_DISPLAY_LIMIT = 7; + +export function makeGetAssistantSuggestions(suggestions: EditorSuggestion[]) { return { + // TODO: Consider refactoring to eliminate the dependency on tippy. items: ({ query }: { query: string }) => { - console.log(">> makeGetAssistantSuggestions:", suggestions); return suggestions .filter((item) => - item.name.toLowerCase().startsWith(query.toLowerCase()) + item.label.toLowerCase().startsWith(query.toLowerCase()) ) - .slice(0, 5); + .slice(0, SUGGESTION_DISPLAY_LIMIT); }, render: () => { - let reactRenderer; - let popup; + let reactRenderer: any; + let popup: any; return { - onStart: (props) => { + onStart: (props: any) => { if (!props.clientRect) { return; } @@ -41,7 +48,7 @@ export function makeGetAssistantSuggestions(suggestions: any[]) { }); }, - onUpdate(props) { + onUpdate(props: any) { reactRenderer.updateProps(props); if (!props.clientRect) { @@ -53,7 +60,7 @@ export function makeGetAssistantSuggestions(suggestions: any[]) { }); }, - onKeyDown(props) { + onKeyDown(props: any) { if (props.event.key === "Escape") { popup[0].hide(); diff --git a/front/components/assistant/conversation/useAssistantSuggestions.ts b/front/components/assistant/conversation/useAssistantSuggestions.ts index ffaeb70b3ccd..eaebf653bd82 100644 --- a/front/components/assistant/conversation/useAssistantSuggestions.ts +++ b/front/components/assistant/conversation/useAssistantSuggestions.ts @@ -14,9 +14,9 @@ const useAssistantSuggestions = ( activeAgents.sort(compareAgentsForSort); return activeAgents.map((agent) => ({ - sId: agent.sId, + id: agent.sId, + label: agent.name, pictureUrl: agent.pictureUrl, - name: agent.name, })); }, [agentConfigurations]); diff --git a/front/components/assistant/conversation/useCustomEditor.tsx b/front/components/assistant/conversation/useCustomEditor.tsx index 765f9a91e3c1..99baf91fcba6 100644 --- a/front/components/assistant/conversation/useCustomEditor.tsx +++ b/front/components/assistant/conversation/useCustomEditor.tsx @@ -4,23 +4,57 @@ import { Editor, Extension, JSONContent, useEditor } from "@tiptap/react"; import { StarterKit } from "@tiptap/starter-kit"; import { useMemo } from "react"; -import { makeGetAssistantSuggestions } from "./suggestion"; +import { EditorSuggestion, makeGetAssistantSuggestions } from "./suggestion"; export interface EditorMention { id: string; label: string; } +function getTextAndMentionsFromNode(node?: JSONContent) { + let textContent = ""; + let mentions: EditorMention[] = []; + + if (!node) { + return { mentions, text: textContent }; + } + + // Check if the node is of type 'text' and concatenate its text. + if (node.type === "text") { + textContent += node.text; + } + + // If the node is a 'mention', concatenate the mention label and add to mentions array. + if (node.type === "mention") { + // TODO: We should not expose `sId` here. + textContent += `:mention[${node.attrs?.label}]{sId=${node.attrs?.id}}`; + mentions.push({ + id: node.attrs?.id, + label: node.attrs?.label, + }); + } + + // If the node is a 'hardBreak' or a 'paragraph', add a newline character. + if (node.type && ["hardBreak", "paragraph"].includes(node.type)) { + textContent += "\n"; + } + + // If the node has content, recursively get text and mentions from each child node + if (node.content) { + node.content.forEach((childNode) => { + const childResult = getTextAndMentionsFromNode(childNode); + textContent += childResult.text; + mentions = mentions.concat(childResult.mentions); + }); + } + + return { text: textContent, mentions: mentions }; +} + const useEditorService = (editor: Editor | null) => { const editorService = useMemo(() => { // Return the service object with utility functions return { - getMentions: () => { - // Implement parsing logic here - // TODO: - return editor?.getJSON(); - }, - // Insert mention helper function insertMention: ({ id, label }: { id: string; label: string }) => { editor @@ -34,7 +68,7 @@ const useEditorService = (editor: Editor | null) => { .run(); }, - resetWithMentions: (mentions: any[]) => { + resetWithMentions: (mentions: EditorMention[]) => { editor?.commands.clearContent(); const chainCommands = editor?.chain().focus(); @@ -63,6 +97,17 @@ const useEditorService = (editor: Editor | null) => { return editor?.getJSON(); }, + getTextAndMentions() { + const { mentions, text } = getTextAndMentionsFromNode( + editor?.getJSON() + ); + + return { + mentions, + text: text.trim(), + }; + }, + getTrimmedText() { return editor?.getText().trim(); }, @@ -70,8 +115,6 @@ const useEditorService = (editor: Editor | null) => { clearEditor() { return editor?.commands.clearContent(); }, - - // Additional helper functions can be added here. }; }, [editor]); @@ -83,10 +126,10 @@ export type EditorService = ReturnType; export interface CustomEditorProps { onEnterKeyDown: ( isEmpty: boolean, - jsonPayload: JSONContent | undefined, + textAndMentions: ReturnType, clearEditor: () => void ) => void; - suggestions: any[]; + suggestions: EditorSuggestion[]; resetEditorContainerSize: () => void; } @@ -107,14 +150,17 @@ const useCustomEditor = ({ return { Enter: () => { - // TODO: Move to a service. const clearEditor = () => { editor.commands.clearContent(); resetEditorContainerSize(); }; // TODO: Parse JSON here, and pass the service. - onEnterKeyDown(editor.isEmpty, editor.getJSON(), clearEditor); + onEnterKeyDown( + editor.isEmpty, + getTextAndMentionsFromNode(editor.getJSON()), + clearEditor + ); return true; }, @@ -131,7 +177,9 @@ const useCustomEditor = ({ // StarterKit.configure({ // history: false, // }), - StarterKit.configure({}), + StarterKit.configure({ + heading: false, + }), PreventEnter, Mention.configure({ HTMLAttributes: { From 2bf290dcb6209298c439c365be71d47c61a54f2d Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 15:09:21 +0100 Subject: [PATCH 10/29] :scissors: --- front/components/assistant/conversation/InputBarTipTap.tsx | 2 -- .../components/assistant/conversation/useCustomEditor.tsx | 7 +------ .../components/assistant/conversation/useHandleMentions.ts | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx index dfaa2cd30df9..1a774fcb8a50 100644 --- a/front/components/assistant/conversation/InputBarTipTap.tsx +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -1,5 +1,3 @@ -// import "./styles.scss"; - import { ArrowUpIcon, AttachmentIcon, diff --git a/front/components/assistant/conversation/useCustomEditor.tsx b/front/components/assistant/conversation/useCustomEditor.tsx index 99baf91fcba6..98a5921cb77c 100644 --- a/front/components/assistant/conversation/useCustomEditor.tsx +++ b/front/components/assistant/conversation/useCustomEditor.tsx @@ -173,10 +173,6 @@ const useCustomEditor = ({ enableInputRules: false, // Disable Markdown when typing. enablePasteRules: false, // Disable Markdown when pasting. extensions: [ - // TODO: Consider, - // StarterKit.configure({ - // history: false, - // }), StarterKit.configure({ heading: false, }), @@ -203,10 +199,9 @@ const useCustomEditor = ({ [getSuggestions] ); - // Use the custom hook to get the editor service const editorService = useEditorService(editor); - // Expose the editor instance and the editor service + // Expose the editor instance and the editor service. return { editor, editorService, diff --git a/front/components/assistant/conversation/useHandleMentions.ts b/front/components/assistant/conversation/useHandleMentions.ts index a7a2b4dcaae9..0bf012754163 100644 --- a/front/components/assistant/conversation/useHandleMentions.ts +++ b/front/components/assistant/conversation/useHandleMentions.ts @@ -1,4 +1,3 @@ -// useHandleMentions.js import { AgentConfigurationType, AgentMention } from "@dust-tt/types"; import { useEffect, useMemo, useRef } from "react"; From c0e069280d995ec9c3d8afe50dc637aae408b9a9 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 15:48:53 +0100 Subject: [PATCH 11/29] :art: --- front/components/assistant/conversation/InputBarTipTap.tsx | 6 +++--- front/components/assistant/conversation/MentionList.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarTipTap.tsx index 1a774fcb8a50..64794be1ec0a 100644 --- a/front/components/assistant/conversation/InputBarTipTap.tsx +++ b/front/components/assistant/conversation/InputBarTipTap.tsx @@ -74,12 +74,12 @@ const InputBarContainer = ({ const contentEditableClasses = classNames( "inline-block w-full", - "border-0 pr-1 pl-2 sm:pl-0 outline-none ring-0 focus:border-0 focus:outline-none focus:ring-0 py-1.5", + "border-0 pr-1 pl-2 sm:pl-0 outline-none ring-0 focus:border-0 focus:outline-none focus:ring-0 py-3.5", "whitespace-pre-wrap font-normal" ); return ( -
+
-
+
)) ) : ( -
No result
+
No result
)}
); From be3534bf8747be8355185e166236ed46f97043bb Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 15:52:04 +0100 Subject: [PATCH 12/29] Rename --- .../conversation/{InputBarTipTap.tsx => InputBarContainer.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename front/components/assistant/conversation/{InputBarTipTap.tsx => InputBarContainer.tsx} (100%) diff --git a/front/components/assistant/conversation/InputBarTipTap.tsx b/front/components/assistant/conversation/InputBarContainer.tsx similarity index 100% rename from front/components/assistant/conversation/InputBarTipTap.tsx rename to front/components/assistant/conversation/InputBarContainer.tsx From b09bef40a2c6d81bd5ef5efee8b3f5b9a0cc31d2 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 16:01:12 +0100 Subject: [PATCH 13/29] :sparkles: --- .../components/assistant/conversation/InputBar.tsx | 5 +++-- .../assistant/conversation/InputBarContainer.tsx | 4 ++-- .../conversation/inputBarEditorContent.tsx | 13 ------------- 3 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 front/components/assistant/conversation/inputBarEditorContent.tsx diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/InputBar.tsx index 77cc15be428d..e4cc5df6f7ed 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/InputBar.tsx @@ -12,14 +12,15 @@ import { import { mutate } from "swr"; import { GenerationContext } from "@app/components/assistant/conversation/GenerationContextProvider"; +import InputBarContainer, { + InputBarContainerProps, +} from "@app/components/assistant/conversation/InputBarContainer"; import { SendNotificationsContext } from "@app/components/sparkle/Notification"; import { compareAgentsForSort } from "@app/lib/assistant"; import { handleFileUploadToText } from "@app/lib/client/handle_file_upload"; import { useAgentConfigurations } from "@app/lib/swr"; import { classNames } from "@app/lib/utils"; -import InputBarContainer, { InputBarContainerProps } from "./InputBarTipTap"; - // AGENT MENTION function AgentMention({ diff --git a/front/components/assistant/conversation/InputBarContainer.tsx b/front/components/assistant/conversation/InputBarContainer.tsx index 64794be1ec0a..8ecdeb239e73 100644 --- a/front/components/assistant/conversation/InputBarContainer.tsx +++ b/front/components/assistant/conversation/InputBarContainer.tsx @@ -11,12 +11,12 @@ import { AgentMention, WorkspaceType, } from "@dust-tt/types"; +import { EditorContent } from "@tiptap/react"; import React, { useRef, useState } from "react"; import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; import { classNames } from "@app/lib/utils"; -import InputBarEditorContent from "./inputBarEditorContent"; import useAssistantSuggestions from "./useAssistantSuggestions"; import useCustomEditor, { CustomEditorProps } from "./useCustomEditor"; import useHandleMentions from "./useHandleMentions"; @@ -80,7 +80,7 @@ const InputBarContainer = ({ return (
- ; -} From d121f2dfef42e0c2a1dd2d2e557cc84de53d2c20 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 16:08:06 +0100 Subject: [PATCH 14/29] Move files around --- .../assistant/conversation/InputBarContainer.tsx | 9 +++++---- .../assistant/conversation/{ => hooks}/MentionList.tsx | 3 +-- .../assistant/conversation/{ => hooks}/suggestion.ts | 2 +- .../conversation/{ => hooks}/useAssistantSuggestions.ts | 0 .../conversation/{ => hooks}/useCustomEditor.tsx | 5 ++++- .../useHandleMentions.tsx} | 5 ++++- 6 files changed, 15 insertions(+), 9 deletions(-) rename front/components/assistant/conversation/{ => hooks}/MentionList.tsx (95%) rename front/components/assistant/conversation/{ => hooks}/suggestion.ts (95%) rename front/components/assistant/conversation/{ => hooks}/useAssistantSuggestions.ts (100%) rename front/components/assistant/conversation/{ => hooks}/useCustomEditor.tsx (97%) rename front/components/assistant/conversation/{useHandleMentions.ts => hooks/useHandleMentions.tsx} (94%) diff --git a/front/components/assistant/conversation/InputBarContainer.tsx b/front/components/assistant/conversation/InputBarContainer.tsx index 8ecdeb239e73..ac25c06baa03 100644 --- a/front/components/assistant/conversation/InputBarContainer.tsx +++ b/front/components/assistant/conversation/InputBarContainer.tsx @@ -15,12 +15,13 @@ import { EditorContent } from "@tiptap/react"; import React, { useRef, useState } from "react"; import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; +import useAssistantSuggestions from "@app/components/assistant/conversation/hooks/useAssistantSuggestions"; +import useCustomEditor, { + CustomEditorProps, +} from "@app/components/assistant/conversation/hooks/useCustomEditor"; +import useHandleMentions from "@app/components/assistant/conversation/hooks/useHandleMentions"; import { classNames } from "@app/lib/utils"; -import useAssistantSuggestions from "./useAssistantSuggestions"; -import useCustomEditor, { CustomEditorProps } from "./useCustomEditor"; -import useHandleMentions from "./useHandleMentions"; - export interface InputBarContainerProps { allAssistants: AgentConfigurationType[]; agentConfigurations: AgentConfigurationType[]; diff --git a/front/components/assistant/conversation/MentionList.tsx b/front/components/assistant/conversation/hooks/MentionList.tsx similarity index 95% rename from front/components/assistant/conversation/MentionList.tsx rename to front/components/assistant/conversation/hooks/MentionList.tsx index b776fe58131d..81d5fcfda17c 100644 --- a/front/components/assistant/conversation/MentionList.tsx +++ b/front/components/assistant/conversation/hooks/MentionList.tsx @@ -8,10 +8,9 @@ import React, { useState, } from "react"; +import { EditorSuggestion } from "@app/components/assistant/conversation/hooks/suggestion"; import { classNames } from "@app/lib/utils"; -import { EditorSuggestion } from "./suggestion"; - interface MentionListProps { command: any; items: EditorSuggestion[]; diff --git a/front/components/assistant/conversation/suggestion.ts b/front/components/assistant/conversation/hooks/suggestion.ts similarity index 95% rename from front/components/assistant/conversation/suggestion.ts rename to front/components/assistant/conversation/hooks/suggestion.ts index 8bc7799a53c3..23816c90c726 100644 --- a/front/components/assistant/conversation/suggestion.ts +++ b/front/components/assistant/conversation/hooks/suggestion.ts @@ -1,7 +1,7 @@ import { ReactRenderer } from "@tiptap/react"; import tippy from "tippy.js"; -import { MentionList } from "./MentionList"; +import { MentionList } from "@app/components/assistant/conversation/hooks/MentionList"; export interface EditorSuggestion { id: string; diff --git a/front/components/assistant/conversation/useAssistantSuggestions.ts b/front/components/assistant/conversation/hooks/useAssistantSuggestions.ts similarity index 100% rename from front/components/assistant/conversation/useAssistantSuggestions.ts rename to front/components/assistant/conversation/hooks/useAssistantSuggestions.ts diff --git a/front/components/assistant/conversation/useCustomEditor.tsx b/front/components/assistant/conversation/hooks/useCustomEditor.tsx similarity index 97% rename from front/components/assistant/conversation/useCustomEditor.tsx rename to front/components/assistant/conversation/hooks/useCustomEditor.tsx index 98a5921cb77c..847e14bc142d 100644 --- a/front/components/assistant/conversation/useCustomEditor.tsx +++ b/front/components/assistant/conversation/hooks/useCustomEditor.tsx @@ -4,7 +4,10 @@ import { Editor, Extension, JSONContent, useEditor } from "@tiptap/react"; import { StarterKit } from "@tiptap/starter-kit"; import { useMemo } from "react"; -import { EditorSuggestion, makeGetAssistantSuggestions } from "./suggestion"; +import { + EditorSuggestion, + makeGetAssistantSuggestions, +} from "@app/components/assistant/conversation/hooks/suggestion"; export interface EditorMention { id: string; diff --git a/front/components/assistant/conversation/useHandleMentions.ts b/front/components/assistant/conversation/hooks/useHandleMentions.tsx similarity index 94% rename from front/components/assistant/conversation/useHandleMentions.ts rename to front/components/assistant/conversation/hooks/useHandleMentions.tsx index 0bf012754163..9cd609d1e37f 100644 --- a/front/components/assistant/conversation/useHandleMentions.ts +++ b/front/components/assistant/conversation/hooks/useHandleMentions.tsx @@ -1,7 +1,10 @@ import { AgentConfigurationType, AgentMention } from "@dust-tt/types"; import { useEffect, useMemo, useRef } from "react"; -import { EditorMention, EditorService } from "./useCustomEditor"; +import type { + EditorMention, + EditorService, +} from "@app/components/assistant/conversation/hooks/useCustomEditor"; const useHandleMentions = ( editorService: EditorService, From aa5eccf8a5c1df53c490e38eef12912d2d680b95 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 16:09:17 +0100 Subject: [PATCH 15/29] :scissors: --- front/components/assistant/conversation/hooks/MentionList.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/front/components/assistant/conversation/hooks/MentionList.tsx b/front/components/assistant/conversation/hooks/MentionList.tsx index 81d5fcfda17c..d9695b3ba611 100644 --- a/front/components/assistant/conversation/hooks/MentionList.tsx +++ b/front/components/assistant/conversation/hooks/MentionList.tsx @@ -1,5 +1,3 @@ -// import './MentionList.scss' - import { Avatar } from "@dust-tt/sparkle"; import React, { forwardRef, From 34e44ff8b6bb4982b9e6deabef3e0f535960e855 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 18:33:34 +0100 Subject: [PATCH 16/29] Move files to input_bar folder --- .../assistant/conversation/{ => input_bar}/InputBar.tsx | 2 +- .../conversation/{ => input_bar}/InputBarContainer.tsx | 6 +++--- .../conversation/{ => input_bar}/hooks/MentionList.tsx | 2 +- .../conversation/{ => input_bar}/hooks/suggestion.ts | 2 +- .../{ => input_bar}/hooks/useAssistantSuggestions.ts | 0 .../conversation/{ => input_bar}/hooks/useCustomEditor.tsx | 2 +- .../{ => input_bar}/hooks/useHandleMentions.tsx | 2 +- front/pages/w/[wId]/assistant/[cId]/index.tsx | 2 +- front/pages/w/[wId]/assistant/new.tsx | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) rename front/components/assistant/conversation/{ => input_bar}/InputBar.tsx (99%) rename front/components/assistant/conversation/{ => input_bar}/InputBarContainer.tsx (96%) rename front/components/assistant/conversation/{ => input_bar}/hooks/MentionList.tsx (98%) rename front/components/assistant/conversation/{ => input_bar}/hooks/suggestion.ts (98%) rename front/components/assistant/conversation/{ => input_bar}/hooks/useAssistantSuggestions.ts (100%) rename front/components/assistant/conversation/{ => input_bar}/hooks/useCustomEditor.tsx (98%) rename front/components/assistant/conversation/{ => input_bar}/hooks/useHandleMentions.tsx (96%) diff --git a/front/components/assistant/conversation/InputBar.tsx b/front/components/assistant/conversation/input_bar/InputBar.tsx similarity index 99% rename from front/components/assistant/conversation/InputBar.tsx rename to front/components/assistant/conversation/input_bar/InputBar.tsx index e4cc5df6f7ed..1b458b9a9fbd 100644 --- a/front/components/assistant/conversation/InputBar.tsx +++ b/front/components/assistant/conversation/input_bar/InputBar.tsx @@ -14,7 +14,7 @@ import { mutate } from "swr"; import { GenerationContext } from "@app/components/assistant/conversation/GenerationContextProvider"; import InputBarContainer, { InputBarContainerProps, -} from "@app/components/assistant/conversation/InputBarContainer"; +} from "@app/components/assistant/conversation/input_bar/InputBarContainer"; import { SendNotificationsContext } from "@app/components/sparkle/Notification"; import { compareAgentsForSort } from "@app/lib/assistant"; import { handleFileUploadToText } from "@app/lib/client/handle_file_upload"; diff --git a/front/components/assistant/conversation/InputBarContainer.tsx b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx similarity index 96% rename from front/components/assistant/conversation/InputBarContainer.tsx rename to front/components/assistant/conversation/input_bar/InputBarContainer.tsx index ac25c06baa03..b8ea5a1430cb 100644 --- a/front/components/assistant/conversation/InputBarContainer.tsx +++ b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx @@ -15,11 +15,11 @@ import { EditorContent } from "@tiptap/react"; import React, { useRef, useState } from "react"; import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; -import useAssistantSuggestions from "@app/components/assistant/conversation/hooks/useAssistantSuggestions"; +import useAssistantSuggestions from "@app/components/assistant/conversation/input_bar/hooks/useAssistantSuggestions"; import useCustomEditor, { CustomEditorProps, -} from "@app/components/assistant/conversation/hooks/useCustomEditor"; -import useHandleMentions from "@app/components/assistant/conversation/hooks/useHandleMentions"; +} from "@app/components/assistant/conversation/input_bar/hooks/useCustomEditor"; +import useHandleMentions from "@app/components/assistant/conversation/input_bar/hooks/useHandleMentions"; import { classNames } from "@app/lib/utils"; export interface InputBarContainerProps { diff --git a/front/components/assistant/conversation/hooks/MentionList.tsx b/front/components/assistant/conversation/input_bar/hooks/MentionList.tsx similarity index 98% rename from front/components/assistant/conversation/hooks/MentionList.tsx rename to front/components/assistant/conversation/input_bar/hooks/MentionList.tsx index d9695b3ba611..738d63f16175 100644 --- a/front/components/assistant/conversation/hooks/MentionList.tsx +++ b/front/components/assistant/conversation/input_bar/hooks/MentionList.tsx @@ -6,7 +6,7 @@ import React, { useState, } from "react"; -import { EditorSuggestion } from "@app/components/assistant/conversation/hooks/suggestion"; +import { EditorSuggestion } from "@app/components/assistant/conversation/input_bar/hooks/suggestion"; import { classNames } from "@app/lib/utils"; interface MentionListProps { diff --git a/front/components/assistant/conversation/hooks/suggestion.ts b/front/components/assistant/conversation/input_bar/hooks/suggestion.ts similarity index 98% rename from front/components/assistant/conversation/hooks/suggestion.ts rename to front/components/assistant/conversation/input_bar/hooks/suggestion.ts index 23816c90c726..037116d8c069 100644 --- a/front/components/assistant/conversation/hooks/suggestion.ts +++ b/front/components/assistant/conversation/input_bar/hooks/suggestion.ts @@ -1,7 +1,7 @@ import { ReactRenderer } from "@tiptap/react"; import tippy from "tippy.js"; -import { MentionList } from "@app/components/assistant/conversation/hooks/MentionList"; +import { MentionList } from "@app/components/assistant/conversation/input_bar/hooks/MentionList"; export interface EditorSuggestion { id: string; diff --git a/front/components/assistant/conversation/hooks/useAssistantSuggestions.ts b/front/components/assistant/conversation/input_bar/hooks/useAssistantSuggestions.ts similarity index 100% rename from front/components/assistant/conversation/hooks/useAssistantSuggestions.ts rename to front/components/assistant/conversation/input_bar/hooks/useAssistantSuggestions.ts diff --git a/front/components/assistant/conversation/hooks/useCustomEditor.tsx b/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx similarity index 98% rename from front/components/assistant/conversation/hooks/useCustomEditor.tsx rename to front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx index 847e14bc142d..5d20f8fb11a2 100644 --- a/front/components/assistant/conversation/hooks/useCustomEditor.tsx +++ b/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx @@ -7,7 +7,7 @@ import { useMemo } from "react"; import { EditorSuggestion, makeGetAssistantSuggestions, -} from "@app/components/assistant/conversation/hooks/suggestion"; +} from "@app/components/assistant/conversation/input_bar/hooks/suggestion"; export interface EditorMention { id: string; diff --git a/front/components/assistant/conversation/hooks/useHandleMentions.tsx b/front/components/assistant/conversation/input_bar/hooks/useHandleMentions.tsx similarity index 96% rename from front/components/assistant/conversation/hooks/useHandleMentions.tsx rename to front/components/assistant/conversation/input_bar/hooks/useHandleMentions.tsx index 9cd609d1e37f..0c1912e68633 100644 --- a/front/components/assistant/conversation/hooks/useHandleMentions.tsx +++ b/front/components/assistant/conversation/input_bar/hooks/useHandleMentions.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useRef } from "react"; import type { EditorMention, EditorService, -} from "@app/components/assistant/conversation/hooks/useCustomEditor"; +} from "@app/components/assistant/conversation/input_bar/hooks/useCustomEditor"; const useHandleMentions = ( editorService: EditorService, diff --git a/front/pages/w/[wId]/assistant/[cId]/index.tsx b/front/pages/w/[wId]/assistant/[cId]/index.tsx index 8c6e3125e9ce..abd6d4f0a81a 100644 --- a/front/pages/w/[wId]/assistant/[cId]/index.tsx +++ b/front/pages/w/[wId]/assistant/[cId]/index.tsx @@ -8,7 +8,7 @@ import { useContext, useEffect, useState } from "react"; import Conversation from "@app/components/assistant/conversation/Conversation"; import { ConversationTitle } from "@app/components/assistant/conversation/ConversationTitle"; import { GenerationContextProvider } from "@app/components/assistant/conversation/GenerationContextProvider"; -import { FixedAssistantInputBar } from "@app/components/assistant/conversation/InputBar"; +import { FixedAssistantInputBar } from "@app/components/assistant/conversation/input_bar/InputBar"; import { AssistantSidebarMenu } from "@app/components/assistant/conversation/SidebarMenu"; import AppLayout from "@app/components/sparkle/AppLayout"; import { subNavigationConversations } from "@app/components/sparkle/navigation"; diff --git a/front/pages/w/[wId]/assistant/new.tsx b/front/pages/w/[wId]/assistant/new.tsx index 3d22844a295f..1ee37c17968b 100644 --- a/front/pages/w/[wId]/assistant/new.tsx +++ b/front/pages/w/[wId]/assistant/new.tsx @@ -31,7 +31,7 @@ import { GenerationContextProvider } from "@app/components/assistant/conversatio import { FixedAssistantInputBar, InputBarContext, -} from "@app/components/assistant/conversation/InputBar"; +} from "@app/components/assistant/conversation/input_bar/InputBar"; import { AssistantSidebarMenu } from "@app/components/assistant/conversation/SidebarMenu"; import AppLayout from "@app/components/sparkle/AppLayout"; import { subNavigationConversations } from "@app/components/sparkle/navigation"; From 88acac595739ffbcf5f8d64d11a7dcc8f2ef2782 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 18:42:51 +0100 Subject: [PATCH 17/29] Fix cursor on placeholder --- .../assistant/conversation/input_bar/hooks/useCustomEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx b/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx index 5d20f8fb11a2..d88198736f14 100644 --- a/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx +++ b/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx @@ -190,7 +190,7 @@ const useCustomEditor = ({ Placeholder.configure({ placeholder: "Ask a question or get some @help", emptyNodeClass: - "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none", + "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none first:before:h-0", }), ], editorProps: { From 35083378ca7c458f8452c15468ed9edf74a67cff Mon Sep 17 00:00:00 2001 From: Flavien David Date: Thu, 21 Dec 2023 21:10:02 +0100 Subject: [PATCH 18/29] :books: --- .../assistant/conversation/input_bar/InputBarContainer.tsx | 6 +++--- .../input_bar/{hooks => editor}/MentionList.tsx | 2 +- .../conversation/input_bar/{hooks => editor}/suggestion.ts | 2 +- .../input_bar/{hooks => editor}/useAssistantSuggestions.ts | 0 .../input_bar/{hooks => editor}/useCustomEditor.tsx | 2 +- .../input_bar/{hooks => editor}/useHandleMentions.tsx | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename front/components/assistant/conversation/input_bar/{hooks => editor}/MentionList.tsx (98%) rename front/components/assistant/conversation/input_bar/{hooks => editor}/suggestion.ts (98%) rename front/components/assistant/conversation/input_bar/{hooks => editor}/useAssistantSuggestions.ts (100%) rename front/components/assistant/conversation/input_bar/{hooks => editor}/useCustomEditor.tsx (98%) rename front/components/assistant/conversation/input_bar/{hooks => editor}/useHandleMentions.tsx (96%) diff --git a/front/components/assistant/conversation/input_bar/InputBarContainer.tsx b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx index b8ea5a1430cb..ef2e61c550c2 100644 --- a/front/components/assistant/conversation/input_bar/InputBarContainer.tsx +++ b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx @@ -15,11 +15,11 @@ import { EditorContent } from "@tiptap/react"; import React, { useRef, useState } from "react"; import { AssistantPicker } from "@app/components/assistant/AssistantPicker"; -import useAssistantSuggestions from "@app/components/assistant/conversation/input_bar/hooks/useAssistantSuggestions"; +import useAssistantSuggestions from "@app/components/assistant/conversation/input_bar/editor/useAssistantSuggestions"; import useCustomEditor, { CustomEditorProps, -} from "@app/components/assistant/conversation/input_bar/hooks/useCustomEditor"; -import useHandleMentions from "@app/components/assistant/conversation/input_bar/hooks/useHandleMentions"; +} from "@app/components/assistant/conversation/input_bar/editor/useCustomEditor"; +import useHandleMentions from "@app/components/assistant/conversation/input_bar/editor/useHandleMentions"; import { classNames } from "@app/lib/utils"; export interface InputBarContainerProps { diff --git a/front/components/assistant/conversation/input_bar/hooks/MentionList.tsx b/front/components/assistant/conversation/input_bar/editor/MentionList.tsx similarity index 98% rename from front/components/assistant/conversation/input_bar/hooks/MentionList.tsx rename to front/components/assistant/conversation/input_bar/editor/MentionList.tsx index 738d63f16175..e8fbed02e6d3 100644 --- a/front/components/assistant/conversation/input_bar/hooks/MentionList.tsx +++ b/front/components/assistant/conversation/input_bar/editor/MentionList.tsx @@ -6,7 +6,7 @@ import React, { useState, } from "react"; -import { EditorSuggestion } from "@app/components/assistant/conversation/input_bar/hooks/suggestion"; +import { EditorSuggestion } from "@app/components/assistant/conversation/input_bar/editor/suggestion"; import { classNames } from "@app/lib/utils"; interface MentionListProps { diff --git a/front/components/assistant/conversation/input_bar/hooks/suggestion.ts b/front/components/assistant/conversation/input_bar/editor/suggestion.ts similarity index 98% rename from front/components/assistant/conversation/input_bar/hooks/suggestion.ts rename to front/components/assistant/conversation/input_bar/editor/suggestion.ts index 037116d8c069..bdc0711ddd95 100644 --- a/front/components/assistant/conversation/input_bar/hooks/suggestion.ts +++ b/front/components/assistant/conversation/input_bar/editor/suggestion.ts @@ -1,7 +1,7 @@ import { ReactRenderer } from "@tiptap/react"; import tippy from "tippy.js"; -import { MentionList } from "@app/components/assistant/conversation/input_bar/hooks/MentionList"; +import { MentionList } from "@app/components/assistant/conversation/input_bar/editor/MentionList"; export interface EditorSuggestion { id: string; diff --git a/front/components/assistant/conversation/input_bar/hooks/useAssistantSuggestions.ts b/front/components/assistant/conversation/input_bar/editor/useAssistantSuggestions.ts similarity index 100% rename from front/components/assistant/conversation/input_bar/hooks/useAssistantSuggestions.ts rename to front/components/assistant/conversation/input_bar/editor/useAssistantSuggestions.ts diff --git a/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx similarity index 98% rename from front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx rename to front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx index d88198736f14..322e79830ccb 100644 --- a/front/components/assistant/conversation/input_bar/hooks/useCustomEditor.tsx +++ b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx @@ -7,7 +7,7 @@ import { useMemo } from "react"; import { EditorSuggestion, makeGetAssistantSuggestions, -} from "@app/components/assistant/conversation/input_bar/hooks/suggestion"; +} from "@app/components/assistant/conversation/input_bar/editor/suggestion"; export interface EditorMention { id: string; diff --git a/front/components/assistant/conversation/input_bar/hooks/useHandleMentions.tsx b/front/components/assistant/conversation/input_bar/editor/useHandleMentions.tsx similarity index 96% rename from front/components/assistant/conversation/input_bar/hooks/useHandleMentions.tsx rename to front/components/assistant/conversation/input_bar/editor/useHandleMentions.tsx index 0c1912e68633..b61875282ce6 100644 --- a/front/components/assistant/conversation/input_bar/hooks/useHandleMentions.tsx +++ b/front/components/assistant/conversation/input_bar/editor/useHandleMentions.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useRef } from "react"; import type { EditorMention, EditorService, -} from "@app/components/assistant/conversation/input_bar/hooks/useCustomEditor"; +} from "@app/components/assistant/conversation/input_bar/editor/useCustomEditor"; const useHandleMentions = ( editorService: EditorService, From 8038b9c0c30d0544268dca2b2033abf23bc376e1 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 10:18:23 +0100 Subject: [PATCH 19/29] Only accept supported file extensions --- .../assistant/conversation/input_bar/InputBarContainer.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/InputBarContainer.tsx b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx index ef2e61c550c2..6475e8af55d1 100644 --- a/front/components/assistant/conversation/input_bar/InputBarContainer.tsx +++ b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx @@ -96,10 +96,11 @@ const InputBarContainer = ({
Date: Fri, 22 Dec 2023 10:42:10 +0100 Subject: [PATCH 20/29] Support tab to complete with selected assistant --- .../input_bar/editor/MentionList.tsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/editor/MentionList.tsx b/front/components/assistant/conversation/input_bar/editor/MentionList.tsx index e8fbed02e6d3..ae7918f864ff 100644 --- a/front/components/assistant/conversation/input_bar/editor/MentionList.tsx +++ b/front/components/assistant/conversation/input_bar/editor/MentionList.tsx @@ -42,26 +42,31 @@ export const MentionList = forwardRef(function mentionList( selectItem(selectedIndex); }; + const tabHandler = () => { + enterHandler(); + }; + useEffect(() => setSelectedIndex(0), [props.items]); useImperativeHandle(ref, () => ({ onKeyDown: ({ event }: { event: { key: string } }) => { - if (event.key === "ArrowUp") { - upHandler(); - return true; - } + switch (event.key) { + case "ArrowUp": + upHandler(); + return true; + case "ArrowDown": + downHandler(); + return true; + case "Enter": + enterHandler(); + return true; + case "Tab": + tabHandler(); + return true; - if (event.key === "ArrowDown") { - downHandler(); - return true; + default: + return false; } - - if (event.key === "Enter") { - enterHandler(); - return true; - } - - return false; }, })); From c61fe56241d11e181583b0e804842a6dcd583090 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 11:07:31 +0100 Subject: [PATCH 21/29] Select item on exact query match, prevent space default --- .../input_bar/editor/MentionList.tsx | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/front/components/assistant/conversation/input_bar/editor/MentionList.tsx b/front/components/assistant/conversation/input_bar/editor/MentionList.tsx index ae7918f864ff..49fb8db2b70a 100644 --- a/front/components/assistant/conversation/input_bar/editor/MentionList.tsx +++ b/front/components/assistant/conversation/input_bar/editor/MentionList.tsx @@ -12,6 +12,7 @@ import { classNames } from "@app/lib/utils"; interface MentionListProps { command: any; items: EditorSuggestion[]; + query: string; } export const MentionList = forwardRef(function mentionList( @@ -46,10 +47,32 @@ export const MentionList = forwardRef(function mentionList( enterHandler(); }; + // Handler that selects the item if the current query matches its label exactly. + const selectItemOnExactMatch = () => { + const { query, items } = props; + + // Check if items are defined and not empty, and get the current selected item. + const currentSelectedItem = items?.[selectedIndex]; + + // Check if a selected item exists and if the query matches its label exactly. + if (currentSelectedItem && query === currentSelectedItem.label) { + selectItem(selectedIndex); + // Indicate that the default action of the Space key should be prevented + return true; + } + + // Allow the default Space key action when there's no exact match or items are undefined + return false; + }; + + const spaceHandler = () => { + return selectItemOnExactMatch(); + }; + useEffect(() => setSelectedIndex(0), [props.items]); useImperativeHandle(ref, () => ({ - onKeyDown: ({ event }: { event: { key: string } }) => { + onKeyDown: ({ event }: { event: KeyboardEvent }) => { switch (event.key) { case "ArrowUp": upHandler(); @@ -63,6 +86,11 @@ export const MentionList = forwardRef(function mentionList( case "Tab": tabHandler(); return true; + case " ": + if (spaceHandler()) { + event.preventDefault(); + } + break; default: return false; From f6f1349f019f280cf7d32a1c392c2a59e45b743f Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 12:22:04 +0100 Subject: [PATCH 22/29] Auto focus the editor --- .../assistant/conversation/input_bar/editor/useCustomEditor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx index 322e79830ccb..bed01732da7f 100644 --- a/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx +++ b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx @@ -173,6 +173,7 @@ const useCustomEditor = ({ const editor = useEditor( { + autofocus: true, enableInputRules: false, // Disable Markdown when typing. enablePasteRules: false, // Disable Markdown when pasting. extensions: [ From 248235040db0cf8bb771bcb2c69f14673968598f Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 12:52:59 +0100 Subject: [PATCH 23/29] Hack to patch up the attachment issue --- .../conversation/input_bar/InputBar.tsx | 48 +++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/InputBar.tsx b/front/components/assistant/conversation/input_bar/InputBar.tsx index 1b458b9a9fbd..fa6f5332f2e0 100644 --- a/front/components/assistant/conversation/input_bar/InputBar.tsx +++ b/front/components/assistant/conversation/input_bar/InputBar.tsx @@ -5,8 +5,10 @@ import { AgentMention, MentionType } from "@dust-tt/types"; import { createContext, Fragment, + useCallback, useContext, useEffect, + useRef, useState, } from "react"; import { mutate } from "swr"; @@ -40,7 +42,31 @@ function AgentMention({ ); } -// AGENT LIST +// Custom hook to synchronize state with a ref as a temporary workaround. +// This hack is used to maintain a synchronous and up-to-date reference to state values +// that are otherwise affected by parent component re-renders due to changing dependencies. +// This approach ensures that the latest state is accessible synchronously when needed +// for operations that cannot rely on the normal asynchronous nature of setState. +// This is not an ideal solution and should be addressed with a more robust state management strategy +// as a follow-up action to minimize unnecessary re-renders and improve component performance. +function useSyncedState(initialValue: T | undefined) { + const [state, setState] = useState(initialValue); + const ref = useRef(initialValue); + + // Synchronize ref with state + useEffect(() => { + ref.current = state; + }, [state]); + + // Function to update both state and ref + const setSyncedState = useCallback((newValue: T | undefined) => { + setState(newValue); + ref.current = newValue; // This line isn't strictly necessary because of the useEffect above + }, []); + + // Return both state and ref, along with the setter function + return [state, ref, setSyncedState] as const; +} export function AssistantInputBar({ owner, @@ -57,12 +83,14 @@ export function AssistantInputBar({ conversationId: string | null; stickyMentions?: AgentMention[]; }) { - const [contentFragmentBody, setContentFragmentBody] = useState< - string | undefined - >(undefined); - const [contentFragmentFilename, setContentFragmentFilename] = useState< - string | undefined - >(undefined); + const [contentFragmentBody, contentFragmentBodyRef, setContentFragmentBody] = + useSyncedState(undefined); + const [ + contentFragmentFilename, + contentFragmentFilenameRef, + setContentFragmentFilename, + ] = useSyncedState(undefined); + const { agentConfigurations } = useAgentConfigurations({ workspaceId: owner.sId, agentsGetView: conversationId ? { conversationId } : "list", @@ -103,10 +131,10 @@ export function AssistantInputBar({ contentType: string; } | undefined = undefined; - if (contentFragmentBody && contentFragmentFilename) { + if (contentFragmentFilenameRef.current && contentFragmentBodyRef.current) { contentFragment = { - title: contentFragmentFilename, - content: contentFragmentBody, + title: contentFragmentFilenameRef.current, + content: contentFragmentBodyRef.current, url: null, contentType: "file_attachment", }; From 5e1f3bdbb464dbb5edf6910ce1fe6a83c1a36f12 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 12:55:24 +0100 Subject: [PATCH 24/29] Clean timeout on useEffect --- .../assistant/conversation/input_bar/InputBar.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/front/components/assistant/conversation/input_bar/InputBar.tsx b/front/components/assistant/conversation/input_bar/InputBar.tsx index fa6f5332f2e0..da71c43584f1 100644 --- a/front/components/assistant/conversation/input_bar/InputBar.tsx +++ b/front/components/assistant/conversation/input_bar/InputBar.tsx @@ -100,10 +100,18 @@ export function AssistantInputBar({ const [isAnimating, setIsAnimating] = useState(false); const { animate, selectedAssistant } = useContext(InputBarContext); useEffect(() => { + let timeoutId: NodeJS.Timeout; if (animate && !isAnimating) { setIsAnimating(true); - setTimeout(() => setIsAnimating(false), 1500); + timeoutId = setTimeout(() => setIsAnimating(false), 1500); } + + // Cleanup function to clear the timeout + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; }, [animate, isAnimating]); const activeAgents = agentConfigurations.filter((a) => a.status === "active"); From 4c7ac301edd0445b71dcaddce40504e07217f3c1 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 13:02:13 +0100 Subject: [PATCH 25/29] :sparkles: --- .../assistant/conversation/input_bar/InputBar.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/InputBar.tsx b/front/components/assistant/conversation/input_bar/InputBar.tsx index da71c43584f1..ccded1158571 100644 --- a/front/components/assistant/conversation/input_bar/InputBar.tsx +++ b/front/components/assistant/conversation/input_bar/InputBar.tsx @@ -53,18 +53,17 @@ function useSyncedState(initialValue: T | undefined) { const [state, setState] = useState(initialValue); const ref = useRef(initialValue); - // Synchronize ref with state + // Synchronize ref with state. useEffect(() => { ref.current = state; }, [state]); - // Function to update both state and ref + // Function to update both state and ref. const setSyncedState = useCallback((newValue: T | undefined) => { setState(newValue); - ref.current = newValue; // This line isn't strictly necessary because of the useEffect above }, []); - // Return both state and ref, along with the setter function + // Return both state and ref, along with the setter function. return [state, ref, setSyncedState] as const; } From 0ed8056da3e744d7ba829b19977b1d028ed07266 Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 13:11:56 +0100 Subject: [PATCH 26/29] Focus editor after input file loaded --- .../assistant/conversation/input_bar/InputBarContainer.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/InputBarContainer.tsx b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx index 6475e8af55d1..56320e6c67bb 100644 --- a/front/components/assistant/conversation/input_bar/InputBarContainer.tsx +++ b/front/components/assistant/conversation/input_bar/InputBarContainer.tsx @@ -27,7 +27,7 @@ export interface InputBarContainerProps { agentConfigurations: AgentConfigurationType[]; disableAttachment: boolean; onEnterKeyDown: CustomEditorProps["onEnterKeyDown"]; - onInputFileChange: (e: React.ChangeEvent) => void; + onInputFileChange: (e: React.ChangeEvent) => Promise; owner: WorkspaceType; selectedAssistant: AgentMention | null; stickyMentions: AgentMention[] | undefined; @@ -97,7 +97,10 @@ const InputBarContainer = ({
{ + await onInputFileChange(e); + editorService.focusEnd(); + }} ref={fileInputRef} style={{ display: "none" }} type="file" From 11622b1a764dcecd0733e3a0e3b08982d2a3263e Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 15:47:59 +0100 Subject: [PATCH 27/29] Revert hack --- .../conversation/input_bar/InputBar.tsx | 47 ++++--------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/InputBar.tsx b/front/components/assistant/conversation/input_bar/InputBar.tsx index ccded1158571..581976e3dcf3 100644 --- a/front/components/assistant/conversation/input_bar/InputBar.tsx +++ b/front/components/assistant/conversation/input_bar/InputBar.tsx @@ -5,10 +5,8 @@ import { AgentMention, MentionType } from "@dust-tt/types"; import { createContext, Fragment, - useCallback, useContext, useEffect, - useRef, useState, } from "react"; import { mutate } from "swr"; @@ -42,31 +40,6 @@ function AgentMention({ ); } -// Custom hook to synchronize state with a ref as a temporary workaround. -// This hack is used to maintain a synchronous and up-to-date reference to state values -// that are otherwise affected by parent component re-renders due to changing dependencies. -// This approach ensures that the latest state is accessible synchronously when needed -// for operations that cannot rely on the normal asynchronous nature of setState. -// This is not an ideal solution and should be addressed with a more robust state management strategy -// as a follow-up action to minimize unnecessary re-renders and improve component performance. -function useSyncedState(initialValue: T | undefined) { - const [state, setState] = useState(initialValue); - const ref = useRef(initialValue); - - // Synchronize ref with state. - useEffect(() => { - ref.current = state; - }, [state]); - - // Function to update both state and ref. - const setSyncedState = useCallback((newValue: T | undefined) => { - setState(newValue); - }, []); - - // Return both state and ref, along with the setter function. - return [state, ref, setSyncedState] as const; -} - export function AssistantInputBar({ owner, onSubmit, @@ -82,14 +55,12 @@ export function AssistantInputBar({ conversationId: string | null; stickyMentions?: AgentMention[]; }) { - const [contentFragmentBody, contentFragmentBodyRef, setContentFragmentBody] = - useSyncedState(undefined); - const [ - contentFragmentFilename, - contentFragmentFilenameRef, - setContentFragmentFilename, - ] = useSyncedState(undefined); - + const [contentFragmentBody, setContentFragmentBody] = useState< + string | undefined + >(undefined); + const [contentFragmentFilename, setContentFragmentFilename] = useState< + string | undefined + >(undefined); const { agentConfigurations } = useAgentConfigurations({ workspaceId: owner.sId, agentsGetView: conversationId ? { conversationId } : "list", @@ -138,10 +109,10 @@ export function AssistantInputBar({ contentType: string; } | undefined = undefined; - if (contentFragmentFilenameRef.current && contentFragmentBodyRef.current) { + if (contentFragmentFilename && contentFragmentBody) { contentFragment = { - title: contentFragmentFilenameRef.current, - content: contentFragmentBodyRef.current, + title: contentFragmentFilename, + content: contentFragmentBody, url: null, contentType: "file_attachment", }; From 47aba8409b05400f215afcc40c8945206fbcfe1c Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 15:50:10 +0100 Subject: [PATCH 28/29] Prevent default on enter. --- .../input_bar/editor/useCustomEditor.tsx | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx index bed01732da7f..e05af415bc84 100644 --- a/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx +++ b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx @@ -1,6 +1,6 @@ import Mention from "@tiptap/extension-mention"; import Placeholder from "@tiptap/extension-placeholder"; -import { Editor, Extension, JSONContent, useEditor } from "@tiptap/react"; +import { Editor, JSONContent, useEditor } from "@tiptap/react"; import { StarterKit } from "@tiptap/starter-kit"; import { useMemo } from "react"; @@ -147,30 +147,6 @@ const useCustomEditor = ({ [suggestions] ); - const PreventEnter = Extension.create({ - addKeyboardShortcuts(this) { - const { editor } = this; - - return { - Enter: () => { - const clearEditor = () => { - editor.commands.clearContent(); - resetEditorContainerSize(); - }; - - // TODO: Parse JSON here, and pass the service. - onEnterKeyDown( - editor.isEmpty, - getTextAndMentionsFromNode(editor.getJSON()), - clearEditor - ); - - return true; - }, - }; - }, - }); - const editor = useEditor( { autofocus: true, @@ -180,7 +156,6 @@ const useCustomEditor = ({ StarterKit.configure({ heading: false, }), - PreventEnter, Mention.configure({ HTMLAttributes: { class: @@ -194,15 +169,41 @@ const useCustomEditor = ({ "first:before:text-gray-400 first:before:float-left first:before:content-[attr(data-placeholder)] first:before:pointer-events-none first:before:h-0", }), ], - editorProps: { - attributes: { - class: "border-0 outline-none overflow-y-auto h-full", - }, - }, }, [getSuggestions] ); + editor?.setOptions({ + editorProps: { + attributes: { + class: "border-0 outline-none overflow-y-auto h-full", + }, + handleKeyDown: (view, event) => { + if (event.key === "Enter") { + // Prevent the default Enter key behavior + event.preventDefault(); + + const clearEditor = () => { + editor.commands.clearContent(); + resetEditorContainerSize(); + }; + + onEnterKeyDown( + editor.isEmpty, + getTextAndMentionsFromNode(editor.getJSON()), + clearEditor + ); + + // Return true to indicate that this key event has been handled + return true; + } + + // Return false to let other keydown handlers or TipTap's default behavior process the event + return false; + }, + }, + }); + const editorService = useEditorService(editor); // Expose the editor instance and the editor service. From b2f597ca45760444407280155f5eff74b939f54b Mon Sep 17 00:00:00 2001 From: Flavien David Date: Fri, 22 Dec 2023 16:02:54 +0100 Subject: [PATCH 29/29] Handle Shift + Enter // Fix autofocus on end --- .../conversation/input_bar/editor/useCustomEditor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx index e05af415bc84..2f32d7f5086c 100644 --- a/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx +++ b/front/components/assistant/conversation/input_bar/editor/useCustomEditor.tsx @@ -149,7 +149,7 @@ const useCustomEditor = ({ const editor = useEditor( { - autofocus: true, + autofocus: "end", enableInputRules: false, // Disable Markdown when typing. enablePasteRules: false, // Disable Markdown when pasting. extensions: [ @@ -179,7 +179,7 @@ const useCustomEditor = ({ class: "border-0 outline-none overflow-y-auto h-full", }, handleKeyDown: (view, event) => { - if (event.key === "Enter") { + if (event.key === "Enter" && !event.shiftKey) { // Prevent the default Enter key behavior event.preventDefault();