### What problem does this PR solve? feat: Delete the file from the upload control of the message input box #1880 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.10.0
| @@ -15,7 +15,7 @@ const IndentedTreeModal = ({ | |||
| return ( | |||
| <Modal | |||
| title={t('chunk.graph')} | |||
| title={t('chunk.mind')} | |||
| open={visible} | |||
| onCancel={hideModal} | |||
| width={'90vw'} | |||
| @@ -1,5 +1,6 @@ | |||
| import { Authorization } from '@/constants/authorization'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { useRemoveNextDocument } from '@/hooks/document-hooks'; | |||
| import { getAuthorization } from '@/utils/authorization-util'; | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| import type { GetProp, UploadFile } from 'antd'; | |||
| @@ -14,7 +15,7 @@ interface IProps { | |||
| value: string; | |||
| sendDisabled: boolean; | |||
| sendLoading: boolean; | |||
| onPressEnter(documentIds: string[]): Promise<any>; | |||
| onPressEnter(documentIds: string[]): void; | |||
| onInputChange: ChangeEventHandler<HTMLInputElement>; | |||
| conversationId: string; | |||
| } | |||
| @@ -37,50 +38,41 @@ const MessageInput = ({ | |||
| conversationId, | |||
| }: IProps) => { | |||
| const { t } = useTranslate('chat'); | |||
| const { removeDocument } = useRemoveNextDocument(); | |||
| const [fileList, setFileList] = useState<UploadFile[]>([ | |||
| // { | |||
| // uid: '-1', | |||
| // name: 'image.png', | |||
| // status: 'done', | |||
| // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| // }, | |||
| // { | |||
| // uid: '-xxx', | |||
| // percent: 50, | |||
| // name: 'image.png', | |||
| // status: 'uploading', | |||
| // url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| // }, | |||
| // { | |||
| // uid: '-5', | |||
| // name: 'image.png', | |||
| // status: 'error', | |||
| // }, | |||
| ]); | |||
| const [fileList, setFileList] = useState<UploadFile[]>([]); | |||
| const handlePreview = async (file: UploadFile) => { | |||
| if (!file.url && !file.preview) { | |||
| file.preview = await getBase64(file.originFileObj as FileType); | |||
| } | |||
| // setPreviewImage(file.url || (file.preview as string)); | |||
| // setPreviewOpen(true); | |||
| }; | |||
| const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { | |||
| console.log('🚀 ~ newFileList:', newFileList); | |||
| setFileList(newFileList); | |||
| }; | |||
| const isUploadingFile = fileList.some((x) => x.status === 'uploading'); | |||
| const handlePressEnter = useCallback(async () => { | |||
| if (isUploadingFile) return; | |||
| const ids = fileList.reduce((pre, cur) => { | |||
| return pre.concat(get(cur, 'response.data', [])); | |||
| }, []); | |||
| await onPressEnter(ids); | |||
| onPressEnter(ids); | |||
| setFileList([]); | |||
| }, [fileList, onPressEnter]); | |||
| }, [fileList, onPressEnter, isUploadingFile]); | |||
| const handleRemove = useCallback( | |||
| (file: UploadFile) => { | |||
| const ids = get(file, 'response.data', []); | |||
| if (ids.length) { | |||
| removeDocument(ids[0]); | |||
| } | |||
| }, | |||
| [removeDocument], | |||
| ); | |||
| const uploadButton = ( | |||
| <button style={{ border: 0, background: 'none' }} type="button"> | |||
| @@ -101,7 +93,7 @@ const MessageInput = ({ | |||
| type="primary" | |||
| onClick={handlePressEnter} | |||
| loading={sendLoading} | |||
| disabled={sendDisabled} | |||
| disabled={sendDisabled || isUploadingFile} | |||
| > | |||
| {t('send')} | |||
| </Button> | |||
| @@ -119,6 +111,7 @@ const MessageInput = ({ | |||
| headers={{ [Authorization]: getAuthorization() }} | |||
| data={{ conversation_id: conversationId }} | |||
| method="post" | |||
| onRemove={handleRemove} | |||
| > | |||
| {fileList.length >= 8 ? null : uploadButton} | |||
| </Upload> | |||
| @@ -7,7 +7,8 @@ | |||
| width: 80%; | |||
| } | |||
| .messageItemSectionRight { | |||
| width: 80%; | |||
| // width: 80%; | |||
| // max-width: 50vw; | |||
| } | |||
| .messageItemContent { | |||
| display: inline-flex; | |||
| @@ -7,15 +7,20 @@ import { IChunk } from '@/interfaces/database/knowledge'; | |||
| import classNames from 'classnames'; | |||
| import { memo, useCallback, useEffect, useMemo, useState } from 'react'; | |||
| import { useFetchDocumentInfosByIds } from '@/hooks/document-hooks'; | |||
| import { | |||
| useFetchDocumentInfosByIds, | |||
| useFetchDocumentThumbnailsByIds, | |||
| } from '@/hooks/document-hooks'; | |||
| import MarkdownContent from '@/pages/chat/markdown-content'; | |||
| import { getExtension, isImage } from '@/utils/document-util'; | |||
| import { Avatar, Button, Flex, List } from 'antd'; | |||
| import { Avatar, Button, Flex, List, Typography } from 'antd'; | |||
| import IndentedTreeModal from '../indented-tree/modal'; | |||
| import NewDocumentLink from '../new-document-link'; | |||
| import SvgIcon from '../svg-icon'; | |||
| import styles from './index.less'; | |||
| const { Text } = Typography; | |||
| interface IProps { | |||
| item: Message; | |||
| reference: IReference; | |||
| @@ -38,7 +43,8 @@ const MessageItem = ({ | |||
| const { t } = useTranslate('chat'); | |||
| const fileThumbnails = useSelectFileThumbnails(); | |||
| const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds(); | |||
| console.log('🚀 ~ documentList:', documentList); | |||
| const { data: documentThumbnails, setDocumentIds: setIds } = | |||
| useFetchDocumentThumbnailsByIds(); | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| const [clickedDocumentId, setClickedDocumentId] = useState(''); | |||
| @@ -66,8 +72,12 @@ const MessageItem = ({ | |||
| const ids = item?.doc_ids ?? []; | |||
| if (ids.length) { | |||
| setDocumentIds(ids); | |||
| const documentIds = ids.filter((x) => !(x in fileThumbnails)); | |||
| if (documentIds.length) { | |||
| setIds(documentIds); | |||
| } | |||
| } | |||
| }, [item.doc_ids, setDocumentIds]); | |||
| }, [item.doc_ids, setDocumentIds, setIds, fileThumbnails]); | |||
| return ( | |||
| <div | |||
| @@ -117,6 +127,7 @@ const MessageItem = ({ | |||
| dataSource={referenceDocumentList} | |||
| renderItem={(item) => { | |||
| const fileThumbnail = fileThumbnails[item.doc_id]; | |||
| const fileExtension = getExtension(item.doc_name); | |||
| return ( | |||
| <List.Item> | |||
| @@ -151,7 +162,8 @@ const MessageItem = ({ | |||
| bordered | |||
| dataSource={documentList} | |||
| renderItem={(item) => { | |||
| const fileThumbnail = fileThumbnails[item.id]; | |||
| const fileThumbnail = | |||
| documentThumbnails[item.id] || fileThumbnails[item.id]; | |||
| const fileExtension = getExtension(item.name); | |||
| return ( | |||
| <List.Item> | |||
| @@ -181,7 +193,12 @@ const MessageItem = ({ | |||
| type={'text'} | |||
| onClick={handleUserDocumentClick(item.id)} | |||
| > | |||
| {item.name} | |||
| <Text | |||
| style={{ maxWidth: '40vw' }} | |||
| ellipsis={{ tooltip: item.name }} | |||
| > | |||
| {item.name} | |||
| </Text> | |||
| </Button> | |||
| )} | |||
| </Flex> | |||
| @@ -4,7 +4,7 @@ import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; | |||
| import kbService from '@/services/knowledge-service'; | |||
| import { api_host } from '@/utils/api'; | |||
| import { buildChunkHighlights } from '@/utils/document-util'; | |||
| import { useQuery } from '@tanstack/react-query'; | |||
| import { useMutation, useQuery } from '@tanstack/react-query'; | |||
| import { UploadFile } from 'antd'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| import { IHighlight } from 'react-pdf-highlighter'; | |||
| @@ -278,15 +278,38 @@ export const useFetchDocumentInfosByIds = () => { | |||
| export const useFetchDocumentThumbnailsByIds = () => { | |||
| const [ids, setDocumentIds] = useState<string[]>([]); | |||
| const { data } = useQuery({ | |||
| const { data } = useQuery<Record<string, string>>({ | |||
| queryKey: ['fetchDocumentThumbnails', ids], | |||
| initialData: [], | |||
| enabled: ids.length > 0, | |||
| initialData: {}, | |||
| queryFn: async () => { | |||
| const { data } = await kbService.document_thumbnails({ doc_ids: ids }); | |||
| if (data.retcode === 0) { | |||
| return data.data; | |||
| } | |||
| return {}; | |||
| }, | |||
| }); | |||
| return { data, setDocumentIds }; | |||
| }; | |||
| export const useRemoveNextDocument = () => { | |||
| // const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['removeDocument'], | |||
| mutationFn: async (documentId: string) => { | |||
| const data = await kbService.document_rm({ doc_id: documentId }); | |||
| // if (data.retcode === 0) { | |||
| // queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] }); | |||
| // } | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, setDocumentIds }; | |||
| return { data, loading, removeDocument: mutateAsync }; | |||
| }; | |||
| @@ -80,24 +80,6 @@ const ChatContainer = () => { | |||
| onPressEnter={handlePressEnter} | |||
| conversationId={conversation.id} | |||
| ></MessageInput> | |||
| {/* <Input | |||
| size="large" | |||
| placeholder={t('sendPlaceholder')} | |||
| value={value} | |||
| disabled={disabled} | |||
| suffix={ | |||
| <Button | |||
| type="primary" | |||
| onClick={handlePressEnter} | |||
| loading={sendLoading} | |||
| disabled={sendDisabled} | |||
| > | |||
| {t('send')} | |||
| </Button> | |||
| } | |||
| onPressEnter={handlePressEnter} | |||
| onChange={handleInputChange} | |||
| /> */} | |||
| </Flex> | |||
| <Drawer | |||
| title="Document Previewer" | |||
| @@ -19,7 +19,12 @@ import { | |||
| } from '@/hooks/common-hooks'; | |||
| import { useSendMessageWithSse } from '@/hooks/logic-hooks'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks'; | |||
| import { IAnswer, IConversation, IDialog } from '@/interfaces/database/chat'; | |||
| import { | |||
| IAnswer, | |||
| IConversation, | |||
| IDialog, | |||
| Message, | |||
| } from '@/interfaces/database/chat'; | |||
| import { IChunk } from '@/interfaces/database/knowledge'; | |||
| import { getFileExtension } from '@/utils'; | |||
| import omit from 'lodash/omit'; | |||
| @@ -379,7 +384,7 @@ export const useSelectCurrentConversation = () => { | |||
| const { conversationId, dialogId } = useGetChatSearchParams(); | |||
| const addNewestConversation = useCallback( | |||
| (message: string, answer: string = '') => { | |||
| (message: Partial<Message>, answer: string = '') => { | |||
| setCurrentConversation((pre) => { | |||
| return { | |||
| ...pre, | |||
| @@ -387,7 +392,8 @@ export const useSelectCurrentConversation = () => { | |||
| ...pre.message, | |||
| { | |||
| role: MessageType.User, | |||
| content: message, | |||
| content: message.content, | |||
| doc_ids: message.doc_ids, | |||
| id: uuid(), | |||
| } as IMessage, | |||
| { | |||
| @@ -535,7 +541,7 @@ export const useHandleMessageInputChange = () => { | |||
| export const useSendMessage = ( | |||
| conversation: IClientConversation, | |||
| addNewestConversation: (message: string, answer?: string) => void, | |||
| addNewestConversation: (message: Partial<Message>, answer?: string) => void, | |||
| removeLatestMessage: () => void, | |||
| addNewestAnswer: (answer: IAnswer) => void, | |||
| ) => { | |||
| @@ -589,12 +595,12 @@ export const useSendMessage = ( | |||
| const handleSendMessage = useCallback( | |||
| async (message: string, documentIds: string[]) => { | |||
| if (conversationId !== '') { | |||
| return sendMessage(message, documentIds); | |||
| sendMessage(message, documentIds); | |||
| } else { | |||
| const data = await setConversation(message); | |||
| if (data.retcode === 0) { | |||
| const id = data.data.id; | |||
| return sendMessage(message, documentIds, id); | |||
| sendMessage(message, documentIds, id); | |||
| } | |||
| } | |||
| }, | |||
| @@ -616,15 +622,14 @@ export const useSendMessage = ( | |||
| }, [setDone, conversationId]); | |||
| const handlePressEnter = useCallback( | |||
| async (documentIds: string[]) => { | |||
| (documentIds: string[]) => { | |||
| if (trim(value) === '') return; | |||
| let ret; | |||
| addNewestConversation({ content: value, doc_ids: documentIds }); | |||
| if (done) { | |||
| setValue(''); | |||
| ret = await handleSendMessage(value.trim(), documentIds); | |||
| handleSendMessage(value.trim(), documentIds); | |||
| } | |||
| addNewestConversation(value); | |||
| return ret; | |||
| }, | |||
| [addNewestConversation, handleSendMessage, done, setValue, value], | |||
| ); | |||