Fix:Metadata saving, copywriting and other related issues (#12169)

### What problem does this PR solve?

Fix:Bugs Fixed
- Text overflow issues that caused rendering problems
- Metadata saving, copywriting and other related issues

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
chanx
2025-12-24 17:21:36 +08:00
committed by Jin Hai
parent df0c092b22
commit 4a2978150c
18 changed files with 497 additions and 240 deletions

View File

@ -18,7 +18,9 @@ import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-reques
import { IModalProps } from '@/interfaces/common'; import { IModalProps } from '@/interfaces/common';
import { IParserConfig } from '@/interfaces/database/document'; import { IParserConfig } from '@/interfaces/database/document';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document'; import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
import { MetadataType } from '@/pages/dataset/components/metedata/hooks/use-manage-modal';
import { import {
AutoMetadata,
ChunkMethodItem, ChunkMethodItem,
EnableTocToggle, EnableTocToggle,
ImageContextWindow, ImageContextWindow,
@ -86,6 +88,7 @@ export function ChunkMethodDialog({
visible, visible,
parserConfig, parserConfig,
loading, loading,
documentId,
}: IProps) { }: IProps) {
const { t } = useTranslation(); const { t } = useTranslation();
@ -142,6 +145,18 @@ export function ChunkMethodDialog({
pages: z pages: z
.array(z.object({ from: z.coerce.number(), to: z.coerce.number() })) .array(z.object({ from: z.coerce.number(), to: z.coerce.number() }))
.optional(), .optional(),
metadata: z
.array(
z
.object({
key: z.string().optional(),
description: z.string().optional(),
enum: z.array(z.string().optional()).optional(),
})
.optional(),
)
.optional(),
enable_metadata: z.boolean().optional(),
}), }),
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
@ -373,6 +388,10 @@ export function ChunkMethodDialog({
)} )}
{showAutoKeywords(selectedTag) && ( {showAutoKeywords(selectedTag) && (
<> <>
<AutoMetadata
type={MetadataType.SingleFileSetting}
otherData={{ documentId }}
/>
<AutoKeywordsFormField></AutoKeywordsFormField> <AutoKeywordsFormField></AutoKeywordsFormField>
<AutoQuestionsFormField></AutoQuestionsFormField> <AutoQuestionsFormField></AutoQuestionsFormField>
</> </>

View File

@ -36,9 +36,11 @@ export function useDefaultParserValues() {
// }, // },
entity_types: [], entity_types: [],
pages: [], pages: [],
metadata: [],
enable_metadata: false,
}; };
return defaultParserValues; return defaultParserValues as IParserConfig;
}, [t]); }, [t]);
return defaultParserValues; return defaultParserValues;

View File

@ -15,6 +15,7 @@ import { Progress } from '@/components/ui/progress';
import { useControllableState } from '@/hooks/use-controllable-state'; import { useControllableState } from '@/hooks/use-controllable-state';
import { cn, formatBytes } from '@/lib/utils'; import { cn, formatBytes } from '@/lib/utils';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
function isFileWithPreview(file: File): file is File & { preview: string } { function isFileWithPreview(file: File): file is File & { preview: string } {
return 'preview' in file && typeof file.preview === 'string'; return 'preview' in file && typeof file.preview === 'string';
@ -58,10 +59,17 @@ function FileCard({ file, progress, onRemove }: FileCardProps) {
</div> </div>
<div className="flex flex-col flex-1 gap-2 overflow-hidden"> <div className="flex flex-col flex-1 gap-2 overflow-hidden">
<div className="flex flex-col gap-px"> <div className="flex flex-col gap-px">
<p className="line-clamp-1 text-sm font-medium text-foreground/80 text-ellipsis"> <Tooltip>
{file.name} <TooltipTrigger asChild>
</p> <p className=" w-fit line-clamp-1 text-sm font-medium text-foreground/80 text-ellipsis truncate max-w-[370px]">
<p className="text-xs text-muted-foreground"> {file.name}
</p>
</TooltipTrigger>
<TooltipContent className="border border-border-button">
{file.name}
</TooltipContent>
</Tooltip>
<p className="text-xs text-text-secondary">
{formatBytes(file.size)} {formatBytes(file.size)}
</p> </p>
</div> </div>
@ -311,7 +319,7 @@ export function FileUploader(props: FileUploaderProps) {
/> />
</div> </div>
<div className="flex flex-col gap-px"> <div className="flex flex-col gap-px">
<p className="font-medium text-text-secondary"> <p className="font-medium text-text-secondary ">
{title || t('knowledgeDetails.uploadTitle')} {title || t('knowledgeDetails.uploadTitle')}
</p> </p>
<p className="text-sm text-text-disabled"> <p className="text-sm text-text-disabled">

View File

@ -43,6 +43,18 @@ export interface IParserConfig {
task_page_size?: number; task_page_size?: number;
raptor?: Raptor; raptor?: Raptor;
graphrag?: GraphRag; graphrag?: GraphRag;
image_context_window?: number;
mineru_parse_method?: 'auto' | 'txt' | 'ocr';
mineru_formula_enable?: boolean;
mineru_table_enable?: boolean;
mineru_lang?: string;
entity_types?: string[];
metadata?: Array<{
key?: string;
description?: string;
enum?: string[];
}>;
enable_metadata?: boolean;
} }
interface Raptor { interface Raptor {

View File

@ -190,12 +190,21 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
manageMetadata: 'Manage metadata', manageMetadata: 'Manage metadata',
metadata: 'Metadata', metadata: 'Metadata',
values: 'Values', values: 'Values',
value: 'Value',
action: 'Action', action: 'Action',
field: 'Field', field: 'Field',
description: 'Description', description: 'Description',
fieldName: 'Field name', fieldName: 'Field name',
editMetadata: 'Edit metadata', editMetadata: 'Edit metadata',
deleteWarn: 'This {{field}} will be removed from all associated files', deleteWarn: 'This {{field}} will be removed from all associated files',
deleteManageFieldAllWarn:
'This field and all its corresponding values will be deleted from all associated files.',
deleteManageValueAllWarn:
'This value will be deleted from from all associated files.',
deleteManageFieldSingleWarn:
'This field and all its corresponding values will be deleted from this files.',
deleteManageValueSingleWarn:
'This value will be deleted from this files.',
}, },
metadataField: 'Metadata field', metadataField: 'Metadata field',
systemAttribute: 'System attribute', systemAttribute: 'System attribute',

View File

@ -177,6 +177,7 @@ export default {
manageMetadata: '管理元数据', manageMetadata: '管理元数据',
metadata: '元数据', metadata: '元数据',
values: '值', values: '值',
value: '值',
action: '操作', action: '操作',
field: '字段', field: '字段',
description: '描述', description: '描述',
@ -186,6 +187,11 @@ export default {
fieldNameExists: '字段名已存在。确认合并重复项并组合所有关联文件。', fieldNameExists: '字段名已存在。确认合并重复项并组合所有关联文件。',
fieldExists: '字段名已存在。', fieldExists: '字段名已存在。',
deleteWarn: '此 {{field}} 将从所有关联文件中移除', deleteWarn: '此 {{field}} 将从所有关联文件中移除',
deleteManageFieldAllWarn:
'此字段及其所有对应值将从所有关联的文件中删除。',
deleteManageValueAllWarn: '此值将从所有关联的文件中删除。',
deleteManageFieldSingleWarn: '此字段及其所有对应值将从此文件中删除。',
deleteManageValueSingleWarn: '此值将从此文件中删除。',
}, },
localUpload: '本地上传', localUpload: '本地上传',
fileSize: '文件大小', fileSize: '文件大小',

View File

@ -9,6 +9,7 @@ import kbService, {
updateMetaData, updateMetaData,
} from '@/services/knowledge-service'; } from '@/services/knowledge-service';
import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { TFunction } from 'i18next';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useParams } from 'umi'; import { useParams } from 'umi';
@ -19,12 +20,43 @@ import {
IMetaDataTableData, IMetaDataTableData,
MetadataOperations, MetadataOperations,
ShowManageMetadataModalProps, ShowManageMetadataModalProps,
} from './interface'; } from '../interface';
export enum MetadataType { export enum MetadataType {
Manage = 1, Manage = 1,
UpdateSingle = 2, UpdateSingle = 2,
Setting = 3, Setting = 3,
SingleFileSetting = 4,
} }
export const MetadataDeleteMap = (
t: TFunction<'translation', undefined>,
): Record<
MetadataType,
{ title: string; warnFieldText: string; warnValueText: string }
> => {
return {
[MetadataType.Manage]: {
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.metadata'),
warnFieldText: t('knowledgeDetails.metadata.deleteManageFieldAllWarn'),
warnValueText: t('knowledgeDetails.metadata.deleteManageValueAllWarn'),
},
[MetadataType.Setting]: {
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.metadata'),
warnFieldText: t('knowledgeDetails.metadata.deleteManageFieldAllWarn'),
warnValueText: t('knowledgeDetails.metadata.deleteManageValueAllWarn'),
},
[MetadataType.UpdateSingle]: {
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.metadata'),
warnFieldText: t('knowledgeDetails.metadata.deleteManageFieldSingleWarn'),
warnValueText: t('knowledgeDetails.metadata.deleteManageValueSingleWarn'),
},
[MetadataType.SingleFileSetting]: {
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.metadata'),
warnFieldText: t('knowledgeDetails.metadata.deleteManageFieldSingleWarn'),
warnValueText: t('knowledgeDetails.metadata.deleteManageValueSingleWarn'),
},
};
};
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]) => {
@ -42,10 +74,21 @@ export const util = {
data: Record<string, string | string[]>, data: Record<string, string | string[]>,
): IMetaDataTableData[] { ): IMetaDataTableData[] {
return Object.entries(data).map(([key, value]) => { return Object.entries(data).map(([key, value]) => {
let thisValue = [] as string[];
if (value && Array.isArray(value)) {
thisValue = value;
} else if (value && typeof value === 'string') {
thisValue = [value];
} else if (value && typeof value === 'object') {
thisValue = [JSON.stringify(value)];
} else if (value) {
thisValue = [value.toString()];
}
return { return {
field: key, field: key,
description: '', description: '',
values: value, values: thisValue,
} as IMetaDataTableData; } as IMetaDataTableData;
}); });
}, },
@ -103,12 +146,42 @@ export const useMetadataOperations = () => {
})); }));
}, []); }, []);
// const addUpdateValue = useCallback(
// (key: string, value: string | string[]) => {
// setOperations((prev) => ({
// ...prev,
// updates: [...prev.updates, { key, value }],
// }));
// },
// [],
// );
const addUpdateValue = useCallback( const addUpdateValue = useCallback(
(key: string, value: string | string[]) => { (key: string, originalValue: string, newValue: string) => {
setOperations((prev) => ({ setOperations((prev) => {
...prev, const existsIndex = prev.updates.findIndex(
updates: [...prev.updates, { key, value }], (update) => update.key === key && update.match === originalValue,
})); );
if (existsIndex > -1) {
const updatedUpdates = [...prev.updates];
updatedUpdates[existsIndex] = {
key,
match: originalValue,
value: newValue,
};
return {
...prev,
updates: updatedUpdates,
};
}
return {
...prev,
updates: [
...prev.updates,
{ key, match: originalValue, value: newValue },
],
};
});
}, },
[], [],
); );
@ -195,8 +268,13 @@ export const useManageMetaDataModal = (
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData); const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { operations, addDeleteRow, addDeleteValue, addUpdateValue } = const {
useMetadataOperations(); operations,
addDeleteRow,
addDeleteValue,
addUpdateValue,
resetOperations,
} = useMetadataOperations();
const { setDocumentMeta } = useSetDocumentMeta(); const { setDocumentMeta } = useSetDocumentMeta();
@ -265,11 +343,12 @@ export const useManageMetaDataModal = (
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: [DocumentApiAction.FetchDocumentList], queryKey: [DocumentApiAction.FetchDocumentList],
}); });
resetOperations();
message.success(t('message.operated')); message.success(t('message.operated'));
callback(); callback();
} }
}, },
[operations, id, t, queryClient], [operations, id, t, queryClient, resetOperations],
); );
const handleSaveUpdateSingle = useCallback( const handleSaveUpdateSingle = useCallback(
@ -303,7 +382,26 @@ export const useManageMetaDataModal = (
return data; return data;
}, },
[tableData, id], [tableData, id, t],
);
const handleSaveSingleFileSettings = useCallback(
async (callback: () => void) => {
const data = util.tableDataToMetaDataSettingJSON(tableData);
if (otherData?.documentId) {
const { data: res } = await kbService.documentUpdateMetaData({
doc_id: otherData.documentId,
metadata: data,
});
if (res.code === 0) {
message.success(t('message.operated'));
callback?.();
}
}
return data;
},
[tableData, t, otherData],
); );
const handleSave = useCallback( const handleSave = useCallback(
@ -317,12 +415,20 @@ export const useManageMetaDataModal = (
break; break;
case MetadataType.Setting: case MetadataType.Setting:
return handleSaveSettings(callback); return handleSaveSettings(callback);
case MetadataType.SingleFileSetting:
return handleSaveSingleFileSettings(callback);
default: default:
handleSaveManage(callback); handleSaveManage(callback);
break; break;
} }
}, },
[handleSaveManage, type, handleSaveUpdateSingle, handleSaveSettings], [
handleSaveManage,
type,
handleSaveUpdateSingle,
handleSaveSettings,
handleSaveSingleFileSettings,
],
); );
return { return {
@ -377,11 +483,3 @@ export const useManageMetadata = () => {
config, config,
}; };
}; };
export const useManageValues = () => {
const [updateValues, setUpdateValues] = useState<{
field: string;
values: string[];
} | null>(null);
return { updateValues, setUpdateValues };
};

View File

@ -0,0 +1,208 @@
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MetadataDeleteMap, MetadataType } from '../hooks/use-manage-modal';
import { IManageValuesProps, IMetaDataTableData } from '../interface';
export const useManageValues = (props: IManageValuesProps) => {
const {
data,
isShowValueSwitch,
hideModal,
onSave,
addUpdateValue,
addDeleteValue,
existsKeys,
type,
} = props;
const { t } = useTranslation();
const [metaData, setMetaData] = useState(data);
const [valueError, setValueError] = useState<Record<string, string>>({
field: '',
values: '',
});
const [deleteDialogContent, setDeleteDialogContent] = useState({
visible: false,
title: '',
name: '',
warnText: '',
onOk: () => {},
onCancel: () => {},
});
const hideDeleteModal = () => {
setDeleteDialogContent({
visible: false,
title: '',
name: '',
warnText: '',
onOk: () => {},
onCancel: () => {},
});
};
// Use functional update to avoid closure issues
const handleChange = useCallback(
(field: string, value: any) => {
if (field === 'field' && existsKeys.includes(value)) {
setValueError((prev) => {
return {
...prev,
field:
type === MetadataType.Setting
? t('knowledgeDetails.metadata.fieldExists')
: t('knowledgeDetails.metadata.fieldNameExists'),
};
});
} else if (field === 'field' && !existsKeys.includes(value)) {
setValueError((prev) => {
return {
...prev,
field: '',
};
});
}
setMetaData((prev) => ({
...prev,
[field]: value,
}));
},
[existsKeys, type, t],
);
// Maintain separate state for each input box
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
useEffect(() => {
setTempValues([...data.values]);
setMetaData(data);
}, [data]);
const handleHideModal = useCallback(() => {
hideModal();
setMetaData({} as IMetaDataTableData);
}, [hideModal]);
const handleSave = useCallback(() => {
if (type === MetadataType.Setting && valueError.field) {
return;
}
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
const newMetaData = { ...metaData, values: [] };
onSave(newMetaData);
} else {
onSave(metaData);
}
handleHideModal();
}, [metaData, onSave, handleHideModal, isShowValueSwitch, type, valueError]);
// Handle value changes, only update temporary state
const handleValueChange = useCallback(
(index: number, value: string) => {
setTempValues((prev) => {
if (prev.includes(value)) {
setValueError((prev) => {
return {
...prev,
values: t('knowledgeDetails.metadata.valueExists'),
};
});
} else {
setValueError((prev) => {
return {
...prev,
values: '',
};
});
}
const newValues = [...prev];
newValues[index] = value;
return newValues;
});
},
[t],
);
// Handle blur event, synchronize to main state
const handleValueBlur = useCallback(() => {
// addUpdateValue(metaData.field, [...new Set([...tempValues])]);
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) => {
setTempValues((prev) => {
const newTempValues = [...prev];
addDeleteValue(metaData.field, newTempValues[index]);
newTempValues.splice(index, 1);
return newTempValues;
});
// Synchronize to main state
setMetaData((prev) => {
const newMetaDataValues = [...prev.values];
newMetaDataValues.splice(index, 1);
return {
...prev,
values: newMetaDataValues,
};
});
},
[addDeleteValue, metaData],
);
const showDeleteModal = (item: string, callback: () => void) => {
setDeleteDialogContent({
visible: true,
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.value'),
name: item,
warnText: MetadataDeleteMap(t)[type as MetadataType].warnValueText,
onOk: () => {
hideDeleteModal();
callback();
},
onCancel: () => {
hideDeleteModal();
},
});
};
// Handle adding new value
const handleAddValue = useCallback(() => {
setTempValues((prev) => [...new Set([...prev, ''])]);
// Synchronize to main state
setMetaData((prev) => ({
...prev,
values: [...new Set([...prev.values, ''])],
}));
}, []);
return {
metaData,
tempValues,
valueError,
deleteDialogContent,
handleChange,
handleValueChange,
handleValueBlur,
handleDelete,
handleAddValue,
showDeleteModal,
handleSave,
handleHideModal,
};
};

View File

@ -50,7 +50,11 @@ export interface IManageValuesProps {
type: MetadataType; type: MetadataType;
hideModal: () => void; hideModal: () => void;
onSave: (data: IMetaDataTableData) => void; onSave: (data: IMetaDataTableData) => void;
addUpdateValue: (key: string, value: string | string[]) => void; addUpdateValue: (
key: string,
originalValue: string,
newValue: string,
) => void;
addDeleteValue: (key: string, value: string) => void; addDeleteValue: (key: string, value: string) => void;
} }
@ -61,7 +65,8 @@ interface DeleteOperation {
interface UpdateOperation { interface UpdateOperation {
key: string; key: string;
value: string | string[]; match: string;
value: string;
} }
export interface MetadataOperations { export interface MetadataOperations {

View File

@ -25,11 +25,16 @@ import {
useReactTable, useReactTable,
} from '@tanstack/react-table'; } from '@tanstack/react-table';
import { Plus, Settings, Trash2 } from 'lucide-react'; import { Plus, Settings, Trash2 } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { MetadataType, useManageMetaDataModal } from './hook'; import {
MetadataDeleteMap,
MetadataType,
useManageMetaDataModal,
} from './hooks/use-manage-modal';
import { IManageModalProps, IMetaDataTableData } from './interface'; import { IManageModalProps, IMetaDataTableData } from './interface';
import { ManageValuesModal } from './manage-values-modal'; import { ManageValuesModal } from './manage-values-modal';
export const ManageMetadataModal = (props: IManageModalProps) => { export const ManageMetadataModal = (props: IManageModalProps) => {
const { const {
title, title,
@ -134,7 +139,8 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
const values = row.getValue('values') as Array<string>; const values = row.getValue('values') as Array<string>;
return ( return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{values.length > 0 && {Array.isArray(values) &&
values.length > 0 &&
values values
.filter((value: string, index: number) => index < 2) .filter((value: string, index: number) => index < 2)
?.map((value: string) => { ?.map((value: string) => {
@ -159,17 +165,12 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
title: title:
t('common.delete') + t('common.delete') +
' ' + ' ' +
t('knowledgeDetails.metadata.metadata'), t('knowledgeDetails.metadata.value'),
name: row.getValue('field') + '/' + value, name: value,
warnText: t( warnText:
'knowledgeDetails.metadata.deleteWarn', MetadataDeleteMap(t)[
{ metadataType as MetadataType
field: ].warnValueText,
t('knowledgeDetails.metadata.field') +
'/' +
t('knowledgeDetails.metadata.values'),
},
),
onOk: () => { onOk: () => {
hideDeleteModal(); hideDeleteModal();
handleDeleteSingleValue( handleDeleteSingleValue(
@ -190,7 +191,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
</Button> </Button>
); );
})} })}
{values.length > 2 && ( {Array.isArray(values) && values.length > 2 && (
<div className="text-text-secondary self-end">...</div> <div className="text-text-secondary self-end">...</div>
)} )}
</div> </div>
@ -221,13 +222,14 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
setDeleteDialogContent({ setDeleteDialogContent({
visible: true, visible: true,
title: title:
t('common.delete') + // t('common.delete') +
' ' + // ' ' +
t('knowledgeDetails.metadata.metadata'), // t('knowledgeDetails.metadata.metadata')
MetadataDeleteMap(t)[metadataType as MetadataType].title,
name: row.getValue('field'), name: row.getValue('field'),
warnText: t('knowledgeDetails.metadata.deleteWarn', { warnText:
field: t('knowledgeDetails.metadata.field'), MetadataDeleteMap(t)[metadataType as MetadataType]
}), .warnFieldText,
onOk: () => { onOk: () => {
hideDeleteModal(); hideDeleteModal();
handleDeleteSingleRow(row.getValue('field')); handleDeleteSingleRow(row.getValue('field'));
@ -266,7 +268,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
manualPagination: true, manualPagination: true,
}); });
const [shouldSave, setShouldSave] = useState(false);
const handleSaveValues = (data: IMetaDataTableData) => { const handleSaveValues = (data: IMetaDataTableData) => {
setTableData((prev) => { setTableData((prev) => {
let newData; let newData;
@ -300,8 +302,20 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
return Array.from(fieldMap.values()); return Array.from(fieldMap.values());
}); });
setShouldSave(true);
}; };
useEffect(() => {
if (shouldSave) {
const timer = setTimeout(() => {
handleSave({ callback: () => {} });
setShouldSave(false);
}, 0);
return () => clearTimeout(timer);
}
}, [tableData, shouldSave, handleSave]);
const existsKeys = useMemo(() => { const existsKeys = useMemo(() => {
return tableData.map((item) => item.field); return tableData.map((item) => item.field);
}, [tableData]); }, [tableData]);
@ -386,7 +400,8 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
<ManageValuesModal <ManageValuesModal
title={ title={
<div> <div>
{metadataType === MetadataType.Setting {metadataType === MetadataType.Setting ||
metadataType === MetadataType.SingleFileSetting
? t('knowledgeDetails.metadata.fieldSetting') ? t('knowledgeDetails.metadata.fieldSetting')
: t('knowledgeDetails.metadata.editMetadata')} : t('knowledgeDetails.metadata.editMetadata')}
</div> </div>

View File

@ -9,10 +9,10 @@ import { Modal } from '@/components/ui/modal/modal';
import { Switch } from '@/components/ui/switch'; import { Switch } from '@/components/ui/switch';
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, useCallback, useEffect, useState } from 'react'; import { memo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { MetadataType } from './hook'; import { useManageValues } from './hooks/use-manage-values-modal';
import { IManageValuesProps, IMetaDataTableData } from './interface'; import { IManageValuesProps } from './interface';
// Create a separate input component, wrapped with memo to avoid unnecessary re-renders // Create a separate input component, wrapped with memo to avoid unnecessary re-renders
const ValueInputItem = memo( const ValueInputItem = memo(
@ -57,188 +57,28 @@ const ValueInputItem = memo(
export const ManageValuesModal = (props: IManageValuesProps) => { export const ManageValuesModal = (props: IManageValuesProps) => {
const { const {
title, title,
data,
isEditField, isEditField,
visible, visible,
isAddValue, isAddValue,
isShowDescription, isShowDescription,
isShowValueSwitch, isShowValueSwitch,
isVerticalShowValue, isVerticalShowValue,
hideModal,
onSave,
addUpdateValue,
addDeleteValue,
existsKeys,
type,
} = props; } = props;
const [metaData, setMetaData] = useState(data); const {
metaData,
tempValues,
valueError,
deleteDialogContent,
handleChange,
handleValueChange,
handleValueBlur,
handleDelete,
handleAddValue,
showDeleteModal,
handleSave,
handleHideModal,
} = useManageValues(props);
const { t } = useTranslation(); const { t } = useTranslation();
const [valueError, setValueError] = useState<Record<string, string>>({
field: '',
values: '',
});
const [deleteDialogContent, setDeleteDialogContent] = useState({
visible: false,
title: '',
name: '',
warnText: '',
onOk: () => {},
onCancel: () => {},
});
const hideDeleteModal = () => {
setDeleteDialogContent({
visible: false,
title: '',
name: '',
warnText: '',
onOk: () => {},
onCancel: () => {},
});
};
// Use functional update to avoid closure issues
const handleChange = useCallback(
(field: string, value: any) => {
if (field === 'field' && existsKeys.includes(value)) {
setValueError((prev) => {
return {
...prev,
field:
type === MetadataType.Setting
? t('knowledgeDetails.metadata.fieldExists')
: t('knowledgeDetails.metadata.fieldNameExists'),
};
});
} else if (field === 'field' && !existsKeys.includes(value)) {
setValueError((prev) => {
return {
...prev,
field: '',
};
});
}
setMetaData((prev) => ({
...prev,
[field]: value,
}));
},
[existsKeys, type, t],
);
// Maintain separate state for each input box
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
useEffect(() => {
setTempValues([...data.values]);
setMetaData(data);
}, [data]);
const handleHideModal = useCallback(() => {
hideModal();
setMetaData({} as IMetaDataTableData);
}, [hideModal]);
const handleSave = useCallback(() => {
if (type === MetadataType.Setting && valueError.field) {
return;
}
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
const newMetaData = { ...metaData, values: [] };
onSave(newMetaData);
} else {
onSave(metaData);
}
handleHideModal();
}, [metaData, onSave, handleHideModal, isShowValueSwitch, type, valueError]);
// Handle value changes, only update temporary state
const handleValueChange = useCallback(
(index: number, value: string) => {
setTempValues((prev) => {
if (prev.includes(value)) {
setValueError((prev) => {
return {
...prev,
values: t('knowledgeDetails.metadata.valueExists'),
};
});
} else {
setValueError((prev) => {
return {
...prev,
values: '',
};
});
}
const newValues = [...prev];
newValues[index] = value;
return newValues;
});
},
[t],
);
// Handle blur event, synchronize to main state
const handleValueBlur = useCallback(() => {
addUpdateValue(metaData.field, [...new Set([...tempValues])]);
handleChange('values', [...new Set([...tempValues])]);
}, [handleChange, tempValues, metaData, addUpdateValue]);
// Handle delete operation
const handleDelete = useCallback(
(index: number) => {
setTempValues((prev) => {
const newTempValues = [...prev];
addDeleteValue(metaData.field, newTempValues[index]);
newTempValues.splice(index, 1);
return newTempValues;
});
// Synchronize to main state
setMetaData((prev) => {
const newMetaDataValues = [...prev.values];
newMetaDataValues.splice(index, 1);
return {
...prev,
values: newMetaDataValues,
};
});
},
[addDeleteValue, metaData],
);
const showDeleteModal = (item: string, callback: () => void) => {
setDeleteDialogContent({
visible: true,
title: t('common.delete') + ' ' + t('knowledgeDetails.metadata.metadata'),
name: metaData.field + '/' + item,
warnText: t('knowledgeDetails.metadata.deleteWarn', {
field:
t('knowledgeDetails.metadata.field') +
'/' +
t('knowledgeDetails.metadata.values'),
}),
onOk: () => {
hideDeleteModal();
callback();
},
onCancel: () => {
hideDeleteModal();
},
});
};
// Handle adding new value
const handleAddValue = useCallback(() => {
setTempValues((prev) => [...new Set([...prev, ''])]);
// Synchronize to main state
setMetaData((prev) => ({
...prev,
values: [...new Set([...prev.values, ''])],
}));
}, []);
return ( return (
<Modal <Modal

View File

@ -35,7 +35,8 @@ import {
MetadataType, MetadataType,
useManageMetadata, useManageMetadata,
util, util,
} from '../../components/metedata/hook'; } from '../../components/metedata/hooks/use-manage-modal';
import { IMetaDataReturnJSONSettings } from '../../components/metedata/interface';
import { ManageMetadataModal } from '../../components/metedata/manage-modal'; import { ManageMetadataModal } from '../../components/metedata/manage-modal';
import { import {
useHandleKbEmbedding, useHandleKbEmbedding,
@ -359,7 +360,13 @@ export function OverlappedPercent() {
); );
} }
export function AutoMetadata() { export function AutoMetadata({
type = MetadataType.Setting,
otherData,
}: {
type?: MetadataType;
otherData?: Record<string, any>;
}) {
// get metadata field // get metadata field
const form = useFormContext(); const form = useFormContext();
const { const {
@ -369,6 +376,7 @@ export function AutoMetadata() {
tableData, tableData,
config: metadataConfig, config: metadataConfig,
} = useManageMetadata(); } = useManageMetadata();
const autoMetadataField: FormFieldConfig = { const autoMetadataField: FormFieldConfig = {
name: 'parser_config.enable_metadata', name: 'parser_config.enable_metadata',
label: t('knowledgeConfiguration.autoMetadata'), label: t('knowledgeConfiguration.autoMetadata'),
@ -379,6 +387,7 @@ export function AutoMetadata() {
render: (fieldProps: ControllerRenderProps) => ( render: (fieldProps: ControllerRenderProps) => (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<Button <Button
type="button"
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
const metadata = form.getValues('parser_config.metadata'); const metadata = form.getValues('parser_config.metadata');
@ -387,7 +396,8 @@ export function AutoMetadata() {
showManageMetadataModal({ showManageMetadataModal({
metadata: tableMetaData, metadata: tableMetaData,
isCanAdd: true, isCanAdd: true,
type: MetadataType.Setting, type: type,
record: otherData,
}); });
}} }}
> >
@ -403,6 +413,10 @@ export function AutoMetadata() {
</div> </div>
), ),
}; };
const handleSaveMetadata = (data?: IMetaDataReturnJSONSettings) => {
form.setValue('parser_config.metadata', data || []);
};
return ( return (
<> <>
<RenderField field={autoMetadataField} /> <RenderField field={autoMetadataField} />
@ -431,8 +445,8 @@ export function AutoMetadata() {
isShowDescription={true} isShowDescription={true}
isShowValueSwitch={true} isShowValueSwitch={true}
isVerticalShowValue={false} isVerticalShowValue={false}
success={(data) => { success={(data?: IMetaDataReturnJSONSettings) => {
form.setValue('parser_config.metadata', data || []); handleSaveMetadata(data);
}} }}
/> />
)} )}

View File

@ -96,7 +96,7 @@ export const formSchema = z
) )
.optional(), .optional(),
enable_metadata: z.boolean().optional(), enable_metadata: z.boolean().optional(),
llm_id: z.string().optional(), llm_id: z.string().min(1, { message: 'Indexing model is required' }),
}) })
.optional(), .optional(),
pagerank: z.number(), pagerank: z.number(),

View File

@ -16,7 +16,10 @@ import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-reques
import { Pen, Upload } from 'lucide-react'; import { Pen, Upload } from 'lucide-react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { MetadataType, useManageMetadata } from '../components/metedata/hook'; import {
MetadataType,
useManageMetadata,
} from '../components/metedata/hooks/use-manage-modal';
import { ManageMetadataModal } from '../components/metedata/manage-modal'; import { ManageMetadataModal } from '../components/metedata/manage-modal';
import { DatasetTable } from './dataset-table'; import { DatasetTable } from './dataset-table';
import Generate from './generate-button/generate'; import Generate from './generate-button/generate';

View File

@ -16,7 +16,10 @@ import { formatDate } from '@/utils/date';
import { ColumnDef } from '@tanstack/table-core'; import { ColumnDef } from '@tanstack/table-core';
import { ArrowUpDown, MonitorUp } from 'lucide-react'; import { ArrowUpDown, MonitorUp } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { MetadataType, util } from '../components/metedata/hook'; import {
MetadataType,
util,
} from '../components/metedata/hooks/use-manage-modal';
import { ShowManageMetadataModalProps } from '../components/metedata/interface'; import { ShowManageMetadataModalProps } from '../components/metedata/interface';
import { DatasetActionCell } from './dataset-action-cell'; import { DatasetActionCell } from './dataset-action-cell';
import { ParsingStatusCell } from './parsing-status-cell'; import { ParsingStatusCell } from './parsing-status-cell';

View File

@ -1,6 +1,11 @@
import FileStatusBadge from '@/components/file-status-badge'; import FileStatusBadge from '@/components/file-status-badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Modal } from '@/components/ui/modal/modal'; import { Modal } from '@/components/ui/modal/modal';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { RunningStatusMap } from '@/constants/knowledge'; import { RunningStatusMap } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks'; import { useTranslate } from '@/hooks/common-hooks';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
@ -40,7 +45,14 @@ const InfoItem: React.FC<{
return ( return (
<div className={`flex flex-col mb-4 ${className}`}> <div className={`flex flex-col mb-4 ${className}`}>
<span className="text-text-secondary text-sm">{label}</span> <span className="text-text-secondary text-sm">{label}</span>
<span className="text-text-primary mt-1">{value}</span> <Tooltip>
<TooltipTrigger asChild>
<span className="text-text-primary mt-1 truncate max-w-[200px]">
{value}
</span>
</TooltipTrigger>
<TooltipContent>{value}</TooltipContent>
</Tooltip>
</div> </div>
); );
}; };
@ -70,9 +82,7 @@ const ProcessLogModal: React.FC<ProcessLogModalProps> = ({
}) => { }) => {
const { t } = useTranslate('knowledgeDetails'); const { t } = useTranslate('knowledgeDetails');
const blackKeyList = ['']; const blackKeyList = [''];
console.log('logInfo', initData);
const logInfo = useMemo(() => { const logInfo = useMemo(() => {
console.log('logInfo', initData);
return initData; return initData;
}, [initData]); }, [initData]);

View File

@ -48,10 +48,10 @@ const {
traceRaptor, traceRaptor,
check_embedding, check_embedding,
kbUpdateMetaData, kbUpdateMetaData,
documentUpdateMetaData,
} = api; } = api;
const methods = { const methods = {
// 知识库管理
createKb: { createKb: {
url: create_kb, url: create_kb,
method: 'post', method: 'post',
@ -220,6 +220,10 @@ const methods = {
url: kbUpdateMetaData, url: kbUpdateMetaData,
method: 'post', method: 'post',
}, },
documentUpdateMetaData: {
url: documentUpdateMetaData,
method: 'post',
},
// getMetaData: { // getMetaData: {
// url: getMetaData, // url: getMetaData,
// method: 'get', // method: 'get',

View File

@ -80,6 +80,7 @@ export default {
getMetaData: `${api_host}/document/metadata/summary`, getMetaData: `${api_host}/document/metadata/summary`,
updateMetaData: `${api_host}/document/metadata/update`, updateMetaData: `${api_host}/document/metadata/update`,
kbUpdateMetaData: `${api_host}/kb/update_metadata_setting`, kbUpdateMetaData: `${api_host}/kb/update_metadata_setting`,
documentUpdateMetaData: `${api_host}/document/update_metadata_setting`,
// tags // tags
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`, listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,