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:
Kevin Hu
2026-01-16 20:14:02 +08:00
committed by GitHub
parent 4f036a881d
commit b6d7733058
9 changed files with 568 additions and 130 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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(),

View File

@ -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: '',
},