mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-25 08:06:48 +08:00
Fix: Add prompts when merging or deleting metadata. (#12138)
### What problem does this PR solve? Fix: Add prompts when merging or deleting metadata. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) --------- Co-authored-by: Kevin Hu <kevinhu.sh@gmail.com>
This commit is contained in:
@ -35,6 +35,7 @@ import { cn } from '@/lib/utils';
|
|||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { Loader } from 'lucide-react';
|
import { Loader } from 'lucide-react';
|
||||||
import { MultiSelect, MultiSelectOptionType } from './ui/multi-select';
|
import { MultiSelect, MultiSelectOptionType } from './ui/multi-select';
|
||||||
|
import { Switch } from './ui/switch';
|
||||||
|
|
||||||
// Field type enumeration
|
// Field type enumeration
|
||||||
export enum FormFieldType {
|
export enum FormFieldType {
|
||||||
@ -46,6 +47,7 @@ export enum FormFieldType {
|
|||||||
Select = 'select',
|
Select = 'select',
|
||||||
MultiSelect = 'multi-select',
|
MultiSelect = 'multi-select',
|
||||||
Checkbox = 'checkbox',
|
Checkbox = 'checkbox',
|
||||||
|
Switch = 'switch',
|
||||||
Tag = 'tag',
|
Tag = 'tag',
|
||||||
Custom = 'custom',
|
Custom = 'custom',
|
||||||
}
|
}
|
||||||
@ -154,6 +156,7 @@ export const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FormFieldType.Checkbox:
|
case FormFieldType.Checkbox:
|
||||||
|
case FormFieldType.Switch:
|
||||||
fieldSchema = z.boolean();
|
fieldSchema = z.boolean();
|
||||||
break;
|
break;
|
||||||
case FormFieldType.Tag:
|
case FormFieldType.Tag:
|
||||||
@ -193,6 +196,8 @@ export const generateSchema = (fields: FormFieldConfig[]): ZodSchema<any> => {
|
|||||||
if (
|
if (
|
||||||
field.type !== FormFieldType.Number &&
|
field.type !== FormFieldType.Number &&
|
||||||
field.type !== FormFieldType.Checkbox &&
|
field.type !== FormFieldType.Checkbox &&
|
||||||
|
field.type !== FormFieldType.Switch &&
|
||||||
|
field.type !== FormFieldType.Custom &&
|
||||||
field.type !== FormFieldType.Tag &&
|
field.type !== FormFieldType.Tag &&
|
||||||
field.required
|
field.required
|
||||||
) {
|
) {
|
||||||
@ -289,7 +294,10 @@ const generateDefaultValues = <T extends FieldValues>(
|
|||||||
const lastKey = keys[keys.length - 1];
|
const lastKey = keys[keys.length - 1];
|
||||||
if (field.defaultValue !== undefined) {
|
if (field.defaultValue !== undefined) {
|
||||||
current[lastKey] = field.defaultValue;
|
current[lastKey] = field.defaultValue;
|
||||||
} else if (field.type === FormFieldType.Checkbox) {
|
} else if (
|
||||||
|
field.type === FormFieldType.Checkbox ||
|
||||||
|
field.type === FormFieldType.Switch
|
||||||
|
) {
|
||||||
current[lastKey] = false;
|
current[lastKey] = false;
|
||||||
} else if (field.type === FormFieldType.Tag) {
|
} else if (field.type === FormFieldType.Tag) {
|
||||||
current[lastKey] = [];
|
current[lastKey] = [];
|
||||||
@ -299,7 +307,10 @@ const generateDefaultValues = <T extends FieldValues>(
|
|||||||
} else {
|
} else {
|
||||||
if (field.defaultValue !== undefined) {
|
if (field.defaultValue !== undefined) {
|
||||||
defaultValues[field.name] = field.defaultValue;
|
defaultValues[field.name] = field.defaultValue;
|
||||||
} else if (field.type === FormFieldType.Checkbox) {
|
} else if (
|
||||||
|
field.type === FormFieldType.Checkbox ||
|
||||||
|
field.type === FormFieldType.Switch
|
||||||
|
) {
|
||||||
defaultValues[field.name] = false;
|
defaultValues[field.name] = false;
|
||||||
} else if (
|
} else if (
|
||||||
field.type === FormFieldType.Tag ||
|
field.type === FormFieldType.Tag ||
|
||||||
@ -502,6 +513,32 @@ export const RenderField = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case FormFieldType.Switch:
|
||||||
|
return (
|
||||||
|
<RAGFlowFormItem
|
||||||
|
{...field}
|
||||||
|
labelClassName={labelClassName || field.labelClassName}
|
||||||
|
>
|
||||||
|
{(fieldProps) => {
|
||||||
|
const finalFieldProps = field.onChange
|
||||||
|
? {
|
||||||
|
...fieldProps,
|
||||||
|
onChange: (checked: boolean) => {
|
||||||
|
fieldProps.onChange(checked);
|
||||||
|
field.onChange?.(checked);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: fieldProps;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
checked={finalFieldProps.value as boolean}
|
||||||
|
onCheckedChange={(checked) => finalFieldProps.onChange(checked)}
|
||||||
|
disabled={field.disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</RAGFlowFormItem>
|
||||||
|
);
|
||||||
|
|
||||||
case FormFieldType.Tag:
|
case FormFieldType.Tag:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -31,14 +31,16 @@ const handleCheckChange = ({
|
|||||||
(value: string) => value !== item.id.toString(),
|
(value: string) => value !== item.id.toString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const newValue = {
|
const newValue = newParentValues?.length
|
||||||
...currentValue,
|
? {
|
||||||
[parentId]: newParentValues,
|
...currentValue,
|
||||||
};
|
[parentId]: newParentValues,
|
||||||
|
}
|
||||||
|
: { ...currentValue };
|
||||||
|
|
||||||
if (newValue[parentId].length === 0) {
|
// if (newValue[parentId].length === 0) {
|
||||||
delete newValue[parentId];
|
// delete newValue[parentId];
|
||||||
}
|
// }
|
||||||
|
|
||||||
return field.onChange(newValue);
|
return field.onChange(newValue);
|
||||||
} else {
|
} else {
|
||||||
@ -66,20 +68,31 @@ const FilterItem = memo(
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center justify-between text-text-primary text-xs ${level > 0 ? 'ml-4' : ''}`}
|
className={`flex items-center justify-between text-text-primary text-xs ${level > 0 ? 'ml-1' : ''}`}
|
||||||
>
|
>
|
||||||
<FormItem className="flex flex-row space-x-3 space-y-0 items-center">
|
<FormItem className="flex flex-row space-x-3 space-y-0 items-center ">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Checkbox
|
<div className="flex space-x-3">
|
||||||
checked={field.value?.includes(item.id.toString())}
|
<Checkbox
|
||||||
onCheckedChange={(checked: boolean) =>
|
checked={field.value?.includes(item.id.toString())}
|
||||||
handleCheckChange({ checked, field, item })
|
onCheckedChange={(checked: boolean) =>
|
||||||
}
|
handleCheckChange({ checked, field, item })
|
||||||
/>
|
}
|
||||||
|
// className="hidden group-hover:block"
|
||||||
|
/>
|
||||||
|
<FormLabel
|
||||||
|
onClick={() =>
|
||||||
|
handleCheckChange({
|
||||||
|
checked: !field.value?.includes(item.id.toString()),
|
||||||
|
field,
|
||||||
|
item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</FormLabel>
|
||||||
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormLabel onClick={(e) => e.stopPropagation()}>
|
|
||||||
{item.label}
|
|
||||||
</FormLabel>
|
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{item.count !== undefined && (
|
{item.count !== undefined && (
|
||||||
<span className="text-sm">{item.count}</span>
|
<span className="text-sm">{item.count}</span>
|
||||||
@ -107,11 +120,11 @@ export const FilterField = memo(
|
|||||||
<FormField
|
<FormField
|
||||||
key={item.id}
|
key={item.id}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={parent.field as string}
|
name={parent.field?.toString() as string}
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
if (hasNestedList) {
|
if (hasNestedList) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col gap-2 ${level > 0 ? 'ml-4' : ''}`}>
|
<div className={`flex flex-col gap-2 ${level > 0 ? 'ml-1' : ''}`}>
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between cursor-pointer"
|
className="flex items-center justify-between cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -138,23 +151,6 @@ export const FilterField = memo(
|
|||||||
}}
|
}}
|
||||||
level={level + 1}
|
level={level + 1}
|
||||||
/>
|
/>
|
||||||
// <FilterItem key={child.id} item={child} field={child.field} level={level+1} />
|
|
||||||
// <div
|
|
||||||
// className="flex flex-row space-x-3 space-y-0 items-center"
|
|
||||||
// key={child.id}
|
|
||||||
// >
|
|
||||||
// <FormControl>
|
|
||||||
// <Checkbox
|
|
||||||
// checked={field.value?.includes(child.id.toString())}
|
|
||||||
// onCheckedChange={(checked) =>
|
|
||||||
// handleCheckChange({ checked, field, item: child })
|
|
||||||
// }
|
|
||||||
// />
|
|
||||||
// </FormControl>
|
|
||||||
// <FormLabel onClick={(e) => e.stopPropagation()}>
|
|
||||||
// {child.label}
|
|
||||||
// </FormLabel>
|
|
||||||
// </div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { FieldPath, useForm } from 'react-hook-form';
|
||||||
import { ZodArray, ZodString, z } from 'zod';
|
import { ZodArray, ZodString, z } from 'zod';
|
||||||
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -178,7 +178,9 @@ function CheckboxFormMultiple({
|
|||||||
<FormField
|
<FormField
|
||||||
key={x.field}
|
key={x.field}
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={x.field}
|
name={
|
||||||
|
x.field.toString() as FieldPath<z.infer<typeof FormSchema>>
|
||||||
|
}
|
||||||
render={() => (
|
render={() => (
|
||||||
<FormItem className="space-y-4">
|
<FormItem className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
@ -186,19 +188,20 @@ function CheckboxFormMultiple({
|
|||||||
{x.label}
|
{x.label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
</div>
|
</div>
|
||||||
{x.list.map((item) => {
|
{x.list?.length &&
|
||||||
return (
|
x.list.map((item) => {
|
||||||
<FilterField
|
return (
|
||||||
key={item.id}
|
<FilterField
|
||||||
item={{ ...item }}
|
key={item.id}
|
||||||
parent={{
|
item={{ ...item }}
|
||||||
...x,
|
parent={{
|
||||||
id: x.field,
|
...x,
|
||||||
// field: `${x.field}${item.field ? '.' + item.field : ''}`,
|
id: x.field,
|
||||||
}}
|
// field: `${x.field}${item.field ? '.' + item.field : ''}`,
|
||||||
/>
|
}}
|
||||||
);
|
/>
|
||||||
})}
|
);
|
||||||
|
})}
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -176,6 +176,11 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
|
|||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
metadata: {
|
metadata: {
|
||||||
|
valueExists:
|
||||||
|
'Value already exists. Confirm to merge duplicates and combine all associated files.',
|
||||||
|
fieldNameExists:
|
||||||
|
'Field name already exists. Confirm to merge duplicates and combine all associated files.',
|
||||||
|
fieldExists: 'Field already exists.',
|
||||||
fieldSetting: 'Field settings',
|
fieldSetting: 'Field settings',
|
||||||
changesAffectNewParses: 'Changes affect new parses only.',
|
changesAffectNewParses: 'Changes affect new parses only.',
|
||||||
editMetadataForDataset: 'View and edit metadata for ',
|
editMetadataForDataset: 'View and edit metadata for ',
|
||||||
@ -190,6 +195,7 @@ Procedural Memory: Learned skills, habits, and automated procedures.`,
|
|||||||
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',
|
||||||
},
|
},
|
||||||
metadataField: 'Metadata field',
|
metadataField: 'Metadata field',
|
||||||
systemAttribute: 'System attribute',
|
systemAttribute: 'System attribute',
|
||||||
|
|||||||
@ -182,6 +182,10 @@ export default {
|
|||||||
description: '描述',
|
description: '描述',
|
||||||
fieldName: '字段名',
|
fieldName: '字段名',
|
||||||
editMetadata: '编辑元数据',
|
editMetadata: '编辑元数据',
|
||||||
|
valueExists: '值已存在。确认合并重复项并组合所有关联文件。',
|
||||||
|
fieldNameExists: '字段名已存在。确认合并重复项并组合所有关联文件。',
|
||||||
|
fieldExists: '字段名已存在。',
|
||||||
|
deleteWarn: '此 {{field}} 将从所有关联文件中移除',
|
||||||
},
|
},
|
||||||
localUpload: '本地上传',
|
localUpload: '本地上传',
|
||||||
fileSize: '文件大小',
|
fileSize: '文件大小',
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import message from '@/components/ui/message';
|
import message from '@/components/ui/message';
|
||||||
import { useSetModalState } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import { useSetDocumentMeta } from '@/hooks/use-document-request';
|
import {
|
||||||
|
DocumentApiAction,
|
||||||
|
useSetDocumentMeta,
|
||||||
|
} from '@/hooks/use-document-request';
|
||||||
import kbService, {
|
import kbService, {
|
||||||
getMetaDataService,
|
getMetaDataService,
|
||||||
updateMetaData,
|
updateMetaData,
|
||||||
} from '@/services/knowledge-service';
|
} from '@/services/knowledge-service';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
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';
|
||||||
@ -191,7 +194,7 @@ export const useManageMetaDataModal = (
|
|||||||
const { data, loading } = useFetchMetaDataManageData(type);
|
const { data, loading } = useFetchMetaDataManageData(type);
|
||||||
|
|
||||||
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
const [tableData, setTableData] = useState<IMetaDataTableData[]>(metaData);
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const { operations, addDeleteRow, addDeleteValue, addUpdateValue } =
|
const { operations, addDeleteRow, addDeleteValue, addUpdateValue } =
|
||||||
useMetadataOperations();
|
useMetadataOperations();
|
||||||
|
|
||||||
@ -259,11 +262,14 @@ export const useManageMetaDataModal = (
|
|||||||
data: operations,
|
data: operations,
|
||||||
});
|
});
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [DocumentApiAction.FetchDocumentList],
|
||||||
|
});
|
||||||
message.success(t('message.operated'));
|
message.success(t('message.operated'));
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[operations, id, t],
|
[operations, id, t, queryClient],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveUpdateSingle = useCallback(
|
const handleSaveUpdateSingle = useCallback(
|
||||||
|
|||||||
@ -39,6 +39,7 @@ export type IManageModalProps = {
|
|||||||
|
|
||||||
export interface IManageValuesProps {
|
export interface IManageValuesProps {
|
||||||
title: ReactNode;
|
title: ReactNode;
|
||||||
|
existsKeys: string[];
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
isEditField?: boolean;
|
isEditField?: boolean;
|
||||||
isAddValue?: boolean;
|
isAddValue?: boolean;
|
||||||
@ -46,6 +47,7 @@ export interface IManageValuesProps {
|
|||||||
isShowValueSwitch?: boolean;
|
isShowValueSwitch?: boolean;
|
||||||
isVerticalShowValue?: boolean;
|
isVerticalShowValue?: boolean;
|
||||||
data: IMetaDataTableData;
|
data: IMetaDataTableData;
|
||||||
|
type: MetadataType;
|
||||||
hideModal: () => void;
|
hideModal: () => void;
|
||||||
onSave: (data: IMetaDataTableData) => void;
|
onSave: (data: IMetaDataTableData) => void;
|
||||||
addUpdateValue: (key: string, value: string | string[]) => void;
|
addUpdateValue: (key: string, value: string | string[]) => void;
|
||||||
|
|||||||
@ -54,6 +54,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
values: [],
|
values: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [currentValueIndex, setCurrentValueIndex] = useState<number>(0);
|
||||||
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
const [deleteDialogContent, setDeleteDialogContent] = useState({
|
||||||
visible: false,
|
visible: false,
|
||||||
title: '',
|
title: '',
|
||||||
@ -94,12 +95,12 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
description: '',
|
description: '',
|
||||||
values: [],
|
values: [],
|
||||||
});
|
});
|
||||||
// setCurrentValueIndex(tableData.length || 0);
|
setCurrentValueIndex(tableData.length || 0);
|
||||||
showManageValuesModal();
|
showManageValuesModal();
|
||||||
};
|
};
|
||||||
const handleEditValueRow = useCallback(
|
const handleEditValueRow = useCallback(
|
||||||
(data: IMetaDataTableData) => {
|
(data: IMetaDataTableData, index: number) => {
|
||||||
// setCurrentValueIndex(index);
|
setCurrentValueIndex(index);
|
||||||
setValueData(data);
|
setValueData(data);
|
||||||
showManageValuesModal();
|
showManageValuesModal();
|
||||||
},
|
},
|
||||||
@ -153,10 +154,33 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
variant={'delete'}
|
variant={'delete'}
|
||||||
className="p-0 bg-transparent"
|
className="p-0 bg-transparent"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteSingleValue(
|
setDeleteDialogContent({
|
||||||
row.getValue('field'),
|
visible: true,
|
||||||
value,
|
title:
|
||||||
);
|
t('common.delete') +
|
||||||
|
' ' +
|
||||||
|
t('knowledgeDetails.metadata.metadata'),
|
||||||
|
name: row.getValue('field') + '/' + value,
|
||||||
|
warnText: t(
|
||||||
|
'knowledgeDetails.metadata.deleteWarn',
|
||||||
|
{
|
||||||
|
field:
|
||||||
|
t('knowledgeDetails.metadata.field') +
|
||||||
|
'/' +
|
||||||
|
t('knowledgeDetails.metadata.values'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onOk: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
handleDeleteSingleValue(
|
||||||
|
row.getValue('field'),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
hideDeleteModal();
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 />
|
<Trash2 />
|
||||||
@ -185,7 +209,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
variant={'ghost'}
|
variant={'ghost'}
|
||||||
className="bg-transparent px-1 py-0"
|
className="bg-transparent px-1 py-0"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleEditValueRow(row.original);
|
handleEditValueRow(row.original, row.index);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Settings />
|
<Settings />
|
||||||
@ -201,7 +225,9 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
' ' +
|
' ' +
|
||||||
t('knowledgeDetails.metadata.metadata'),
|
t('knowledgeDetails.metadata.metadata'),
|
||||||
name: row.getValue('field'),
|
name: row.getValue('field'),
|
||||||
warnText: t('knowledgeDetails.metadata.deleteWarn'),
|
warnText: t('knowledgeDetails.metadata.deleteWarn', {
|
||||||
|
field: t('knowledgeDetails.metadata.field'),
|
||||||
|
}),
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
hideDeleteModal();
|
hideDeleteModal();
|
||||||
handleDeleteSingleRow(row.getValue('field'));
|
handleDeleteSingleRow(row.getValue('field'));
|
||||||
@ -243,12 +269,26 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
|
|
||||||
const handleSaveValues = (data: IMetaDataTableData) => {
|
const handleSaveValues = (data: IMetaDataTableData) => {
|
||||||
setTableData((prev) => {
|
setTableData((prev) => {
|
||||||
//If the keys are the same, they need to be merged.
|
let newData;
|
||||||
const fieldMap = new Map<string, any>();
|
if (currentValueIndex >= prev.length) {
|
||||||
|
// Add operation
|
||||||
|
newData = [...prev, data];
|
||||||
|
} else {
|
||||||
|
// Edit operation
|
||||||
|
newData = prev.map((item, index) => {
|
||||||
|
if (index === currentValueIndex) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
prev.forEach((item) => {
|
// Deduplicate by field and merge values
|
||||||
|
const fieldMap = new Map<string, IMetaDataTableData>();
|
||||||
|
newData.forEach((item) => {
|
||||||
if (fieldMap.has(item.field)) {
|
if (fieldMap.has(item.field)) {
|
||||||
const existingItem = fieldMap.get(item.field);
|
// Merge values if field exists
|
||||||
|
const existingItem = fieldMap.get(item.field)!;
|
||||||
const mergedValues = [
|
const mergedValues = [
|
||||||
...new Set([...existingItem.values, ...item.values]),
|
...new Set([...existingItem.values, ...item.values]),
|
||||||
];
|
];
|
||||||
@ -258,20 +298,14 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fieldMap.has(data.field)) {
|
|
||||||
const existingItem = fieldMap.get(data.field);
|
|
||||||
const mergedValues = [
|
|
||||||
...new Set([...existingItem.values, ...data.values]),
|
|
||||||
];
|
|
||||||
fieldMap.set(data.field, { ...existingItem, values: mergedValues });
|
|
||||||
} else {
|
|
||||||
fieldMap.set(data.field, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(fieldMap.values());
|
return Array.from(fieldMap.values());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const existsKeys = useMemo(() => {
|
||||||
|
return tableData.map((item) => item.field);
|
||||||
|
}, [tableData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@ -357,6 +391,8 @@ export const ManageMetadataModal = (props: IManageModalProps) => {
|
|||||||
: t('knowledgeDetails.metadata.editMetadata')}
|
: t('knowledgeDetails.metadata.editMetadata')}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
type={metadataType}
|
||||||
|
existsKeys={existsKeys}
|
||||||
visible={manageValuesVisible}
|
visible={manageValuesVisible}
|
||||||
hideModal={hideManageValuesModal}
|
hideModal={hideManageValuesModal}
|
||||||
data={valueData}
|
data={valueData}
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
import {
|
||||||
|
ConfirmDeleteDialog,
|
||||||
|
ConfirmDeleteDialogNode,
|
||||||
|
} from '@/components/confirm-delete-dialog';
|
||||||
import EditTag from '@/components/edit-tag';
|
import EditTag from '@/components/edit-tag';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
@ -7,6 +11,7 @@ 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, useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { MetadataType } from './hook';
|
||||||
import { IManageValuesProps, IMetaDataTableData } from './interface';
|
import { IManageValuesProps, IMetaDataTableData } 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
|
||||||
@ -63,17 +68,62 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
onSave,
|
onSave,
|
||||||
addUpdateValue,
|
addUpdateValue,
|
||||||
addDeleteValue,
|
addDeleteValue,
|
||||||
|
existsKeys,
|
||||||
|
type,
|
||||||
} = props;
|
} = props;
|
||||||
const [metaData, setMetaData] = useState(data);
|
const [metaData, setMetaData] = useState(data);
|
||||||
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
|
// Use functional update to avoid closure issues
|
||||||
const handleChange = useCallback((field: string, value: any) => {
|
const handleChange = useCallback(
|
||||||
setMetaData((prev) => ({
|
(field: string, value: any) => {
|
||||||
...prev,
|
if (field === 'field' && existsKeys.includes(value)) {
|
||||||
[field]: 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
|
// Maintain separate state for each input box
|
||||||
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
|
const [tempValues, setTempValues] = useState<string[]>([...data.values]);
|
||||||
@ -89,6 +139,9 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
}, [hideModal]);
|
}, [hideModal]);
|
||||||
|
|
||||||
const handleSave = useCallback(() => {
|
const handleSave = useCallback(() => {
|
||||||
|
if (type === MetadataType.Setting && valueError.field) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
|
if (!metaData.restrictDefinedValues && isShowValueSwitch) {
|
||||||
const newMetaData = { ...metaData, values: [] };
|
const newMetaData = { ...metaData, values: [] };
|
||||||
onSave(newMetaData);
|
onSave(newMetaData);
|
||||||
@ -96,17 +149,35 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
onSave(metaData);
|
onSave(metaData);
|
||||||
}
|
}
|
||||||
handleHideModal();
|
handleHideModal();
|
||||||
}, [metaData, onSave, handleHideModal, isShowValueSwitch]);
|
}, [metaData, onSave, handleHideModal, isShowValueSwitch, type, valueError]);
|
||||||
|
|
||||||
// Handle value changes, only update temporary state
|
// Handle value changes, only update temporary state
|
||||||
const handleValueChange = useCallback((index: number, value: string) => {
|
const handleValueChange = useCallback(
|
||||||
setTempValues((prev) => {
|
(index: number, value: string) => {
|
||||||
const newValues = [...prev];
|
setTempValues((prev) => {
|
||||||
newValues[index] = value;
|
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;
|
return newValues;
|
||||||
});
|
});
|
||||||
}, []);
|
},
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
// Handle blur event, synchronize to main state
|
// Handle blur event, synchronize to main state
|
||||||
const handleValueBlur = useCallback(() => {
|
const handleValueBlur = useCallback(() => {
|
||||||
@ -137,6 +208,27 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
[addDeleteValue, metaData],
|
[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
|
// Handle adding new value
|
||||||
const handleAddValue = useCallback(() => {
|
const handleAddValue = useCallback(() => {
|
||||||
setTempValues((prev) => [...new Set([...prev, ''])]);
|
setTempValues((prev) => [...new Set([...prev, ''])]);
|
||||||
@ -172,9 +264,13 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
<Input
|
<Input
|
||||||
value={metaData.field}
|
value={metaData.field}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleChange('field', e.target?.value || '');
|
const value = e.target?.value || '';
|
||||||
|
if (/^[a-zA-Z_]*$/.test(value)) {
|
||||||
|
handleChange('field', value);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<div className="text-state-error text-sm">{valueError.field}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -230,7 +326,11 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
item={item}
|
item={item}
|
||||||
index={index}
|
index={index}
|
||||||
onValueChange={handleValueChange}
|
onValueChange={handleValueChange}
|
||||||
onDelete={handleDelete}
|
onDelete={(idx: number) => {
|
||||||
|
showDeleteModal(item, () => {
|
||||||
|
handleDelete(idx);
|
||||||
|
});
|
||||||
|
}}
|
||||||
onBlur={handleValueBlur}
|
onBlur={handleValueBlur}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -240,11 +340,41 @@ export const ManageValuesModal = (props: IManageValuesProps) => {
|
|||||||
{!isVerticalShowValue && (
|
{!isVerticalShowValue && (
|
||||||
<EditTag
|
<EditTag
|
||||||
value={metaData.values}
|
value={metaData.values}
|
||||||
onChange={(value) => handleChange('values', value)}
|
onChange={(value) => {
|
||||||
|
// find deleted value
|
||||||
|
const item = metaData.values.find(
|
||||||
|
(item) => !value.includes(item),
|
||||||
|
);
|
||||||
|
if (item) {
|
||||||
|
showDeleteModal(item, () => {
|
||||||
|
// handleDelete(idx);
|
||||||
|
handleChange('values', value);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleChange('values', value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<div className="text-state-error text-sm">{valueError.values}</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{deleteDialogContent.visible && (
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
open={deleteDialogContent.visible}
|
||||||
|
onCancel={deleteDialogContent.onCancel}
|
||||||
|
onOk={deleteDialogContent.onOk}
|
||||||
|
title={deleteDialogContent.title}
|
||||||
|
content={{
|
||||||
|
node: (
|
||||||
|
<ConfirmDeleteDialogNode
|
||||||
|
name={deleteDialogContent.name}
|
||||||
|
warnText={deleteDialogContent.warnText}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { FormLayout } from '@/constants/form';
|
import { FormLayout } from '@/constants/form';
|
||||||
|
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||||
import { IModalProps } from '@/interfaces/common';
|
import { IModalProps } from '@/interfaces/common';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@ -33,6 +34,7 @@ const FormId = 'dataset-creating-form';
|
|||||||
|
|
||||||
export function InputForm({ onOk }: IModalProps<any>) {
|
export function InputForm({ onOk }: IModalProps<any>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { data: tenantInfo } = useFetchTenantInfo();
|
||||||
|
|
||||||
const FormSchema = z
|
const FormSchema = z
|
||||||
.object({
|
.object({
|
||||||
@ -80,7 +82,7 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
|||||||
name: '',
|
name: '',
|
||||||
parseType: 1,
|
parseType: 1,
|
||||||
parser_id: '',
|
parser_id: '',
|
||||||
embd_id: '',
|
embd_id: tenantInfo?.embd_id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -263,7 +263,7 @@ export const documentFilter = (kb_id: string) =>
|
|||||||
export const getMetaDataService = ({ kb_id }: { kb_id: string }) =>
|
export const getMetaDataService = ({ kb_id }: { kb_id: string }) =>
|
||||||
request.post(api.getMetaData, { data: { kb_id } });
|
request.post(api.getMetaData, { data: { kb_id } });
|
||||||
export const updateMetaData = ({ kb_id, data }: { kb_id: string; data: any }) =>
|
export const updateMetaData = ({ kb_id, data }: { kb_id: string; data: any }) =>
|
||||||
request.post(api.updateMetaData, { data: { kb_id, data } });
|
request.post(api.updateMetaData, { data: { kb_id, ...data } });
|
||||||
|
|
||||||
export const listDataPipelineLogDocument = (
|
export const listDataPipelineLogDocument = (
|
||||||
params?: IFetchKnowledgeListRequestParams,
|
params?: IFetchKnowledgeListRequestParams,
|
||||||
|
|||||||
Reference in New Issue
Block a user