From 5a7026cf552d7d8631c6440bbd0208e92ab0eda4 Mon Sep 17 00:00:00 2001 From: chanx <1243304602@qq.com> Date: Wed, 21 Jan 2026 11:31:26 +0800 Subject: [PATCH] Feat: Improve metadata logic (#12730) ### What problem does this PR solve? Feat: Improve metadata logic ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/components/dynamic-form.tsx | 4 +- web/src/components/ui/input-date.tsx | 108 +++++ web/src/components/ui/input-select.tsx | 308 ++++++++++++--- web/src/hooks/logic-hooks.ts | 1 - web/src/locales/en.ts | 6 +- web/src/locales/zh.ts | 4 + .../metedata/hooks/use-manage-modal.ts | 352 +++++++++-------- .../metedata/hooks/use-manage-values-modal.ts | 103 ++--- .../dataset/components/metedata/interface.ts | 18 +- .../metedata/manage-modal-column.tsx | 372 ++++++++++++++++++ .../components/metedata/manage-modal.tsx | 354 +++-------------- .../metedata/manage-values-modal.tsx | 205 +++++++--- web/src/pages/dataset/dataset/index.tsx | 107 +++-- .../dataset/use-bulk-operate-dataset.tsx | 7 +- .../dataset/use-dataset-table-columns.tsx | 14 +- web/src/services/knowledge-service.ts | 20 +- 16 files changed, 1318 insertions(+), 665 deletions(-) create mode 100644 web/src/components/ui/input-date.tsx create mode 100644 web/src/pages/dataset/components/metedata/manage-modal-column.tsx diff --git a/web/src/components/dynamic-form.tsx b/web/src/components/dynamic-form.tsx index 1900d5794..3dd869690 100644 --- a/web/src/components/dynamic-form.tsx +++ b/web/src/components/dynamic-form.tsx @@ -834,6 +834,7 @@ const DynamicForm = { useImperativeHandle( ref, () => ({ + form: form, submit: () => { form.handleSubmit((values) => { const filteredValues = filterActiveValues(values); @@ -938,7 +939,6 @@ const DynamicForm = { ) as ( props: DynamicFormProps & { ref?: React.Ref }, ) => React.ReactElement, - SavingButton: ({ submitLoading, buttonText, @@ -1015,4 +1015,6 @@ const DynamicForm = { }, }; +DynamicForm.Root.displayName = 'DynamicFormRoot'; + export { DynamicForm }; diff --git a/web/src/components/ui/input-date.tsx b/web/src/components/ui/input-date.tsx new file mode 100644 index 000000000..4e8daa495 --- /dev/null +++ b/web/src/components/ui/input-date.tsx @@ -0,0 +1,108 @@ +import { Calendar } from '@/components/originui/calendar'; +import { Input } from '@/components/ui/input'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from '@/components/ui/popover'; +import { cn } from '@/lib/utils'; +import { Locale } from 'date-fns'; +import dayjs from 'dayjs'; +import { Calendar as CalendarIcon } from 'lucide-react'; +import * as React from 'react'; + +interface DateInputProps extends Omit< + React.InputHTMLAttributes, + 'value' | 'onChange' +> { + value?: Date; + onChange?: (date: Date | undefined) => void; + showTimeSelect?: boolean; + dateFormat?: string; + timeFormat?: string; + showTimeSelectOnly?: boolean; + showTimeInput?: boolean; + timeInputLabel?: string; + locale?: Locale; // Support for internationalization +} + +const DateInput = React.forwardRef( + ( + { + className, + value, + onChange, + dateFormat = 'DD/MM/YYYY', + timeFormat = 'HH:mm:ss', + showTimeSelect = false, + showTimeSelectOnly = false, + showTimeInput = false, + timeInputLabel = '', + ...props + }, + ref, + ) => { + const [open, setOpen] = React.useState(false); + + const handleDateSelect = (date: Date | undefined) => { + onChange?.(date); + setOpen(false); + }; + + // Determine display format based on the type of date picker + let displayFormat = dateFormat; + if (showTimeSelect) { + displayFormat = `${dateFormat} ${timeFormat}`; + } else if (showTimeSelectOnly) { + displayFormat = timeFormat; + } + + // Format the date according to the specified format + const formattedValue = React.useMemo(() => { + return value && !isNaN(value.getTime()) + ? dayjs(value).format(displayFormat) + : ''; + }, [value, displayFormat]); + + return ( +
+ + +
+ + +
+
+ + + +
+
+ ); + }, +); + +DateInput.displayName = 'DateInput'; + +export { DateInput }; diff --git a/web/src/components/ui/input-select.tsx b/web/src/components/ui/input-select.tsx index 9c7099944..513411865 100644 --- a/web/src/components/ui/input-select.tsx +++ b/web/src/components/ui/input-select.tsx @@ -1,5 +1,6 @@ import { Input } from '@/components/ui/input'; import { cn } from '@/lib/utils'; +import { isEmpty } from 'lodash'; import { X } from 'lucide-react'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; @@ -17,10 +18,12 @@ export interface InputSelectOption { export interface InputSelectProps { /** Options for the select component */ options?: InputSelectOption[]; - /** Selected values - string for single select, array for multi select */ - value?: string | string[]; + /** Selected values - type depends on the input type */ + value?: string | string[] | number | number[] | Date | Date[]; /** Callback when value changes */ - onChange?: (value: string | string[]) => void; + onChange?: ( + value: string | string[] | number | number[] | Date | Date[], + ) => void; /** Placeholder text */ placeholder?: string; /** Additional class names */ @@ -29,6 +32,8 @@ export interface InputSelectProps { style?: React.CSSProperties; /** Whether to allow multiple selections */ multi?: boolean; + /** Type of input: text, number, date, or datetime */ + type?: 'text' | 'number' | 'date' | 'datetime'; } const InputSelect = React.forwardRef( @@ -41,6 +46,7 @@ const InputSelect = React.forwardRef( className, style, multi = false, + type = 'text', }, ref, ) => { @@ -50,36 +56,108 @@ const InputSelect = React.forwardRef( const inputRef = React.useRef(null); const { t } = useTranslation(); - // Normalize value to array for consistent handling - const normalizedValue = Array.isArray(value) ? value : value ? [value] : []; + // Normalize value to array for consistent handling based on type + const normalizedValue = React.useMemo(() => { + if (Array.isArray(value)) { + return value; + } else if (value !== undefined && value !== null) { + if (type === 'number') { + return typeof value === 'number' ? [value] : [Number(value)]; + } else if (type === 'date' || type === 'datetime') { + return value instanceof Date ? [value] : [new Date(value as any)]; + } else { + return typeof value === 'string' ? [value] : [String(value)]; + } + } else { + return []; + } + }, [value, type]); /** * Removes a tag from the selected values * @param tagValue - The value of the tag to remove */ - const handleRemoveTag = (tagValue: string) => { - const newValue = normalizedValue.filter((v) => v !== tagValue); + const handleRemoveTag = (tagValue: any) => { + let newValue: any[]; + + if (type === 'number') { + newValue = (normalizedValue as number[]).filter((v) => v !== tagValue); + } else if (type === 'date' || type === 'datetime') { + newValue = (normalizedValue as Date[]).filter( + (v) => v.getTime() !== tagValue.getTime(), + ); + } else { + newValue = (normalizedValue as string[]).filter((v) => v !== tagValue); + } + // Return single value if not multi-select, otherwise return array - onChange?.(multi ? newValue : newValue[0] || ''); + let result: string | number | Date | string[] | number[] | Date[]; + if (multi) { + result = newValue; + } else { + if (type === 'number') { + result = newValue[0] || 0; + } else if (type === 'date' || type === 'datetime') { + result = newValue[0] || new Date(); + } else { + result = newValue[0] || ''; + } + } + + onChange?.(result); }; /** * Adds a tag to the selected values * @param optionValue - The value of the tag to add */ - const handleAddTag = (optionValue: string) => { - let newValue: string[]; + const handleAddTag = (optionValue: any) => { + let newValue: any[]; if (multi) { // For multi-select, add to array if not already included - if (!normalizedValue.includes(optionValue)) { - newValue = [...normalizedValue, optionValue]; - onChange?.(newValue); + if (type === 'number') { + const numValue = + typeof optionValue === 'number' ? optionValue : Number(optionValue); + if ( + !(normalizedValue as number[]).includes(numValue) && + !isNaN(numValue) + ) { + newValue = [...(normalizedValue as number[]), numValue]; + onChange?.(newValue as number[]); + } + } else if (type === 'date' || type === 'datetime') { + const dateValue = + optionValue instanceof Date ? optionValue : new Date(optionValue); + if ( + !(normalizedValue as Date[]).some( + (d) => d.getTime() === dateValue.getTime(), + ) + ) { + newValue = [...(normalizedValue as Date[]), dateValue]; + onChange?.(newValue as Date[]); + } + } else { + if (!(normalizedValue as string[]).includes(optionValue)) { + newValue = [...(normalizedValue as string[]), optionValue]; + onChange?.(newValue as string[]); + } } } else { // For single-select, replace the value - newValue = [optionValue]; - onChange?.(optionValue); + if (type === 'number') { + const numValue = + typeof optionValue === 'number' ? optionValue : Number(optionValue); + if (!isNaN(numValue)) { + onChange?.(numValue); + } + } else if (type === 'date' || type === 'datetime') { + const dateValue = + optionValue instanceof Date ? optionValue : new Date(optionValue); + onChange?.(dateValue); + } else { + onChange?.(optionValue); + } } setInputValue(''); @@ -89,16 +167,7 @@ const InputSelect = React.forwardRef( const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; setInputValue(newValue); - setOpen(newValue.length > 0); // Open popover when there's input - - // If input matches an option exactly, add it - const matchedOption = options.find( - (opt) => opt.label.toLowerCase() === newValue.toLowerCase(), - ); - - if (matchedOption && !normalizedValue.includes(matchedOption.value)) { - handleAddTag(matchedOption.value); - } + setOpen(!!newValue); // Open popover when there's input }; const handleKeyDown = (e: React.KeyboardEvent) => { @@ -111,9 +180,37 @@ const InputSelect = React.forwardRef( const newValue = [...normalizedValue]; newValue.pop(); // Return single value if not multi-select, otherwise return array - onChange?.(multi ? newValue : newValue[0] || ''); + let result: string | number | Date | string[] | number[] | Date[]; + if (multi) { + result = newValue; + } else { + if (type === 'number') { + result = newValue[0] || 0; + } else if (type === 'date' || type === 'datetime') { + result = newValue[0] || new Date(); + } else { + result = newValue[0] || ''; + } + } + + onChange?.(result); } else if (e.key === 'Enter' && inputValue.trim() !== '') { e.preventDefault(); + + let valueToAdd: any; + + if (type === 'number') { + const numValue = Number(inputValue); + if (isNaN(numValue)) return; // Don't add invalid numbers + valueToAdd = numValue; + } else if (type === 'date' || type === 'datetime') { + const dateValue = new Date(inputValue); + if (isNaN(dateValue.getTime())) return; // Don't add invalid dates + valueToAdd = dateValue; + } else { + valueToAdd = inputValue; + } + // Add input value as a new tag if it doesn't exist in options const matchedOption = options.find( (opt) => opt.label.toLowerCase() === inputValue.toLowerCase(), @@ -124,10 +221,16 @@ const InputSelect = React.forwardRef( } else { // If not in options, create a new tag with the input value if ( - !normalizedValue.includes(inputValue) && + !normalizedValue.some((v) => + type === 'number' + ? Number(v) === Number(valueToAdd) + : type === 'date' || type === 'datetime' + ? new Date(v as any).getTime() === valueToAdd.getTime() + : String(v) === valueToAdd, + ) && inputValue.trim() !== '' ) { - handleAddTag(inputValue); + handleAddTag(valueToAdd); } } } else if (e.key === 'Escape') { @@ -160,26 +263,68 @@ const InputSelect = React.forwardRef( // Filter options to exclude already selected ones (only for multi-select) const availableOptions = multi - ? options.filter((option) => !normalizedValue.includes(option.value)) + ? options.filter( + (option) => + !normalizedValue.some((v) => + type === 'number' + ? Number(v) === Number(option.value) + : type === 'date' || type === 'datetime' + ? new Date(v as any).getTime() === + new Date(option.value).getTime() + : String(v) === option.value, + ), + ) : options; const filteredOptions = availableOptions.filter( (option) => !inputValue || - option.label.toLowerCase().includes(inputValue.toLowerCase()), + option.label + .toLowerCase() + .includes(inputValue.toString().toLowerCase()), ); // If there are no matching options but there is an input value, create a new option with the input value - const hasMatchingOptions = filteredOptions.length > 0; - const showInputAsOption = - inputValue && - !hasMatchingOptions && - !normalizedValue.includes(inputValue); + const showInputAsOption = React.useMemo(() => { + if (!inputValue) return false; + + const hasLabelMatch = options.some( + (option) => + option.label.toLowerCase() === inputValue.toString().toLowerCase(), + ); + + let isAlreadySelected = false; + if (type === 'number') { + const numValue = Number(inputValue); + isAlreadySelected = + !isNaN(numValue) && (normalizedValue as number[]).includes(numValue); + } else if (type === 'date' || type === 'datetime') { + const dateValue = new Date(inputValue); + isAlreadySelected = + !isNaN(dateValue.getTime()) && + (normalizedValue as Date[]).some( + (d) => d.getTime() === dateValue.getTime(), + ); + } else { + isAlreadySelected = (normalizedValue as string[]).includes(inputValue); + } + console.log( + 'showInputAsOption', + hasLabelMatch, + isAlreadySelected, + inputValue.toString().trim(), + ); + return ( + !hasLabelMatch && + !isAlreadySelected && + inputValue.toString().trim() !== '' + ); + }, [inputValue, options, normalizedValue, type]); const triggerElement = (
( > {/* Render selected tags - only show tags if multi is true or if single select has a value */} {multi && - normalizedValue.map((tagValue) => { - const option = options.find((opt) => opt.value === tagValue) || { - value: tagValue, - label: tagValue, + normalizedValue.map((tagValue, index) => { + const option = options.find((opt) => + type === 'number' + ? Number(opt.value) === Number(tagValue) + : type === 'date' || type === 'datetime' + ? new Date(opt.value).getTime() === + new Date(tagValue).getTime() + : String(opt.value) === String(tagValue), + ) || { + value: String(tagValue), + label: String(tagValue), }; + return (
{option.label} @@ -215,11 +368,22 @@ const InputSelect = React.forwardRef( })} {/* For single select, show the selected value as text instead of a tag */} - {!multi && normalizedValue[0] && ( -
+ {!multi && !isEmpty(normalizedValue[0]) && ( +
- {options.find((opt) => opt.value === normalizedValue[0])?.label || - normalizedValue[0]} + {options.find((opt) => + type === 'number' + ? Number(opt.value) === Number(normalizedValue[0]) + : type === 'date' || type === 'datetime' + ? new Date(opt.value).getTime() === + new Date(normalizedValue[0]).getTime() + : String(opt.value) === String(normalizedValue[0]), + )?.label || + (type === 'number' + ? String(normalizedValue[0]) + : type === 'date' || type === 'datetime' + ? new Date(normalizedValue[0] as any).toLocaleString() + : String(normalizedValue[0]))}
+ )} +
+ + ); + })} + {hasMore && !expanded && ( +
...
+ )} +
+
+ ); + }, + }, + { + accessorKey: 'action', + header: () => {t('knowledgeDetails.metadata.action')}, + meta: { + cellClassName: 'w-12', + }, + cell: ({ row }) => ( +
+ + +
+ ), + }, + ]; + + if (!isShowDescription) { + return cols.filter((col) => { + if ('accessorKey' in col && col.accessorKey === 'description') { + return false; + } + return true; + }); + } + return cols; + }, [ + handleDeleteSingleRow, + t, + handleDeleteSingleValue, + isShowDescription, + isDeleteSingleValue, + handleEditValueRow, + metadataType, + expanded, + editingValue, + saveEditedValue, + showTypeColumn, + ]); + + return { + columns, + deleteDialogContent, + }; +}; diff --git a/web/src/pages/dataset/components/metedata/manage-modal.tsx b/web/src/pages/dataset/components/metedata/manage-modal.tsx index 68053b6bd..79ff390cb 100644 --- a/web/src/pages/dataset/components/metedata/manage-modal.tsx +++ b/web/src/pages/dataset/components/metedata/manage-modal.tsx @@ -1,3 +1,4 @@ +import { BulkOperateBar } from '@/components/bulk-operate-bar'; import { ConfirmDeleteDialog, ConfirmDeleteDialogNode, @@ -5,7 +6,6 @@ import { import { EmptyType } from '@/components/empty/constant'; import Empty from '@/components/empty/empty'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { Modal } from '@/components/ui/modal/modal'; import { Switch } from '@/components/ui/switch'; import { @@ -18,9 +18,9 @@ import { } from '@/components/ui/table'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { useSetModalState } from '@/hooks/common-hooks'; +import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection'; import { Routes } from '@/routes'; import { - ColumnDef, flexRender, getCoreRowModel, getFilteredRowModel, @@ -28,28 +28,22 @@ import { getSortedRowModel, useReactTable, } from '@tanstack/react-table'; -import { - ListChevronsDownUp, - ListChevronsUpDown, - Plus, - Settings, - Trash2, -} from 'lucide-react'; +import { Plus, Trash2 } from 'lucide-react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useHandleMenuClick } from '../../sidebar/hooks'; import { - MetadataDeleteMap, - MetadataType, getMetadataValueTypeLabel, - isMetadataValueTypeWithEnum, + MetadataType, useManageMetaDataModal, + useOperateData, } from './hooks/use-manage-modal'; import { IBuiltInMetadataItem, IManageModalProps, IMetaDataTableData, } from './interface'; +import { useMetadataColumns } from './manage-modal-column'; import { ManageValuesModal } from './manage-values-modal'; type MetadataSettingsTab = 'generation' | 'built-in'; @@ -71,6 +65,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => { isVerticalShowValue = true, builtInMetadata, success, + documentIds, } = props; const { t } = useTranslation(); const [valueData, setValueData] = useState({ @@ -80,25 +75,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => { valueType: 'string', }); - const [expanded, setExpanded] = useState(true); const [activeTab, setActiveTab] = useState('generation'); const [currentValueIndex, setCurrentValueIndex] = useState(0); const [builtInSelection, setBuiltInSelection] = useState< IBuiltInMetadataItem[] >([]); - const [deleteDialogContent, setDeleteDialogContent] = useState({ - visible: false, - title: '', - name: '', - warnText: '', - onOk: () => {}, - onCancel: () => {}, - }); - const [editingValue, setEditingValue] = useState<{ - field: string; - value: string; - newValue: string; - } | null>(null); const { tableData, @@ -108,7 +89,13 @@ export const ManageMetadataModal = (props: IManageModalProps) => { handleSave, addUpdateValue, addDeleteValue, - } = useManageMetaDataModal(originalTableData, metadataType, otherData); + handleDeleteBatchRow, + } = useManageMetaDataModal( + originalTableData, + metadataType, + otherData, + documentIds, + ); const { handleMenuClick } = useHandleMenuClick(); const [shouldSave, setShouldSave] = useState(false); const { @@ -116,20 +103,12 @@ export const ManageMetadataModal = (props: IManageModalProps) => { showModal: showManageValuesModal, hideModal: hideManageValuesModal, } = useSetModalState(); - const hideDeleteModal = () => { - setDeleteDialogContent({ - visible: false, - title: '', - name: '', - warnText: '', - onOk: () => {}, - onCancel: () => {}, - }); - }; const isSettingsMode = metadataType === MetadataType.Setting || - metadataType === MetadataType.SingleFileSetting; + metadataType === MetadataType.SingleFileSetting || + metadataType === MetadataType.UpdateSingle; + const showTypeColumn = isSettingsMode; const builtInRows = useMemo( () => [ @@ -183,31 +162,6 @@ export const ManageMetadataModal = (props: IManageModalProps) => { [builtInSelection], ); - const handleEditValue = (field: string, value: string) => { - setEditingValue({ field, value, newValue: value }); - }; - - const saveEditedValue = useCallback(() => { - if (editingValue) { - setTableData((prev) => { - return prev.map((row) => { - if (row.field === editingValue.field) { - const updatedValues = row.values.map((v) => - v === editingValue.value ? editingValue.newValue : v, - ); - return { ...row, values: updatedValues }; - } - return row; - }); - }); - setEditingValue(null); - setShouldSave(true); - } - }, [editingValue, setTableData]); - - const cancelEditValue = () => { - setEditingValue(null); - }; const handAddValueRow = () => { setValueData({ field: '', @@ -226,229 +180,19 @@ export const ManageMetadataModal = (props: IManageModalProps) => { }, [showManageValuesModal], ); - - const columns: ColumnDef[] = useMemo(() => { - const cols: ColumnDef[] = [ - { - accessorKey: 'field', - header: () => {t('knowledgeDetails.metadata.field')}, - cell: ({ row }) => ( -
- {row.getValue('field')} -
- ), - }, - ...(showTypeColumn - ? ([ - { - accessorKey: 'valueType', - header: () => Type, - cell: ({ row }) => ( -
- {getMetadataValueTypeLabel( - row.original.valueType as IMetaDataTableData['valueType'], - )} -
- ), - }, - ] as ColumnDef[]) - : []), - { - accessorKey: 'description', - header: () => {t('knowledgeDetails.metadata.description')}, - cell: ({ row }) => ( -
- {row.getValue('description')} -
- ), - }, - { - accessorKey: 'values', - header: () => ( -
- {t('knowledgeDetails.metadata.values')} -
{ - setExpanded(!expanded); - }} - > - {expanded ? ( - - ) : ( - - )} - {expanded} -
-
- ), - cell: ({ row }) => { - const values = row.getValue('values') as Array; - const supportsEnum = isMetadataValueTypeWithEnum( - row.original.valueType, - ); - - if (!supportsEnum || !Array.isArray(values) || values.length === 0) { - return
; - } - - const displayedValues = expanded ? values : values.slice(0, 2); - const hasMore = Array.isArray(values) && values.length > 2; - - return ( -
-
- {displayedValues?.map((value: string) => { - const isEditing = - editingValue && - editingValue.field === row.getValue('field') && - editingValue.value === value; - - return isEditing ? ( -
- - setEditingValue({ - ...editingValue, - newValue: e.target.value, - }) - } - onBlur={saveEditedValue} - onKeyDown={(e) => { - if (e.key === 'Enter') { - saveEditedValue(); - } else if (e.key === 'Escape') { - cancelEditValue(); - } - }} - autoFocus - // className="text-sm min-w-20 max-w-32 outline-none bg-transparent px-1 py-0.5" - /> -
- ) : ( - - )} -
- - ); - })} - {hasMore && !expanded && ( -
...
- )} -
-
- ); - }, - }, - { - accessorKey: 'action', - header: () => {t('knowledgeDetails.metadata.action')}, - meta: { - cellClassName: 'w-12', - }, - cell: ({ row }) => ( -
- - -
- ), - }, - ]; - if (!isShowDescription) { - return cols.filter((col) => col.accessorKey !== 'description'); - } - return cols; - }, [ - handleDeleteSingleRow, - t, - handleDeleteSingleValue, - isShowDescription, - isDeleteSingleValue, - handleEditValueRow, + const { rowSelection, rowSelectionIsEmpty, setRowSelection, selectedCount } = + useRowSelection(); + const { columns, deleteDialogContent } = useMetadataColumns({ + isDeleteSingleValue: !!isDeleteSingleValue, metadataType, - expanded, - editingValue, - saveEditedValue, + setTableData, + handleDeleteSingleValue, + handleDeleteSingleRow, + handleEditValueRow, + isShowDescription, showTypeColumn, - ]); + setShouldSave, + }); const table = useReactTable({ data: tableData as IMetaDataTableData[], @@ -457,7 +201,11 @@ export const ManageMetadataModal = (props: IManageModalProps) => { getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), + onRowSelectionChange: setRowSelection, manualPagination: true, + state: { + rowSelection, + }, }); const handleSaveValues = (data: IMetaDataTableData) => { @@ -506,7 +254,7 @@ export const ManageMetadataModal = (props: IManageModalProps) => { handleSave({ callback: () => {}, builtInMetadata: builtInSelection }); setShouldSave(false); }, 0); - + console.log('shouldSave'); return () => clearTimeout(timer); } }, [tableData, shouldSave, handleSave, builtInSelection]); @@ -515,6 +263,25 @@ export const ManageMetadataModal = (props: IManageModalProps) => { return tableData.map((item) => item.field); }, [tableData]); + const { handleDelete } = useOperateData({ + rowSelection, + list: tableData, + handleDeleteBatchRow, + }); + + const operateList = [ + { + id: 'delete', + label: t('common.delete'), + icon: , + onClick: async () => { + await handleDelete(); + // if (code === 0) { + // setRowSelection({}); + // } + }, + }, + ]; return ( <> { callback: hideModal, builtInMetadata: builtInSelection, }); - console.log('data', res); success?.(res); }} > @@ -559,15 +325,25 @@ export const ManageMetadataModal = (props: IManageModalProps) => { )} - {metadataType === MetadataType.Setting ? ( + + {rowSelectionIsEmpty || ( + + )} + {metadataType === MetadataType.Setting || + metadataType === MetadataType.SingleFileSetting ? ( setActiveTab(v as MetadataSettingsTab)} > - Generation + + {t('knowledgeDetails.metadata.generation')} + - {t('knowledgeConfiguration.builtIn')} + {t('knowledgeDetails.metadata.builtIn')} diff --git a/web/src/pages/dataset/components/metedata/manage-values-modal.tsx b/web/src/pages/dataset/components/metedata/manage-values-modal.tsx index 2498dd887..70be2b3ca 100644 --- a/web/src/pages/dataset/components/metedata/manage-values-modal.tsx +++ b/web/src/pages/dataset/components/metedata/manage-values-modal.tsx @@ -2,49 +2,87 @@ import { ConfirmDeleteDialog, ConfirmDeleteDialogNode, } from '@/components/confirm-delete-dialog'; +import { DynamicForm, FormFieldType } from '@/components/dynamic-form'; import EditTag from '@/components/edit-tag'; import { Button } from '@/components/ui/button'; -import { FormLabel } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; +import { DateInput } from '@/components/ui/input-date'; import { Modal } from '@/components/ui/modal/modal'; -import { RAGFlowSelect } from '@/components/ui/select'; -import { Textarea } from '@/components/ui/textarea'; +import { formatPureDate } from '@/utils/date'; +import dayjs from 'dayjs'; import { Plus, Trash2 } from 'lucide-react'; -import { memo } from 'react'; +import { memo, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { - isMetadataValueTypeWithEnum, - metadataValueTypeOptions, -} from './hooks/use-manage-modal'; +import { metadataValueTypeOptions } from './hooks/use-manage-modal'; import { useManageValues } from './hooks/use-manage-values-modal'; -import { IManageValuesProps } from './interface'; +import { IManageValuesProps, MetadataValueType } from './interface'; // Create a separate input component, wrapped with memo to avoid unnecessary re-renders const ValueInputItem = memo( ({ item, index, + type, onValueChange, onDelete, onBlur, }: { item: string; index: number; - onValueChange: (index: number, value: string) => void; + type: MetadataValueType; + onValueChange: (index: number, value: string, isUpdate?: boolean) => void; onDelete: (index: number) => void; onBlur: (index: number) => void; }) => { + const value = useMemo(() => { + if (type === 'time') { + if (item) { + try { + // Using dayjs to parse date strings in various formats including DD/MM/YYYY + const parsedDate = dayjs(item, [ + 'YYYY-MM-DD', + 'DD/MM/YYYY', + 'MM/DD/YYYY', + 'DD-MM-YYYY', + 'MM-DD-YYYY', + ]); + + if (!parsedDate.isValid()) { + console.error('Invalid date format:', item); + return undefined; // Return current date as fallback + } + return parsedDate.toDate(); + } catch (error) { + console.error('Error parsing date:', item, error); + return undefined; // Return current date as fallback + } + } + return undefined; + } + return item; + }, [item, type]); return (
- onValueChange(index, e.target.value)} - onBlur={() => onBlur(index)} - /> + {type === 'time' && ( + { + onValueChange(index, formatPureDate(value), true); + }} + /> + )} + {type !== 'time' && ( + onValueChange(index, e.target.value)} + onBlur={() => onBlur(index)} + /> + )}
)} - {isEditField && ( -
-
{t('knowledgeDetails.metadata.fieldName')}
-
- { - const value = e.target?.value || ''; - if (/^[a-zA-Z_]*$/.test(value)) { - handleChange('field', value); - } - }} - /> -
{valueError.field}
-
-
+ + {formFields.length > 0 && ( + )} - {isShowType && ( -
-
Type
- handleChange('valueType', value)} - /> -
- )} - {isShowDescription && ( -
- - {t('knowledgeDetails.metadata.description')} - -
-