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)
This commit is contained in:
balibabu
2025-04-25 18:38:15 +08:00
committed by GitHub
parent 1662c7eda3
commit 3052006ba8
8 changed files with 282 additions and 29 deletions

View File

@ -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>
);
}

View File

@ -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>

View 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>
);
}

View File

@ -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>
);
},

View 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'
>;

View File

@ -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>