Fix: Delete the uploaded file in the chat input box, the corresponding file ID is not deleted #9701 (#9702)

### What problem does this PR solve?

Fix: Delete the uploaded file in the chat input box, the corresponding
file ID is not deleted #9701
### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
balibabu
2025-08-26 09:27:49 +08:00
committed by GitHub
parent 8d8a5f73b6
commit 63b5c2292d
6 changed files with 88 additions and 17 deletions

View File

@ -34,6 +34,7 @@ interface IProps {
createConversationBeforeUploadDocument?(message: string): Promise<any>; createConversationBeforeUploadDocument?(message: string): Promise<any>;
stopOutputMessage?(): void; stopOutputMessage?(): void;
onUpload?: NonNullable<FileUploadProps['onUpload']>; onUpload?: NonNullable<FileUploadProps['onUpload']>;
removeFile?(file: File): void;
} }
export function NextMessageInput({ export function NextMessageInput({
@ -47,6 +48,7 @@ export function NextMessageInput({
onInputChange, onInputChange,
stopOutputMessage, stopOutputMessage,
onPressEnter, onPressEnter,
removeFile,
}: IProps) { }: IProps) {
const [files, setFiles] = React.useState<File[]>([]); const [files, setFiles] = React.useState<File[]>([]);
@ -77,6 +79,13 @@ export function NextMessageInput({
[submit], [submit],
); );
const handleRemoveFile = React.useCallback(
(file: File) => () => {
removeFile?.(file);
},
[removeFile],
);
return ( return (
<FileUpload <FileUpload
value={files} value={files}
@ -121,6 +130,7 @@ export function NextMessageInput({
variant="secondary" variant="secondary"
size="icon" size="icon"
className="-top-1 -right-1 absolute size-4 shrink-0 cursor-pointer rounded-full" className="-top-1 -right-1 absolute size-4 shrink-0 cursor-pointer rounded-full"
onClick={handleRemoveFile(file)}
> >
<X className="size-2.5" /> <X className="size-2.5" />
</Button> </Button>

View File

@ -390,7 +390,7 @@ export const useUploadCanvasFileWithProgress = (
files.forEach((file) => { files.forEach((file) => {
onError(file, error as Error); onError(file, error as Error);
}); });
message.error('error', error?.message); message.error(error?.message);
} }
}, },
}); });

View File

@ -1,3 +1,4 @@
import { FileUploadProps } from '@/components/file-upload';
import message from '@/components/ui/message'; import message from '@/components/ui/message';
import { ChatSearchParams } from '@/constants/chat'; import { ChatSearchParams } from '@/constants/chat';
import { import {
@ -14,7 +15,7 @@ import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks'; import { useDebounce } from 'ahooks';
import { has } from 'lodash'; import { has } from 'lodash';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'umi'; import { useParams, useSearchParams } from 'umi';
import { import {
@ -395,9 +396,14 @@ export const useDeleteMessage = () => {
return { data, loading, deleteMessage: mutateAsync }; return { data, loading, deleteMessage: mutateAsync };
}; };
type UploadParameters = Parameters<NonNullable<FileUploadProps['onUpload']>>;
type X = { file: UploadParameters[0][0]; options: UploadParameters[1] };
export function useUploadAndParseFile() { export function useUploadAndParseFile() {
const { conversationId } = useGetChatSearchParams(); const { conversationId } = useGetChatSearchParams();
const { t } = useTranslation(); const { t } = useTranslation();
const controller = useRef(new AbortController());
const { const {
data, data,
@ -405,22 +411,48 @@ export function useUploadAndParseFile() {
mutateAsync, mutateAsync,
} = useMutation({ } = useMutation({
mutationKey: [ChatApiAction.UploadAndParse], mutationKey: [ChatApiAction.UploadAndParse],
mutationFn: async (file: File) => { mutationFn: async ({
const formData = new FormData(); file,
formData.append('file', file); options: { onProgress, onSuccess, onError },
formData.append('conversation_id', conversationId); }: X) => {
try {
const formData = new FormData();
formData.append('file', file);
formData.append('conversation_id', conversationId);
const { data } = await chatService.uploadAndParse(formData); const { data } = await chatService.uploadAndParse(
{
signal: controller.current.signal,
data: formData,
onUploadProgress: ({ progress }) => {
onProgress(file, (progress || 0) * 100 - 1);
},
},
true,
);
if (data.code === 0) { onProgress(file, 100);
message.success(t(`message.uploaded`));
if (data.code === 0) {
onSuccess(file);
message.success(t(`message.uploaded`));
} else {
onError(file, new Error(data.message));
}
return data;
} catch (error) {
onError(file, error as Error);
} }
return data;
}, },
}); });
return { data, loading, uploadAndParseFile: mutateAsync }; const cancel = useCallback(() => {
controller.current.abort();
controller.current = new AbortController();
}, [controller]);
return { data, loading, uploadAndParseFile: mutateAsync, cancel };
} }
export const useFetchExternalChatInfo = () => { export const useFetchExternalChatInfo = () => {

View File

@ -36,6 +36,7 @@ export function SingleChatBox({ controller }: IProps) {
removeMessageById, removeMessageById,
stopOutputMessage, stopOutputMessage,
handleUploadFile, handleUploadFile,
removeFile,
} = useSendMessage(controller); } = useSendMessage(controller);
const { data: userInfo } = useFetchUserInfo(); const { data: userInfo } = useFetchUserInfo();
const { data: currentDialog } = useFetchDialog(); const { data: currentDialog } = useFetchDialog();
@ -97,6 +98,7 @@ export function SingleChatBox({ controller }: IProps) {
stopOutputMessage={stopOutputMessage} stopOutputMessage={stopOutputMessage}
onUpload={handleUploadFile} onUpload={handleUploadFile}
isUploading={isUploading} isUploading={isUploading}
removeFile={removeFile}
/> />
{visible && ( {visible && (
<PdfDrawer <PdfDrawer

View File

@ -138,7 +138,7 @@ export const useSendMessage = (controller: AbortController) => {
const { conversationId, isNew } = useGetChatSearchParams(); const { conversationId, isNew } = useGetChatSearchParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange(); const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleUploadFile, fileIds, clearFileIds, isUploading } = const { handleUploadFile, fileIds, clearFileIds, isUploading, removeFile } =
useUploadFile(); useUploadFile();
const { send, answer, done } = useSendMessageWithSse( const { send, answer, done } = useSendMessageWithSse(
@ -287,5 +287,6 @@ export const useSendMessage = (controller: AbortController) => {
stopOutputMessage, stopOutputMessage,
handleUploadFile, handleUploadFile,
isUploading, isUploading,
removeFile,
}; };
}; };

View File

@ -3,16 +3,21 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
export function useUploadFile() { export function useUploadFile() {
const { uploadAndParseFile, loading } = useUploadAndParseFile(); const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile();
const [fileIds, setFileIds] = useState<string[]>([]); const [fileIds, setFileIds] = useState<string[]>([]);
const [fileMap, setFileMap] = useState<Map<File, string>>(new Map());
const handleUploadFile: NonNullable<FileUploadProps['onUpload']> = const handleUploadFile: NonNullable<FileUploadProps['onUpload']> =
useCallback( useCallback(
async (files) => { async (files, options) => {
if (Array.isArray(files) && files.length) { if (Array.isArray(files) && files.length) {
const ret = await uploadAndParseFile(files[0]); const ret = await uploadAndParseFile({ file: files[0], options });
if (ret.code === 0 && Array.isArray(ret.data)) { if (ret.code === 0 && Array.isArray(ret.data)) {
setFileIds((list) => [...list, ...ret.data]); setFileIds((list) => [...list, ...ret.data]);
setFileMap((map) => {
map.set(files[0], ret.data[0]);
return map;
});
} }
} }
}, },
@ -21,7 +26,28 @@ export function useUploadFile() {
const clearFileIds = useCallback(() => { const clearFileIds = useCallback(() => {
setFileIds([]); setFileIds([]);
setFileMap(new Map());
}, []); }, []);
return { handleUploadFile, clearFileIds, fileIds, isUploading: loading }; const removeFile = useCallback(
(file: File) => {
if (loading) {
cancel();
return;
}
const id = fileMap.get(file);
if (id) {
setFileIds((list) => list.filter((item) => item !== id));
}
},
[cancel, fileMap, loading],
);
return {
handleUploadFile,
clearFileIds,
fileIds,
isUploading: loading,
removeFile,
};
} }