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; topn_tags?: number;
graphrag?: { use_graphrag?: boolean }; graphrag?: { use_graphrag?: boolean };
enable_metadata?: boolean; enable_metadata?: boolean;
metadata?: any;
built_in_metadata?: Array<{ key: string; type: string }>;
} }
export interface IKnowledgeFileParserConfig { export interface IKnowledgeFileParserConfig {

View File

@ -14,11 +14,14 @@ import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router'; import { useParams } from 'react-router';
import { import {
IBuiltInMetadataItem,
IMetaDataJsonSchemaProperty,
IMetaDataReturnJSONSettings, IMetaDataReturnJSONSettings,
IMetaDataReturnJSONType, IMetaDataReturnJSONType,
IMetaDataReturnType, IMetaDataReturnType,
IMetaDataTableData, IMetaDataTableData,
MetadataOperations, MetadataOperations,
MetadataValueType,
ShowManageMetadataModalProps, ShowManageMetadataModalProps,
} from '../interface'; } from '../interface';
export enum MetadataType { 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 = { export const util = {
changeToMetaDataTableData(data: IMetaDataReturnType): IMetaDataTableData[] { changeToMetaDataTableData(data: IMetaDataReturnType): IMetaDataTableData[] {
return Object.entries(data).map(([key, value]) => { return Object.entries(data).map(([key, value]) => {
@ -117,25 +204,58 @@ export const util = {
tableDataToMetaDataSettingJSON( tableDataToMetaDataSettingJSON(
data: IMetaDataTableData[], data: IMetaDataTableData[],
): IMetaDataReturnJSONSettings { ): IMetaDataReturnJSONSettings {
return data.map((item) => { const properties = data.reduce<Record<string, IMetaDataJsonSchemaProperty>>(
return { (acc, item) => {
key: item.field, if (!item.field) {
description: item.description, return acc;
enum: item.values, }
}; 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( metaDataSettingJSONToMetaDataTableData(
data: IMetaDataReturnJSONSettings, data: IMetaDataReturnJSONSettings,
): IMetaDataTableData[] { ): IMetaDataTableData[] {
if (!Array.isArray(data)) return []; if (!data) return [];
return data.map((item) => { 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 { return {
field: item.key, field: key,
description: item.description, description: property.description || '',
values: item.enum, values,
restrictDefinedValues: !!item.enum?.length, restrictDefinedValues: !!values.length,
valueType,
} as IMetaDataTableData; } as IMetaDataTableData;
}); });
}, },
@ -384,21 +504,15 @@ export const useManageMetaDataModal = (
); );
const handleSaveSettings = useCallback( const handleSaveSettings = useCallback(
async (callback: () => void) => { async (callback: () => void, builtInMetadata?: IBuiltInMetadataItem[]) => {
const data = util.tableDataToMetaDataSettingJSON(tableData); const data = util.tableDataToMetaDataSettingJSON(tableData);
const { data: res } = await kbService.kbUpdateMetaData({ callback?.();
kb_id: id, return {
metadata: data, metadata: data,
enable_metadata: true, builtInMetadata: builtInMetadata || [],
}); };
if (res.code === 0) {
message.success(t('message.operated'));
callback?.();
}
return data;
}, },
[tableData, id, t], [tableData],
); );
const handleSaveSingleFileSettings = useCallback( const handleSaveSingleFileSettings = useCallback(
@ -421,7 +535,13 @@ export const useManageMetaDataModal = (
); );
const handleSave = useCallback( const handleSave = useCallback(
async ({ callback }: { callback: () => void }) => { async ({
callback,
builtInMetadata,
}: {
callback: () => void;
builtInMetadata?: string[];
}) => {
switch (type) { switch (type) {
case MetadataType.UpdateSingle: case MetadataType.UpdateSingle:
handleSaveUpdateSingle(callback); handleSaveUpdateSingle(callback);
@ -430,7 +550,7 @@ export const useManageMetaDataModal = (
handleSaveManage(callback); handleSaveManage(callback);
break; break;
case MetadataType.Setting: case MetadataType.Setting:
return handleSaveSettings(callback); return handleSaveSettings(callback, builtInMetadata);
case MetadataType.SingleFileSetting: case MetadataType.SingleFileSetting:
return handleSaveSingleFileSettings(callback); return handleSaveSingleFileSettings(callback);
default: default:

View File

@ -1,13 +1,16 @@
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; 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'; import { IManageValuesProps, IMetaDataTableData } from '../interface';
export const useManageValues = (props: IManageValuesProps) => { export const useManageValues = (props: IManageValuesProps) => {
const { const {
data, data,
isShowValueSwitch,
hideModal, hideModal,
onSave, onSave,
addUpdateValue, addUpdateValue,
@ -16,7 +19,10 @@ export const useManageValues = (props: IManageValuesProps) => {
type, type,
} = props; } = props;
const { t } = useTranslation(); 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>>({ const [valueError, setValueError] = useState<Record<string, string>>({
field: '', field: '',
values: '', values: '',
@ -61,10 +67,28 @@ export const useManageValues = (props: IManageValuesProps) => {
}; };
}); });
} }
setMetaData((prev) => ({ setMetaData((prev) => {
...prev, if (field === 'valueType') {
[field]: value, 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], [existsKeys, type, t],
); );
@ -74,7 +98,10 @@ export const useManageValues = (props: IManageValuesProps) => {
useEffect(() => { useEffect(() => {
setTempValues([...data.values]); setTempValues([...data.values]);
setMetaData(data); setMetaData({
...data,
valueType: data.valueType || 'string',
});
}, [data]); }, [data]);
const handleHideModal = useCallback(() => { const handleHideModal = useCallback(() => {
@ -86,14 +113,19 @@ export const useManageValues = (props: IManageValuesProps) => {
if (type === MetadataType.Setting && valueError.field) { if (type === MetadataType.Setting && valueError.field) {
return; return;
} }
if (!metaData.restrictDefinedValues && isShowValueSwitch) { const supportsEnum = isMetadataValueTypeWithEnum(metaData.valueType);
const newMetaData = { ...metaData, values: [] }; if (!supportsEnum) {
onSave(newMetaData); onSave({
} else { ...metaData,
onSave(metaData); values: [],
restrictDefinedValues: false,
});
handleHideModal();
return;
} }
onSave(metaData);
handleHideModal(); handleHideModal();
}, [metaData, onSave, handleHideModal, isShowValueSwitch, type, valueError]); }, [metaData, onSave, handleHideModal, type, valueError]);
// Handle value changes, only update temporary state // Handle value changes, only update temporary state
const handleValueChange = useCallback( const handleValueChange = useCallback(

View File

@ -11,13 +11,44 @@ export interface IMetaDataReturnJSONSettingItem {
description?: string; description?: string;
enum?: 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 = { export type IMetaDataTableData = {
field: string; field: string;
description: string; description: string;
restrictDefinedValues?: boolean; restrictDefinedValues?: boolean;
values: string[]; values: string[];
valueType?: MetadataValueType;
};
export type IBuiltInMetadataItem = {
key: string;
type: MetadataValueType;
}; };
export type IManageModalProps = { export type IManageModalProps = {
@ -34,6 +65,7 @@ export type IManageModalProps = {
isAddValue?: boolean; isAddValue?: boolean;
isShowValueSwitch?: boolean; isShowValueSwitch?: boolean;
isVerticalShowValue?: boolean; isVerticalShowValue?: boolean;
builtInMetadata?: IBuiltInMetadataItem[];
success?: (data: any) => void; success?: (data: any) => void;
}; };
@ -45,6 +77,7 @@ export interface IManageValuesProps {
isAddValue?: boolean; isAddValue?: boolean;
isShowDescription?: boolean; isShowDescription?: boolean;
isShowValueSwitch?: boolean; isShowValueSwitch?: boolean;
isShowType?: boolean;
isVerticalShowValue?: boolean; isVerticalShowValue?: boolean;
data: IMetaDataTableData; data: IMetaDataTableData;
type: MetadataType; type: MetadataType;
@ -81,6 +114,7 @@ export type ShowManageMetadataModalProps = Partial<IManageModalProps> & {
isCanAdd: boolean; isCanAdd: boolean;
type: MetadataType; type: MetadataType;
record?: Record<string, any>; record?: Record<string, any>;
builtInMetadata?: IBuiltInMetadataItem[];
options?: ShowManageMetadataModalOptions; options?: ShowManageMetadataModalOptions;
title?: ReactNode | string; title?: ReactNode | string;
isDeleteSingleValue?: boolean; isDeleteSingleValue?: boolean;

View File

@ -7,6 +7,7 @@ import Empty from '@/components/empty/empty';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal/modal'; import { Modal } from '@/components/ui/modal/modal';
import { Switch } from '@/components/ui/switch';
import { import {
Table, Table,
TableBody, TableBody,
@ -15,6 +16,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { Routes } from '@/routes'; import { Routes } from '@/routes';
import { import {
@ -39,11 +41,19 @@ import { useHandleMenuClick } from '../../sidebar/hooks';
import { import {
MetadataDeleteMap, MetadataDeleteMap,
MetadataType, MetadataType,
getMetadataValueTypeLabel,
isMetadataValueTypeWithEnum,
useManageMetaDataModal, useManageMetaDataModal,
} from './hooks/use-manage-modal'; } from './hooks/use-manage-modal';
import { IManageModalProps, IMetaDataTableData } from './interface'; import {
IBuiltInMetadataItem,
IManageModalProps,
IMetaDataTableData,
} from './interface';
import { ManageValuesModal } from './manage-values-modal'; import { ManageValuesModal } from './manage-values-modal';
type MetadataSettingsTab = 'generation' | 'built-in';
export const ManageMetadataModal = (props: IManageModalProps) => { export const ManageMetadataModal = (props: IManageModalProps) => {
const { const {
title, title,
@ -59,6 +69,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
isShowDescription = false, isShowDescription = false,
isShowValueSwitch = false, isShowValueSwitch = false,
isVerticalShowValue = true, isVerticalShowValue = true,
builtInMetadata,
success, success,
} = props; } = props;
const { t } = useTranslation(); const { t } = useTranslation();
@ -66,10 +77,15 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
field: '', field: '',
description: '', description: '',
values: [], values: [],
valueType: 'string',
}); });
const [expanded, setExpanded] = useState(true); const [expanded, setExpanded] = useState(true);
const [activeTab, setActiveTab] = useState<MetadataSettingsTab>('generation');
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0); const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
const [builtInSelection, setBuiltInSelection] = useState<
IBuiltInMetadataItem[]
>([]);
const [deleteDialogContent, setDeleteDialogContent] = useState({ const [deleteDialogContent, setDeleteDialogContent] = useState({
visible: false, visible: false,
title: '', 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) => { const handleEditValue = (field: string, value: string) => {
setEditingValue({ field, value, newValue: value }); setEditingValue({ field, value, newValue: value });
}; };
@ -141,6 +213,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
field: '', field: '',
description: '', description: '',
values: [], values: [],
valueType: 'string',
}); });
setCurrentValueIndex(tableData.length || 0); setCurrentValueIndex(tableData.length || 0);
showManageValuesModal(); showManageValuesModal();
@ -165,6 +238,21 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
</div> </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', accessorKey: 'description',
header: () => <span>{t('knowledgeDetails.metadata.description')}</span>, header: () => <span>{t('knowledgeDetails.metadata.description')}</span>,
@ -196,8 +284,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
), ),
cell: ({ row }) => { cell: ({ row }) => {
const values = row.getValue('values') as Array<string>; 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>; return <div></div>;
} }
@ -342,7 +433,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
}, },
]; ];
if (!isShowDescription) { if (!isShowDescription) {
cols.splice(1, 1); return cols.filter((col) => col.accessorKey !== 'description');
} }
return cols; return cols;
}, [ }, [
@ -356,6 +447,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
expanded, expanded,
editingValue, editingValue,
saveEditedValue, saveEditedValue,
showTypeColumn,
]); ]);
const table = useReactTable({ const table = useReactTable({
@ -393,7 +485,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
const mergedValues = [ const mergedValues = [
...new Set([...existingItem.values, ...item.values]), ...new Set([...existingItem.values, ...item.values]),
]; ];
fieldMap.set(item.field, { ...existingItem, values: mergedValues }); fieldMap.set(item.field, {
...existingItem,
...item,
values: mergedValues,
});
} else { } else {
fieldMap.set(item.field, item); fieldMap.set(item.field, item);
} }
@ -407,13 +503,13 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
useEffect(() => { useEffect(() => {
if (shouldSave) { if (shouldSave) {
const timer = setTimeout(() => { const timer = setTimeout(() => {
handleSave({ callback: () => {} }); handleSave({ callback: () => {}, builtInMetadata: builtInSelection });
setShouldSave(false); setShouldSave(false);
}, 0); }, 0);
return () => clearTimeout(timer); return () => clearTimeout(timer);
} }
}, [tableData, shouldSave, handleSave]); }, [tableData, shouldSave, handleSave, builtInSelection]);
const existsKeys = useMemo(() => { const existsKeys = useMemo(() => {
return tableData.map((item) => item.field); return tableData.map((item) => item.field);
@ -428,7 +524,10 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
maskClosable={false} maskClosable={false}
okText={t('common.save')} okText={t('common.save')}
onOk={async () => { onOk={async () => {
const res = await handleSave({ callback: hideModal }); const res = await handleSave({
callback: hideModal,
builtInMetadata: builtInSelection,
});
console.log('data', res); console.log('data', res);
success?.(res); success?.(res);
}} }}
@ -449,7 +548,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
{t('knowledgeDetails.metadata.toMetadataSetting')} {t('knowledgeDetails.metadata.toMetadataSetting')}
</Button> </Button>
)} )}
{isCanAdd && ( {isCanAdd && activeTab !== 'built-in' && (
<Button <Button
variant={'ghost'} variant={'ghost'}
className="border border-border-button" className="border border-border-button"
@ -460,53 +559,188 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
</Button> </Button>
)} )}
</div> </div>
<Table rootClassName="max-h-[800px]"> {metadataType === MetadataType.Setting ? (
<TableHeader> <Tabs
{table.getHeaderGroups().map((headerGroup) => ( value={activeTab}
<TableRow key={headerGroup.id}> onValueChange={(v) => setActiveTab(v as MetadataSettingsTab)}
{headerGroup.headers.map((header) => ( >
<TableHead key={header.id}> <TabsList className="w-fit">
{header.isPlaceholder <TabsTrigger value="generation">Generation</TabsTrigger>
? null <TabsTrigger value="built-in">
: flexRender( {t('knowledgeConfiguration.builtIn')}
header.column.columnDef.header, </TabsTrigger>
header.getContext(), </TabsList>
)} <TabsContent value="generation">
</TableHead> <Table rootClassName="max-h-[800px]">
))} <TableHeader>
</TableRow> {table.getHeaderGroups().map((headerGroup) => (
))} <TableRow key={headerGroup.id}>
</TableHeader> {headerGroup.headers.map((header) => (
<TableBody className="relative"> <TableHead key={header.id}>
{table.getRowModel().rows?.length ? ( {header.isPlaceholder
table.getRowModel().rows.map((row) => ( ? null
<TableRow : flexRender(
key={row.id} header.column.columnDef.header,
data-state={row.getIsSelected() && 'selected'} header.getContext(),
className="group" )}
> </TableHead>
{row.getVisibleCells().map((cell) => ( ))}
<TableCell key={cell.id}> </TableRow>
{flexRender( ))}
cell.column.columnDef.cell, </TableHeader>
cell.getContext(), <TableBody className="relative">
)} {table.getRowModel().rows?.length ? (
</TableCell> 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>
)) ))}
) : ( </TableHeader>
<TableRow> <TableBody className="relative">
<TableCell {table.getRowModel().rows?.length ? (
colSpan={columns.length} table.getRowModel().rows.map((row) => (
className="h-24 text-center" <TableRow
> key={row.id}
<Empty type={EmptyType.Data} /> data-state={row.getIsSelected() && 'selected'}
</TableCell> className="group"
</TableRow> >
)} {row.getVisibleCells().map((cell) => (
</TableBody> <TableCell key={cell.id}>
</Table> {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> </div>
{metadataType === MetadataType.Manage && ( {metadataType === MetadataType.Manage && (
<div className=" absolute bottom-6 left-5 text-text-secondary text-sm"> <div className=" absolute bottom-6 left-5 text-text-secondary text-sm">
@ -537,6 +771,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
isAddValue={isAddValue || isCanAdd} isAddValue={isAddValue || isCanAdd}
isShowDescription={isShowDescription} isShowDescription={isShowDescription}
isShowValueSwitch={isShowValueSwitch} isShowValueSwitch={isShowValueSwitch}
isShowType={isSettingsMode}
isVerticalShowValue={isVerticalShowValue} isVerticalShowValue={isVerticalShowValue}
// handleDeleteSingleValue={handleDeleteSingleValue} // handleDeleteSingleValue={handleDeleteSingleValue}
// handleDeleteSingleRow={handleDeleteSingleRow} // handleDeleteSingleRow={handleDeleteSingleRow}

View File

@ -7,11 +7,15 @@ import { Button } from '@/components/ui/button';
import { FormLabel } from '@/components/ui/form'; import { FormLabel } from '@/components/ui/form';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal/modal'; 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 { Textarea } from '@/components/ui/textarea';
import { Plus, Trash2 } from 'lucide-react'; import { Plus, Trash2 } from 'lucide-react';
import { memo } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import {
isMetadataValueTypeWithEnum,
metadataValueTypeOptions,
} from './hooks/use-manage-modal';
import { useManageValues } from './hooks/use-manage-values-modal'; import { useManageValues } from './hooks/use-manage-values-modal';
import { IManageValuesProps } from './interface'; import { IManageValuesProps } from './interface';
@ -62,8 +66,8 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
visible, visible,
isAddValue, isAddValue,
isShowDescription, isShowDescription,
isShowValueSwitch,
isVerticalShowValue, isVerticalShowValue,
isShowType,
} = props; } = props;
const { const {
metaData, metaData,
@ -80,6 +84,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
handleHideModal, handleHideModal,
} = useManageValues(props); } = useManageValues(props);
const { t } = useTranslation(); const { t } = useTranslation();
const canShowValues = isMetadataValueTypeWithEnum(metaData.valueType);
return ( return (
<Modal <Modal
@ -115,6 +120,16 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
</div> </div>
</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 && ( {isShowDescription && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<FormLabel <FormLabel
@ -133,26 +148,7 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
</div> </div>
</div> </div>
)} )}
{isShowValueSwitch && ( {canShowValues && (
<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) && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div>{t('knowledgeDetails.metadata.values')}</div> <div>{t('knowledgeDetails.metadata.values')}</div>

View File

@ -39,7 +39,10 @@ import {
useManageMetadata, useManageMetadata,
util, util,
} from '../../components/metedata/hooks/use-manage-modal'; } 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 { ManageMetadataModal } from '../../components/metedata/manage-modal';
import { import {
useHandleKbEmbedding, useHandleKbEmbedding,
@ -384,12 +387,14 @@ export function AutoMetadata({
const handleClickOpenMetadata = useCallback(() => { const handleClickOpenMetadata = useCallback(() => {
const metadata = form.getValues('parser_config.metadata'); const metadata = form.getValues('parser_config.metadata');
const builtInMetadata = form.getValues('parser_config.built_in_metadata');
const tableMetaData = util.metaDataSettingJSONToMetaDataTableData(metadata); const tableMetaData = util.metaDataSettingJSONToMetaDataTableData(metadata);
showManageMetadataModal({ showManageMetadataModal({
metadata: tableMetaData, metadata: tableMetaData,
isCanAdd: true, isCanAdd: true,
type: type, type: type,
record: otherData, record: otherData,
builtInMetadata,
}); });
}, [form, otherData, showManageMetadataModal, type]); }, [form, otherData, showManageMetadataModal, type]);
@ -429,8 +434,15 @@ export function AutoMetadata({
), ),
}; };
const handleSaveMetadata = (data?: IMetaDataReturnJSONSettings) => { const handleSaveMetadata = (data?: {
form.setValue('parser_config.metadata', 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); form.setValue('parser_config.enable_metadata', true);
}; };
return ( return (
@ -461,7 +473,11 @@ export function AutoMetadata({
isShowDescription={true} isShowDescription={true}
isShowValueSwitch={true} isShowValueSwitch={true}
isVerticalShowValue={false} isVerticalShowValue={false}
success={(data?: IMetaDataReturnJSONSettings) => { builtInMetadata={metadataConfig.builtInMetadata}
success={(data?: {
metadata?: IMetaDataReturnJSONSettings;
builtInMetadata?: IBuiltInMetadataItem[];
}) => {
handleSaveMetadata(data); handleSaveMetadata(data);
}} }}
/> />

View File

@ -84,15 +84,13 @@ export const formSchema = z
path: ['entity_types'], path: ['entity_types'],
}, },
), ),
metadata: z metadata: z.any().optional(),
built_in_metadata: z
.array( .array(
z z.object({
.object({ key: z.string().optional(),
key: z.string().optional(), type: z.string().optional(),
description: z.string().optional(), }),
enum: z.array(z.string().optional()).optional(),
})
.optional(),
) )
.optional(), .optional(),
enable_metadata: z.boolean().optional(), enable_metadata: z.boolean().optional(),

View File

@ -95,7 +95,12 @@ export default function DatasetSettings() {
entity_types: initialEntityTypes, entity_types: initialEntityTypes,
method: MethodValue.Light, method: MethodValue.Light,
}, },
metadata: [], metadata: {
type: 'object',
properties: {},
additionalProperties: false,
},
built_in_metadata: [],
enable_metadata: false, enable_metadata: false,
llm_id: '', llm_id: '',
}, },