import { Box, IconButton, Typography } from '@mui/material'; import { FC, Fragment, useMemo, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import icons from '@/components/icons/Icons'; import { generateId, getCreateFieldType } from '@/utils'; import { DataTypeEnum, VectorTypes } from '@/consts'; import { DEFAULT_ATTU_DIM, DEFAULT_ATTU_VARCHAR_MAX_LENGTH } from '@/consts'; import type { CreateFieldsProps, CreateFieldType, FieldType, } from '../../databases/collections/Types'; import PrimaryKeyFieldRow from './rows/PrimaryKeyFieldRow'; import ScalarFieldRow from './rows/ScalarFieldRow'; import VectorFieldRow from './rows/VectorFieldRow'; const CreateFields: FC = ({ fields, setFields, onValidationChange, }) => { // i18n const { t: collectionTrans } = useTranslation('collection'); // UI stats const localFieldAnalyzers = useRef( new Map>(new Map()) ); const localFieldsValidation = useRef>( new Map( fields .filter(field => field.id !== undefined) .map(field => [field.id!, field.name !== '']) ) ); // Add this helper function const updateValidationStatus = useCallback(() => { const areFieldsValid = Array.from( localFieldsValidation.current.values() ).every(v => v); onValidationChange(areFieldsValid); }, [onValidationChange]); // UI icons const AddIcon = icons.addOutline; // calculate required and scalar fields const { requiredFields, scalarFields } = useMemo( () => fields.reduce( (acc, field) => { const createType: CreateFieldType = getCreateFieldType(field); const requiredTypes: CreateFieldType[] = [ 'primaryKey', 'defaultVector', 'vector', ]; const key = requiredTypes.includes(createType) ? 'requiredFields' : 'scalarFields'; acc[key].push({ ...field, createType, }); return acc; }, { requiredFields: [] as FieldType[], scalarFields: [] as FieldType[], } ), [fields] ); // UI handlers const changeFields = ( id: string, changes: Partial, isValid?: boolean ) => { const newFields = fields.map(f => { if (f.id !== id) { return f; } const updatedField = { ...f, ...changes, }; // remove array params, if not array if (updatedField.data_type !== DataTypeEnum.Array) { delete updatedField.max_capacity; delete updatedField.element_type; } // remove varchar params, if not varchar if ( updatedField.data_type !== DataTypeEnum.VarChar && updatedField.element_type !== DataTypeEnum.VarChar ) { delete updatedField.max_length; } // remove dimension, if not vector if ( !VectorTypes.includes(updatedField.data_type) || updatedField.data_type === DataTypeEnum.SparseFloatVector ) { delete updatedField.dim; } else { // add dimension if not exist updatedField.dim = Number(updatedField.dim || DEFAULT_ATTU_DIM); } return updatedField; }); setFields(newFields); // Update validation in ref if (isValid !== undefined) { localFieldsValidation.current.set(id, isValid); } else { localFieldsValidation.current.delete(id); } updateValidationStatus(); }; const handleAddNewField = (index: number, type = DataTypeEnum.Int16) => { const id = generateId(); // Count existing scalar fields to generate new index let scalarFieldCount = fields.filter( f => !f.is_primary_key && !VectorTypes.includes(f.data_type) ).length; let name = `scalar_field_${scalarFieldCount}`; const existingNames = new Set(fields.map(f => f.name)); while (existingNames.has(name)) { scalarFieldCount += 1; name = `scalar_field_${scalarFieldCount}`; } const newDefaultItem: FieldType = { id, name, data_type: type, is_primary_key: false, description: '', isDefault: false, dim: DEFAULT_ATTU_DIM, max_length: DEFAULT_ATTU_VARCHAR_MAX_LENGTH, enable_analyzer: false, }; fields.splice(index + 1, 0, newDefaultItem); setFields([...fields]); // Add validation to ref localFieldsValidation.current.set(id, true); updateValidationStatus(); }; const handleRemoveField = (id: string) => { const newFields = fields.filter(f => f.id !== id); setFields(newFields); // Remove validation from ref localFieldsValidation.current.delete(id); updateValidationStatus(); }; const generateRequiredFieldRow = ( field: FieldType, index: number, fields: FieldType[], requiredFields: FieldType[] ) => { // required type is primaryKey or defaultVector if (field.createType === 'primaryKey') { return ( ); } if (field.createType === 'defaultVector') { return ( ); } // generate other vector rows return ( ); }; return ( {`${collectionTrans('idAndVectorFields')}(${requiredFields.length})`} {requiredFields.map((field, index) => ( {generateRequiredFieldRow(field, index, fields, requiredFields)} ))} {`${collectionTrans('scalarFields')}(${scalarFields.length})`} { handleAddNewField(requiredFields.length + 1); }} sx={{ p: 0, position: 'relative', top: '-8px', '& svg': { width: 15, }, }} aria-label="add" size="large" > {scalarFields.map((field, index) => ( ))} ); }; export default CreateFields;