Feat: Allow users to parse documents directly after uploading files #3221 (#9633)

### What problem does this PR solve?

Feat: Allow users to parse documents directly after uploading files
#3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-08-21 16:56:22 +08:00
committed by GitHub
parent d482173c9b
commit fbdde0259a
8 changed files with 120 additions and 34 deletions

View File

@ -8,42 +8,93 @@ import {
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { Dispatch, SetStateAction, useCallback, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod';
import { TFunction } from 'i18next';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { z } from 'zod';
import { FileUploader } from '../file-uploader'; import { FileUploader } from '../file-uploader';
import { RAGFlowFormItem } from '../ragflow-form';
import { Form } from '../ui/form';
import { Switch } from '../ui/switch';
type UploaderTabsProps = { function buildUploadFormSchema(t: TFunction) {
setFiles: Dispatch<SetStateAction<File[]>>; const FormSchema = z.object({
parseOnCreation: z.boolean().optional(),
fileList: z
.array(z.instanceof(File))
.min(1, { message: t('fileManager.pleaseUploadAtLeastOneFile') }),
});
return FormSchema;
}
export type UploadFormSchemaType = z.infer<
ReturnType<typeof buildUploadFormSchema>
>;
const UploadFormId = 'UploadFormId';
type UploadFormProps = {
submit: (values?: UploadFormSchemaType) => void;
showParseOnCreation?: boolean;
}; };
function UploadForm({ submit, showParseOnCreation }: UploadFormProps) {
export function UploaderTabs({ setFiles }: UploaderTabsProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const FormSchema = buildUploadFormSchema(t);
type UploadFormSchemaType = z.infer<typeof FormSchema>;
const form = useForm<UploadFormSchemaType>({
resolver: zodResolver(FormSchema),
defaultValues: {
parseOnCreation: false,
fileList: [],
},
});
return ( return (
<Tabs defaultValue="account"> <Form {...form}>
<TabsList className="grid w-full grid-cols-2 mb-4"> <form
<TabsTrigger value="account">{t('fileManager.local')}</TabsTrigger> onSubmit={form.handleSubmit(submit)}
<TabsTrigger value="password">{t('fileManager.s3')}</TabsTrigger> id={UploadFormId}
</TabsList> className="space-y-4"
<TabsContent value="account"> >
<FileUploader onValueChange={setFiles} accept={{ '*': [] }} /> {showParseOnCreation && (
</TabsContent> <RAGFlowFormItem
<TabsContent value="password">{t('common.comingSoon')}</TabsContent> name="parseOnCreation"
</Tabs> label={t('fileManager.parseOnCreation')}
>
{(field) => (
<Switch
onCheckedChange={field.onChange}
checked={field.value}
></Switch>
)}
</RAGFlowFormItem>
)}
<RAGFlowFormItem name="fileList" label={t('fileManager.file')}>
{(field) => (
<FileUploader
value={field.value}
onValueChange={field.onChange}
accept={{ '*': [] }}
/>
)}
</RAGFlowFormItem>
</form>
</Form>
); );
} }
type FileUploadDialogProps = IModalProps<UploadFormSchemaType> &
Pick<UploadFormProps, 'showParseOnCreation'>;
export function FileUploadDialog({ export function FileUploadDialog({
hideModal, hideModal,
onOk, onOk,
loading, loading,
}: IModalProps<File[]>) { showParseOnCreation = false,
}: FileUploadDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [files, setFiles] = useState<File[]>([]);
const handleOk = useCallback(() => {
onOk?.(files);
}, [files, onOk]);
return ( return (
<Dialog open onOpenChange={hideModal}> <Dialog open onOpenChange={hideModal}>
@ -51,9 +102,21 @@ export function FileUploadDialog({
<DialogHeader> <DialogHeader>
<DialogTitle>{t('fileManager.uploadFile')}</DialogTitle> <DialogTitle>{t('fileManager.uploadFile')}</DialogTitle>
</DialogHeader> </DialogHeader>
<UploaderTabs setFiles={setFiles}></UploaderTabs> <Tabs defaultValue="account">
<TabsList className="grid w-full grid-cols-2 mb-4">
<TabsTrigger value="account">{t('fileManager.local')}</TabsTrigger>
<TabsTrigger value="password">{t('fileManager.s3')}</TabsTrigger>
</TabsList>
<TabsContent value="account">
<UploadForm
submit={onOk!}
showParseOnCreation={showParseOnCreation}
></UploadForm>
</TabsContent>
<TabsContent value="password">{t('common.comingSoon')}</TabsContent>
</Tabs>
<DialogFooter> <DialogFooter>
<ButtonLoading type="submit" onClick={handleOk} loading={loading}> <ButtonLoading type="submit" loading={loading} form={UploadFormId}>
{t('common.save')} {t('common.save')}
</ButtonLoading> </ButtonLoading>
</DialogFooter> </DialogFooter>

View File

@ -1,4 +1,5 @@
import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit'; import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit';
import { ResponseType } from '@/interfaces/database/base';
import { import {
IDocumentInfo, IDocumentInfo,
IDocumentInfoFilter, IDocumentInfoFilter,
@ -45,9 +46,9 @@ export const useUploadNextDocument = () => {
data, data,
isPending: loading, isPending: loading,
mutateAsync, mutateAsync,
} = useMutation({ } = useMutation<ResponseType<IDocumentInfo[]>, Error, File[]>({
mutationKey: [DocumentApiAction.UploadDocument], mutationKey: [DocumentApiAction.UploadDocument],
mutationFn: async (fileList: File[]) => { mutationFn: async (fileList) => {
const formData = new FormData(); const formData = new FormData();
formData.append('kb_id', id!); formData.append('kb_id', id!);
fileList.forEach((file: any) => { fileList.forEach((file: any) => {

View File

@ -845,6 +845,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
uploadLimit: uploadLimit:
'Each file must not exceed 10MB, and the total number of files must not exceed 128.', 'Each file must not exceed 10MB, and the total number of files must not exceed 128.',
destinationFolder: 'Destination folder', destinationFolder: 'Destination folder',
pleaseUploadAtLeastOneFile: 'Please upload at least one file',
}, },
flow: { flow: {
cite: 'Cite', cite: 'Cite',

View File

@ -799,6 +799,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
fileError: '文件错误', fileError: '文件错误',
uploadLimit: '文件大小不能超过10M文件总数不超过128个', uploadLimit: '文件大小不能超过10M文件总数不超过128个',
destinationFolder: '目标文件夹', destinationFolder: '目标文件夹',
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
}, },
flow: { flow: {
flow: '工作流', flow: '工作流',

View File

@ -111,6 +111,7 @@ export default function Dataset() {
hideModal={hideDocumentUploadModal} hideModal={hideDocumentUploadModal}
onOk={onDocumentUploadOk} onOk={onDocumentUploadOk}
loading={documentUploadLoading} loading={documentUploadLoading}
showParseOnCreation
></FileUploadDialog> ></FileUploadDialog>
)} )}
{createVisible && ( {createVisible && (

View File

@ -1,5 +1,9 @@
import { UploadFormSchemaType } from '@/components/file-upload-dialog';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useUploadNextDocument } from '@/hooks/use-document-request'; import {
useRunDocument,
useUploadNextDocument,
} from '@/hooks/use-document-request';
import { getUnSupportedFilesCount } from '@/utils/document-util'; import { getUnSupportedFilesCount } from '@/utils/document-util';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -10,14 +14,24 @@ export const useHandleUploadDocument = () => {
showModal: showDocumentUploadModal, showModal: showDocumentUploadModal,
} = useSetModalState(); } = useSetModalState();
const { uploadDocument, loading } = useUploadNextDocument(); const { uploadDocument, loading } = useUploadNextDocument();
const { runDocumentByIds } = useRunDocument();
const onDocumentUploadOk = useCallback( const onDocumentUploadOk = useCallback(
async (fileList: File[]): Promise<number | undefined> => { async ({ fileList, parseOnCreation }: UploadFormSchemaType) => {
if (fileList.length > 0) { if (fileList.length > 0) {
const ret: any = await uploadDocument(fileList); const ret = await uploadDocument(fileList);
if (typeof ret?.message !== 'string') { if (typeof ret?.message !== 'string') {
return; return;
} }
if (ret.code === 0 && parseOnCreation) {
runDocumentByIds({
documentIds: ret.data.map((x) => x.id),
run: 1,
shouldDelete: false,
});
}
const count = getUnSupportedFilesCount(ret?.message); const count = getUnSupportedFilesCount(ret?.message);
/// 500 error code indicates that some file types are not supported /// 500 error code indicates that some file types are not supported
let code = ret?.code; let code = ret?.code;
@ -31,7 +45,7 @@ export const useHandleUploadDocument = () => {
return code; return code;
} }
}, },
[uploadDocument, hideDocumentUploadModal], [uploadDocument, runDocumentByIds, hideDocumentUploadModal],
); );
return { return {

View File

@ -1,4 +1,4 @@
import { Button } from '@/components/ui/button'; import { ButtonLoading } from '@/components/ui/button';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -74,7 +74,11 @@ export function InputForm({ onOk }: IModalProps<any>) {
); );
} }
export function DatasetCreatingDialog({ hideModal, onOk }: IModalProps<any>) { export function DatasetCreatingDialog({
hideModal,
onOk,
loading,
}: IModalProps<any>) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
@ -85,9 +89,9 @@ export function DatasetCreatingDialog({ hideModal, onOk }: IModalProps<any>) {
</DialogHeader> </DialogHeader>
<InputForm onOk={onOk}></InputForm> <InputForm onOk={onOk}></InputForm>
<DialogFooter> <DialogFooter>
<Button type="submit" form={FormId}> <ButtonLoading type="submit" form={FormId} loading={loading}>
{t('common.save')} {t('common.save')}
</Button> </ButtonLoading>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -1,3 +1,4 @@
import { UploadFormSchemaType } from '@/components/file-upload-dialog';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useUploadFile } from '@/hooks/use-file-request'; import { useUploadFile } from '@/hooks/use-file-request';
import { useCallback } from 'react'; import { useCallback } from 'react';
@ -13,7 +14,7 @@ export const useHandleUploadFile = () => {
const id = useGetFolderId(); const id = useGetFolderId();
const onFileUploadOk = useCallback( const onFileUploadOk = useCallback(
async (fileList: File[]): Promise<number | undefined> => { async ({ fileList }: UploadFormSchemaType): Promise<number | undefined> => {
if (fileList.length > 0) { if (fileList.length > 0) {
const ret: number = await uploadFile({ fileList, parentId: id }); const ret: number = await uploadFile({ fileList, parentId: id });
if (ret === 0) { if (ret === 0) {