diff --git a/web/src/components/message-input/next.tsx b/web/src/components/message-input/next.tsx index 200f3d139..323a4fb18 100644 --- a/web/src/components/message-input/next.tsx +++ b/web/src/components/message-input/next.tsx @@ -34,6 +34,7 @@ interface IProps { createConversationBeforeUploadDocument?(message: string): Promise; stopOutputMessage?(): void; onUpload?: NonNullable; + removeFile?(file: File): void; } export function NextMessageInput({ @@ -47,6 +48,7 @@ export function NextMessageInput({ onInputChange, stopOutputMessage, onPressEnter, + removeFile, }: IProps) { const [files, setFiles] = React.useState([]); @@ -77,6 +79,13 @@ export function NextMessageInput({ [submit], ); + const handleRemoveFile = React.useCallback( + (file: File) => () => { + removeFile?.(file); + }, + [removeFile], + ); + return ( diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index ce28a1e09..a40ef0f16 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -390,7 +390,7 @@ export const useUploadCanvasFileWithProgress = ( files.forEach((file) => { onError(file, error as Error); }); - message.error('error', error?.message); + message.error(error?.message); } }, }); diff --git a/web/src/hooks/use-chat-request.ts b/web/src/hooks/use-chat-request.ts index 1cf17f7e7..6da7196d8 100644 --- a/web/src/hooks/use-chat-request.ts +++ b/web/src/hooks/use-chat-request.ts @@ -1,3 +1,4 @@ +import { FileUploadProps } from '@/components/file-upload'; import message from '@/components/ui/message'; import { ChatSearchParams } from '@/constants/chat'; import { @@ -14,7 +15,7 @@ import { buildMessageListWithUuid, getConversationId } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; import { has } from 'lodash'; -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'umi'; import { @@ -395,9 +396,14 @@ export const useDeleteMessage = () => { return { data, loading, deleteMessage: mutateAsync }; }; +type UploadParameters = Parameters>; + +type X = { file: UploadParameters[0][0]; options: UploadParameters[1] }; + export function useUploadAndParseFile() { const { conversationId } = useGetChatSearchParams(); const { t } = useTranslation(); + const controller = useRef(new AbortController()); const { data, @@ -405,22 +411,48 @@ export function useUploadAndParseFile() { mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.UploadAndParse], - mutationFn: async (file: File) => { - const formData = new FormData(); - formData.append('file', file); - formData.append('conversation_id', conversationId); + mutationFn: async ({ + file, + options: { onProgress, onSuccess, onError }, + }: 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) { - message.success(t(`message.uploaded`)); + onProgress(file, 100); + + 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 = () => { diff --git a/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx b/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx index 323661776..0d31821a1 100644 --- a/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx +++ b/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx @@ -36,6 +36,7 @@ export function SingleChatBox({ controller }: IProps) { removeMessageById, stopOutputMessage, handleUploadFile, + removeFile, } = useSendMessage(controller); const { data: userInfo } = useFetchUserInfo(); const { data: currentDialog } = useFetchDialog(); @@ -97,6 +98,7 @@ export function SingleChatBox({ controller }: IProps) { stopOutputMessage={stopOutputMessage} onUpload={handleUploadFile} isUploading={isUploading} + removeFile={removeFile} /> {visible && ( { const { conversationId, isNew } = useGetChatSearchParams(); const { handleInputChange, value, setValue } = useHandleMessageInputChange(); - const { handleUploadFile, fileIds, clearFileIds, isUploading } = + const { handleUploadFile, fileIds, clearFileIds, isUploading, removeFile } = useUploadFile(); const { send, answer, done } = useSendMessageWithSse( @@ -287,5 +287,6 @@ export const useSendMessage = (controller: AbortController) => { stopOutputMessage, handleUploadFile, isUploading, + removeFile, }; }; diff --git a/web/src/pages/next-chats/hooks/use-upload-file.ts b/web/src/pages/next-chats/hooks/use-upload-file.ts index 38e260ebe..8f9e1e810 100644 --- a/web/src/pages/next-chats/hooks/use-upload-file.ts +++ b/web/src/pages/next-chats/hooks/use-upload-file.ts @@ -3,16 +3,21 @@ import { useUploadAndParseFile } from '@/hooks/use-chat-request'; import { useCallback, useState } from 'react'; export function useUploadFile() { - const { uploadAndParseFile, loading } = useUploadAndParseFile(); + const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile(); const [fileIds, setFileIds] = useState([]); + const [fileMap, setFileMap] = useState>(new Map()); const handleUploadFile: NonNullable = useCallback( - async (files) => { + async (files, options) => { 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)) { 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(() => { 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, + }; }