diff --git a/frontend/src/components/common/LinkField/LinkField.tsx b/frontend/src/components/common/LinkField/LinkField.tsx index 480fafdab..7219249ee 100644 --- a/frontend/src/components/common/LinkField/LinkField.tsx +++ b/frontend/src/components/common/LinkField/LinkField.tsx @@ -19,10 +19,11 @@ export interface LinkFieldProps { autofocus?: boolean, dropdownClass?: string, required?: boolean, + suggestedItems?: SearchResult[], } -const LinkField = ({ doctype, filters, label, placeholder, value, required, setValue, disabled, autofocus, dropdownClass }: LinkFieldProps) => { +const LinkField = ({ doctype, filters, label, placeholder, value, required, setValue, disabled, autofocus, dropdownClass, suggestedItems }: LinkFieldProps) => { const [searchText, setSearchText] = useState(value ?? '') @@ -30,7 +31,7 @@ const LinkField = ({ doctype, filters, label, placeholder, value, required, setV const { data } = useSearch(doctype, searchText, filters) - const items: SearchResult[] = data?.message ?? [] + const items: SearchResult[] = [...(suggestedItems ?? []), ...(data?.message ?? [])] const { isOpen, @@ -44,6 +45,9 @@ const LinkField = ({ doctype, filters, label, placeholder, value, required, setV } = useCombobox({ onInputValueChange({ inputValue }) { setSearchText(inputValue ?? '') + if (!inputValue) { + setValue('') + } }, items: items, itemToString(item) { @@ -54,7 +58,6 @@ const LinkField = ({ doctype, filters, label, placeholder, value, required, setV setValue(selectedItem?.value ?? '') }, defaultInputValue: value, - defaultIsOpen: isDesktop && autofocus, defaultSelectedItem: items.find(item => item.value === value), }) diff --git a/frontend/src/components/feature/CommandMenu/SettingsList.tsx b/frontend/src/components/feature/CommandMenu/SettingsList.tsx index 2aecfc335..adc0cb403 100644 --- a/frontend/src/components/feature/CommandMenu/SettingsList.tsx +++ b/frontend/src/components/feature/CommandMenu/SettingsList.tsx @@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom' import { commandMenuOpenAtom } from './CommandMenu' import { PiOpenAiLogo } from 'react-icons/pi' import { LuFunctionSquare } from 'react-icons/lu' +import { AiOutlineApi } from 'react-icons/ai' type Props = {} @@ -30,7 +31,7 @@ const SettingsList = (props: Props) => { Users - + @@ -46,6 +47,16 @@ const SettingsList = (props: Props) => { HR + + + Scheduled Messages + + + + + Webhooks + + Bots diff --git a/frontend/src/components/feature/chat/ChatInput/DocumentLinkButton.tsx b/frontend/src/components/feature/chat/ChatInput/DocumentLinkButton.tsx new file mode 100644 index 000000000..ba582aadb --- /dev/null +++ b/frontend/src/components/feature/chat/ChatInput/DocumentLinkButton.tsx @@ -0,0 +1,147 @@ +import { Box, Button, Dialog, Flex, IconButton, Tooltip } from '@radix-ui/themes' +import { DEFAULT_BUTTON_STYLE, ICON_PROPS } from './ToolPanel' +import { LuFileBox } from 'react-icons/lu' +import { DIALOG_CONTENT_CLASS } from '@/utils/layout/dialog' +import { FormProvider, useForm } from 'react-hook-form' +import LinkFormField from '@/components/common/LinkField/LinkFormField' +import { ErrorText } from '@/components/common/Form' +import { useBoolean } from '@/hooks/useBoolean' +import clsx from 'clsx' +import { useFrappeCreateDoc } from 'frappe-react-sdk' +import { Loader } from '@/components/common/Loader' +import { DoctypeLinkRenderer } from '../ChatMessage/Renderers/DoctypeLinkRenderer' +import { RavenMessage } from '@/types/RavenMessaging/RavenMessage' +import { Stack } from '@/components/layout/Stack' +import { ErrorBanner } from '@/components/layout/AlertBanner' +import useRecentlyUsedDocType from '@/hooks/useRecentlyUsedDocType' + +type Props = {} + +const DocumentLinkButton = ({ channelID }: { channelID: string }) => { + + const [open, { off }, setOpen] = useBoolean() + + return + + + + + + + + + Send a document + Choose a document from the system to send. + + + +} + +interface DocumentLinkFormData { + doctype: string + docname: string +} + +const DocumentLinkForm = ({ channelID, onClose }: { channelID: string, onClose: () => void }) => { + + const { loading, error, createDoc } = useFrappeCreateDoc() + + const methods = useForm() + + const { watch } = methods + + const handleClose = () => { + methods.reset() + onClose() + } + + const doctype = watch('doctype') + const docname = watch('docname') + + const { recentlyUsedDoctypes, addRecentlyUsedDocType } = useRecentlyUsedDocType() + + const onSubmit = (data: DocumentLinkFormData) => { + + addRecentlyUsedDocType(data.doctype) + + createDoc('Raven Message', { + message_type: 'Text', + channel_id: channelID, + link_doctype: data.doctype, + link_document: data.docname + } as RavenMessage) + .then(() => { + handleClose() + }) + } + + const onDoctypeChange = () => { + // Reset docname when doctype changes + methods.setValue('docname', '') + } + + return
+ + + {error && } + + + + {methods.formState.errors.doctype?.message} + + + {doctype && + + + + {methods.formState.errors.docname?.message} + + + } + +
+ {doctype && docname && } +
+ + + + + + + +
+
+
+ +} + +export default DocumentLinkButton \ No newline at end of file diff --git a/frontend/src/components/feature/chat/ChatInput/RightToolbarButtons.tsx b/frontend/src/components/feature/chat/ChatInput/RightToolbarButtons.tsx index 8d4dc2455..2b1828cda 100644 --- a/frontend/src/components/feature/chat/ChatInput/RightToolbarButtons.tsx +++ b/frontend/src/components/feature/chat/ChatInput/RightToolbarButtons.tsx @@ -11,6 +11,7 @@ import { useBoolean } from '@/hooks/useBoolean' import { MdOutlineBarChart } from 'react-icons/md' import { DIALOG_CONTENT_CLASS } from '@/utils/layout/dialog' import AISavedPromptsButton from './AISavedPromptsButton' +import DocumentLinkButton from './DocumentLinkButton' const EmojiPicker = lazy(() => import('@/components/common/EmojiPicker/EmojiPicker')) @@ -21,7 +22,9 @@ export type RightToolbarButtonsProps = { fileProps?: ToolbarFileProps, sendMessage: (html: string, json: any) => Promise, messageSending: boolean, - setContent: (content: string) => void + setContent: (content: string) => void, + channelID?: string, + isEdit?: boolean } /** * Component to render the right toolbar buttons: @@ -34,14 +37,18 @@ export type RightToolbarButtonsProps = { * @param props * @returns */ -export const RightToolbarButtons = ({ fileProps, ...sendProps }: RightToolbarButtonsProps) => { +export const RightToolbarButtons = ({ fileProps, channelID, isEdit, ...sendProps }: RightToolbarButtonsProps) => { return ( - - + + {!isEdit && channelID && } + + - - + + + + @@ -49,7 +56,7 @@ export const RightToolbarButtons = ({ fileProps, ...sendProps }: RightToolbarBut {fileProps && } - + ) } diff --git a/frontend/src/components/feature/chat/ChatInput/Tiptap.tsx b/frontend/src/components/feature/chat/ChatInput/Tiptap.tsx index bbfb16deb..317a8d986 100644 --- a/frontend/src/components/feature/chat/ChatInput/Tiptap.tsx +++ b/frontend/src/components/feature/chat/ChatInput/Tiptap.tsx @@ -61,7 +61,8 @@ type TiptapEditorProps = { messageSending: boolean, defaultText?: string, replyMessage?: Message | null, - channelMembers?: ChannelMembers + channelMembers?: ChannelMembers, + channelID?: string } export const UserMention = Mention.extend({ @@ -84,7 +85,7 @@ export const ChannelMention = Mention.extend({ } }) -const Tiptap = ({ isEdit, slotBefore, fileProps, onMessageSend, channelMembers, replyMessage, clearReplyMessage, placeholder = 'Type a message...', messageSending, sessionStorageKey = 'tiptap-editor', disableSessionStorage = false, defaultText = '' }: TiptapEditorProps) => { +const Tiptap = ({ isEdit, slotBefore, fileProps, onMessageSend, channelMembers, channelID, replyMessage, clearReplyMessage, placeholder = 'Type a message...', messageSending, sessionStorageKey = 'tiptap-editor', disableSessionStorage = false, defaultText = '' }: TiptapEditorProps) => { const { enabledUsers } = useContext(UserListContext) @@ -484,7 +485,9 @@ const Tiptap = ({ isEdit, slotBefore, fileProps, onMessageSend, channelMembers, - + diff --git a/frontend/src/components/feature/chat/ChatMessage/ActionModals/AttachFileToDocumentModal.tsx b/frontend/src/components/feature/chat/ChatMessage/ActionModals/AttachFileToDocumentModal.tsx index 06d409ad8..c244fe2cd 100644 --- a/frontend/src/components/feature/chat/ChatMessage/ActionModals/AttachFileToDocumentModal.tsx +++ b/frontend/src/components/feature/chat/ChatMessage/ActionModals/AttachFileToDocumentModal.tsx @@ -11,6 +11,7 @@ import LinkFormField from "@/components/common/LinkField/LinkFormField" import { useContext, useState } from "react" import { FileExtensionIcon } from "@/utils/layout/FileExtIcon" import { getFileExtension, getFileName } from "@/utils/operations" +import useRecentlyUsedDocType from "@/hooks/useRecentlyUsedDocType" interface AttachFileToDocumentModalProps { onClose: () => void, @@ -36,8 +37,11 @@ const AttachFileToDocumentModal = ({ onClose, message }: AttachFileToDocumentMod const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const { recentlyUsedDoctypes, addRecentlyUsedDocType } = useRecentlyUsedDocType() + const onSubmit = (data: AttachFileToDocumentForm) => { if ((message as FileMessage).file) { + addRecentlyUsedDocType(data.doctype) setLoading(true) call.get('frappe.client.get_value', { doctype: 'File', @@ -111,6 +115,7 @@ const AttachFileToDocumentModal = ({ onClose, message }: AttachFileToDocumentMod name='doctype' label='Document Type' autofocus + suggestedItems={recentlyUsedDoctypes} rules={{ required: 'Document Type is required', onChange: onDoctypeChange diff --git a/frontend/src/components/feature/chat/ChatMessage/MessageItem.tsx b/frontend/src/components/feature/chat/ChatMessage/MessageItem.tsx index fd36528cd..efe942a7a 100644 --- a/frontend/src/components/feature/chat/ChatMessage/MessageItem.tsx +++ b/frontend/src/components/feature/chat/ChatMessage/MessageItem.tsx @@ -145,6 +145,7 @@ export const MessageItem = ({ message, setDeleteMessage, isHighlighted, onReplyM transition-colors px-1 py-1.5 + sm:p-1.5 rounded-md`, isHighlighted ? 'bg-yellow-50 hover:bg-yellow-50 dark:bg-yellow-300/20 dark:hover:bg-yellow-300/20' : !isDesktop && isHovered ? 'bg-gray-2 dark:bg-gray-3' : '', isEmojiPickerOpen ? 'bg-gray-2 dark:bg-gray-3' : '')}> diff --git a/frontend/src/components/feature/chat/ChatStream/ChatBoxBody.tsx b/frontend/src/components/feature/chat/ChatStream/ChatBoxBody.tsx index df861a062..e47d744ca 100644 --- a/frontend/src/components/feature/chat/ChatStream/ChatBoxBody.tsx +++ b/frontend/src/components/feature/chat/ChatStream/ChatBoxBody.tsx @@ -101,6 +101,7 @@ export const ChatBoxBody = ({ channelData }: ChatBoxBodyProps) => { && user={user} />} {isUserInChannel && { + + const recentlyUsedDoctypes = useMemo(() => { + const existingItems = sessionStorage.getItem(`recently-used-doctype`) ?? '[]' + return JSON.parse(existingItems).map((doctype: string) => ({ + value: doctype, + description: "Recently used" + })) + }, []) + + const addRecentlyUsedDocType = (doctype: string) => { + // Save recently used doctype to session storage + const existingItems = sessionStorage.getItem(`recently-used-doctype`) ?? '[]' + let recentlyUsedDoctypes = JSON.parse(existingItems) + if (!recentlyUsedDoctypes.includes(doctype)) { + recentlyUsedDoctypes = [doctype, ...recentlyUsedDoctypes].slice(0, 5) + } + sessionStorage.setItem(`recently-used-doctype`, JSON.stringify(recentlyUsedDoctypes)) + } + + return { + recentlyUsedDoctypes, + addRecentlyUsedDocType + } +} + +export default useRecentlyUsedDocType \ No newline at end of file