From 3052006ba87af6dc4c3862bb6883df5781c96c0b Mon Sep 17 00:00:00 2001 From: balibabu Date: Fri, 25 Apr 2025 18:38:15 +0800 Subject: [PATCH] Feat: Save document metadata #3221 (#7323) ### What problem does this PR solve? Feat: Save document metadata #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- .../components/chunk-method-dialog/index.tsx | 52 +++---- web/src/hooks/use-document-request.ts | 39 +++++- .../pages/dataset/dataset/dataset-table.tsx | 21 +++ .../dataset/dataset/parsing-status-cell.tsx | 11 +- .../pages/dataset/dataset/set-meta-dialog.tsx | 128 ++++++++++++++++++ .../dataset/use-dataset-table-columns.tsx | 6 +- .../pages/dataset/dataset/use-save-meta.ts | 50 +++++++ web/src/pages/dataset/sidebar/index.tsx | 4 +- 8 files changed, 282 insertions(+), 29 deletions(-) create mode 100644 web/src/pages/dataset/dataset/set-meta-dialog.tsx create mode 100644 web/src/pages/dataset/dataset/use-save-meta.ts diff --git a/web/src/components/chunk-method-dialog/index.tsx b/web/src/components/chunk-method-dialog/index.tsx index fa2fabd10..5205438d1 100644 --- a/web/src/components/chunk-method-dialog/index.tsx +++ b/web/src/components/chunk-method-dialog/index.tsx @@ -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>({ resolver: zodResolver(FormSchema), defaultValues: { @@ -316,9 +320,9 @@ export function ChunkMethodDialog({ - + diff --git a/web/src/hooks/use-document-request.ts b/web/src/hooks/use-document-request.ts index edd05fcb4..103df4326 100644 --- a/web/src/hooks/use-document-request.ts +++ b/web/src/hooks/use-document-request.ts @@ -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 }; +}; diff --git a/web/src/pages/dataset/dataset/dataset-table.tsx b/web/src/pages/dataset/dataset/dataset-table.tsx index 25c771814..fe21ca65f 100644 --- a/web/src/pages/dataset/dataset/dataset-table.tsx +++ b/web/src/pages/dataset/dataset/dataset-table.tsx @@ -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} > )} + + {setMetaVisible && ( + + )} ); } diff --git a/web/src/pages/dataset/dataset/parsing-status-cell.tsx b/web/src/pages/dataset/dataset/parsing-status-cell.tsx index 114284743..4ff386de7 100644 --- a/web/src/pages/dataset/dataset/parsing-status-cell.tsx +++ b/web/src/pages/dataset/dataset/parsing-status-cell.tsx @@ -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 (
@@ -61,7 +68,7 @@ export function ParsingStatusCell({ {t('knowledgeDetails.chunkMethod')} - + {t('knowledgeDetails.setMetaData')} diff --git a/web/src/pages/dataset/dataset/set-meta-dialog.tsx b/web/src/pages/dataset/dataset/set-meta-dialog.tsx new file mode 100644 index 000000000..58bf69744 --- /dev/null +++ b/web/src/pages/dataset/dataset/set-meta-dialog.tsx @@ -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 & { 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>({ + resolver: zodResolver(FormSchema), + defaultValues: {}, + }); + + async function onSubmit(data: z.infer) { + const ret = await onOk?.(data.meta); + if (ret) { + hideModal?.(); + } + } + + useEffect(() => { + form.setValue('meta', JSON.stringify(initialMetaData, null, 4)); + }, [form, initialMetaData]); + + return ( + + + + {t('knowledgeDetails.setMetaData')} + +
+ + ( + +
+ } + > + {t('knowledgeDetails.metaData')} + + + + + + + )} + /> + + + + + {t('common.save')} + + + + + ); +} diff --git a/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx b/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx index 4e626c0d8..7b6e770ae 100644 --- a/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx +++ b/web/src/pages/dataset/dataset/use-dataset-table-columns.tsx @@ -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({ ); }, diff --git a/web/src/pages/dataset/dataset/use-save-meta.ts b/web/src/pages/dataset/dataset/use-save-meta.ts new file mode 100644 index 000000000..c16eb1755 --- /dev/null +++ b/web/src/pages/dataset/dataset/use-save-meta.ts @@ -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({} 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, + 'showSetMetaModal' +>; diff --git a/web/src/pages/dataset/sidebar/index.tsx b/web/src/pages/dataset/sidebar/index.tsx index 6b443a37b..09c4875ea 100644 --- a/web/src/pages/dataset/sidebar/index.tsx +++ b/web/src/pages/dataset/sidebar/index.tsx @@ -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 (