mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-21 05:16:54 +08:00
Feature: Implement metadata functionality (#12049)
### What problem does this PR solve? Feature: Implement metadata functionality ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -691,7 +691,7 @@ const DynamicForm = {
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
submit: form.handleSubmit,
|
||||
submit: form.handleSubmit(onSubmit),
|
||||
getValues: form.getValues,
|
||||
reset: (values?: T) => {
|
||||
if (values) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { X } from 'lucide-react';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
import {
|
||||
HoverCard,
|
||||
@ -57,14 +57,15 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
|
||||
<HoverCard key={tag}>
|
||||
<HoverCardContent side="top">{tag}</HoverCardContent>
|
||||
<HoverCardTrigger asChild>
|
||||
<div className="w-fit flex items-center justify-center gap-2 border-dashed border px-2 py-1 rounded-sm bg-bg-card">
|
||||
<div className="w-fit flex items-center justify-center gap-2 border border-border-button px-2 py-1 rounded-sm bg-bg-card">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="max-w-80 overflow-hidden text-ellipsis">
|
||||
{tag}
|
||||
</div>
|
||||
{!disabled && (
|
||||
<X
|
||||
className="w-4 h-4 text-muted-foreground hover:text-primary"
|
||||
<Trash2
|
||||
size={14}
|
||||
className="text-text-secondary hover:text-state-error"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
handleClose(tag);
|
||||
@ -80,10 +81,6 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
|
||||
|
||||
const tagChild = value?.map(forMap);
|
||||
|
||||
const tagPlusStyle: React.CSSProperties = {
|
||||
borderStyle: 'dashed',
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{inputVisible && (
|
||||
@ -102,15 +99,14 @@ const EditTag = React.forwardRef<HTMLDivElement, EditTagsProps>(
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-2 py-1">
|
||||
<div className="flex gap-2 py-1 flex-wrap">
|
||||
{Array.isArray(tagChild) && tagChild.length > 0 && <>{tagChild}</>}
|
||||
{!inputVisible && !disabled && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-fit flex items-center justify-center gap-2 bg-bg-card border-dashed border"
|
||||
className="w-fit flex items-center justify-center gap-2 bg-bg-card border-border-button border"
|
||||
onClick={showInput}
|
||||
disabled={disabled}
|
||||
style={tagPlusStyle}
|
||||
>
|
||||
<PlusOutlined />
|
||||
</Button>
|
||||
|
||||
@ -175,6 +175,21 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
|
||||
parserRequired: 'Chunk method is required',
|
||||
},
|
||||
knowledgeDetails: {
|
||||
metadata: {
|
||||
changesAffectNewParses: 'Changes affect new parses only.',
|
||||
editMetadataForDataset: 'View and edit metadata for ',
|
||||
restrictDefinedValues: 'Restrict to defined values',
|
||||
metadataGenerationSettings: 'Metadata generation settings',
|
||||
manageMetadataForDataset: 'Manage metadata for this dataset',
|
||||
manageMetadata: 'Manage metadata',
|
||||
metadata: 'Metadata',
|
||||
values: 'Values',
|
||||
action: 'Action',
|
||||
field: 'Field',
|
||||
description: 'Description',
|
||||
fieldName: 'Field name',
|
||||
editMetadata: 'Edit metadata',
|
||||
},
|
||||
localUpload: 'Local upload',
|
||||
fileSize: 'File size',
|
||||
fileType: 'File type',
|
||||
@ -348,6 +363,8 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
|
||||
reRankModelWaring: 'Re-rank model is very time consuming.',
|
||||
},
|
||||
knowledgeConfiguration: {
|
||||
settings: 'Settings',
|
||||
autoMetadata: 'Auto metadata',
|
||||
mineruOptions: 'MinerU Options',
|
||||
mineruParseMethod: 'Parse Method',
|
||||
mineruParseMethodTip:
|
||||
|
||||
@ -267,6 +267,8 @@ export default {
|
||||
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
|
||||
},
|
||||
knowledgeConfiguration: {
|
||||
settings: '设置',
|
||||
autoMetadata: '自动元数据',
|
||||
mineruOptions: 'MinerU 选项',
|
||||
mineruParseMethod: '解析方法',
|
||||
mineruParseMethodTip:
|
||||
|
||||
370
web/src/pages/dataset/components/metedata/hook.ts
Normal file
370
web/src/pages/dataset/components/metedata/hook.ts
Normal file
@ -0,0 +1,370 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useSetDocumentMeta } from '@/hooks/use-document-request';
|
||||
import {
|
||||
getMetaDataService,
|
||||
updateMetaData,
|
||||
} from '@/services/knowledge-service';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import {
|
||||
IMetaDataReturnJSONSettings,
|
||||
IMetaDataReturnJSONType,
|
||||
IMetaDataReturnType,
|
||||
IMetaDataTableData,
|
||||
MetadataOperations,
|
||||
ShowManageMetadataModalProps,
|
||||
} from './interface';
|
||||
export enum MetadataType {
|
||||
Manage = 1,
|
||||
UpdateSingle = 2,
|
||||
Setting = 3,
|
||||
}
|
||||
export const util = {
|
||||
changeToMetaDataTableData(data: IMetaDataReturnType): IMetaDataTableData[] {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
const values = value.map(([v]) => v.toString());
|
||||
console.log('values', values);
|
||||
return {
|
||||
field: key,
|
||||
description: '',
|
||||
values: values,
|
||||
} as IMetaDataTableData;
|
||||
});
|
||||
},
|
||||
|
||||
JSONToMetaDataTableData(
|
||||
data: Record<string, string | string[]>,
|
||||
): IMetaDataTableData[] {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
return {
|
||||
field: key,
|
||||
description: '',
|
||||
values: value,
|
||||
} as IMetaDataTableData;
|
||||
});
|
||||
},
|
||||
|
||||
tableDataToMetaDataJSON(data: IMetaDataTableData[]): IMetaDataReturnJSONType {
|
||||
return data.reduce<IMetaDataReturnJSONType>((pre, cur) => {
|
||||
pre[cur.field] = cur.values;
|
||||
return pre;
|
||||
}, {});
|
||||
},
|
||||
|
||||
tableDataToMetaDataSettingJSON(
|
||||
data: IMetaDataTableData[],
|
||||
): IMetaDataReturnJSONSettings {
|
||||
return data.map((item) => {
|
||||
return {
|
||||
key: item.field,
|
||||
description: item.description,
|
||||
enum: item.values,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
metaDataSettingJSONToMetaDataTableData(
|
||||
data: IMetaDataReturnJSONSettings,
|
||||
): IMetaDataTableData[] {
|
||||
return data.map((item) => {
|
||||
return {
|
||||
field: item.key,
|
||||
description: item.description,
|
||||
values: item.enum,
|
||||
restrictDefinedValues: !!item.enum?.length,
|
||||
} as IMetaDataTableData;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const useMetadataOperations = () => {
|
||||
const [operations, setOperations] = useState<MetadataOperations>({
|
||||
deletes: [],
|
||||
updates: [],
|
||||
});
|
||||
|
||||
const addDeleteRow = useCallback((key: string) => {
|
||||
setOperations((prev) => ({
|
||||
...prev,
|
||||
deletes: [...prev.deletes, { key }],
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const addDeleteValue = useCallback((key: string, value: string) => {
|
||||
setOperations((prev) => ({
|
||||
...prev,
|
||||
deletes: [...prev.deletes, { key, value }],
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const addUpdateValue = useCallback(
|
||||
(key: string, value: string | string[]) => {
|
||||
setOperations((prev) => ({
|
||||
...prev,
|
||||
updates: [...prev.updates, { key, value }],
|
||||
}));
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const resetOperations = useCallback(() => {
|
||||
setOperations({
|
||||
deletes: [],
|
||||
updates: [],
|
||||
});
|
||||
}, []);
|
||||
|
||||
return {
|
||||
operations,
|
||||
addDeleteRow,
|
||||
addDeleteValue,
|
||||
addUpdateValue,
|
||||
resetOperations,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchMetaDataManageData = (
|
||||
type: MetadataType = MetadataType.Manage,
|
||||
) => {
|
||||
const { id } = useParams();
|
||||
// const [data, setData] = useState<IMetaDataTableData[]>([]);
|
||||
// const [loading, setLoading] = useState(false);
|
||||
// const fetchData = useCallback(async (): Promise<IMetaDataTableData[]> => {
|
||||
// setLoading(true);
|
||||
// const { data } = await getMetaDataService({
|
||||
// kb_id: id as string,
|
||||
// });
|
||||
// setLoading(false);
|
||||
// if (data?.data?.summary) {
|
||||
// return util.changeToMetaDataTableData(data.data.summary);
|
||||
// }
|
||||
// return [];
|
||||
// }, [id]);
|
||||
// useEffect(() => {
|
||||
// if (type === MetadataType.Manage) {
|
||||
// fetchData()
|
||||
// .then((res) => {
|
||||
// setData(res);
|
||||
// })
|
||||
// .catch((res) => {
|
||||
// console.error(res);
|
||||
// });
|
||||
// }
|
||||
// }, [type, fetchData]);
|
||||
|
||||
const {
|
||||
data,
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<IMetaDataTableData[]>({
|
||||
queryKey: ['fetchMetaData', id],
|
||||
enabled: !!id && type === MetadataType.Manage,
|
||||
initialData: [],
|
||||
gcTime: 1000,
|
||||
queryFn: async () => {
|
||||
const { data } = await getMetaDataService({
|
||||
kb_id: id as string,
|
||||
});
|
||||
if (data?.data?.summary) {
|
||||
return util.changeToMetaDataTableData(data.data.summary);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
});
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
refetch,
|
||||
};
|
||||
};
|
||||
|
||||
export const useManageMetaDataModal = (
|
||||
metaData: IMetaDataTableData[] = [],
|
||||
type: MetadataType = MetadataType.Manage,
|
||||
otherData?: Record<string, any>,
|
||||
) => {
|
||||
const { id } = useParams();
|
||||
const { t } = useTranslation();
|
||||
const { data, loading } = useFetchMetaDataManageData(type);
|
||||
|
||||
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
||||
|
||||
const { operations, addDeleteRow, addDeleteValue, addUpdateValue } =
|
||||
useMetadataOperations();
|
||||
|
||||
const { setDocumentMeta } = useSetDocumentMeta();
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setTableData(data);
|
||||
} else {
|
||||
setTableData([]);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metaData) {
|
||||
setTableData(metaData);
|
||||
} else {
|
||||
setTableData([]);
|
||||
}
|
||||
}, [metaData]);
|
||||
|
||||
const handleDeleteSingleValue = useCallback(
|
||||
(field: string, value: string) => {
|
||||
addDeleteValue(field, value);
|
||||
|
||||
setTableData((prevTableData) => {
|
||||
const newTableData = prevTableData.map((item) => {
|
||||
if (item.field === field) {
|
||||
return {
|
||||
...item,
|
||||
values: item.values.filter((v) => v !== value),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
// console.log('newTableData', newTableData, prevTableData);
|
||||
return newTableData;
|
||||
});
|
||||
},
|
||||
[addDeleteValue],
|
||||
);
|
||||
|
||||
const handleDeleteSingleRow = useCallback(
|
||||
(field: string) => {
|
||||
addDeleteRow(field);
|
||||
setTableData((prevTableData) => {
|
||||
const newTableData = prevTableData.filter(
|
||||
(item) => item.field !== field,
|
||||
);
|
||||
// console.log('newTableData', newTableData, prevTableData);
|
||||
return newTableData;
|
||||
});
|
||||
},
|
||||
[addDeleteRow],
|
||||
);
|
||||
|
||||
const handleSaveManage = useCallback(
|
||||
async (callback: () => void) => {
|
||||
const { data: res } = await updateMetaData({
|
||||
kb_id: id as string,
|
||||
data: operations,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
message.success(t('message.success'));
|
||||
callback();
|
||||
}
|
||||
},
|
||||
[operations, id, t],
|
||||
);
|
||||
|
||||
const handleSaveUpdateSingle = useCallback(
|
||||
async (callback: () => void) => {
|
||||
const reqData = util.tableDataToMetaDataJSON(tableData);
|
||||
if (otherData?.id) {
|
||||
const ret = await setDocumentMeta({
|
||||
documentId: otherData?.id,
|
||||
meta: JSON.stringify(reqData),
|
||||
});
|
||||
if (ret === 0) {
|
||||
// message.success(t('message.success'));
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
[tableData, otherData, setDocumentMeta],
|
||||
);
|
||||
|
||||
const handleSaveSettings = useCallback(
|
||||
async (callback: () => void) => {
|
||||
const data = util.tableDataToMetaDataSettingJSON(tableData);
|
||||
callback();
|
||||
|
||||
return data;
|
||||
},
|
||||
[tableData],
|
||||
);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async ({ callback }: { callback: () => void }) => {
|
||||
switch (type) {
|
||||
case MetadataType.UpdateSingle:
|
||||
handleSaveUpdateSingle(callback);
|
||||
break;
|
||||
case MetadataType.Manage:
|
||||
handleSaveManage(callback);
|
||||
break;
|
||||
case MetadataType.Setting:
|
||||
return handleSaveSettings(callback);
|
||||
default:
|
||||
handleSaveManage(callback);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[handleSaveManage, type, handleSaveUpdateSingle, handleSaveSettings],
|
||||
);
|
||||
|
||||
return {
|
||||
tableData,
|
||||
setTableData,
|
||||
handleDeleteSingleValue,
|
||||
handleDeleteSingleRow,
|
||||
loading,
|
||||
handleSave,
|
||||
addUpdateValue,
|
||||
addDeleteValue,
|
||||
};
|
||||
};
|
||||
|
||||
export const useManageMetadata = () => {
|
||||
const [tableData, setTableData] = useState<IMetaDataTableData[]>([]);
|
||||
const [config, setConfig] = useState<ShowManageMetadataModalProps>(
|
||||
{} as ShowManageMetadataModalProps,
|
||||
);
|
||||
const {
|
||||
visible: manageMetadataVisible,
|
||||
showModal,
|
||||
hideModal: hideManageMetadataModal,
|
||||
} = useSetModalState();
|
||||
const showManageMetadataModal = useCallback(
|
||||
(config?: ShowManageMetadataModalProps) => {
|
||||
const { metadata } = config || {};
|
||||
if (metadata) {
|
||||
// const dataTemp = Object.entries(metadata).map(([key, value]) => {
|
||||
// return {
|
||||
// field: key,
|
||||
// description: '',
|
||||
// values: Array.isArray(value) ? value : [value],
|
||||
// } as IMetaDataTableData;
|
||||
// });
|
||||
setTableData(metadata);
|
||||
console.log('metadata-2', metadata);
|
||||
}
|
||||
console.log('metadata-3', metadata);
|
||||
if (config) {
|
||||
setConfig(config);
|
||||
}
|
||||
showModal();
|
||||
},
|
||||
[showModal],
|
||||
);
|
||||
return {
|
||||
manageMetadataVisible,
|
||||
showManageMetadataModal,
|
||||
hideManageMetadataModal,
|
||||
tableData,
|
||||
config,
|
||||
};
|
||||
};
|
||||
|
||||
export const useManageValues = () => {
|
||||
const [updateValues, setUpdateValues] = useState<{
|
||||
field: string;
|
||||
values: string[];
|
||||
} | null>(null);
|
||||
return { updateValues, setUpdateValues };
|
||||
};
|
||||
80
web/src/pages/dataset/components/metedata/interface.ts
Normal file
80
web/src/pages/dataset/components/metedata/interface.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MetadataType } from './hook';
|
||||
export type IMetaDataReturnType = Record<string, Array<Array<string | number>>>;
|
||||
export type IMetaDataReturnJSONType = Record<
|
||||
string,
|
||||
Array<string | number> | string
|
||||
>;
|
||||
|
||||
export interface IMetaDataReturnJSONSettingItem {
|
||||
key: string;
|
||||
description?: string;
|
||||
enum?: string[];
|
||||
}
|
||||
export type IMetaDataReturnJSONSettings = Array<IMetaDataReturnJSONSettingItem>;
|
||||
|
||||
export type IMetaDataTableData = {
|
||||
field: string;
|
||||
description: string;
|
||||
restrictDefinedValues?: boolean;
|
||||
values: string[];
|
||||
};
|
||||
|
||||
export type IManageModalProps = {
|
||||
title: ReactNode;
|
||||
isShowDescription?: boolean;
|
||||
isDeleteSingleValue?: boolean;
|
||||
visible: boolean;
|
||||
hideModal: () => void;
|
||||
tableData?: IMetaDataTableData[];
|
||||
isCanAdd: boolean;
|
||||
type: MetadataType;
|
||||
otherData?: Record<string, any>;
|
||||
isEditField?: boolean;
|
||||
isAddValue?: boolean;
|
||||
isShowValueSwitch?: boolean;
|
||||
isVerticalShowValue?: boolean;
|
||||
success?: (data: any) => void;
|
||||
};
|
||||
|
||||
export interface IManageValuesProps {
|
||||
title: ReactNode;
|
||||
visible: boolean;
|
||||
isEditField?: boolean;
|
||||
isAddValue?: boolean;
|
||||
isShowDescription?: boolean;
|
||||
isShowValueSwitch?: boolean;
|
||||
isVerticalShowValue?: boolean;
|
||||
data: IMetaDataTableData;
|
||||
hideModal: () => void;
|
||||
onSave: (data: IMetaDataTableData) => void;
|
||||
addUpdateValue: (key: string, value: string | string[]) => void;
|
||||
addDeleteValue: (key: string, value: string) => void;
|
||||
}
|
||||
|
||||
interface DeleteOperation {
|
||||
key: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
interface UpdateOperation {
|
||||
key: string;
|
||||
value: string | string[];
|
||||
}
|
||||
|
||||
export interface MetadataOperations {
|
||||
deletes: DeleteOperation[];
|
||||
updates: UpdateOperation[];
|
||||
}
|
||||
export interface ShowManageMetadataModalOptions {
|
||||
title?: ReactNode | string;
|
||||
}
|
||||
export type ShowManageMetadataModalProps = Partial<IManageModalProps> & {
|
||||
metadata?: IMetaDataTableData[];
|
||||
isCanAdd: boolean;
|
||||
type: MetadataType;
|
||||
record?: Record<string, any>;
|
||||
options?: ShowManageMetadataModalOptions;
|
||||
title?: ReactNode | string;
|
||||
isDeleteSingleValue?: boolean;
|
||||
};
|
||||
373
web/src/pages/dataset/components/metedata/manage-modal.tsx
Normal file
373
web/src/pages/dataset/components/metedata/manage-modal.tsx
Normal file
@ -0,0 +1,373 @@
|
||||
import {
|
||||
ConfirmDeleteDialog,
|
||||
ConfirmDeleteDialogNode,
|
||||
} from '@/components/confirm-delete-dialog';
|
||||
import { EmptyType } from '@/components/empty/constant';
|
||||
import Empty from '@/components/empty/empty';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import {
|
||||
ColumnDef,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from '@tanstack/react-table';
|
||||
import { Plus, Settings, Trash2 } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useManageMetaDataModal } from './hook';
|
||||
import { IManageModalProps, IMetaDataTableData } from './interface';
|
||||
import { ManageValuesModal } from './manage-values-modal';
|
||||
export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
const {
|
||||
title,
|
||||
visible,
|
||||
hideModal,
|
||||
isDeleteSingleValue,
|
||||
tableData: originalTableData,
|
||||
isCanAdd,
|
||||
type: metadataType,
|
||||
otherData,
|
||||
isEditField,
|
||||
isAddValue,
|
||||
isShowDescription = false,
|
||||
isShowValueSwitch = false,
|
||||
isVerticalShowValue = true,
|
||||
success,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
const [valueData, setValueData] = useState<IMetaDataTableData>({
|
||||
field: '',
|
||||
description: '',
|
||||
values: [],
|
||||
});
|
||||
|
||||
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
||||
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||
visible: false,
|
||||
title: '',
|
||||
name: '',
|
||||
warnText: '',
|
||||
onOk: () => {},
|
||||
onCancel: () => {},
|
||||
});
|
||||
|
||||
const {
|
||||
tableData,
|
||||
setTableData,
|
||||
handleDeleteSingleValue,
|
||||
handleDeleteSingleRow,
|
||||
handleSave,
|
||||
addUpdateValue,
|
||||
addDeleteValue,
|
||||
} = useManageMetaDataModal(originalTableData, metadataType, otherData);
|
||||
|
||||
const {
|
||||
visible: manageValuesVisible,
|
||||
showModal: showManageValuesModal,
|
||||
hideModal: hideManageValuesModal,
|
||||
} = useSetModalState();
|
||||
const hideDeleteModal = () => {
|
||||
setDeleteDialogContent({
|
||||
visible: false,
|
||||
title: '',
|
||||
name: '',
|
||||
warnText: '',
|
||||
onOk: () => {},
|
||||
onCancel: () => {},
|
||||
});
|
||||
};
|
||||
const handAddValueRow = () => {
|
||||
setValueData({
|
||||
field: '',
|
||||
description: '',
|
||||
values: [],
|
||||
});
|
||||
setCurrentValueIndex(tableData.length || 0);
|
||||
showManageValuesModal();
|
||||
};
|
||||
const handleEditValueRow = useCallback(
|
||||
(data: IMetaDataTableData, index: number) => {
|
||||
setCurrentValueIndex(index);
|
||||
setValueData(data);
|
||||
showManageValuesModal();
|
||||
},
|
||||
[showManageValuesModal],
|
||||
);
|
||||
|
||||
const columns: ColumnDef<IMetaDataTableData>[] = useMemo(() => {
|
||||
const cols: ColumnDef<IMetaDataTableData>[] = [
|
||||
{
|
||||
accessorKey: 'field',
|
||||
header: () => <span>{t('knowledgeDetails.metadata.field')}</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="text-sm text-accent-primary">
|
||||
{row.getValue('field')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="text-sm truncate max-w-32">
|
||||
{row.getValue('description')}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'values',
|
||||
header: () => <span>{t('knowledgeDetails.metadata.values')}</span>,
|
||||
cell: ({ row }) => {
|
||||
const values = row.getValue('values') as Array<string>;
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{values.length > 0 &&
|
||||
values
|
||||
.filter((value: string, index: number) => index < 2)
|
||||
?.map((value: string) => {
|
||||
return (
|
||||
<Button
|
||||
key={value}
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
aria-label="Edit"
|
||||
>
|
||||
<div className="flex gap-1 items-center">
|
||||
<div className="text-sm truncate max-w-24">
|
||||
{value}
|
||||
</div>
|
||||
{isDeleteSingleValue && (
|
||||
<Button
|
||||
variant={'delete'}
|
||||
className="p-0 bg-transparent"
|
||||
onClick={() => {
|
||||
handleDeleteSingleValue(
|
||||
row.getValue('field'),
|
||||
value,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{values.length > 2 && (
|
||||
<div className="text-text-secondary self-end">...</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'action',
|
||||
header: () => <span>{t('knowledgeDetails.metadata.action')}</span>,
|
||||
meta: {
|
||||
cellClassName: 'w-12',
|
||||
},
|
||||
cell: ({ row }) => (
|
||||
<div className=" flex opacity-0 group-hover:opacity-100 gap-2">
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="bg-transparent px-1 py-0"
|
||||
onClick={() => {
|
||||
handleEditValueRow(row.original, row.index);
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
</Button>
|
||||
<Button
|
||||
variant={'delete'}
|
||||
className="p-0 bg-transparent"
|
||||
onClick={() => {
|
||||
setDeleteDialogContent({
|
||||
visible: true,
|
||||
title:
|
||||
t('common.delete') +
|
||||
' ' +
|
||||
t('knowledgeDetails.metadata.metadata'),
|
||||
name: row.getValue('field'),
|
||||
warnText: t('knowledgeDetails.metadata.deleteWarn'),
|
||||
onOk: () => {
|
||||
hideDeleteModal();
|
||||
handleDeleteSingleRow(row.getValue('field'));
|
||||
},
|
||||
onCancel: () => {
|
||||
hideDeleteModal();
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
if (!isShowDescription) {
|
||||
cols.splice(1, 1);
|
||||
}
|
||||
return cols;
|
||||
}, [
|
||||
handleDeleteSingleRow,
|
||||
t,
|
||||
handleDeleteSingleValue,
|
||||
isShowDescription,
|
||||
isDeleteSingleValue,
|
||||
handleEditValueRow,
|
||||
]);
|
||||
|
||||
const table = useReactTable({
|
||||
data: tableData as IMetaDataTableData[],
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
manualPagination: true,
|
||||
});
|
||||
|
||||
const handleSaveValues = (data: IMetaDataTableData) => {
|
||||
setTableData((prev) => {
|
||||
if (currentValueIndex >= prev.length) {
|
||||
return [...prev, data];
|
||||
} else {
|
||||
return prev.map((item, index) => {
|
||||
if (index === currentValueIndex) {
|
||||
return data;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title={title}
|
||||
open={visible}
|
||||
onCancel={hideModal}
|
||||
maskClosable={false}
|
||||
okText={t('common.save')}
|
||||
onOk={async () => {
|
||||
const res = await handleSave({ callback: hideModal });
|
||||
console.log('data', res);
|
||||
success?.(res);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>{t('knowledgeDetails.metadata.metadata')}</div>
|
||||
{isCanAdd && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
onClick={handAddValueRow}
|
||||
>
|
||||
<Plus />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<Table rootClassName="max-h-[800px]">
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<TableHead key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext(),
|
||||
)}
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody className="relative">
|
||||
{table.getRowModel().rows?.length ? (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
className="group"
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
<Empty type={EmptyType.Data} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</Modal>
|
||||
{manageValuesVisible && (
|
||||
<ManageValuesModal
|
||||
title={<div>{t('knowledgeDetails.metadata.editMetadata')}</div>}
|
||||
visible={manageValuesVisible}
|
||||
hideModal={hideManageValuesModal}
|
||||
data={valueData}
|
||||
onSave={handleSaveValues}
|
||||
addUpdateValue={addUpdateValue}
|
||||
addDeleteValue={addDeleteValue}
|
||||
isEditField={isEditField || isCanAdd}
|
||||
isAddValue={isAddValue || isCanAdd}
|
||||
isShowDescription={isShowDescription}
|
||||
isShowValueSwitch={isShowValueSwitch}
|
||||
isVerticalShowValue={isVerticalShowValue}
|
||||
// handleDeleteSingleValue={handleDeleteSingleValue}
|
||||
// handleDeleteSingleRow={handleDeleteSingleRow}
|
||||
/>
|
||||
)}
|
||||
|
||||
{deleteDialogContent.visible && (
|
||||
<ConfirmDeleteDialog
|
||||
open={deleteDialogContent.visible}
|
||||
onCancel={deleteDialogContent.onCancel}
|
||||
onOk={deleteDialogContent.onOk}
|
||||
title={deleteDialogContent.title}
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode
|
||||
name={deleteDialogContent.name}
|
||||
warnText={deleteDialogContent.warnText}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,250 @@
|
||||
import EditTag from '@/components/edit-tag';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { IManageValuesProps, IMetaDataTableData } from './interface';
|
||||
|
||||
// Create a separate input component, wrapped with memo to avoid unnecessary re-renders
|
||||
const ValueInputItem = memo(
|
||||
({
|
||||
item,
|
||||
index,
|
||||
onValueChange,
|
||||
onDelete,
|
||||
onBlur,
|
||||
}: {
|
||||
item: string;
|
||||
index: number;
|
||||
onValueChange: (index: number, value: string) => void;
|
||||
onDelete: (index: number) => void;
|
||||
onBlur: (index: number) => void;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
key={`value-item-${index}`}
|
||||
className="flex items-center gap-2.5 w-full"
|
||||
>
|
||||
<div className="flex-1 w-full">
|
||||
<Input
|
||||
value={item}
|
||||
onChange={(e) => onValueChange(index, e.target.value)}
|
||||
onBlur={() => onBlur(index)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="delete"
|
||||
className="border border-border-button px-1 h-6 w-6 rounded-sm"
|
||||
onClick={() => onDelete(index)}
|
||||
>
|
||||
<Trash2 size={14} className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||
const {
|
||||
title,
|
||||
data,
|
||||
isEditField,
|
||||
visible,
|
||||
isAddValue,
|
||||
isShowDescription,
|
||||
isShowValueSwitch,
|
||||
isVerticalShowValue,
|
||||
hideModal,
|
||||
onSave,
|
||||
addUpdateValue,
|
||||
addDeleteValue,
|
||||
} = props;
|
||||
const [metaData, setMetaData] = useState(data);
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Use functional update to avoid closure issues
|
||||
const handleChange = useCallback((field: string, value: any) => {
|
||||
setMetaData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
// Maintain separate state for each input box
|
||||
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
|
||||
|
||||
useEffect(() => {
|
||||
setTempValues([...data.values]);
|
||||
setMetaData(data);
|
||||
}, [data]);
|
||||
|
||||
const handleHideModal = useCallback(() => {
|
||||
hideModal();
|
||||
setMetaData({} as IMetaDataTableData);
|
||||
}, [hideModal]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
|
||||
const newMetaData = { ...metaData, values: [] };
|
||||
onSave(newMetaData);
|
||||
} else {
|
||||
onSave(metaData);
|
||||
}
|
||||
handleHideModal();
|
||||
}, [metaData, onSave, handleHideModal, isShowValueSwitch]);
|
||||
|
||||
// Handle value changes, only update temporary state
|
||||
const handleValueChange = useCallback((index: number, value: string) => {
|
||||
setTempValues((prev) => {
|
||||
const newValues = [...prev];
|
||||
newValues[index] = value;
|
||||
|
||||
return newValues;
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Handle blur event, synchronize to main state
|
||||
const handleValueBlur = useCallback(() => {
|
||||
addUpdateValue(metaData.field, [...tempValues]);
|
||||
handleChange('values', [...tempValues]);
|
||||
}, [handleChange, tempValues, metaData, addUpdateValue]);
|
||||
|
||||
// Handle delete operation
|
||||
const handleDelete = useCallback(
|
||||
(index: number) => {
|
||||
setTempValues((prev) => {
|
||||
const newTempValues = [...prev];
|
||||
addDeleteValue(metaData.field, newTempValues[index]);
|
||||
newTempValues.splice(index, 1);
|
||||
return newTempValues;
|
||||
});
|
||||
|
||||
// Synchronize to main state
|
||||
setMetaData((prev) => {
|
||||
const newMetaDataValues = [...prev.values];
|
||||
newMetaDataValues.splice(index, 1);
|
||||
return {
|
||||
...prev,
|
||||
values: newMetaDataValues,
|
||||
};
|
||||
});
|
||||
},
|
||||
[addDeleteValue, metaData],
|
||||
);
|
||||
|
||||
// Handle adding new value
|
||||
const handleAddValue = useCallback(() => {
|
||||
setTempValues((prev) => [...prev, '']);
|
||||
|
||||
// Synchronize to main state
|
||||
setMetaData((prev) => ({
|
||||
...prev,
|
||||
values: [...prev.values, ''],
|
||||
}));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={title}
|
||||
open={visible}
|
||||
onCancel={handleHideModal}
|
||||
className="!w-[460px]"
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
maskClosable={false}
|
||||
footer={null}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
{!isEditField && (
|
||||
<div className="text-base p-5 border border-border-button rounded-lg">
|
||||
{metaData.field}
|
||||
</div>
|
||||
)}
|
||||
{isEditField && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>{t('knowledgeDetails.metadata.fieldName')}</div>
|
||||
<div>
|
||||
<Input
|
||||
value={metaData.field}
|
||||
onChange={(e) => {
|
||||
handleChange('field', e.target?.value || '');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isShowDescription && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>{t('knowledgeDetails.metadata.description')}</div>
|
||||
<div>
|
||||
<Textarea
|
||||
value={metaData.description}
|
||||
onChange={(e) => {
|
||||
handleChange('description', e.target?.value || '');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isShowValueSwitch && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>{t('knowledgeDetails.metadata.restrictDefinedValues')}</div>
|
||||
<div>
|
||||
<Switch
|
||||
checked={metaData.restrictDefinedValues || false}
|
||||
onCheckedChange={(checked) =>
|
||||
handleChange('restrictDefinedValues', checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{metaData.restrictDefinedValues && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>{t('knowledgeDetails.metadata.values')}</div>
|
||||
{isAddValue && isVerticalShowValue && (
|
||||
<div>
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
onClick={handleAddValue}
|
||||
>
|
||||
<Plus />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isVerticalShowValue && (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
{tempValues?.map((item, index) => {
|
||||
return (
|
||||
<ValueInputItem
|
||||
key={`value-item-${index}`}
|
||||
item={item}
|
||||
index={index}
|
||||
onValueChange={handleValueChange}
|
||||
onDelete={handleDelete}
|
||||
onBlur={handleValueBlur}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{!isVerticalShowValue && (
|
||||
<EditTag
|
||||
value={metaData.values}
|
||||
onChange={(value) => handleChange('values', value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@ -1,5 +1,11 @@
|
||||
import {
|
||||
FormFieldConfig,
|
||||
FormFieldType,
|
||||
RenderField,
|
||||
} from '@/components/dynamic-form';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
@ -13,8 +19,19 @@ import { Switch } from '@/components/ui/switch';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { t } from 'i18next';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { FieldValues, useFormContext } from 'react-hook-form';
|
||||
import {
|
||||
ControllerRenderProps,
|
||||
FieldValues,
|
||||
useFormContext,
|
||||
} from 'react-hook-form';
|
||||
import {
|
||||
MetadataType,
|
||||
useManageMetadata,
|
||||
util,
|
||||
} from '../../components/metedata/hook';
|
||||
import { ManageMetadataModal } from '../../components/metedata/manage-modal';
|
||||
import {
|
||||
useHandleKbEmbedding,
|
||||
useHasParsedDocument,
|
||||
@ -304,3 +321,84 @@ export function OverlappedPercent() {
|
||||
></SliderInputFormField>
|
||||
);
|
||||
}
|
||||
|
||||
export function AutoMetadata() {
|
||||
// get metadata field
|
||||
const form = useFormContext();
|
||||
const {
|
||||
manageMetadataVisible,
|
||||
showManageMetadataModal,
|
||||
hideManageMetadataModal,
|
||||
tableData,
|
||||
config: metadataConfig,
|
||||
} = useManageMetadata();
|
||||
const autoMetadataField: FormFieldConfig = {
|
||||
name: 'parser_config.enable_metadata',
|
||||
label: t('knowledgeConfiguration.autoMetadata'),
|
||||
type: FormFieldType.Custom,
|
||||
horizontal: true,
|
||||
defaultValue: true,
|
||||
|
||||
render: (fieldProps: ControllerRenderProps) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
const metadata = form.getValues('parser_config.metadata');
|
||||
const tableMetaData =
|
||||
util.metaDataSettingJSONToMetaDataTableData(metadata);
|
||||
showManageMetadataModal({
|
||||
metadata: tableMetaData,
|
||||
isCanAdd: true,
|
||||
type: MetadataType.Setting,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings />
|
||||
{t('knowledgeConfiguration.settings')}
|
||||
</div>
|
||||
</Button>
|
||||
<Switch
|
||||
checked={fieldProps.value}
|
||||
onCheckedChange={fieldProps.onChange}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<RenderField field={autoMetadataField} />
|
||||
{manageMetadataVisible && (
|
||||
<ManageMetadataModal
|
||||
title={
|
||||
metadataConfig.title || (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-base font-normal">
|
||||
{t('knowledgeDetails.metadata.metadataGenerationSettings')}
|
||||
</div>
|
||||
<div className="text-sm text-text-secondary">
|
||||
{t('knowledgeDetails.metadata.changesAffectNewParses')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
visible={manageMetadataVisible}
|
||||
hideModal={hideManageMetadataModal}
|
||||
// selectedRowKeys={selectedRowKeys}
|
||||
tableData={tableData}
|
||||
isCanAdd={metadataConfig.isCanAdd}
|
||||
isDeleteSingleValue={metadataConfig.isDeleteSingleValue}
|
||||
type={metadataConfig.type}
|
||||
otherData={metadataConfig.record}
|
||||
isShowDescription={true}
|
||||
isShowValueSwitch={true}
|
||||
isVerticalShowValue={false}
|
||||
success={(data) => {
|
||||
form.setValue('parser_config.metadata', data || []);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,7 +11,11 @@ import {
|
||||
ConfigurationFormContainer,
|
||||
MainContainer,
|
||||
} from '../configuration-form-container';
|
||||
import { EnableTocToggle, OverlappedPercent } from './common-item';
|
||||
import {
|
||||
AutoMetadata,
|
||||
EnableTocToggle,
|
||||
OverlappedPercent,
|
||||
} from './common-item';
|
||||
|
||||
export function NaiveConfiguration() {
|
||||
return (
|
||||
@ -22,6 +26,7 @@ export function NaiveConfiguration() {
|
||||
<DelimiterFormField></DelimiterFormField>
|
||||
<ChildrenDelimiterForm />
|
||||
<EnableTocToggle />
|
||||
<AutoMetadata />
|
||||
<OverlappedPercent />
|
||||
</ConfigurationFormContainer>
|
||||
<ConfigurationFormContainer>
|
||||
|
||||
@ -83,6 +83,18 @@ export const formSchema = z
|
||||
path: ['entity_types'],
|
||||
},
|
||||
),
|
||||
metadata: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
key: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
enum: z.array(z.string().optional()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
)
|
||||
.optional(),
|
||||
enable_metadata: z.boolean().optional(),
|
||||
})
|
||||
.optional(),
|
||||
pagerank: z.number(),
|
||||
|
||||
@ -90,6 +90,8 @@ export default function DatasetSettings() {
|
||||
entity_types: initialEntityTypes,
|
||||
method: MethodValue.Light,
|
||||
},
|
||||
metadata: [],
|
||||
enable_metadata: false,
|
||||
},
|
||||
pipeline_id: '',
|
||||
parseType: 1,
|
||||
@ -238,11 +240,8 @@ export default function DatasetSettings() {
|
||||
}
|
||||
return connector;
|
||||
});
|
||||
console.log('🚀 ~ DatasetSettings ~ connectors:', connectors);
|
||||
setSourceData(connectors as IDataSourceNodeProps[]);
|
||||
form.setValue('connectors', connectors || []);
|
||||
// form.setValue('pipeline_name', data.name || '');
|
||||
// form.setValue('pipeline_avatar', data.avatar || '');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -58,7 +58,11 @@ export function SavingButton() {
|
||||
onClick={() => {
|
||||
(async () => {
|
||||
try {
|
||||
let beValid = await form.formControl.trigger();
|
||||
let beValid = await form.trigger();
|
||||
if (!beValid) {
|
||||
const errors = form.formState.errors;
|
||||
console.error('Validation errors:', errors);
|
||||
}
|
||||
if (beValid) {
|
||||
form.handleSubmit(async (values) => {
|
||||
console.log('saveKnowledgeConfiguration: ', values);
|
||||
|
||||
@ -32,19 +32,20 @@ import { getExtension } from '@/utils/document-util';
|
||||
import { t } from 'i18next';
|
||||
import { pick } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
import { ShowManageMetadataModalProps } from '../components/metedata/interface';
|
||||
import ProcessLogModal from '../process-log-modal';
|
||||
import { useShowLog } from './hooks';
|
||||
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 type DatasetTableProps = Pick<
|
||||
ReturnType<typeof useFetchDocumentList>,
|
||||
'documents' | 'setPagination' | 'pagination' | 'loading'
|
||||
> &
|
||||
Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'>;
|
||||
Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'> & {
|
||||
showManageMetadataModal: (config: ShowManageMetadataModalProps) => void;
|
||||
};
|
||||
|
||||
export function DatasetTable({
|
||||
documents,
|
||||
@ -52,6 +53,7 @@ export function DatasetTable({
|
||||
setPagination,
|
||||
rowSelection,
|
||||
setRowSelection,
|
||||
showManageMetadataModal,
|
||||
}: DatasetTableProps) {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||
@ -78,20 +80,19 @@ export function DatasetTable({
|
||||
initialName,
|
||||
} = useRenameDocument();
|
||||
|
||||
const {
|
||||
showSetMetaModal,
|
||||
hideSetMetaModal,
|
||||
setMetaVisible,
|
||||
setMetaLoading,
|
||||
onSetMetaModalOk,
|
||||
metaRecord,
|
||||
} = useSaveMeta();
|
||||
// const {
|
||||
// hideSetMetaModal,
|
||||
// setMetaVisible,
|
||||
// setMetaLoading,
|
||||
// onSetMetaModalOk,
|
||||
// metaRecord,
|
||||
// } = useSaveMeta();
|
||||
const { showLog, logInfo, logVisible, hideLog } = useShowLog(documents);
|
||||
|
||||
const columns = useDatasetTableColumns({
|
||||
showChangeParserModal,
|
||||
showRenameModal,
|
||||
showSetMetaModal,
|
||||
showManageMetadataModal,
|
||||
showLog,
|
||||
});
|
||||
|
||||
@ -207,14 +208,14 @@ export function DatasetTable({
|
||||
></RenameDialog>
|
||||
)}
|
||||
|
||||
{setMetaVisible && (
|
||||
{/* {setMetaVisible && (
|
||||
<SetMetaDialog
|
||||
hideModal={hideSetMetaModal}
|
||||
loading={setMetaLoading}
|
||||
onOk={onSetMetaModalOk}
|
||||
initialMetaData={metaRecord.meta_fields}
|
||||
></SetMetaDialog>
|
||||
)}
|
||||
)} */}
|
||||
{logVisible && (
|
||||
<ProcessLogModal
|
||||
title={t('knowledgeDetails.fileLogs')}
|
||||
|
||||
@ -13,9 +13,11 @@ import {
|
||||
import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection';
|
||||
import { useFetchDocumentList } from '@/hooks/use-document-request';
|
||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { Pen, Upload } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useManageMetadata } from '../components/metedata/hook';
|
||||
import { ManageMetadataModal } from '../components/metedata/manage-modal';
|
||||
import { DatasetTable } from './dataset-table';
|
||||
import Generate from './generate-button/generate';
|
||||
import { useBulkOperateDataset } from './use-bulk-operate-dataset';
|
||||
@ -61,6 +63,14 @@ export default function Dataset() {
|
||||
showCreateModal,
|
||||
} = useCreateEmptyDocument();
|
||||
|
||||
const {
|
||||
manageMetadataVisible,
|
||||
showManageMetadataModal,
|
||||
hideManageMetadataModal,
|
||||
tableData,
|
||||
config: metadataConfig,
|
||||
} = useManageMetadata();
|
||||
|
||||
const { rowSelection, rowSelectionIsEmpty, setRowSelection, selectedCount } =
|
||||
useRowSelection();
|
||||
|
||||
@ -91,6 +101,18 @@ export default function Dataset() {
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
preChildren={
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
onClick={() => showManageMetadataModal()}
|
||||
>
|
||||
<div className="flex gap-1 items-center">
|
||||
<Pen size={14} />
|
||||
{t('knowledgeDetails.metadata.metadata')}
|
||||
</div>
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@ -119,6 +141,7 @@ export default function Dataset() {
|
||||
setPagination={setPagination}
|
||||
rowSelection={rowSelection}
|
||||
setRowSelection={setRowSelection}
|
||||
showManageMetadataModal={showManageMetadataModal}
|
||||
loading={loading}
|
||||
></DatasetTable>
|
||||
{documentUploadVisible && (
|
||||
@ -137,6 +160,30 @@ export default function Dataset() {
|
||||
title={'File Name'}
|
||||
></RenameDialog>
|
||||
)}
|
||||
{manageMetadataVisible && (
|
||||
<ManageMetadataModal
|
||||
title={
|
||||
metadataConfig.title || (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-base font-normal">
|
||||
{t('knowledgeDetails.metadata.manageMetadata')}
|
||||
</div>
|
||||
<div className="text-sm text-text-secondary">
|
||||
{t('knowledgeDetails.metadata.manageMetadataForDataset')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
visible={manageMetadataVisible}
|
||||
hideModal={hideManageMetadataModal}
|
||||
// selectedRowKeys={selectedRowKeys}
|
||||
tableData={tableData}
|
||||
isCanAdd={metadataConfig.isCanAdd}
|
||||
isDeleteSingleValue={metadataConfig.isDeleteSingleValue}
|
||||
type={metadataConfig.type}
|
||||
otherData={metadataConfig.record}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -16,20 +16,23 @@ import { formatDate } from '@/utils/date';
|
||||
import { ColumnDef } from '@tanstack/table-core';
|
||||
import { ArrowUpDown, MonitorUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MetadataType, util } from '../components/metedata/hook';
|
||||
import { ShowManageMetadataModalProps } from '../components/metedata/interface';
|
||||
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 &
|
||||
UseRenameDocumentShowType &
|
||||
UseSaveMetaShowType & { showLog: (record: IDocumentInfo) => void };
|
||||
UseRenameDocumentShowType & {
|
||||
showLog: (record: IDocumentInfo) => void;
|
||||
showManageMetadataModal: (config: ShowManageMetadataModalProps) => void;
|
||||
};
|
||||
|
||||
export function useDatasetTableColumns({
|
||||
showChangeParserModal,
|
||||
showRenameModal,
|
||||
showSetMetaModal,
|
||||
showManageMetadataModal,
|
||||
showLog,
|
||||
}: UseDatasetTableColumnsType) {
|
||||
const { t } = useTranslation('translation', {
|
||||
@ -174,7 +177,26 @@ export function useDatasetTableColumns({
|
||||
<ParsingStatusCell
|
||||
record={row.original}
|
||||
showChangeParserModal={showChangeParserModal}
|
||||
showSetMetaModal={showSetMetaModal}
|
||||
showSetMetaModal={(row) =>
|
||||
showManageMetadataModal({
|
||||
metadata: util.JSONToMetaDataTableData(row.meta_fields || {}),
|
||||
isCanAdd: true,
|
||||
type: MetadataType.UpdateSingle,
|
||||
record: row,
|
||||
title: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="text-base font-normal">
|
||||
{t('metadata.editMetadata')}
|
||||
</div>
|
||||
<div className="text-sm text-text-secondary">
|
||||
{t('metadata.editMetadataForDataset')}
|
||||
{row.name}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
isDeleteSingleValue: true,
|
||||
})
|
||||
}
|
||||
showLog={showLog}
|
||||
></ParsingStatusCell>
|
||||
);
|
||||
|
||||
@ -16,18 +16,10 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { LanguageTranslationMap } from '@/constants/common';
|
||||
import { FormLayout } from '@/constants/form';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
@ -42,13 +34,6 @@ const FormId = 'dataset-creating-form';
|
||||
export function InputForm({ onOk }: IModalProps<any>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const languageOptions = useMemo(() => {
|
||||
return Object.keys(LanguageTranslationMap).map((x) => ({
|
||||
label: x,
|
||||
value: x,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const FormSchema = z
|
||||
.object({
|
||||
name: z
|
||||
@ -66,7 +51,6 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
||||
.trim(),
|
||||
parser_id: z.string().optional(),
|
||||
pipeline_id: z.string().optional(),
|
||||
language: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
// When parseType === 1, parser_id is required
|
||||
@ -80,8 +64,6 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
||||
path: ['parser_id'],
|
||||
});
|
||||
}
|
||||
|
||||
console.log('form-data', data);
|
||||
// When parseType === 1, pipline_id required
|
||||
if (data.parseType === 2 && !data.pipeline_id) {
|
||||
ctx.addIssue({
|
||||
@ -99,7 +81,6 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
||||
parseType: 1,
|
||||
parser_id: '',
|
||||
embd_id: '',
|
||||
language: 'English',
|
||||
},
|
||||
});
|
||||
|
||||
@ -147,33 +128,6 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="language"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.language')}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={t('common.languagePlaceholder')}
|
||||
/>
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{languageOptions.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<EmbeddingModelItem line={2} isEdit={false} />
|
||||
<ParseTypeItem />
|
||||
{parseType === 1 && <ChunkMethodItem></ChunkMethodItem>}
|
||||
|
||||
@ -215,6 +215,10 @@ const methods = {
|
||||
url: check_embedding,
|
||||
method: 'post',
|
||||
},
|
||||
// getMetaData: {
|
||||
// url: getMetaData,
|
||||
// method: 'get',
|
||||
// },
|
||||
};
|
||||
|
||||
const kbService = registerServer<keyof typeof methods>(methods, request);
|
||||
@ -251,6 +255,11 @@ export const listDocument = (
|
||||
export const documentFilter = (kb_id: string) =>
|
||||
request.post(api.get_dataset_filter, { kb_id });
|
||||
|
||||
export const getMetaDataService = ({ kb_id }: { kb_id: string }) =>
|
||||
request.post(api.getMetaData, { data: { kb_id } });
|
||||
export const updateMetaData = ({ kb_id, data }: { kb_id: string; data: any }) =>
|
||||
request.post(api.updateMetaData, { data: { kb_id, data } });
|
||||
|
||||
export const listDataPipelineLogDocument = (
|
||||
params?: IFetchKnowledgeListRequestParams,
|
||||
body?: IFetchDocumentListRequestBody,
|
||||
|
||||
@ -77,6 +77,8 @@ export default {
|
||||
unbindPipelineTask: ({ kb_id, type }: { kb_id: string; type: string }) =>
|
||||
`${api_host}/kb/unbind_task?kb_id=${kb_id}&pipeline_task_type=${type}`,
|
||||
pipelineRerun: `${api_host}/canvas/rerun`,
|
||||
getMetaData: `${api_host}/document/metadata/summary`,
|
||||
updateMetaData: `${api_host}/document/metadata/update`,
|
||||
|
||||
// tags
|
||||
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
|
||||
|
||||
Reference in New Issue
Block a user