mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### 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:
@ -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>
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -799,6 +799,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
fileError: '文件错误',
|
fileError: '文件错误',
|
||||||
uploadLimit: '文件大小不能超过10M,文件总数不超过128个',
|
uploadLimit: '文件大小不能超过10M,文件总数不超过128个',
|
||||||
destinationFolder: '目标文件夹',
|
destinationFolder: '目标文件夹',
|
||||||
|
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
|
||||||
},
|
},
|
||||||
flow: {
|
flow: {
|
||||||
flow: '工作流',
|
flow: '工作流',
|
||||||
|
|||||||
@ -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 && (
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user