Feat: Improve metadata logic (#12730)

### What problem does this PR solve?

Feat: Improve metadata logic

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
chanx
2026-01-21 11:31:26 +08:00
committed by GitHub
parent bc7935d627
commit 5a7026cf55
16 changed files with 1318 additions and 665 deletions

View File

@ -1,5 +1,6 @@
import message from '@/components/ui/message';
import { useSetModalState } from '@/hooks/common-hooks';
import { useSelectedIds } from '@/hooks/logic-hooks/use-row-selection';
import {
DocumentApiAction,
useSetDocumentMeta,
@ -9,13 +10,13 @@ import kbService, {
updateMetaData,
} from '@/services/knowledge-service';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { RowSelectionState } from '@tanstack/react-table';
import { TFunction } from 'i18next';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router';
import {
IBuiltInMetadataItem,
IMetaDataJsonSchemaProperty,
IMetaDataReturnJSONSettings,
IMetaDataReturnJSONType,
IMetaDataReturnType,
@ -76,14 +77,16 @@ export const MetadataDeleteMap = (
};
const DEFAULT_VALUE_TYPE: MetadataValueType = 'string';
const VALUE_TYPES_WITH_ENUM = new Set<MetadataValueType>(['enum']);
// 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',
number: 'Number',
// bool: 'Bool',
// enum: 'Enum',
// 'list<string>': 'List<String>',
// int: 'Int',
// float: 'Float',
};
export const metadataValueTypeOptions = Object.entries(VALUE_TYPE_LABELS).map(
@ -93,82 +96,101 @@ export const metadataValueTypeOptions = Object.entries(VALUE_TYPE_LABELS).map(
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);
// 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 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 || '',
};
// 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;
}
};
// 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]) => {
const values = value.map(([v]) => v.toString());
console.log('values', values);
return {
field: key,
description: '',
values: values,
} as IMetaDataTableData;
});
const res = Object.entries(data).map(
([key, value]: [
string,
(
| { type: string; values: Array<Array<string | number>> }
| Array<Array<string | number>>
),
]) => {
if (Array.isArray(value)) {
const values = value.map(([v]) => v.toString());
return {
field: key,
valueType: DEFAULT_VALUE_TYPE,
description: '',
values: values,
} as IMetaDataTableData;
}
const { type, values } = value;
const valueArr = values.map(([v]) => v.toString());
return {
field: key,
valueType: type,
description: '',
values: valueArr,
} as IMetaDataTableData;
},
);
return res;
},
JSONToMetaDataTableData(
@ -204,31 +226,13 @@ export const util = {
tableDataToMetaDataSettingJSON(
data: IMetaDataTableData[],
): IMetaDataReturnJSONSettings {
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,
};
return data.map((item) => {
return {
key: item.field,
description: item.description,
enum: item.values,
};
});
},
metaDataSettingJSONToMetaDataTableData(
@ -248,7 +252,7 @@ export const util = {
}
const properties = data.properties || {};
return Object.entries(properties).map(([key, property]) => {
const valueType = schemaToValueType(property);
const valueType = 'string';
const values = property.enum || property.items?.enum || [];
return {
field: key,
@ -274,6 +278,13 @@ export const useMetadataOperations = () => {
}));
}, []);
const addDeleteBatch = useCallback((keys: string[]) => {
setOperations((prev) => ({
...prev,
deletes: [...prev.deletes, ...keys.map((key) => ({ key }))],
}));
}, []);
const addDeleteValue = useCallback((key: string, value: string) => {
setOperations((prev) => ({
...prev,
@ -281,15 +292,6 @@ export const useMetadataOperations = () => {
}));
}, []);
// const addUpdateValue = useCallback(
// (key: string, value: string | string[]) => {
// setOperations((prev) => ({
// ...prev,
// updates: [...prev.updates, { key, value }],
// }));
// },
// [],
// );
const addUpdateValue = useCallback(
(key: string, originalValue: string, newValue: string) => {
setOperations((prev) => {
@ -330,6 +332,7 @@ export const useMetadataOperations = () => {
return {
operations,
addDeleteBatch,
addDeleteRow,
addDeleteValue,
addUpdateValue,
@ -339,48 +342,29 @@ export const useMetadataOperations = () => {
export const useFetchMetaDataManageData = (
type: MetadataType = MetadataType.Manage,
documentIds?: string[],
) => {
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,
queryKey: ['fetchMetaData', id, documentIds],
enabled:
!!id &&
(type === MetadataType.Manage || type === MetadataType.UpdateSingle),
initialData: [],
gcTime: 1000,
queryFn: async () => {
const { data } = await getMetaDataService({
kb_id: id as string,
doc_ids: documentIds,
});
if (data?.data?.summary) {
return util.changeToMetaDataTableData(data.data.summary);
const res = util.changeToMetaDataTableData(data.data.summary);
return res;
}
return [];
},
@ -392,20 +376,23 @@ export const useFetchMetaDataManageData = (
};
};
const fetchTypeList = [MetadataType.Manage, MetadataType.UpdateSingle];
export const useManageMetaDataModal = (
metaData: IMetaDataTableData[] = [],
type: MetadataType = MetadataType.Manage,
otherData?: Record<string, any>,
documentIds?: string[],
) => {
const { id } = useParams();
const { t } = useTranslation();
const { data, loading } = useFetchMetaDataManageData(type);
const { data, loading } = useFetchMetaDataManageData(type, documentIds);
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
const queryClient = useQueryClient();
const {
operations,
addDeleteRow,
addDeleteBatch,
addDeleteValue,
addUpdateValue,
resetOperations,
@ -414,7 +401,7 @@ export const useManageMetaDataModal = (
const { setDocumentMeta } = useSetDocumentMeta();
useEffect(() => {
if (type === MetadataType.Manage) {
if (fetchTypeList.includes(type)) {
if (data) {
setTableData(data);
} else {
@ -424,7 +411,7 @@ export const useManageMetaDataModal = (
}, [data, type]);
useEffect(() => {
if (type !== MetadataType.Manage) {
if (!fetchTypeList.includes(type)) {
if (metaData) {
setTableData(metaData);
} else {
@ -447,7 +434,6 @@ export const useManageMetaDataModal = (
}
return item;
});
// console.log('newTableData', newTableData, prevTableData);
return newTableData;
});
},
@ -461,18 +447,31 @@ export const useManageMetaDataModal = (
const newTableData = prevTableData.filter(
(item) => item.field !== field,
);
// console.log('newTableData', newTableData, prevTableData);
return newTableData;
});
},
[addDeleteRow],
);
const handleDeleteBatchRow = useCallback(
(fields: string[]) => {
addDeleteBatch(fields);
setTableData((prevTableData) => {
const newTableData = prevTableData.filter(
(item) => !fields.includes(item.field),
);
return newTableData;
});
},
[addDeleteBatch],
);
const handleSaveManage = useCallback(
async (callback: () => void) => {
const { data: res } = await updateMetaData({
kb_id: id as string,
data: operations,
doc_ids: documentIds,
});
if (res.code === 0) {
queryClient.invalidateQueries({
@ -483,7 +482,7 @@ export const useManageMetaDataModal = (
callback();
}
},
[operations, id, t, queryClient, resetOperations],
[operations, id, t, queryClient, resetOperations, documentIds],
);
const handleSaveUpdateSingle = useCallback(
@ -506,13 +505,22 @@ export const useManageMetaDataModal = (
const handleSaveSettings = useCallback(
async (callback: () => void, builtInMetadata?: IBuiltInMetadataItem[]) => {
const data = util.tableDataToMetaDataSettingJSON(tableData);
callback?.();
const { data: res } = await kbService.kbUpdateMetaData({
kb_id: id,
metadata: data,
builtInMetadata: builtInMetadata || [],
});
if (res.code === 0) {
message.success(t('message.operated'));
callback?.();
}
// callback?.();
return {
metadata: data,
builtInMetadata: builtInMetadata || [],
};
},
[tableData],
[tableData, t, id],
);
const handleSaveSingleFileSettings = useCallback(
@ -540,17 +548,19 @@ export const useManageMetaDataModal = (
builtInMetadata,
}: {
callback: () => void;
builtInMetadata?: string[];
builtInMetadata?: IBuiltInMetadataItem[];
}) => {
switch (type) {
case MetadataType.UpdateSingle:
handleSaveUpdateSingle(callback);
// handleSaveUpdateSingle(callback);
handleSaveManage(callback);
break;
case MetadataType.Manage:
handleSaveManage(callback);
break;
case MetadataType.Setting:
return handleSaveSettings(callback, builtInMetadata);
case MetadataType.SingleFileSetting:
return handleSaveSingleFileSettings(callback);
default:
@ -561,7 +571,7 @@ export const useManageMetaDataModal = (
[
handleSaveManage,
type,
handleSaveUpdateSingle,
// handleSaveUpdateSingle,
handleSaveSettings,
handleSaveSingleFileSettings,
],
@ -572,6 +582,7 @@ export const useManageMetaDataModal = (
setTableData,
handleDeleteSingleValue,
handleDeleteSingleRow,
handleDeleteBatchRow,
loading,
handleSave,
addUpdateValue,
@ -593,17 +604,8 @@ export const useManageMetadata = () => {
(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);
}
@ -619,3 +621,35 @@ export const useManageMetadata = () => {
config,
};
};
export const useOperateData = ({
rowSelection,
list,
handleDeleteBatchRow,
}: {
rowSelection: RowSelectionState;
list: IMetaDataTableData[];
handleDeleteBatchRow: (keys: string[]) => void;
}) => {
const mapList = useMemo(() => {
return list.map((x) => {
return { ...x, id: x.field };
});
}, [list]);
const { selectedIds: selectedRowKeys } = useSelectedIds(
rowSelection,
mapList,
);
// const handleDelete = useCallback(() => {
// console.log('rowSelection', rowSelection);
// }, [rowSelection]);
const handleDelete = useCallback(() => {
const deletedKeys = selectedRowKeys.filter((x) =>
mapList.some((y) => y.id === x),
);
handleDeleteBatchRow(deletedKeys);
return;
}, [selectedRowKeys, mapList, handleDeleteBatchRow]);
return { handleDelete };
};

View File

@ -1,10 +1,6 @@
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
isMetadataValueTypeWithEnum,
MetadataDeleteMap,
MetadataType,
} from '../hooks/use-manage-modal';
import { MetadataDeleteMap, MetadataType } from '../hooks/use-manage-modal';
import { IManageValuesProps, IMetaDataTableData } from '../interface';
export const useManageValues = (props: IManageValuesProps) => {
@ -48,7 +44,7 @@ export const useManageValues = (props: IManageValuesProps) => {
// Use functional update to avoid closure issues
const handleChange = useCallback(
(field: string, value: any) => {
async (field: string, value: any) => {
if (field === 'field' && existsKeys.includes(value)) {
setValueError((prev) => {
return {
@ -71,17 +67,16 @@ export const useManageValues = (props: IManageValuesProps) => {
if (field === 'valueType') {
const nextValueType = (value ||
'string') as IMetaDataTableData['valueType'];
const supportsEnum = isMetadataValueTypeWithEnum(nextValueType);
if (!supportsEnum) {
setTempValues([]);
}
// const supportsEnum = isMetadataValueTypeWithEnum(nextValueType);
// if (!supportsEnum) {
// setTempValues([]);
// }
return {
...prev,
valueType: nextValueType,
values: supportsEnum ? prev.values : [],
restrictDefinedValues: supportsEnum
? prev.restrictDefinedValues || nextValueType === 'enum'
: false,
values: prev.values || [],
restrictDefinedValues: prev.restrictDefinedValues,
};
}
return {
@ -89,6 +84,7 @@ export const useManageValues = (props: IManageValuesProps) => {
[field]: value,
};
});
return true;
},
[existsKeys, type, t],
);
@ -113,23 +109,46 @@ export const useManageValues = (props: IManageValuesProps) => {
if (type === MetadataType.Setting && valueError.field) {
return;
}
const supportsEnum = isMetadataValueTypeWithEnum(metaData.valueType);
if (!supportsEnum) {
onSave({
...metaData,
values: [],
restrictDefinedValues: false,
});
handleHideModal();
return;
}
// const supportsEnum = isMetadataValueTypeWithEnum(metaData.valueType);
// if (!supportsEnum) {
// onSave({
// ...metaData,
// values: [],
// restrictDefinedValues: false,
// });
// handleHideModal();
// return;
// }
onSave(metaData);
handleHideModal();
}, [metaData, onSave, handleHideModal, type, valueError]);
// Handle blur event, synchronize to main state
const handleValueBlur = useCallback(
(values?: string[]) => {
const newValues = values || tempValues;
if (data.values.length > 0) {
newValues.forEach((newValue, index) => {
if (index < data.values.length) {
const originalValue = data.values[index];
if (originalValue !== newValue) {
addUpdateValue(metaData.field, originalValue, newValue);
}
} else {
if (newValue) {
addUpdateValue(metaData.field, '', newValue);
}
}
});
}
handleChange('values', [...new Set([...newValues])]);
},
[handleChange, tempValues, metaData, data, addUpdateValue],
);
// Handle value changes, only update temporary state
const handleValueChange = useCallback(
(index: number, value: string) => {
(index: number, value: string, isUpdate: boolean = false) => {
setTempValues((prev) => {
if (prev.includes(value)) {
setValueError((prev) => {
@ -149,32 +168,15 @@ export const useManageValues = (props: IManageValuesProps) => {
}
const newValues = [...prev];
newValues[index] = value;
if (isUpdate) {
handleValueBlur(newValues);
}
return newValues;
});
},
[t, type],
[t, type, handleValueBlur],
);
// Handle blur event, synchronize to main state
const handleValueBlur = useCallback(() => {
if (data.values.length > 0) {
tempValues.forEach((newValue, index) => {
if (index < data.values.length) {
const originalValue = data.values[index];
if (originalValue !== newValue) {
addUpdateValue(metaData.field, originalValue, newValue);
}
} else {
if (newValue) {
addUpdateValue(metaData.field, '', newValue);
}
}
});
}
handleChange('values', [...new Set([...tempValues])]);
}, [handleChange, tempValues, metaData, data, addUpdateValue]);
// Handle delete operation
const handleDelete = useCallback(
(index: number) => {
@ -198,6 +200,14 @@ export const useManageValues = (props: IManageValuesProps) => {
[addDeleteValue, metaData],
);
const handleClearValues = useCallback(() => {
setTempValues([]);
setMetaData((prev) => ({
...prev,
values: [],
}));
}, [setTempValues, setMetaData]);
const showDeleteModal = (item: string, callback: () => void) => {
setDeleteDialogContent({
visible: true,
@ -227,6 +237,7 @@ export const useManageValues = (props: IManageValuesProps) => {
return {
metaData,
handleClearValues,
tempValues,
valueError,
deleteDialogContent,