mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-01-19 11:45:10 +08:00
Feat: metadata settings in KB. (#12662)
### What problem does this PR solve? #11910 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -68,6 +68,8 @@ export interface ParserConfig {
|
||||
topn_tags?: number;
|
||||
graphrag?: { use_graphrag?: boolean };
|
||||
enable_metadata?: boolean;
|
||||
metadata?: any;
|
||||
built_in_metadata?: Array<{ key: string; type: string }>;
|
||||
}
|
||||
|
||||
export interface IKnowledgeFileParserConfig {
|
||||
|
||||
@ -14,11 +14,14 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
import {
|
||||
IBuiltInMetadataItem,
|
||||
IMetaDataJsonSchemaProperty,
|
||||
IMetaDataReturnJSONSettings,
|
||||
IMetaDataReturnJSONType,
|
||||
IMetaDataReturnType,
|
||||
IMetaDataTableData,
|
||||
MetadataOperations,
|
||||
MetadataValueType,
|
||||
ShowManageMetadataModalProps,
|
||||
} from '../interface';
|
||||
export enum MetadataType {
|
||||
@ -71,6 +74,90 @@ export const MetadataDeleteMap = (
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const DEFAULT_VALUE_TYPE: MetadataValueType = 'string';
|
||||
const VALUE_TYPES_WITH_ENUM = new Set<MetadataValueType>(['enum']);
|
||||
const VALUE_TYPE_LABELS: Record<MetadataValueType, string> = {
|
||||
string: 'String',
|
||||
bool: 'Bool',
|
||||
enum: 'Enum',
|
||||
time: 'Time',
|
||||
int: 'Int',
|
||||
float: 'Float',
|
||||
};
|
||||
|
||||
export const metadataValueTypeOptions = Object.entries(VALUE_TYPE_LABELS).map(
|
||||
([value, label]) => ({ label, value }),
|
||||
);
|
||||
|
||||
export const getMetadataValueTypeLabel = (value?: MetadataValueType) =>
|
||||
VALUE_TYPE_LABELS[value || DEFAULT_VALUE_TYPE] || VALUE_TYPE_LABELS.string;
|
||||
|
||||
export const isMetadataValueTypeWithEnum = (value?: MetadataValueType) =>
|
||||
VALUE_TYPES_WITH_ENUM.has(value || DEFAULT_VALUE_TYPE);
|
||||
|
||||
const schemaToValueType = (
|
||||
property?: IMetaDataJsonSchemaProperty,
|
||||
): MetadataValueType => {
|
||||
if (!property) return DEFAULT_VALUE_TYPE;
|
||||
if (
|
||||
property.type === 'array' &&
|
||||
property.items?.type === 'string' &&
|
||||
(property.items.enum?.length || 0) > 0
|
||||
) {
|
||||
return 'enum';
|
||||
}
|
||||
if (property.type === 'boolean') return 'bool';
|
||||
if (property.type === 'integer') return 'int';
|
||||
if (property.type === 'number') return 'float';
|
||||
if (property.type === 'string' && property.format) {
|
||||
return 'time';
|
||||
}
|
||||
if (property.type === 'string' && property.enum?.length) {
|
||||
return 'enum';
|
||||
}
|
||||
return DEFAULT_VALUE_TYPE;
|
||||
};
|
||||
|
||||
const valueTypeToSchema = (
|
||||
valueType: MetadataValueType,
|
||||
description: string,
|
||||
values: string[],
|
||||
): IMetaDataJsonSchemaProperty => {
|
||||
const schema: IMetaDataJsonSchemaProperty = {
|
||||
description: description || '',
|
||||
};
|
||||
|
||||
switch (valueType) {
|
||||
case 'bool':
|
||||
schema.type = 'boolean';
|
||||
return schema;
|
||||
case 'int':
|
||||
schema.type = 'integer';
|
||||
return schema;
|
||||
case 'float':
|
||||
schema.type = 'number';
|
||||
return schema;
|
||||
case 'time':
|
||||
schema.type = 'string';
|
||||
schema.format = 'date-time';
|
||||
return schema;
|
||||
case 'enum':
|
||||
schema.type = 'string';
|
||||
if (values?.length) {
|
||||
schema.enum = values;
|
||||
}
|
||||
return schema;
|
||||
case 'string':
|
||||
default:
|
||||
schema.type = 'string';
|
||||
if (values?.length) {
|
||||
schema.enum = values;
|
||||
}
|
||||
return schema;
|
||||
}
|
||||
};
|
||||
|
||||
export const util = {
|
||||
changeToMetaDataTableData(data: IMetaDataReturnType): IMetaDataTableData[] {
|
||||
return Object.entries(data).map(([key, value]) => {
|
||||
@ -117,25 +204,58 @@ export const util = {
|
||||
tableDataToMetaDataSettingJSON(
|
||||
data: IMetaDataTableData[],
|
||||
): IMetaDataReturnJSONSettings {
|
||||
return data.map((item) => {
|
||||
return {
|
||||
key: item.field,
|
||||
description: item.description,
|
||||
enum: item.values,
|
||||
};
|
||||
});
|
||||
const properties = data.reduce<Record<string, IMetaDataJsonSchemaProperty>>(
|
||||
(acc, item) => {
|
||||
if (!item.field) {
|
||||
return acc;
|
||||
}
|
||||
const valueType = item.valueType || DEFAULT_VALUE_TYPE;
|
||||
const values =
|
||||
isMetadataValueTypeWithEnum(valueType) && item.restrictDefinedValues
|
||||
? item.values
|
||||
: [];
|
||||
acc[item.field] = valueTypeToSchema(
|
||||
valueType,
|
||||
item.description,
|
||||
values,
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
return {
|
||||
type: 'object',
|
||||
properties,
|
||||
additionalProperties: false,
|
||||
};
|
||||
},
|
||||
|
||||
metaDataSettingJSONToMetaDataTableData(
|
||||
data: IMetaDataReturnJSONSettings,
|
||||
): IMetaDataTableData[] {
|
||||
if (!Array.isArray(data)) return [];
|
||||
return data.map((item) => {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => {
|
||||
return {
|
||||
field: item.key,
|
||||
description: item.description,
|
||||
values: item.enum || [],
|
||||
restrictDefinedValues: !!item.enum?.length,
|
||||
valueType: DEFAULT_VALUE_TYPE,
|
||||
} as IMetaDataTableData;
|
||||
});
|
||||
}
|
||||
const properties = data.properties || {};
|
||||
return Object.entries(properties).map(([key, property]) => {
|
||||
const valueType = schemaToValueType(property);
|
||||
const values = property.enum || property.items?.enum || [];
|
||||
return {
|
||||
field: item.key,
|
||||
description: item.description,
|
||||
values: item.enum,
|
||||
restrictDefinedValues: !!item.enum?.length,
|
||||
field: key,
|
||||
description: property.description || '',
|
||||
values,
|
||||
restrictDefinedValues: !!values.length,
|
||||
valueType,
|
||||
} as IMetaDataTableData;
|
||||
});
|
||||
},
|
||||
@ -384,21 +504,15 @@ export const useManageMetaDataModal = (
|
||||
);
|
||||
|
||||
const handleSaveSettings = useCallback(
|
||||
async (callback: () => void) => {
|
||||
async (callback: () => void, builtInMetadata?: IBuiltInMetadataItem[]) => {
|
||||
const data = util.tableDataToMetaDataSettingJSON(tableData);
|
||||
const { data: res } = await kbService.kbUpdateMetaData({
|
||||
kb_id: id,
|
||||
callback?.();
|
||||
return {
|
||||
metadata: data,
|
||||
enable_metadata: true,
|
||||
});
|
||||
if (res.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
callback?.();
|
||||
}
|
||||
|
||||
return data;
|
||||
builtInMetadata: builtInMetadata || [],
|
||||
};
|
||||
},
|
||||
[tableData, id, t],
|
||||
[tableData],
|
||||
);
|
||||
|
||||
const handleSaveSingleFileSettings = useCallback(
|
||||
@ -421,7 +535,13 @@ export const useManageMetaDataModal = (
|
||||
);
|
||||
|
||||
const handleSave = useCallback(
|
||||
async ({ callback }: { callback: () => void }) => {
|
||||
async ({
|
||||
callback,
|
||||
builtInMetadata,
|
||||
}: {
|
||||
callback: () => void;
|
||||
builtInMetadata?: string[];
|
||||
}) => {
|
||||
switch (type) {
|
||||
case MetadataType.UpdateSingle:
|
||||
handleSaveUpdateSingle(callback);
|
||||
@ -430,7 +550,7 @@ export const useManageMetaDataModal = (
|
||||
handleSaveManage(callback);
|
||||
break;
|
||||
case MetadataType.Setting:
|
||||
return handleSaveSettings(callback);
|
||||
return handleSaveSettings(callback, builtInMetadata);
|
||||
case MetadataType.SingleFileSetting:
|
||||
return handleSaveSingleFileSettings(callback);
|
||||
default:
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MetadataDeleteMap, MetadataType } from '../hooks/use-manage-modal';
|
||||
import {
|
||||
isMetadataValueTypeWithEnum,
|
||||
MetadataDeleteMap,
|
||||
MetadataType,
|
||||
} from '../hooks/use-manage-modal';
|
||||
import { IManageValuesProps, IMetaDataTableData } from '../interface';
|
||||
|
||||
export const useManageValues = (props: IManageValuesProps) => {
|
||||
const {
|
||||
data,
|
||||
|
||||
isShowValueSwitch,
|
||||
hideModal,
|
||||
onSave,
|
||||
addUpdateValue,
|
||||
@ -16,7 +19,10 @@ export const useManageValues = (props: IManageValuesProps) => {
|
||||
type,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
const [metaData, setMetaData] = useState(data);
|
||||
const [metaData, setMetaData] = useState<IMetaDataTableData>({
|
||||
...data,
|
||||
valueType: data.valueType || 'string',
|
||||
});
|
||||
const [valueError, setValueError] = useState<Record<string, string>>({
|
||||
field: '',
|
||||
values: '',
|
||||
@ -61,10 +67,28 @@ export const useManageValues = (props: IManageValuesProps) => {
|
||||
};
|
||||
});
|
||||
}
|
||||
setMetaData((prev) => ({
|
||||
...prev,
|
||||
[field]: value,
|
||||
}));
|
||||
setMetaData((prev) => {
|
||||
if (field === 'valueType') {
|
||||
const nextValueType = (value ||
|
||||
'string') as IMetaDataTableData['valueType'];
|
||||
const supportsEnum = isMetadataValueTypeWithEnum(nextValueType);
|
||||
if (!supportsEnum) {
|
||||
setTempValues([]);
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
valueType: nextValueType,
|
||||
values: supportsEnum ? prev.values : [],
|
||||
restrictDefinedValues: supportsEnum
|
||||
? prev.restrictDefinedValues || nextValueType === 'enum'
|
||||
: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
[field]: value,
|
||||
};
|
||||
});
|
||||
},
|
||||
[existsKeys, type, t],
|
||||
);
|
||||
@ -74,7 +98,10 @@ export const useManageValues = (props: IManageValuesProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
setTempValues([...data.values]);
|
||||
setMetaData(data);
|
||||
setMetaData({
|
||||
...data,
|
||||
valueType: data.valueType || 'string',
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
const handleHideModal = useCallback(() => {
|
||||
@ -86,14 +113,19 @@ export const useManageValues = (props: IManageValuesProps) => {
|
||||
if (type === MetadataType.Setting && valueError.field) {
|
||||
return;
|
||||
}
|
||||
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
|
||||
const newMetaData = { ...metaData, values: [] };
|
||||
onSave(newMetaData);
|
||||
} else {
|
||||
onSave(metaData);
|
||||
const supportsEnum = isMetadataValueTypeWithEnum(metaData.valueType);
|
||||
if (!supportsEnum) {
|
||||
onSave({
|
||||
...metaData,
|
||||
values: [],
|
||||
restrictDefinedValues: false,
|
||||
});
|
||||
handleHideModal();
|
||||
return;
|
||||
}
|
||||
onSave(metaData);
|
||||
handleHideModal();
|
||||
}, [metaData, onSave, handleHideModal, isShowValueSwitch, type, valueError]);
|
||||
}, [metaData, onSave, handleHideModal, type, valueError]);
|
||||
|
||||
// Handle value changes, only update temporary state
|
||||
const handleValueChange = useCallback(
|
||||
|
||||
@ -11,13 +11,44 @@ export interface IMetaDataReturnJSONSettingItem {
|
||||
description?: string;
|
||||
enum?: string[];
|
||||
}
|
||||
export type IMetaDataReturnJSONSettings = Array<IMetaDataReturnJSONSettingItem>;
|
||||
export interface IMetaDataJsonSchemaProperty {
|
||||
type?: string;
|
||||
description?: string;
|
||||
enum?: string[];
|
||||
items?: {
|
||||
type?: string;
|
||||
enum?: string[];
|
||||
};
|
||||
format?: string;
|
||||
}
|
||||
export interface IMetaDataJsonSchema {
|
||||
type?: 'object';
|
||||
properties?: Record<string, IMetaDataJsonSchemaProperty>;
|
||||
additionalProperties?: boolean;
|
||||
}
|
||||
export type IMetaDataReturnJSONSettings =
|
||||
| IMetaDataJsonSchema
|
||||
| Array<IMetaDataReturnJSONSettingItem>;
|
||||
|
||||
export type MetadataValueType =
|
||||
| 'string'
|
||||
| 'bool'
|
||||
| 'enum'
|
||||
| 'time'
|
||||
| 'int'
|
||||
| 'float';
|
||||
|
||||
export type IMetaDataTableData = {
|
||||
field: string;
|
||||
description: string;
|
||||
restrictDefinedValues?: boolean;
|
||||
values: string[];
|
||||
valueType?: MetadataValueType;
|
||||
};
|
||||
|
||||
export type IBuiltInMetadataItem = {
|
||||
key: string;
|
||||
type: MetadataValueType;
|
||||
};
|
||||
|
||||
export type IManageModalProps = {
|
||||
@ -34,6 +65,7 @@ export type IManageModalProps = {
|
||||
isAddValue?: boolean;
|
||||
isShowValueSwitch?: boolean;
|
||||
isVerticalShowValue?: boolean;
|
||||
builtInMetadata?: IBuiltInMetadataItem[];
|
||||
success?: (data: any) => void;
|
||||
};
|
||||
|
||||
@ -45,6 +77,7 @@ export interface IManageValuesProps {
|
||||
isAddValue?: boolean;
|
||||
isShowDescription?: boolean;
|
||||
isShowValueSwitch?: boolean;
|
||||
isShowType?: boolean;
|
||||
isVerticalShowValue?: boolean;
|
||||
data: IMetaDataTableData;
|
||||
type: MetadataType;
|
||||
@ -81,6 +114,7 @@ export type ShowManageMetadataModalProps = Partial<IManageModalProps> & {
|
||||
isCanAdd: boolean;
|
||||
type: MetadataType;
|
||||
record?: Record<string, any>;
|
||||
builtInMetadata?: IBuiltInMetadataItem[];
|
||||
options?: ShowManageMetadataModalOptions;
|
||||
title?: ReactNode | string;
|
||||
isDeleteSingleValue?: boolean;
|
||||
|
||||
@ -7,6 +7,7 @@ import Empty from '@/components/empty/empty';
|
||||
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 {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -15,6 +16,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { Routes } from '@/routes';
|
||||
import {
|
||||
@ -39,11 +41,19 @@ import { useHandleMenuClick } from '../../sidebar/hooks';
|
||||
import {
|
||||
MetadataDeleteMap,
|
||||
MetadataType,
|
||||
getMetadataValueTypeLabel,
|
||||
isMetadataValueTypeWithEnum,
|
||||
useManageMetaDataModal,
|
||||
} from './hooks/use-manage-modal';
|
||||
import { IManageModalProps, IMetaDataTableData } from './interface';
|
||||
import {
|
||||
IBuiltInMetadataItem,
|
||||
IManageModalProps,
|
||||
IMetaDataTableData,
|
||||
} from './interface';
|
||||
import { ManageValuesModal } from './manage-values-modal';
|
||||
|
||||
type MetadataSettingsTab = 'generation' | 'built-in';
|
||||
|
||||
export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
const {
|
||||
title,
|
||||
@ -59,6 +69,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
isShowDescription = false,
|
||||
isShowValueSwitch = false,
|
||||
isVerticalShowValue = true,
|
||||
builtInMetadata,
|
||||
success,
|
||||
} = props;
|
||||
const { t } = useTranslation();
|
||||
@ -66,10 +77,15 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
field: '',
|
||||
description: '',
|
||||
values: [],
|
||||
valueType: 'string',
|
||||
});
|
||||
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState<MetadataSettingsTab>('generation');
|
||||
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
||||
const [builtInSelection, setBuiltInSelection] = useState<
|
||||
IBuiltInMetadataItem[]
|
||||
>([]);
|
||||
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||
visible: false,
|
||||
title: '',
|
||||
@ -111,6 +127,62 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
});
|
||||
};
|
||||
|
||||
const isSettingsMode =
|
||||
metadataType === MetadataType.Setting ||
|
||||
metadataType === MetadataType.SingleFileSetting;
|
||||
const showTypeColumn = isSettingsMode;
|
||||
const builtInRows = useMemo(
|
||||
() => [
|
||||
{
|
||||
field: 'update_time',
|
||||
valueType: 'time',
|
||||
description: t('knowledgeConfiguration.builtIn'),
|
||||
},
|
||||
{
|
||||
field: 'file_name',
|
||||
valueType: 'string',
|
||||
description: t('knowledgeConfiguration.builtIn'),
|
||||
},
|
||||
],
|
||||
[t],
|
||||
);
|
||||
const builtInTypeByKey = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
builtInRows.map((row) => [
|
||||
row.field,
|
||||
row.valueType as IBuiltInMetadataItem['type'],
|
||||
]),
|
||||
),
|
||||
[builtInRows],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
setBuiltInSelection(
|
||||
(builtInMetadata || []).map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return {
|
||||
key: item,
|
||||
type: builtInTypeByKey.get(item) || 'string',
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: item.key,
|
||||
type: (item.type ||
|
||||
builtInTypeByKey.get(item.key) ||
|
||||
'string') as IBuiltInMetadataItem['type'],
|
||||
};
|
||||
}),
|
||||
);
|
||||
setActiveTab('generation');
|
||||
}, [builtInMetadata, builtInTypeByKey, visible]);
|
||||
|
||||
const builtInSelectionKeys = useMemo(
|
||||
() => new Set(builtInSelection.map((item) => item.key)),
|
||||
[builtInSelection],
|
||||
);
|
||||
|
||||
const handleEditValue = (field: string, value: string) => {
|
||||
setEditingValue({ field, value, newValue: value });
|
||||
};
|
||||
@ -141,6 +213,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
field: '',
|
||||
description: '',
|
||||
values: [],
|
||||
valueType: 'string',
|
||||
});
|
||||
setCurrentValueIndex(tableData.length || 0);
|
||||
showManageValuesModal();
|
||||
@ -165,6 +238,21 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
...(showTypeColumn
|
||||
? ([
|
||||
{
|
||||
accessorKey: 'valueType',
|
||||
header: () => <span>Type</span>,
|
||||
cell: ({ row }) => (
|
||||
<div className="text-sm">
|
||||
{getMetadataValueTypeLabel(
|
||||
row.original.valueType as IMetaDataTableData['valueType'],
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<IMetaDataTableData>[])
|
||||
: []),
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
|
||||
@ -196,8 +284,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const values = row.getValue('values') as Array<string>;
|
||||
const supportsEnum = isMetadataValueTypeWithEnum(
|
||||
row.original.valueType,
|
||||
);
|
||||
|
||||
if (!Array.isArray(values) || values.length === 0) {
|
||||
if (!supportsEnum || !Array.isArray(values) || values.length === 0) {
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
@ -342,7 +433,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
},
|
||||
];
|
||||
if (!isShowDescription) {
|
||||
cols.splice(1, 1);
|
||||
return cols.filter((col) => col.accessorKey !== 'description');
|
||||
}
|
||||
return cols;
|
||||
}, [
|
||||
@ -356,6 +447,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
expanded,
|
||||
editingValue,
|
||||
saveEditedValue,
|
||||
showTypeColumn,
|
||||
]);
|
||||
|
||||
const table = useReactTable({
|
||||
@ -393,7 +485,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
const mergedValues = [
|
||||
...new Set([...existingItem.values, ...item.values]),
|
||||
];
|
||||
fieldMap.set(item.field, { ...existingItem, values: mergedValues });
|
||||
fieldMap.set(item.field, {
|
||||
...existingItem,
|
||||
...item,
|
||||
values: mergedValues,
|
||||
});
|
||||
} else {
|
||||
fieldMap.set(item.field, item);
|
||||
}
|
||||
@ -407,13 +503,13 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
useEffect(() => {
|
||||
if (shouldSave) {
|
||||
const timer = setTimeout(() => {
|
||||
handleSave({ callback: () => {} });
|
||||
handleSave({ callback: () => {}, builtInMetadata: builtInSelection });
|
||||
setShouldSave(false);
|
||||
}, 0);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [tableData, shouldSave, handleSave]);
|
||||
}, [tableData, shouldSave, handleSave, builtInSelection]);
|
||||
|
||||
const existsKeys = useMemo(() => {
|
||||
return tableData.map((item) => item.field);
|
||||
@ -428,7 +524,10 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
maskClosable={false}
|
||||
okText={t('common.save')}
|
||||
onOk={async () => {
|
||||
const res = await handleSave({ callback: hideModal });
|
||||
const res = await handleSave({
|
||||
callback: hideModal,
|
||||
builtInMetadata: builtInSelection,
|
||||
});
|
||||
console.log('data', res);
|
||||
success?.(res);
|
||||
}}
|
||||
@ -449,7 +548,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
{t('knowledgeDetails.metadata.toMetadataSetting')}
|
||||
</Button>
|
||||
)}
|
||||
{isCanAdd && (
|
||||
{isCanAdd && activeTab !== 'built-in' && (
|
||||
<Button
|
||||
variant={'ghost'}
|
||||
className="border border-border-button"
|
||||
@ -460,53 +559,188 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
</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>
|
||||
{metadataType === MetadataType.Setting ? (
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => setActiveTab(v as MetadataSettingsTab)}
|
||||
>
|
||||
<TabsList className="w-fit">
|
||||
<TabsTrigger value="generation">Generation</TabsTrigger>
|
||||
<TabsTrigger value="built-in">
|
||||
{t('knowledgeConfiguration.builtIn')}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="generation">
|
||||
<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>
|
||||
</TabsContent>
|
||||
<TabsContent value="built-in">
|
||||
<Table rootClassName="max-h-[800px]">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
{t('knowledgeDetails.metadata.field')}
|
||||
</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>
|
||||
{t('knowledgeDetails.metadata.description')}
|
||||
</TableHead>
|
||||
<TableHead className="text-right">
|
||||
{t('knowledgeDetails.metadata.action')}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody className="relative">
|
||||
{builtInRows.map((row) => (
|
||||
<TableRow key={row.field}>
|
||||
<TableCell>
|
||||
<div className="text-sm text-accent-primary">
|
||||
{row.field}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-sm">
|
||||
{getMetadataValueTypeLabel(
|
||||
row.valueType as IMetaDataTableData['valueType'],
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-sm truncate max-w-32">
|
||||
{row.description}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Switch
|
||||
checked={builtInSelectionKeys.has(row.field)}
|
||||
onCheckedChange={(checked) => {
|
||||
setBuiltInSelection((prev) => {
|
||||
if (checked) {
|
||||
const nextType =
|
||||
row.valueType as IBuiltInMetadataItem['type'];
|
||||
if (
|
||||
prev.some(
|
||||
(item) => item.key === row.field,
|
||||
)
|
||||
) {
|
||||
return prev.map((item) =>
|
||||
item.key === row.field
|
||||
? { ...item, type: nextType }
|
||||
: item,
|
||||
);
|
||||
}
|
||||
return [
|
||||
...prev,
|
||||
{ key: row.field, type: nextType },
|
||||
];
|
||||
}
|
||||
return prev.filter(
|
||||
(item) => item.key !== row.field,
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
) : (
|
||||
<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>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={columns.length}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
<Empty type={EmptyType.Data} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
))}
|
||||
</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>
|
||||
{metadataType === MetadataType.Manage && (
|
||||
<div className=" absolute bottom-6 left-5 text-text-secondary text-sm">
|
||||
@ -537,6 +771,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
||||
isAddValue={isAddValue || isCanAdd}
|
||||
isShowDescription={isShowDescription}
|
||||
isShowValueSwitch={isShowValueSwitch}
|
||||
isShowType={isSettingsMode}
|
||||
isVerticalShowValue={isVerticalShowValue}
|
||||
// handleDeleteSingleValue={handleDeleteSingleValue}
|
||||
// handleDeleteSingleRow={handleDeleteSingleRow}
|
||||
|
||||
@ -7,11 +7,15 @@ import { Button } from '@/components/ui/button';
|
||||
import { FormLabel } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
isMetadataValueTypeWithEnum,
|
||||
metadataValueTypeOptions,
|
||||
} from './hooks/use-manage-modal';
|
||||
import { useManageValues } from './hooks/use-manage-values-modal';
|
||||
import { IManageValuesProps } from './interface';
|
||||
|
||||
@ -62,8 +66,8 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||
visible,
|
||||
isAddValue,
|
||||
isShowDescription,
|
||||
isShowValueSwitch,
|
||||
isVerticalShowValue,
|
||||
isShowType,
|
||||
} = props;
|
||||
const {
|
||||
metaData,
|
||||
@ -80,6 +84,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||
handleHideModal,
|
||||
} = useManageValues(props);
|
||||
const { t } = useTranslation();
|
||||
const canShowValues = isMetadataValueTypeWithEnum(metaData.valueType);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -115,6 +120,16 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isShowType && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div>Type</div>
|
||||
<RAGFlowSelect
|
||||
value={metaData.valueType || 'string'}
|
||||
options={metadataValueTypeOptions}
|
||||
onChange={(value) => handleChange('valueType', value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isShowDescription && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<FormLabel
|
||||
@ -133,26 +148,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{isShowValueSwitch && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<FormLabel
|
||||
className="text-text-primary text-base"
|
||||
tooltip={t('knowledgeDetails.metadata.restrictTDefinedValuesTip')}
|
||||
>
|
||||
{t('knowledgeDetails.metadata.restrictDefinedValues')}
|
||||
</FormLabel>
|
||||
<div>
|
||||
<Switch
|
||||
checked={metaData.restrictDefinedValues || false}
|
||||
onCheckedChange={(checked) =>
|
||||
handleChange('restrictDefinedValues', checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{((metaData.restrictDefinedValues && isShowValueSwitch) ||
|
||||
!isShowValueSwitch) && (
|
||||
{canShowValues && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>{t('knowledgeDetails.metadata.values')}</div>
|
||||
|
||||
@ -39,7 +39,10 @@ import {
|
||||
useManageMetadata,
|
||||
util,
|
||||
} from '../../components/metedata/hooks/use-manage-modal';
|
||||
import { IMetaDataReturnJSONSettings } from '../../components/metedata/interface';
|
||||
import {
|
||||
IBuiltInMetadataItem,
|
||||
IMetaDataReturnJSONSettings,
|
||||
} from '../../components/metedata/interface';
|
||||
import { ManageMetadataModal } from '../../components/metedata/manage-modal';
|
||||
import {
|
||||
useHandleKbEmbedding,
|
||||
@ -384,12 +387,14 @@ export function AutoMetadata({
|
||||
|
||||
const handleClickOpenMetadata = useCallback(() => {
|
||||
const metadata = form.getValues('parser_config.metadata');
|
||||
const builtInMetadata = form.getValues('parser_config.built_in_metadata');
|
||||
const tableMetaData = util.metaDataSettingJSONToMetaDataTableData(metadata);
|
||||
showManageMetadataModal({
|
||||
metadata: tableMetaData,
|
||||
isCanAdd: true,
|
||||
type: type,
|
||||
record: otherData,
|
||||
builtInMetadata,
|
||||
});
|
||||
}, [form, otherData, showManageMetadataModal, type]);
|
||||
|
||||
@ -429,8 +434,15 @@ export function AutoMetadata({
|
||||
),
|
||||
};
|
||||
|
||||
const handleSaveMetadata = (data?: IMetaDataReturnJSONSettings) => {
|
||||
form.setValue('parser_config.metadata', data || []);
|
||||
const handleSaveMetadata = (data?: {
|
||||
metadata?: IMetaDataReturnJSONSettings;
|
||||
builtInMetadata?: IBuiltInMetadataItem[];
|
||||
}) => {
|
||||
form.setValue('parser_config.metadata', data?.metadata || []);
|
||||
form.setValue(
|
||||
'parser_config.built_in_metadata',
|
||||
data?.builtInMetadata || [],
|
||||
);
|
||||
form.setValue('parser_config.enable_metadata', true);
|
||||
};
|
||||
return (
|
||||
@ -461,7 +473,11 @@ export function AutoMetadata({
|
||||
isShowDescription={true}
|
||||
isShowValueSwitch={true}
|
||||
isVerticalShowValue={false}
|
||||
success={(data?: IMetaDataReturnJSONSettings) => {
|
||||
builtInMetadata={metadataConfig.builtInMetadata}
|
||||
success={(data?: {
|
||||
metadata?: IMetaDataReturnJSONSettings;
|
||||
builtInMetadata?: IBuiltInMetadataItem[];
|
||||
}) => {
|
||||
handleSaveMetadata(data);
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -84,15 +84,13 @@ export const formSchema = z
|
||||
path: ['entity_types'],
|
||||
},
|
||||
),
|
||||
metadata: z
|
||||
metadata: z.any().optional(),
|
||||
built_in_metadata: z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
key: z.string().optional(),
|
||||
description: z.string().optional(),
|
||||
enum: z.array(z.string().optional()).optional(),
|
||||
})
|
||||
.optional(),
|
||||
z.object({
|
||||
key: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
enable_metadata: z.boolean().optional(),
|
||||
|
||||
@ -95,7 +95,12 @@ export default function DatasetSettings() {
|
||||
entity_types: initialEntityTypes,
|
||||
method: MethodValue.Light,
|
||||
},
|
||||
metadata: [],
|
||||
metadata: {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
additionalProperties: false,
|
||||
},
|
||||
built_in_metadata: [],
|
||||
enable_metadata: false,
|
||||
llm_id: '',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user