feat: Delete the file from the upload control of the message input box #1880 (#1946)

### 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)
This commit is contained in:
balibabu 2024-08-15 09:19:17 +08:00 committed by GitHub
parent 6b3a40be5c
commit c6c3961250
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 100 additions and 80 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -15,7 +15,7 @@ const IndentedTreeModal = ({
return ( return (
<Modal <Modal
title={t('chunk.graph')} title={t('chunk.mind')}
open={visible} open={visible}
onCancel={hideModal} onCancel={hideModal}
width={'90vw'} width={'90vw'}

View File

@ -1,5 +1,6 @@
import { Authorization } from '@/constants/authorization'; import { Authorization } from '@/constants/authorization';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { useRemoveNextDocument } from '@/hooks/document-hooks';
import { getAuthorization } from '@/utils/authorization-util'; import { getAuthorization } from '@/utils/authorization-util';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import type { GetProp, UploadFile } from 'antd'; import type { GetProp, UploadFile } from 'antd';
@ -14,7 +15,7 @@ interface IProps {
value: string; value: string;
sendDisabled: boolean; sendDisabled: boolean;
sendLoading: boolean; sendLoading: boolean;
onPressEnter(documentIds: string[]): Promise<any>; onPressEnter(documentIds: string[]): void;
onInputChange: ChangeEventHandler<HTMLInputElement>; onInputChange: ChangeEventHandler<HTMLInputElement>;
conversationId: string; conversationId: string;
} }
@ -37,50 +38,41 @@ const MessageInput = ({
conversationId, conversationId,
}: IProps) => { }: IProps) => {
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const { removeDocument } = useRemoveNextDocument();
const [fileList, setFileList] = useState<UploadFile[]>([ 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 handlePreview = async (file: UploadFile) => { const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) { if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType); file.preview = await getBase64(file.originFileObj as FileType);
} }
// setPreviewImage(file.url || (file.preview as string));
// setPreviewOpen(true);
}; };
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => {
console.log('🚀 ~ newFileList:', newFileList); console.log('🚀 ~ newFileList:', newFileList);
setFileList(newFileList); setFileList(newFileList);
}; };
const isUploadingFile = fileList.some((x) => x.status === 'uploading');
const handlePressEnter = useCallback(async () => { const handlePressEnter = useCallback(async () => {
if (isUploadingFile) return;
const ids = fileList.reduce((pre, cur) => { const ids = fileList.reduce((pre, cur) => {
return pre.concat(get(cur, 'response.data', [])); return pre.concat(get(cur, 'response.data', []));
}, []); }, []);
await onPressEnter(ids); onPressEnter(ids);
setFileList([]); 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 = ( const uploadButton = (
<button style={{ border: 0, background: 'none' }} type="button"> <button style={{ border: 0, background: 'none' }} type="button">
@ -101,7 +93,7 @@ const MessageInput = ({
type="primary" type="primary"
onClick={handlePressEnter} onClick={handlePressEnter}
loading={sendLoading} loading={sendLoading}
disabled={sendDisabled} disabled={sendDisabled || isUploadingFile}
> >
{t('send')} {t('send')}
</Button> </Button>
@ -119,6 +111,7 @@ const MessageInput = ({
headers={{ [Authorization]: getAuthorization() }} headers={{ [Authorization]: getAuthorization() }}
data={{ conversation_id: conversationId }} data={{ conversation_id: conversationId }}
method="post" method="post"
onRemove={handleRemove}
> >
{fileList.length >= 8 ? null : uploadButton} {fileList.length >= 8 ? null : uploadButton}
</Upload> </Upload>

View File

@ -7,7 +7,8 @@
width: 80%; width: 80%;
} }
.messageItemSectionRight { .messageItemSectionRight {
width: 80%; // width: 80%;
// max-width: 50vw;
} }
.messageItemContent { .messageItemContent {
display: inline-flex; display: inline-flex;

View File

@ -7,15 +7,20 @@ import { IChunk } from '@/interfaces/database/knowledge';
import classNames from 'classnames'; import classNames from 'classnames';
import { memo, useCallback, useEffect, useMemo, useState } from 'react'; 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 MarkdownContent from '@/pages/chat/markdown-content';
import { getExtension, isImage } from '@/utils/document-util'; 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 IndentedTreeModal from '../indented-tree/modal';
import NewDocumentLink from '../new-document-link'; import NewDocumentLink from '../new-document-link';
import SvgIcon from '../svg-icon'; import SvgIcon from '../svg-icon';
import styles from './index.less'; import styles from './index.less';
const { Text } = Typography;
interface IProps { interface IProps {
item: Message; item: Message;
reference: IReference; reference: IReference;
@ -38,7 +43,8 @@ const MessageItem = ({
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const fileThumbnails = useSelectFileThumbnails(); const fileThumbnails = useSelectFileThumbnails();
const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds(); const { data: documentList, setDocumentIds } = useFetchDocumentInfosByIds();
console.log('🚀 ~ documentList:', documentList); const { data: documentThumbnails, setDocumentIds: setIds } =
useFetchDocumentThumbnailsByIds();
const { visible, hideModal, showModal } = useSetModalState(); const { visible, hideModal, showModal } = useSetModalState();
const [clickedDocumentId, setClickedDocumentId] = useState(''); const [clickedDocumentId, setClickedDocumentId] = useState('');
@ -66,8 +72,12 @@ const MessageItem = ({
const ids = item?.doc_ids ?? []; const ids = item?.doc_ids ?? [];
if (ids.length) { if (ids.length) {
setDocumentIds(ids); 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 ( return (
<div <div
@ -117,6 +127,7 @@ const MessageItem = ({
dataSource={referenceDocumentList} dataSource={referenceDocumentList}
renderItem={(item) => { renderItem={(item) => {
const fileThumbnail = fileThumbnails[item.doc_id]; const fileThumbnail = fileThumbnails[item.doc_id];
const fileExtension = getExtension(item.doc_name); const fileExtension = getExtension(item.doc_name);
return ( return (
<List.Item> <List.Item>
@ -151,7 +162,8 @@ const MessageItem = ({
bordered bordered
dataSource={documentList} dataSource={documentList}
renderItem={(item) => { renderItem={(item) => {
const fileThumbnail = fileThumbnails[item.id]; const fileThumbnail =
documentThumbnails[item.id] || fileThumbnails[item.id];
const fileExtension = getExtension(item.name); const fileExtension = getExtension(item.name);
return ( return (
<List.Item> <List.Item>
@ -181,7 +193,12 @@ const MessageItem = ({
type={'text'} type={'text'}
onClick={handleUserDocumentClick(item.id)} onClick={handleUserDocumentClick(item.id)}
> >
{item.name} <Text
style={{ maxWidth: '40vw' }}
ellipsis={{ tooltip: item.name }}
>
{item.name}
</Text>
</Button> </Button>
)} )}
</Flex> </Flex>

View File

@ -4,7 +4,7 @@ import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
import kbService from '@/services/knowledge-service'; import kbService from '@/services/knowledge-service';
import { api_host } from '@/utils/api'; import { api_host } from '@/utils/api';
import { buildChunkHighlights } from '@/utils/document-util'; import { buildChunkHighlights } from '@/utils/document-util';
import { useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { UploadFile } from 'antd'; import { UploadFile } from 'antd';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter'; import { IHighlight } from 'react-pdf-highlighter';
@ -278,15 +278,38 @@ export const useFetchDocumentInfosByIds = () => {
export const useFetchDocumentThumbnailsByIds = () => { export const useFetchDocumentThumbnailsByIds = () => {
const [ids, setDocumentIds] = useState<string[]>([]); const [ids, setDocumentIds] = useState<string[]>([]);
const { data } = useQuery({ const { data } = useQuery<Record<string, string>>({
queryKey: ['fetchDocumentThumbnails', ids], queryKey: ['fetchDocumentThumbnails', ids],
initialData: [], enabled: ids.length > 0,
initialData: {},
queryFn: async () => { queryFn: async () => {
const { data } = await kbService.document_thumbnails({ doc_ids: ids }); const { data } = await kbService.document_thumbnails({ doc_ids: ids });
if (data.retcode === 0) {
return data; return data.data;
}
return {};
}, },
}); });
return { data, setDocumentIds }; 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, loading, removeDocument: mutateAsync };
};

View File

@ -80,24 +80,6 @@ const ChatContainer = () => {
onPressEnter={handlePressEnter} onPressEnter={handlePressEnter}
conversationId={conversation.id} conversationId={conversation.id}
></MessageInput> ></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> </Flex>
<Drawer <Drawer
title="Document Previewer" title="Document Previewer"

View File

@ -19,7 +19,12 @@ import {
} from '@/hooks/common-hooks'; } from '@/hooks/common-hooks';
import { useSendMessageWithSse } from '@/hooks/logic-hooks'; import { useSendMessageWithSse } from '@/hooks/logic-hooks';
import { useOneNamespaceEffectsLoading } from '@/hooks/store-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 { IChunk } from '@/interfaces/database/knowledge';
import { getFileExtension } from '@/utils'; import { getFileExtension } from '@/utils';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
@ -379,7 +384,7 @@ export const useSelectCurrentConversation = () => {
const { conversationId, dialogId } = useGetChatSearchParams(); const { conversationId, dialogId } = useGetChatSearchParams();
const addNewestConversation = useCallback( const addNewestConversation = useCallback(
(message: string, answer: string = '') => { (message: Partial<Message>, answer: string = '') => {
setCurrentConversation((pre) => { setCurrentConversation((pre) => {
return { return {
...pre, ...pre,
@ -387,7 +392,8 @@ export const useSelectCurrentConversation = () => {
...pre.message, ...pre.message,
{ {
role: MessageType.User, role: MessageType.User,
content: message, content: message.content,
doc_ids: message.doc_ids,
id: uuid(), id: uuid(),
} as IMessage, } as IMessage,
{ {
@ -535,7 +541,7 @@ export const useHandleMessageInputChange = () => {
export const useSendMessage = ( export const useSendMessage = (
conversation: IClientConversation, conversation: IClientConversation,
addNewestConversation: (message: string, answer?: string) => void, addNewestConversation: (message: Partial<Message>, answer?: string) => void,
removeLatestMessage: () => void, removeLatestMessage: () => void,
addNewestAnswer: (answer: IAnswer) => void, addNewestAnswer: (answer: IAnswer) => void,
) => { ) => {
@ -589,12 +595,12 @@ export const useSendMessage = (
const handleSendMessage = useCallback( const handleSendMessage = useCallback(
async (message: string, documentIds: string[]) => { async (message: string, documentIds: string[]) => {
if (conversationId !== '') { if (conversationId !== '') {
return sendMessage(message, documentIds); sendMessage(message, documentIds);
} else { } else {
const data = await setConversation(message); const data = await setConversation(message);
if (data.retcode === 0) { if (data.retcode === 0) {
const id = data.data.id; const id = data.data.id;
return sendMessage(message, documentIds, id); sendMessage(message, documentIds, id);
} }
} }
}, },
@ -616,15 +622,14 @@ export const useSendMessage = (
}, [setDone, conversationId]); }, [setDone, conversationId]);
const handlePressEnter = useCallback( const handlePressEnter = useCallback(
async (documentIds: string[]) => { (documentIds: string[]) => {
if (trim(value) === '') return; if (trim(value) === '') return;
let ret;
addNewestConversation({ content: value, doc_ids: documentIds });
if (done) { if (done) {
setValue(''); setValue('');
ret = await handleSendMessage(value.trim(), documentIds); handleSendMessage(value.trim(), documentIds);
} }
addNewestConversation(value);
return ret;
}, },
[addNewestConversation, handleSendMessage, done, setValue, value], [addNewestConversation, handleSendMessage, done, setValue, value],
); );