### What problem does this PR solve? feat: Delete the files uploaded in the external dialog box #1880 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.10.0
| @@ -14,6 +14,8 @@ | |||
| } | |||
| .listWrapper { | |||
| padding: 0 10px; | |||
| overflow: auto; | |||
| max-height: 170px; | |||
| } | |||
| .inputWrapper { | |||
| border-radius: 8px; | |||
| @@ -1,13 +1,18 @@ | |||
| import { Authorization } from '@/constants/authorization'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { | |||
| useDeleteDocument, | |||
| useFetchDocumentInfosByIds, | |||
| useRemoveNextDocument, | |||
| } from '@/hooks/document-hooks'; | |||
| import { getAuthorization } from '@/utils/authorization-util'; | |||
| import { getExtension } from '@/utils/document-util'; | |||
| import { formatBytes } from '@/utils/file-util'; | |||
| import { CloseCircleOutlined, LoadingOutlined } from '@ant-design/icons'; | |||
| import { | |||
| CloseCircleOutlined, | |||
| InfoCircleOutlined, | |||
| LoadingOutlined, | |||
| } from '@ant-design/icons'; | |||
| import type { GetProp, UploadFile } from 'antd'; | |||
| import { | |||
| Button, | |||
| @@ -41,6 +46,16 @@ const getFileIds = (fileList: UploadFile[]) => { | |||
| return ids; | |||
| }; | |||
| const isUploadError = (file: UploadFile) => { | |||
| const retcode = get(file, 'response.retcode'); | |||
| return typeof retcode === 'number' && retcode !== 0; | |||
| }; | |||
| const isUploadSuccess = (file: UploadFile) => { | |||
| const retcode = get(file, 'response.retcode'); | |||
| return typeof retcode === 'number' && retcode === 0; | |||
| }; | |||
| interface IProps { | |||
| disabled: boolean; | |||
| value: string; | |||
| @@ -50,6 +65,7 @@ interface IProps { | |||
| onInputChange: ChangeEventHandler<HTMLInputElement>; | |||
| conversationId: string; | |||
| uploadUrl?: string; | |||
| isShared?: boolean; | |||
| } | |||
| const getBase64 = (file: FileType): Promise<string> => | |||
| @@ -61,6 +77,7 @@ const getBase64 = (file: FileType): Promise<string> => | |||
| }); | |||
| const MessageInput = ({ | |||
| isShared = false, | |||
| disabled, | |||
| value, | |||
| onPressEnter, | |||
| @@ -72,6 +89,7 @@ const MessageInput = ({ | |||
| }: IProps) => { | |||
| const { t } = useTranslate('chat'); | |||
| const { removeDocument } = useRemoveNextDocument(); | |||
| const { deleteDocument } = useDeleteDocument(); | |||
| const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds(); | |||
| const [fileList, setFileList] = useState<UploadFile[]>([]); | |||
| @@ -89,7 +107,7 @@ const MessageInput = ({ | |||
| const handlePressEnter = useCallback(async () => { | |||
| if (isUploadingFile) return; | |||
| const ids = getFileIds(fileList); | |||
| const ids = getFileIds(fileList.filter((x) => isUploadSuccess(x))); | |||
| onPressEnter(ids); | |||
| setFileList([]); | |||
| @@ -98,14 +116,24 @@ const MessageInput = ({ | |||
| const handleRemove = useCallback( | |||
| async (file: UploadFile) => { | |||
| const ids = get(file, 'response.data', []); | |||
| if (ids.length) { | |||
| await removeDocument(ids[0]); | |||
| // Upload Successfully | |||
| if (Array.isArray(ids) && ids.length) { | |||
| if (isShared) { | |||
| await deleteDocument(ids); | |||
| } else { | |||
| await removeDocument(ids[0]); | |||
| } | |||
| setFileList((preList) => { | |||
| return preList.filter((x) => getFileId(x) !== ids[0]); | |||
| }); | |||
| } else { | |||
| // Upload failed | |||
| setFileList((preList) => { | |||
| return preList.filter((x) => x.uid !== file.uid); | |||
| }); | |||
| } | |||
| }, | |||
| [removeDocument], | |||
| [removeDocument, deleteDocument, isShared], | |||
| ); | |||
| const getDocumentInfoById = useCallback( | |||
| @@ -192,6 +220,11 @@ const MessageInput = ({ | |||
| <LoadingOutlined style={{ fontSize: 24 }} spin /> | |||
| } | |||
| /> | |||
| ) : !getFileId(item) ? ( | |||
| <InfoCircleOutlined | |||
| size={30} | |||
| // width={30} | |||
| ></InfoCircleOutlined> | |||
| ) : ( | |||
| <FileIcon id={id} name={item.name}></FileIcon> | |||
| )} | |||
| @@ -202,26 +235,33 @@ const MessageInput = ({ | |||
| > | |||
| <b> {item.name}</b> | |||
| </Text> | |||
| {item.percent !== 100 ? ( | |||
| t('uploading') | |||
| ) : !item.response ? ( | |||
| t('parsing') | |||
| {isUploadError(item) ? ( | |||
| t('uploadFailed') | |||
| ) : ( | |||
| <Space> | |||
| <span>{fileExtension?.toUpperCase()},</span> | |||
| <span> | |||
| {formatBytes(getDocumentInfoById(id)?.size ?? 0)} | |||
| </span> | |||
| </Space> | |||
| <> | |||
| {item.percent !== 100 ? ( | |||
| t('uploading') | |||
| ) : !item.response ? ( | |||
| t('parsing') | |||
| ) : ( | |||
| <Space> | |||
| <span>{fileExtension?.toUpperCase()},</span> | |||
| <span> | |||
| {formatBytes( | |||
| getDocumentInfoById(id)?.size ?? 0, | |||
| )} | |||
| </span> | |||
| </Space> | |||
| )} | |||
| </> | |||
| )} | |||
| </Flex> | |||
| </Flex> | |||
| {item.status !== 'uploading' && ( | |||
| <CloseCircleOutlined | |||
| className={styles.deleteIcon} | |||
| onClick={() => handleRemove(item)} | |||
| /> | |||
| <span className={styles.deleteIcon}> | |||
| <CloseCircleOutlined onClick={() => handleRemove(item)} /> | |||
| </span> | |||
| )} | |||
| </Card> | |||
| </List.Item> | |||
| @@ -313,3 +313,23 @@ export const useRemoveNextDocument = () => { | |||
| return { data, loading, removeDocument: mutateAsync }; | |||
| }; | |||
| export const useDeleteDocument = () => { | |||
| // const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['deleteDocument'], | |||
| mutationFn: async (documentIds: string[]) => { | |||
| const data = await kbService.document_delete({ doc_ids: documentIds }); | |||
| // if (data.retcode === 0) { | |||
| // queryClient.invalidateQueries({ queryKey: ['fetchFlowList'] }); | |||
| // } | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, deleteDocument: mutateAsync }; | |||
| }; | |||
| @@ -424,6 +424,7 @@ The above is the content you need to summarize.`, | |||
| searching: 'searching...', | |||
| parsing: 'Parsing', | |||
| uploading: 'Uploading', | |||
| uploadFailed: 'Upload failed', | |||
| }, | |||
| setting: { | |||
| profile: 'Profile', | |||
| @@ -394,6 +394,7 @@ export default { | |||
| searching: '搜索中', | |||
| parsing: '解析中', | |||
| uploading: '上傳中', | |||
| uploadFailed: '上傳失敗', | |||
| }, | |||
| setting: { | |||
| profile: '概述', | |||
| @@ -411,6 +411,7 @@ export default { | |||
| searching: '搜索中', | |||
| parsing: '解析中', | |||
| uploading: '上传中', | |||
| uploadFailed: '上传失败', | |||
| }, | |||
| setting: { | |||
| profile: '概要', | |||
| @@ -65,6 +65,7 @@ const ChatContainer = () => { | |||
| </Flex> | |||
| <MessageInput | |||
| isShared | |||
| value={value} | |||
| disabled={false} | |||
| sendDisabled={sendDisabled} | |||
| @@ -5,7 +5,7 @@ import { | |||
| } from '@/hooks/chat-hooks'; | |||
| import { useSendMessageWithSse } from '@/hooks/logic-hooks'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks'; | |||
| import { IAnswer } from '@/interfaces/database/chat'; | |||
| import { IAnswer, Message } from '@/interfaces/database/chat'; | |||
| import api from '@/utils/api'; | |||
| import omit from 'lodash/omit'; | |||
| import trim from 'lodash/trim'; | |||
| @@ -57,7 +57,7 @@ export const useSelectCurrentSharedConversation = (conversationId: string) => { | |||
| const ref = useScrollToBottom(currentConversation); | |||
| const addNewestConversation = useCallback((message: string) => { | |||
| const addNewestConversation = useCallback((message: Partial<Message>) => { | |||
| setCurrentConversation((pre) => { | |||
| return { | |||
| ...pre, | |||
| @@ -65,14 +65,15 @@ export const useSelectCurrentSharedConversation = (conversationId: string) => { | |||
| ...(pre.message ?? []), | |||
| { | |||
| role: MessageType.User, | |||
| content: message, | |||
| content: message.content, | |||
| doc_ids: message.doc_ids, | |||
| id: uuid(), | |||
| } as IMessage, | |||
| { | |||
| role: MessageType.Assistant, | |||
| content: '', | |||
| id: uuid(), | |||
| reference: [], | |||
| reference: {}, | |||
| } as IMessage, | |||
| ], | |||
| }; | |||
| @@ -140,7 +141,7 @@ export const useSendButtonDisabled = (value: string) => { | |||
| export const useSendSharedMessage = ( | |||
| conversation: IClientConversation, | |||
| addNewestConversation: (message: string) => void, | |||
| addNewestConversation: (message: Partial<Message>, answer?: string) => void, | |||
| removeLatestMessage: () => void, | |||
| setCurrentConversation: Dispatch<SetStateAction<IClientConversation>>, | |||
| addNewestAnswer: (answer: IAnswer) => void, | |||
| @@ -205,14 +206,17 @@ export const useSendSharedMessage = ( | |||
| } | |||
| }, [answer, addNewestAnswer]); | |||
| const handlePressEnter = useCallback(() => { | |||
| if (trim(value) === '') return; | |||
| if (done) { | |||
| setValue(''); | |||
| addNewestConversation(value); | |||
| handleSendMessage(value.trim()); | |||
| } | |||
| }, [addNewestConversation, done, handleSendMessage, setValue, value]); | |||
| const handlePressEnter = useCallback( | |||
| (documentIds: string[]) => { | |||
| if (trim(value) === '') return; | |||
| if (done) { | |||
| setValue(''); | |||
| addNewestConversation({ content: value, doc_ids: documentIds }); | |||
| handleSendMessage(value.trim()); | |||
| } | |||
| }, | |||
| [addNewestConversation, done, handleSendMessage, setValue, value], | |||
| ); | |||
| return { | |||
| handlePressEnter, | |||
| @@ -12,6 +12,7 @@ const { | |||
| get_document_list, | |||
| document_change_status, | |||
| document_rm, | |||
| document_delete, | |||
| document_create, | |||
| document_change_parser, | |||
| document_thumbnails, | |||
| @@ -131,6 +132,10 @@ const methods = { | |||
| url: knowledge_graph, | |||
| method: 'get', | |||
| }, | |||
| document_delete: { | |||
| url: document_delete, | |||
| method: 'delete', | |||
| }, | |||
| }; | |||
| const kbService = registerServer<keyof typeof methods>(methods, request); | |||
| @@ -41,6 +41,7 @@ export default { | |||
| get_document_list: `${api_host}/document/list`, | |||
| document_change_status: `${api_host}/document/change_status`, | |||
| document_rm: `${api_host}/document/rm`, | |||
| document_delete: `${api_host}/api/document`, | |||
| document_rename: `${api_host}/document/rename`, | |||
| document_create: `${api_host}/document/create`, | |||
| document_run: `${api_host}/document/run`, | |||
| @@ -6,6 +6,8 @@ type Service<T extends string> = Record< | |||
| (params?: any, urlAppendix?: string) => any | |||
| >; | |||
| const Methods = ['post', 'delete', 'put']; | |||
| const registerServer = <T extends string>( | |||
| opt: Record<T, { url: string; method: string }>, | |||
| request: RequestMethod, | |||
| @@ -18,7 +20,7 @@ const registerServer = <T extends string>( | |||
| if (urlAppendix) { | |||
| url = url + '/' + urlAppendix; | |||
| } | |||
| if (opt[key].method === 'post' || opt[key].method === 'POST') { | |||
| if (Methods.some((x) => x === opt[key].method.toLowerCase())) { | |||
| return request(url, { | |||
| method: opt[key].method, | |||
| data: params, | |||