CreateFields.tsx 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import { Box, IconButton, Typography } from '@mui/material';
  2. import { FC, Fragment, useMemo, useRef, useCallback } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import icons from '@/components/icons/Icons';
  5. import { generateId, getCreateFieldType } from '@/utils';
  6. import { DataTypeEnum, VectorTypes } from '@/consts';
  7. import { DEFAULT_ATTU_DIM, DEFAULT_ATTU_VARCHAR_MAX_LENGTH } from '@/consts';
  8. import type {
  9. CreateFieldsProps,
  10. CreateFieldType,
  11. FieldType,
  12. } from '../../databases/collections/Types';
  13. import PrimaryKeyFieldRow from './rows/PrimaryKeyFieldRow';
  14. import ScalarFieldRow from './rows/ScalarFieldRow';
  15. import VectorFieldRow from './rows/VectorFieldRow';
  16. const CreateFields: FC<CreateFieldsProps> = ({
  17. fields,
  18. setFields,
  19. onValidationChange,
  20. }) => {
  21. // i18n
  22. const { t: collectionTrans } = useTranslation('collection');
  23. // UI stats
  24. const localFieldAnalyzers = useRef(
  25. new Map<string, Record<string, {}>>(new Map())
  26. );
  27. const localFieldsValidation = useRef<Map<string, boolean>>(
  28. new Map(
  29. fields
  30. .filter(field => field.id !== undefined)
  31. .map(field => [field.id!, field.name !== ''])
  32. )
  33. );
  34. // Add this helper function
  35. const updateValidationStatus = useCallback(() => {
  36. const areFieldsValid = Array.from(
  37. localFieldsValidation.current.values()
  38. ).every(v => v);
  39. onValidationChange(areFieldsValid);
  40. }, [onValidationChange]);
  41. // UI icons
  42. const AddIcon = icons.addOutline;
  43. // calculate required and scalar fields
  44. const { requiredFields, scalarFields } = useMemo(
  45. () =>
  46. fields.reduce(
  47. (acc, field) => {
  48. const createType: CreateFieldType = getCreateFieldType(field);
  49. const requiredTypes: CreateFieldType[] = [
  50. 'primaryKey',
  51. 'defaultVector',
  52. 'vector',
  53. ];
  54. const key = requiredTypes.includes(createType)
  55. ? 'requiredFields'
  56. : 'scalarFields';
  57. acc[key].push({
  58. ...field,
  59. createType,
  60. });
  61. return acc;
  62. },
  63. {
  64. requiredFields: [] as FieldType[],
  65. scalarFields: [] as FieldType[],
  66. }
  67. ),
  68. [fields]
  69. );
  70. // UI handlers
  71. const changeFields = (
  72. id: string,
  73. changes: Partial<FieldType>,
  74. isValid?: boolean
  75. ) => {
  76. const newFields = fields.map(f => {
  77. if (f.id !== id) {
  78. return f;
  79. }
  80. const updatedField = {
  81. ...f,
  82. ...changes,
  83. };
  84. // remove array params, if not array
  85. if (updatedField.data_type !== DataTypeEnum.Array) {
  86. delete updatedField.max_capacity;
  87. delete updatedField.element_type;
  88. }
  89. // remove varchar params, if not varchar
  90. if (
  91. updatedField.data_type !== DataTypeEnum.VarChar &&
  92. updatedField.element_type !== DataTypeEnum.VarChar
  93. ) {
  94. delete updatedField.max_length;
  95. }
  96. // remove dimension, if not vector
  97. if (
  98. !VectorTypes.includes(updatedField.data_type) ||
  99. updatedField.data_type === DataTypeEnum.SparseFloatVector
  100. ) {
  101. delete updatedField.dim;
  102. } else {
  103. // add dimension if not exist
  104. updatedField.dim = Number(updatedField.dim || DEFAULT_ATTU_DIM);
  105. }
  106. return updatedField;
  107. });
  108. setFields(newFields);
  109. // Update validation in ref
  110. if (isValid !== undefined) {
  111. localFieldsValidation.current.set(id, isValid);
  112. } else {
  113. localFieldsValidation.current.delete(id);
  114. }
  115. updateValidationStatus();
  116. };
  117. const handleAddNewField = (index: number, type = DataTypeEnum.Int16) => {
  118. const id = generateId();
  119. // Count existing scalar fields to generate new index
  120. let scalarFieldCount = fields.filter(
  121. f => !f.is_primary_key && !VectorTypes.includes(f.data_type)
  122. ).length;
  123. let name = `scalar_field_${scalarFieldCount}`;
  124. const existingNames = new Set(fields.map(f => f.name));
  125. while (existingNames.has(name)) {
  126. scalarFieldCount += 1;
  127. name = `scalar_field_${scalarFieldCount}`;
  128. }
  129. const newDefaultItem: FieldType = {
  130. id,
  131. name,
  132. data_type: type,
  133. is_primary_key: false,
  134. description: '',
  135. isDefault: false,
  136. dim: DEFAULT_ATTU_DIM,
  137. max_length: DEFAULT_ATTU_VARCHAR_MAX_LENGTH,
  138. enable_analyzer: false,
  139. };
  140. fields.splice(index + 1, 0, newDefaultItem);
  141. setFields([...fields]);
  142. // Add validation to ref
  143. localFieldsValidation.current.set(id, true);
  144. updateValidationStatus();
  145. };
  146. const handleRemoveField = (id: string) => {
  147. const newFields = fields.filter(f => f.id !== id);
  148. setFields(newFields);
  149. // Remove validation from ref
  150. localFieldsValidation.current.delete(id);
  151. updateValidationStatus();
  152. };
  153. const generateRequiredFieldRow = (
  154. field: FieldType,
  155. index: number,
  156. fields: FieldType[],
  157. requiredFields: FieldType[]
  158. ) => {
  159. // required type is primaryKey or defaultVector
  160. if (field.createType === 'primaryKey') {
  161. return (
  162. <PrimaryKeyFieldRow
  163. field={field}
  164. fields={fields}
  165. onFieldChange={changeFields}
  166. />
  167. );
  168. }
  169. if (field.createType === 'defaultVector') {
  170. return (
  171. <VectorFieldRow
  172. field={field}
  173. fields={fields}
  174. index={index}
  175. requiredFields={requiredFields}
  176. onFieldChange={changeFields}
  177. onAddField={handleAddNewField}
  178. onRemoveField={handleRemoveField}
  179. showDeleteButton={true}
  180. />
  181. );
  182. }
  183. // generate other vector rows
  184. return (
  185. <VectorFieldRow
  186. field={field}
  187. fields={fields}
  188. index={index}
  189. requiredFields={requiredFields}
  190. onFieldChange={changeFields}
  191. onAddField={handleAddNewField}
  192. onRemoveField={handleRemoveField}
  193. showDeleteButton={true}
  194. />
  195. );
  196. };
  197. return (
  198. <Box>
  199. <Typography
  200. variant="h4"
  201. sx={{
  202. fontSize: 14,
  203. mb: 1.5,
  204. }}
  205. >
  206. {`${collectionTrans('idAndVectorFields')}(${requiredFields.length})`}
  207. </Typography>
  208. {requiredFields.map((field, index) => (
  209. <Fragment key={field.id}>
  210. {generateRequiredFieldRow(field, index, fields, requiredFields)}
  211. </Fragment>
  212. ))}
  213. <Typography
  214. variant="h4"
  215. sx={{
  216. fontSize: 14,
  217. mt: 2,
  218. mb: 1.5,
  219. '& button': {
  220. position: 'relative',
  221. top: '-1px',
  222. ml: 0.5,
  223. },
  224. }}
  225. >
  226. {`${collectionTrans('scalarFields')}(${scalarFields.length})`}
  227. <IconButton
  228. onClick={() => {
  229. handleAddNewField(requiredFields.length + 1);
  230. }}
  231. sx={{
  232. p: 0,
  233. position: 'relative',
  234. top: '-8px',
  235. '& svg': {
  236. width: 15,
  237. },
  238. }}
  239. aria-label="add"
  240. size="large"
  241. >
  242. <AddIcon />
  243. </IconButton>
  244. </Typography>
  245. <Box>
  246. {scalarFields.map((field, index) => (
  247. <ScalarFieldRow
  248. key={field.id}
  249. field={field}
  250. index={index + requiredFields.length}
  251. fields={fields}
  252. onFieldChange={changeFields}
  253. onAddField={handleAddNewField}
  254. onRemoveField={handleRemoveField}
  255. localFieldAnalyzers={localFieldAnalyzers}
  256. />
  257. ))}
  258. </Box>
  259. </Box>
  260. );
  261. };
  262. export default CreateFields;