mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-03 23:10:36 +08:00
### What problem does this PR solve? Feat: Save document metadata #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
1662c7eda3
commit
3052006ba8
@ -1,4 +1,3 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@ -45,6 +44,7 @@ import RaptorFormFields, {
|
||||
showRaptorParseConfiguration,
|
||||
} from '../parse-configuration/raptor-form-fields';
|
||||
import { Input } from '../ui/input';
|
||||
import { LoadingButton } from '../ui/loading-button';
|
||||
import { RAGFlowSelect } from '../ui/select';
|
||||
import { DynamicPageRange } from './dynamic-page-range';
|
||||
import { useFetchParserListOnMount, useShowAutoKeywords } from './hooks';
|
||||
@ -84,6 +84,7 @@ export function ChunkMethodDialog({
|
||||
documentExtension,
|
||||
visible,
|
||||
parserConfig,
|
||||
loading,
|
||||
}: IProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -108,34 +109,37 @@ export function ChunkMethodDialog({
|
||||
parser_id: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: 'namePlaceholder',
|
||||
message: t('common.pleaseSelect'),
|
||||
})
|
||||
.trim(),
|
||||
parser_config: z.object({
|
||||
task_page_size: z.coerce.number(),
|
||||
layout_recognize: z.string(),
|
||||
chunk_token_num: z.coerce.number(),
|
||||
delimiter: z.string(),
|
||||
auto_keywords: z.coerce.number(),
|
||||
auto_questions: z.coerce.number(),
|
||||
html4excel: z.boolean(),
|
||||
raptor: z.object({
|
||||
use_raptor: z.boolean().optional(),
|
||||
prompt: z.string(),
|
||||
max_token: z.coerce.number(),
|
||||
threshold: z.coerce.number(),
|
||||
max_cluster: z.coerce.number(),
|
||||
random_seed: z.coerce.number(),
|
||||
}),
|
||||
task_page_size: z.coerce.number().optional(),
|
||||
layout_recognize: z.string().optional(),
|
||||
chunk_token_num: z.coerce.number().optional(),
|
||||
delimiter: z.string().optional(),
|
||||
auto_keywords: z.coerce.number().optional(),
|
||||
auto_questions: z.coerce.number().optional(),
|
||||
html4excel: z.boolean().optional(),
|
||||
raptor: z
|
||||
.object({
|
||||
use_raptor: z.boolean().optional(),
|
||||
prompt: z.string().optional().optional(),
|
||||
max_token: z.coerce.number().optional(),
|
||||
threshold: z.coerce.number().optional(),
|
||||
max_cluster: z.coerce.number().optional(),
|
||||
random_seed: z.coerce.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
graphrag: z.object({
|
||||
use_graphrag: z.boolean(),
|
||||
use_graphrag: z.boolean().optional(),
|
||||
}),
|
||||
entity_types: z.array(z.string()),
|
||||
pages: z.array(
|
||||
z.object({ from: z.coerce.number(), to: z.coerce.number() }),
|
||||
),
|
||||
entity_types: z.array(z.string()).optional(),
|
||||
pages: z
|
||||
.array(z.object({ from: z.coerce.number(), to: z.coerce.number() }))
|
||||
.optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
@ -316,9 +320,9 @@ export function ChunkMethodDialog({
|
||||
</form>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
<Button type="submit" form={FormId}>
|
||||
<LoadingButton type="submit" form={FormId} loading={loading}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
|
||||
import {
|
||||
IChangeParserConfigRequestBody,
|
||||
IDocumentMetaRequestBody,
|
||||
} from '@/interfaces/request/document';
|
||||
import i18n from '@/locales/config';
|
||||
import kbService from '@/services/knowledge-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
@ -22,6 +25,7 @@ export const enum DocumentApiAction {
|
||||
RemoveDocument = 'removeDocument',
|
||||
SaveDocumentName = 'saveDocumentName',
|
||||
SetDocumentParser = 'setDocumentParser',
|
||||
SetDocumentMeta = 'setDocumentMeta',
|
||||
}
|
||||
|
||||
export const useUploadNextDocument = () => {
|
||||
@ -286,3 +290,36 @@ export const useSetDocumentParser = () => {
|
||||
|
||||
return { setDocumentParser: mutateAsync, data, loading };
|
||||
};
|
||||
|
||||
export const useSetDocumentMeta = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [DocumentApiAction.SetDocumentMeta],
|
||||
mutationFn: async (params: IDocumentMetaRequestBody) => {
|
||||
try {
|
||||
const { data } = await kbService.setMeta({
|
||||
meta: params.meta,
|
||||
doc_id: params.documentId,
|
||||
});
|
||||
|
||||
if (data?.code === 0) {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||
});
|
||||
|
||||
message.success(i18n.t('message.modified'));
|
||||
}
|
||||
return data?.code;
|
||||
} catch (error) {
|
||||
message.error('error');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { setDocumentMeta: mutateAsync, data, loading };
|
||||
};
|
||||
|
@ -29,9 +29,11 @@ import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { getExtension } from '@/utils/document-util';
|
||||
import { useMemo } from 'react';
|
||||
import { SetMetaDialog } from './set-meta-dialog';
|
||||
import { useChangeDocumentParser } from './use-change-document-parser';
|
||||
import { useDatasetTableColumns } from './use-dataset-table-columns';
|
||||
import { useRenameDocument } from './use-rename-document';
|
||||
import { useSaveMeta } from './use-save-meta';
|
||||
|
||||
export function DatasetTable() {
|
||||
const {
|
||||
@ -69,10 +71,20 @@ export function DatasetTable() {
|
||||
initialName,
|
||||
} = useRenameDocument();
|
||||
|
||||
const {
|
||||
showSetMetaModal,
|
||||
hideSetMetaModal,
|
||||
setMetaVisible,
|
||||
setMetaLoading,
|
||||
onSetMetaModalOk,
|
||||
metaRecord,
|
||||
} = useSaveMeta();
|
||||
|
||||
const columns = useDatasetTableColumns({
|
||||
showChangeParserModal,
|
||||
setCurrentRecord: setRecord,
|
||||
showRenameModal,
|
||||
showSetMetaModal,
|
||||
});
|
||||
|
||||
const currentPagination = useMemo(() => {
|
||||
@ -219,6 +231,15 @@ export function DatasetTable() {
|
||||
initialName={initialName}
|
||||
></RenameDialog>
|
||||
)}
|
||||
|
||||
{setMetaVisible && (
|
||||
<SetMetaDialog
|
||||
hideModal={hideSetMetaModal}
|
||||
loading={setMetaLoading}
|
||||
onOk={onSetMetaModalOk}
|
||||
initialMetaData={metaRecord.meta_fields}
|
||||
></SetMetaDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import { RunningStatus } from './constant';
|
||||
import { ParsingCard } from './parsing-card';
|
||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||
import { useHandleRunDocumentByIds } from './use-run-document';
|
||||
import { UseSaveMetaShowType } from './use-save-meta';
|
||||
import { isParserRunning } from './utils';
|
||||
|
||||
const IconMap = {
|
||||
@ -29,7 +30,9 @@ const IconMap = {
|
||||
export function ParsingStatusCell({
|
||||
record,
|
||||
showChangeParserModal,
|
||||
}: { record: IDocumentInfo } & UseChangeDocumentParserShowType) {
|
||||
showSetMetaModal,
|
||||
}: { record: IDocumentInfo } & UseChangeDocumentParserShowType &
|
||||
UseSaveMetaShowType) {
|
||||
const { t } = useTranslation();
|
||||
const { run, parser_id, progress, chunk_num, id } = record;
|
||||
const operationIcon = IconMap[run];
|
||||
@ -48,6 +51,10 @@ export function ParsingStatusCell({
|
||||
showChangeParserModal(record);
|
||||
}, [record, showChangeParserModal]);
|
||||
|
||||
const handleShowSetMetaModal = useCallback(() => {
|
||||
showSetMetaModal(record);
|
||||
}, [record, showSetMetaModal]);
|
||||
|
||||
return (
|
||||
<section className="flex gap-2 items-center ">
|
||||
<div>
|
||||
@ -61,7 +68,7 @@ export function ParsingStatusCell({
|
||||
<DropdownMenuItem onClick={handleShowChangeParserModal}>
|
||||
{t('knowledgeDetails.chunkMethod')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleShowSetMetaModal}>
|
||||
{t('knowledgeDetails.setMetaData')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
128
web/src/pages/dataset/dataset/set-meta-dialog.tsx
Normal file
128
web/src/pages/dataset/dataset/set-meta-dialog.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { LoadingButton } from '@/components/ui/loading-button';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import Editor, { loader } from '@monaco-editor/react';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
loader.config({ paths: { vs: '/vs' } });
|
||||
|
||||
export function SetMetaDialog({
|
||||
hideModal,
|
||||
onOk,
|
||||
loading,
|
||||
initialMetaData,
|
||||
}: IModalProps<any> & { initialMetaData?: IDocumentInfo['meta_fields'] }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const FormSchema = z.object({
|
||||
meta: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('knowledgeDetails.pleaseInputJson'),
|
||||
})
|
||||
.trim()
|
||||
.refine(
|
||||
(value) => {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: t('knowledgeDetails.pleaseInputJson') },
|
||||
),
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
async function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
const ret = await onOk?.(data.meta);
|
||||
if (ret) {
|
||||
hideModal?.();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue('meta', JSON.stringify(initialMetaData, null, 4));
|
||||
}, [form, initialMetaData]);
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('knowledgeDetails.setMetaData')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
id={TagRenameId}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="meta"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel
|
||||
tooltip={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(
|
||||
t('knowledgeDetails.documentMetaTips'),
|
||||
),
|
||||
}}
|
||||
></div>
|
||||
}
|
||||
>
|
||||
{t('knowledgeDetails.metaData')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Editor
|
||||
height={200}
|
||||
defaultLanguage="json"
|
||||
theme="vs-dark"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
<DialogFooter>
|
||||
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
|
||||
{t('common.save')}
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
@ -20,15 +20,18 @@ import { DatasetActionCell } from './dataset-action-cell';
|
||||
import { ParsingStatusCell } from './parsing-status-cell';
|
||||
import { UseChangeDocumentParserShowType } from './use-change-document-parser';
|
||||
import { UseRenameDocumentShowType } from './use-rename-document';
|
||||
import { UseSaveMetaShowType } from './use-save-meta';
|
||||
|
||||
type UseDatasetTableColumnsType = UseChangeDocumentParserShowType & {
|
||||
setCurrentRecord: (record: IDocumentInfo) => void;
|
||||
} & UseRenameDocumentShowType;
|
||||
} & UseRenameDocumentShowType &
|
||||
UseSaveMetaShowType;
|
||||
|
||||
export function useDatasetTableColumns({
|
||||
showChangeParserModal,
|
||||
setCurrentRecord,
|
||||
showRenameModal,
|
||||
showSetMetaModal,
|
||||
}: UseDatasetTableColumnsType) {
|
||||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'knowledgeDetails',
|
||||
@ -161,6 +164,7 @@ export function useDatasetTableColumns({
|
||||
<ParsingStatusCell
|
||||
record={row.original}
|
||||
showChangeParserModal={showChangeParserModal}
|
||||
showSetMetaModal={showSetMetaModal}
|
||||
></ParsingStatusCell>
|
||||
);
|
||||
},
|
||||
|
50
web/src/pages/dataset/dataset/use-save-meta.ts
Normal file
50
web/src/pages/dataset/dataset/use-save-meta.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useSetDocumentMeta } from '@/hooks/use-document-request';
|
||||
import { IDocumentInfo } from '@/interfaces/database/document';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useSaveMeta = () => {
|
||||
const { setDocumentMeta, loading } = useSetDocumentMeta();
|
||||
const [record, setRecord] = useState<IDocumentInfo>({} as IDocumentInfo);
|
||||
|
||||
const {
|
||||
visible: setMetaVisible,
|
||||
hideModal: hideSetMetaModal,
|
||||
showModal: showSetMetaModal,
|
||||
} = useSetModalState();
|
||||
|
||||
const onSetMetaModalOk = useCallback(
|
||||
async (meta: string) => {
|
||||
const ret = await setDocumentMeta({
|
||||
documentId: record?.id,
|
||||
meta,
|
||||
});
|
||||
if (ret === 0) {
|
||||
hideSetMetaModal();
|
||||
}
|
||||
},
|
||||
[setDocumentMeta, record?.id, hideSetMetaModal],
|
||||
);
|
||||
|
||||
const handleShowSetMetaModal = useCallback(
|
||||
(row: IDocumentInfo) => {
|
||||
setRecord(row);
|
||||
showSetMetaModal();
|
||||
},
|
||||
[showSetMetaModal],
|
||||
);
|
||||
|
||||
return {
|
||||
setMetaLoading: loading,
|
||||
onSetMetaModalOk,
|
||||
setMetaVisible,
|
||||
hideSetMetaModal,
|
||||
showSetMetaModal: handleShowSetMetaModal,
|
||||
metaRecord: record,
|
||||
};
|
||||
};
|
||||
|
||||
export type UseSaveMetaShowType = Pick<
|
||||
ReturnType<typeof useSaveMeta>,
|
||||
'showSetMetaModal'
|
||||
>;
|
@ -1,5 +1,6 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useSecondPathName } from '@/hooks/route-hook';
|
||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { Banknote, LayoutGrid, User } from 'lucide-react';
|
||||
@ -27,6 +28,7 @@ const dataset = {
|
||||
export function SideBar() {
|
||||
const pathName = useSecondPathName();
|
||||
const { handleMenuClick } = useHandleMenuClick();
|
||||
const { data } = useFetchKnowledgeBaseConfiguration();
|
||||
|
||||
return (
|
||||
<aside className="w-[303px] relative border-r ">
|
||||
@ -36,7 +38,7 @@ export function SideBar() {
|
||||
style={{ backgroundImage: `url(${dataset.image})` }}
|
||||
/>
|
||||
|
||||
<h3 className="text-lg font-semibold mb-2">{dataset.title}</h3>
|
||||
<h3 className="text-lg font-semibold mb-2">{data.name}</h3>
|
||||
<div className="text-sm opacity-80">
|
||||
{dataset.files} | {dataset.size}
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user