feat: Delete the files uploaded in the external dialog box #1880 (#1967)

### 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)
This commit is contained in:
balibabu 2024-08-15 18:31:46 +08:00 committed by GitHub
parent d3ff1a30bf
commit 7bdd5a48c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 111 additions and 33 deletions

View File

@ -14,6 +14,8 @@
} }
.listWrapper { .listWrapper {
padding: 0 10px; padding: 0 10px;
overflow: auto;
max-height: 170px;
} }
.inputWrapper { .inputWrapper {
border-radius: 8px; border-radius: 8px;

View File

@ -1,13 +1,18 @@
import { Authorization } from '@/constants/authorization'; import { Authorization } from '@/constants/authorization';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import { import {
useDeleteDocument,
useFetchDocumentInfosByIds, useFetchDocumentInfosByIds,
useRemoveNextDocument, useRemoveNextDocument,
} from '@/hooks/document-hooks'; } from '@/hooks/document-hooks';
import { getAuthorization } from '@/utils/authorization-util'; import { getAuthorization } from '@/utils/authorization-util';
import { getExtension } from '@/utils/document-util'; import { getExtension } from '@/utils/document-util';
import { formatBytes } from '@/utils/file-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 type { GetProp, UploadFile } from 'antd';
import { import {
Button, Button,
@ -41,6 +46,16 @@ const getFileIds = (fileList: UploadFile[]) => {
return ids; 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 { interface IProps {
disabled: boolean; disabled: boolean;
value: string; value: string;
@ -50,6 +65,7 @@ interface IProps {
onInputChange: ChangeEventHandler<HTMLInputElement>; onInputChange: ChangeEventHandler<HTMLInputElement>;
conversationId: string; conversationId: string;
uploadUrl?: string; uploadUrl?: string;
isShared?: boolean;
} }
const getBase64 = (file: FileType): Promise<string> => const getBase64 = (file: FileType): Promise<string> =>
@ -61,6 +77,7 @@ const getBase64 = (file: FileType): Promise<string> =>
}); });
const MessageInput = ({ const MessageInput = ({
isShared = false,
disabled, disabled,
value, value,
onPressEnter, onPressEnter,
@ -72,6 +89,7 @@ const MessageInput = ({
}: IProps) => { }: IProps) => {
const { t } = useTranslate('chat'); const { t } = useTranslate('chat');
const { removeDocument } = useRemoveNextDocument(); const { removeDocument } = useRemoveNextDocument();
const { deleteDocument } = useDeleteDocument();
const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds(); const { data: documentInfos, setDocumentIds } = useFetchDocumentInfosByIds();
const [fileList, setFileList] = useState<UploadFile[]>([]); const [fileList, setFileList] = useState<UploadFile[]>([]);
@ -89,7 +107,7 @@ const MessageInput = ({
const handlePressEnter = useCallback(async () => { const handlePressEnter = useCallback(async () => {
if (isUploadingFile) return; if (isUploadingFile) return;
const ids = getFileIds(fileList); const ids = getFileIds(fileList.filter((x) => isUploadSuccess(x)));
onPressEnter(ids); onPressEnter(ids);
setFileList([]); setFileList([]);
@ -98,14 +116,24 @@ const MessageInput = ({
const handleRemove = useCallback( const handleRemove = useCallback(
async (file: UploadFile) => { async (file: UploadFile) => {
const ids = get(file, 'response.data', []); const ids = get(file, 'response.data', []);
if (ids.length) { // Upload Successfully
await removeDocument(ids[0]); if (Array.isArray(ids) && ids.length) {
if (isShared) {
await deleteDocument(ids);
} else {
await removeDocument(ids[0]);
}
setFileList((preList) => { setFileList((preList) => {
return preList.filter((x) => getFileId(x) !== ids[0]); 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( const getDocumentInfoById = useCallback(
@ -192,6 +220,11 @@ const MessageInput = ({
<LoadingOutlined style={{ fontSize: 24 }} spin /> <LoadingOutlined style={{ fontSize: 24 }} spin />
} }
/> />
) : !getFileId(item) ? (
<InfoCircleOutlined
size={30}
// width={30}
></InfoCircleOutlined>
) : ( ) : (
<FileIcon id={id} name={item.name}></FileIcon> <FileIcon id={id} name={item.name}></FileIcon>
)} )}
@ -202,26 +235,33 @@ const MessageInput = ({
> >
<b> {item.name}</b> <b> {item.name}</b>
</Text> </Text>
{item.percent !== 100 ? ( {isUploadError(item) ? (
t('uploading') t('uploadFailed')
) : !item.response ? (
t('parsing')
) : ( ) : (
<Space> <>
<span>{fileExtension?.toUpperCase()},</span> {item.percent !== 100 ? (
<span> t('uploading')
{formatBytes(getDocumentInfoById(id)?.size ?? 0)} ) : !item.response ? (
</span> t('parsing')
</Space> ) : (
<Space>
<span>{fileExtension?.toUpperCase()},</span>
<span>
{formatBytes(
getDocumentInfoById(id)?.size ?? 0,
)}
</span>
</Space>
)}
</>
)} )}
</Flex> </Flex>
</Flex> </Flex>
{item.status !== 'uploading' && ( {item.status !== 'uploading' && (
<CloseCircleOutlined <span className={styles.deleteIcon}>
className={styles.deleteIcon} <CloseCircleOutlined onClick={() => handleRemove(item)} />
onClick={() => handleRemove(item)} </span>
/>
)} )}
</Card> </Card>
</List.Item> </List.Item>

View File

@ -313,3 +313,23 @@ export const useRemoveNextDocument = () => {
return { data, loading, removeDocument: mutateAsync }; 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 };
};

View File

@ -424,6 +424,7 @@ The above is the content you need to summarize.`,
searching: 'searching...', searching: 'searching...',
parsing: 'Parsing', parsing: 'Parsing',
uploading: 'Uploading', uploading: 'Uploading',
uploadFailed: 'Upload failed',
}, },
setting: { setting: {
profile: 'Profile', profile: 'Profile',

View File

@ -394,6 +394,7 @@ export default {
searching: '搜索中', searching: '搜索中',
parsing: '解析中', parsing: '解析中',
uploading: '上傳中', uploading: '上傳中',
uploadFailed: '上傳失敗',
}, },
setting: { setting: {
profile: '概述', profile: '概述',

View File

@ -411,6 +411,7 @@ export default {
searching: '搜索中', searching: '搜索中',
parsing: '解析中', parsing: '解析中',
uploading: '上传中', uploading: '上传中',
uploadFailed: '上传失败',
}, },
setting: { setting: {
profile: '概要', profile: '概要',

View File

@ -65,6 +65,7 @@ const ChatContainer = () => {
</Flex> </Flex>
<MessageInput <MessageInput
isShared
value={value} value={value}
disabled={false} disabled={false}
sendDisabled={sendDisabled} sendDisabled={sendDisabled}

View File

@ -5,7 +5,7 @@ import {
} from '@/hooks/chat-hooks'; } from '@/hooks/chat-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 } from '@/interfaces/database/chat'; import { IAnswer, Message } from '@/interfaces/database/chat';
import api from '@/utils/api'; import api from '@/utils/api';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import trim from 'lodash/trim'; import trim from 'lodash/trim';
@ -57,7 +57,7 @@ export const useSelectCurrentSharedConversation = (conversationId: string) => {
const ref = useScrollToBottom(currentConversation); const ref = useScrollToBottom(currentConversation);
const addNewestConversation = useCallback((message: string) => { const addNewestConversation = useCallback((message: Partial<Message>) => {
setCurrentConversation((pre) => { setCurrentConversation((pre) => {
return { return {
...pre, ...pre,
@ -65,14 +65,15 @@ export const useSelectCurrentSharedConversation = (conversationId: string) => {
...(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,
{ {
role: MessageType.Assistant, role: MessageType.Assistant,
content: '', content: '',
id: uuid(), id: uuid(),
reference: [], reference: {},
} as IMessage, } as IMessage,
], ],
}; };
@ -140,7 +141,7 @@ export const useSendButtonDisabled = (value: string) => {
export const useSendSharedMessage = ( export const useSendSharedMessage = (
conversation: IClientConversation, conversation: IClientConversation,
addNewestConversation: (message: string) => void, addNewestConversation: (message: Partial<Message>, answer?: string) => void,
removeLatestMessage: () => void, removeLatestMessage: () => void,
setCurrentConversation: Dispatch<SetStateAction<IClientConversation>>, setCurrentConversation: Dispatch<SetStateAction<IClientConversation>>,
addNewestAnswer: (answer: IAnswer) => void, addNewestAnswer: (answer: IAnswer) => void,
@ -205,14 +206,17 @@ export const useSendSharedMessage = (
} }
}, [answer, addNewestAnswer]); }, [answer, addNewestAnswer]);
const handlePressEnter = useCallback(() => { const handlePressEnter = useCallback(
if (trim(value) === '') return; (documentIds: string[]) => {
if (done) { if (trim(value) === '') return;
setValue(''); if (done) {
addNewestConversation(value); setValue('');
handleSendMessage(value.trim()); addNewestConversation({ content: value, doc_ids: documentIds });
} handleSendMessage(value.trim());
}, [addNewestConversation, done, handleSendMessage, setValue, value]); }
},
[addNewestConversation, done, handleSendMessage, setValue, value],
);
return { return {
handlePressEnter, handlePressEnter,

View File

@ -12,6 +12,7 @@ const {
get_document_list, get_document_list,
document_change_status, document_change_status,
document_rm, document_rm,
document_delete,
document_create, document_create,
document_change_parser, document_change_parser,
document_thumbnails, document_thumbnails,
@ -131,6 +132,10 @@ const methods = {
url: knowledge_graph, url: knowledge_graph,
method: 'get', method: 'get',
}, },
document_delete: {
url: document_delete,
method: 'delete',
},
}; };
const kbService = registerServer<keyof typeof methods>(methods, request); const kbService = registerServer<keyof typeof methods>(methods, request);

View File

@ -41,6 +41,7 @@ export default {
get_document_list: `${api_host}/document/list`, get_document_list: `${api_host}/document/list`,
document_change_status: `${api_host}/document/change_status`, document_change_status: `${api_host}/document/change_status`,
document_rm: `${api_host}/document/rm`, document_rm: `${api_host}/document/rm`,
document_delete: `${api_host}/api/document`,
document_rename: `${api_host}/document/rename`, document_rename: `${api_host}/document/rename`,
document_create: `${api_host}/document/create`, document_create: `${api_host}/document/create`,
document_run: `${api_host}/document/run`, document_run: `${api_host}/document/run`,

View File

@ -6,6 +6,8 @@ type Service<T extends string> = Record<
(params?: any, urlAppendix?: string) => any (params?: any, urlAppendix?: string) => any
>; >;
const Methods = ['post', 'delete', 'put'];
const registerServer = <T extends string>( const registerServer = <T extends string>(
opt: Record<T, { url: string; method: string }>, opt: Record<T, { url: string; method: string }>,
request: RequestMethod, request: RequestMethod,
@ -18,7 +20,7 @@ const registerServer = <T extends string>(
if (urlAppendix) { if (urlAppendix) {
url = url + '/' + 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, { return request(url, {
method: opt[key].method, method: opt[key].method,
data: params, data: params,