CreateFields.tsx 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  1. import {
  2. Theme,
  3. TextField,
  4. IconButton,
  5. Switch,
  6. FormControlLabel,
  7. Checkbox,
  8. } from '@mui/material';
  9. import { FC, Fragment, ReactElement, useMemo, useContext } from 'react';
  10. import { useTranslation } from 'react-i18next';
  11. import CustomSelector from '@/components/customSelector/CustomSelector';
  12. import icons from '@/components/icons/Icons';
  13. import CustomToolTip from '@/components/customToolTip/CustomToolTip';
  14. import {
  15. generateId,
  16. getCreateFieldType,
  17. checkEmptyValid,
  18. checkRange,
  19. getCheckResult,
  20. getAnalyzerParams,
  21. } from '@/utils';
  22. import { rootContext } from '@/context';
  23. import {
  24. ALL_OPTIONS,
  25. PRIMARY_FIELDS_OPTIONS,
  26. VECTOR_FIELDS_OPTIONS,
  27. ANALYZER_OPTIONS,
  28. } from './Constants';
  29. import {
  30. CreateFieldsProps,
  31. CreateFieldType,
  32. FieldType,
  33. } from '../../databases/collections/Types';
  34. import { DataTypeEnum, VectorTypes } from '@/consts';
  35. import {
  36. DEFAULT_ATTU_DIM,
  37. DEFAULT_ATTU_MAX_CAPACITY,
  38. DEFAULT_ATTU_VARCHAR_MAX_LENGTH,
  39. DEFAULT_ATTU_ELEMENT_TYPE,
  40. } from '@/consts';
  41. import { makeStyles } from '@mui/styles';
  42. import CustomIconButton from '@/components/customButton/CustomIconButton';
  43. import EditAnalyzerDialog from '@/pages/dialogs/EditAnalyzerDialog';
  44. const useStyles = makeStyles((theme: Theme) => ({
  45. scalarFieldsWrapper: {
  46. width: '100%',
  47. paddingRight: theme.spacing(1),
  48. overflowY: 'auto',
  49. },
  50. title: {
  51. marginTop: theme.spacing(2),
  52. '& button': {
  53. position: 'relative',
  54. top: '-1px',
  55. marginLeft: 4,
  56. },
  57. },
  58. rowWrapper: {
  59. display: 'flex',
  60. flexWrap: 'nowrap',
  61. alignItems: 'center',
  62. gap: '8px',
  63. marginBottom: 4,
  64. '& .MuiFormLabel-root': {
  65. fontSize: 14,
  66. },
  67. '& .MuiInputBase-root': {
  68. fontSize: 14,
  69. },
  70. '& .MuiSelect-filled': {
  71. fontSize: 14,
  72. },
  73. '& .MuiCheckbox-root': {
  74. padding: 4,
  75. },
  76. '& .MuiFormControlLabel-label': {
  77. fontSize: 14,
  78. },
  79. },
  80. fieldInput: {
  81. width: '130px',
  82. },
  83. select: {
  84. width: '150px',
  85. marginTop: '-20px',
  86. },
  87. smallSelect: {
  88. width: '105px',
  89. marginTop: '-20px',
  90. },
  91. autoIdSelect: {
  92. width: '120px',
  93. marginTop: '-20px',
  94. },
  95. numberBox: {
  96. width: '80px',
  97. },
  98. maxLength: {
  99. maxWidth: '80px',
  100. },
  101. descInput: {
  102. width: '64px',
  103. },
  104. btnTxt: {
  105. textTransform: 'uppercase',
  106. },
  107. iconBtn: {
  108. padding: 0,
  109. position: 'relative',
  110. top: '-8px',
  111. '& svg': {
  112. width: 15,
  113. },
  114. },
  115. helperText: {
  116. lineHeight: '20px',
  117. fontSize: '10px',
  118. margin: theme.spacing(0),
  119. marginLeft: '11px',
  120. },
  121. toggle: {
  122. marginLeft: theme.spacing(0.5),
  123. marginRight: theme.spacing(0.5),
  124. },
  125. icon: {
  126. fontSize: '14px',
  127. marginLeft: theme.spacing(0.5),
  128. },
  129. paramsGrp: {
  130. border: `1px dashed ${theme.palette.divider}`,
  131. borderRadius: 4,
  132. display: 'flex',
  133. flexDirection: 'column',
  134. paddingLeft: 0,
  135. paddingTop: 0,
  136. paddingRight: 8,
  137. minHeight: 44,
  138. alignSelf: 'flex-start',
  139. alignItems: 'flex-start',
  140. justifyContent: 'center',
  141. },
  142. analyzerInput: {
  143. paddingTop: 8,
  144. '& .select': {
  145. width: '110px',
  146. },
  147. },
  148. setting: { fontSize: 12, alignItems: 'center', display: 'flex' },
  149. }));
  150. type inputType = {
  151. label: string;
  152. value: string | number | null;
  153. handleChange?: (value: string) => void;
  154. className?: string;
  155. inputClassName?: string;
  156. isReadOnly?: boolean;
  157. validate?: (value: string | number | null) => string;
  158. type?: 'number' | 'text';
  159. };
  160. const CreateFields: FC<CreateFieldsProps> = ({
  161. fields,
  162. setFields,
  163. setAutoID,
  164. autoID,
  165. setFieldsValidation,
  166. }) => {
  167. const { setDialog2, handleCloseDialog2 } = useContext(rootContext);
  168. const { t: collectionTrans } = useTranslation('collection');
  169. const { t: warningTrans } = useTranslation('warning');
  170. const classes = useStyles();
  171. const AddIcon = icons.addOutline;
  172. const RemoveIcon = icons.remove;
  173. const { requiredFields, scalarFields } = useMemo(
  174. () =>
  175. fields.reduce(
  176. (acc, field) => {
  177. const createType: CreateFieldType = getCreateFieldType(field);
  178. const requiredTypes: CreateFieldType[] = [
  179. 'primaryKey',
  180. 'defaultVector',
  181. 'vector',
  182. ];
  183. const key = requiredTypes.includes(createType)
  184. ? 'requiredFields'
  185. : 'scalarFields';
  186. acc[key].push({
  187. ...field,
  188. createType,
  189. });
  190. return acc;
  191. },
  192. {
  193. requiredFields: [] as FieldType[],
  194. scalarFields: [] as FieldType[],
  195. }
  196. ),
  197. [fields]
  198. );
  199. const getSelector = (
  200. type: 'scalar' | 'vector' | 'element' | 'primaryKey',
  201. label: string,
  202. value: number,
  203. onChange: (value: DataTypeEnum) => void,
  204. className: string = classes.select
  205. ) => {
  206. let _options = ALL_OPTIONS;
  207. switch (type) {
  208. case 'primaryKey':
  209. _options = PRIMARY_FIELDS_OPTIONS;
  210. break;
  211. case 'scalar':
  212. _options = ALL_OPTIONS;
  213. break;
  214. case 'vector':
  215. _options = VECTOR_FIELDS_OPTIONS;
  216. break;
  217. case 'element':
  218. _options = ALL_OPTIONS.filter(
  219. d =>
  220. d.label !== 'Array' &&
  221. d.label !== 'JSON' &&
  222. d.label !== 'VarChar(BM25)' &&
  223. !d.label.includes('Vector')
  224. );
  225. break;
  226. default:
  227. break;
  228. }
  229. return (
  230. <CustomSelector
  231. wrapperClass={className}
  232. options={_options}
  233. size="small"
  234. onChange={e => {
  235. onChange(e.target.value as DataTypeEnum);
  236. }}
  237. value={value}
  238. variant="filled"
  239. label={label}
  240. />
  241. );
  242. };
  243. const getInput = (data: inputType) => {
  244. const {
  245. label,
  246. value,
  247. handleChange = () => {},
  248. className = '',
  249. inputClassName = '',
  250. isReadOnly = false,
  251. validate = (value: string | number | null) => ' ',
  252. type = 'text',
  253. } = data;
  254. return (
  255. <TextField
  256. label={label}
  257. // value={value}
  258. onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
  259. handleChange(e.target.value as string);
  260. }}
  261. variant="filled"
  262. className={className}
  263. InputProps={{
  264. classes: {
  265. input: inputClassName,
  266. },
  267. }}
  268. InputLabelProps={{
  269. shrink: true,
  270. }}
  271. size="small"
  272. disabled={isReadOnly}
  273. error={validate(value) !== ' '}
  274. helperText={validate(value)}
  275. FormHelperTextProps={{
  276. className: classes.helperText,
  277. }}
  278. defaultValue={value}
  279. type={type}
  280. />
  281. );
  282. };
  283. const generateFieldName = (
  284. field: FieldType,
  285. label?: string,
  286. className?: string
  287. ) => {
  288. const defaultLabel = collectionTrans(
  289. VectorTypes.includes(field.data_type) ? 'vectorFieldName' : 'fieldName'
  290. );
  291. return getInput({
  292. label: label || defaultLabel,
  293. value: field.name,
  294. className: `${classes.fieldInput} ${className}`,
  295. handleChange: (value: string) => {
  296. const isValid = checkEmptyValid(value);
  297. setFieldsValidation(v =>
  298. v.map(item =>
  299. item.id === field.id! ? { ...item, name: isValid } : item
  300. )
  301. );
  302. changeFields(field.id!, { name: value });
  303. },
  304. validate: (value: any) => {
  305. if (value === null) return ' ';
  306. const isValid = checkEmptyValid(value);
  307. return isValid ? ' ' : warningTrans('requiredOnly');
  308. },
  309. });
  310. };
  311. const generateDesc = (field: FieldType) => {
  312. return getInput({
  313. label: collectionTrans('description'),
  314. value: field.description,
  315. handleChange: (value: string) =>
  316. changeFields(field.id!, { description: value }),
  317. inputClassName: classes.descInput,
  318. });
  319. };
  320. const generateDefaultValue = (field: FieldType) => {
  321. let type: 'number' | 'text' = 'number';
  322. switch (field.data_type) {
  323. case DataTypeEnum.Int8:
  324. case DataTypeEnum.Int16:
  325. case DataTypeEnum.Int32:
  326. case DataTypeEnum.Int64:
  327. case DataTypeEnum.Float:
  328. case DataTypeEnum.Double:
  329. type = 'number';
  330. break;
  331. case DataTypeEnum.Bool:
  332. type = 'text';
  333. break;
  334. default:
  335. type = 'text';
  336. break;
  337. }
  338. return getInput({
  339. label: collectionTrans('defaultValue'),
  340. value: field.default_value,
  341. type: type,
  342. handleChange: (value: string) =>
  343. changeFields(field.id!, { default_value: value }),
  344. inputClassName: classes.descInput,
  345. });
  346. };
  347. const generateDimension = (field: FieldType) => {
  348. // sparse doesn't support dimension
  349. if (field.data_type === DataTypeEnum.SparseFloatVector) {
  350. return null;
  351. }
  352. const validateDimension = (value: string) => {
  353. const isPositive = getCheckResult({
  354. value,
  355. rule: 'positiveNumber',
  356. });
  357. const isMultiple = getCheckResult({
  358. value,
  359. rule: 'multiple',
  360. extraParam: {
  361. multipleNumber: 8,
  362. },
  363. });
  364. if (field.data_type === DataTypeEnum.BinaryVector) {
  365. return {
  366. isMultiple,
  367. isPositive,
  368. };
  369. }
  370. return {
  371. isPositive,
  372. };
  373. };
  374. return getInput({
  375. label: collectionTrans('dimension'),
  376. value: field.dim as number,
  377. inputClassName: classes.numberBox,
  378. handleChange: (value: string) => {
  379. const { isPositive, isMultiple } = validateDimension(value);
  380. const isValid =
  381. field.data_type === DataTypeEnum.BinaryVector
  382. ? !!isMultiple && isPositive
  383. : isPositive;
  384. changeFields(field.id!, { dim: value });
  385. setFieldsValidation(v =>
  386. v.map(item =>
  387. item.id === field.id! ? { ...item, dim: isValid } : item
  388. )
  389. );
  390. },
  391. type: 'number',
  392. validate: (value: any) => {
  393. const { isPositive, isMultiple } = validateDimension(value);
  394. if (isMultiple === false) {
  395. return collectionTrans('dimensionMultipleWarning');
  396. }
  397. return isPositive ? ' ' : collectionTrans('dimensionPositiveWarning');
  398. },
  399. });
  400. };
  401. const generateMaxLength = (field: FieldType) => {
  402. // update data if needed
  403. if (typeof field.max_length === 'undefined') {
  404. changeFields(field.id!, { max_length: DEFAULT_ATTU_VARCHAR_MAX_LENGTH });
  405. }
  406. return getInput({
  407. label: 'Max Length',
  408. value: field.max_length! || DEFAULT_ATTU_VARCHAR_MAX_LENGTH,
  409. type: 'number',
  410. inputClassName: classes.maxLength,
  411. handleChange: (value: string) =>
  412. changeFields(field.id!, { max_length: value }),
  413. validate: (value: any) => {
  414. if (value === null) return ' ';
  415. const isEmptyValid = checkEmptyValid(value);
  416. const isRangeValid = checkRange({
  417. value,
  418. min: 1,
  419. max: 65535,
  420. type: 'number',
  421. });
  422. return !isEmptyValid
  423. ? warningTrans('requiredOnly')
  424. : !isRangeValid
  425. ? warningTrans('range', {
  426. min: 1,
  427. max: 65535,
  428. })
  429. : ' ';
  430. },
  431. });
  432. };
  433. const generateMaxCapacity = (field: FieldType) => {
  434. return getInput({
  435. label: 'Max Capacity',
  436. value: field.max_capacity || DEFAULT_ATTU_MAX_CAPACITY,
  437. type: 'number',
  438. inputClassName: classes.maxLength,
  439. handleChange: (value: string) =>
  440. changeFields(field.id!, { max_capacity: value }),
  441. validate: (value: any) => {
  442. if (value === null) return ' ';
  443. const isEmptyValid = checkEmptyValid(value);
  444. const isRangeValid = checkRange({
  445. value,
  446. min: 1,
  447. max: 4096,
  448. type: 'number',
  449. });
  450. return !isEmptyValid
  451. ? warningTrans('requiredOnly')
  452. : !isRangeValid
  453. ? warningTrans('range', {
  454. min: 1,
  455. max: 4096,
  456. })
  457. : ' ';
  458. },
  459. });
  460. };
  461. const generatePartitionKeyCheckbox = (
  462. field: FieldType,
  463. fields: FieldType[]
  464. ) => {
  465. return (
  466. <div className={classes.setting}>
  467. <Checkbox
  468. checked={!!field.is_partition_key}
  469. size="small"
  470. disabled={
  471. (fields.some(f => f.is_partition_key) && !field.is_partition_key) ||
  472. field.nullable
  473. }
  474. onChange={() => {
  475. changeFields(field.id!, {
  476. is_partition_key: !field.is_partition_key,
  477. });
  478. }}
  479. />
  480. <CustomToolTip
  481. title={collectionTrans('partitionKeyTooltip')}
  482. placement="top"
  483. >
  484. <>{collectionTrans('partitionKey')}</>
  485. </CustomToolTip>
  486. </div>
  487. );
  488. };
  489. const generateNullableCheckbox = (field: FieldType, fields: FieldType[]) => {
  490. return (
  491. <div className={classes.setting}>
  492. <Checkbox
  493. checked={!!field.nullable}
  494. size="small"
  495. onChange={() => {
  496. changeFields(field.id!, {
  497. nullable: !field.nullable,
  498. is_partition_key: false,
  499. });
  500. }}
  501. />
  502. <CustomToolTip
  503. title={collectionTrans('nullableTooltip')}
  504. placement="top"
  505. >
  506. <>{collectionTrans('nullable')}</>
  507. </CustomToolTip>
  508. </div>
  509. );
  510. };
  511. const generateTextMatchCheckBox = (field: FieldType, fields: FieldType[]) => {
  512. const update: Partial<FieldType> = {
  513. enable_match: !field.enable_match,
  514. };
  515. if (!field.enable_match) {
  516. update.enable_analyzer = true;
  517. }
  518. return (
  519. <div className={classes.setting}>
  520. <Checkbox
  521. checked={!!field.enable_match}
  522. size="small"
  523. onChange={() => {
  524. changeFields(field.id!, update);
  525. }}
  526. />
  527. <CustomToolTip
  528. title={collectionTrans('textMatchTooltip')}
  529. placement="top"
  530. >
  531. <>{collectionTrans('enableMatch')}</>
  532. </CustomToolTip>
  533. </div>
  534. );
  535. };
  536. const generateAnalyzerCheckBox = (field: FieldType, fields: FieldType[]) => {
  537. let analyzer = '';
  538. if (typeof field.analyzer_params === 'object') {
  539. analyzer = field.analyzer_params.tokenizer || field.analyzer_params.type;
  540. } else {
  541. analyzer = field.analyzer_params || 'standard';
  542. }
  543. return (
  544. <div className={classes.analyzerInput}>
  545. <Checkbox
  546. checked={
  547. !!field.enable_analyzer ||
  548. field.data_type === DataTypeEnum.VarCharBM25
  549. }
  550. size="small"
  551. onChange={() => {
  552. changeFields(field.id!, {
  553. enable_analyzer: !field.enable_analyzer,
  554. });
  555. }}
  556. disabled={field.data_type === DataTypeEnum.VarCharBM25}
  557. />
  558. <CustomSelector
  559. wrapperClass="select"
  560. options={ANALYZER_OPTIONS}
  561. size="small"
  562. onChange={e => {
  563. changeFields(field.id!, { analyzer_params: e.target.value });
  564. }}
  565. disabled={
  566. !field.enable_analyzer &&
  567. field.data_type !== DataTypeEnum.VarCharBM25
  568. }
  569. value={analyzer}
  570. variant="filled"
  571. label={collectionTrans('analyzer')}
  572. />
  573. <CustomIconButton
  574. disabled={
  575. !field.enable_analyzer &&
  576. field.data_type !== DataTypeEnum.VarCharBM25
  577. }
  578. onClick={() => {
  579. setDialog2({
  580. open: true,
  581. type: 'custom',
  582. params: {
  583. component: (
  584. <EditAnalyzerDialog
  585. data={getAnalyzerParams(
  586. field.analyzer_params || 'standard'
  587. )}
  588. handleConfirm={data => {
  589. changeFields(field.id!, { analyzer_params: data });
  590. }}
  591. handleCloseDialog={handleCloseDialog2}
  592. />
  593. ),
  594. },
  595. });
  596. }}
  597. >
  598. <icons.settings className={classes.icon} />
  599. </CustomIconButton>
  600. </div>
  601. );
  602. };
  603. const changeFields = (id: string, changes: Partial<FieldType>) => {
  604. const newFields = fields.map(f => {
  605. if (f.id !== id) {
  606. return f;
  607. }
  608. const updatedField = {
  609. ...f,
  610. ...changes,
  611. };
  612. // remove array params, if not array
  613. if (updatedField.data_type !== DataTypeEnum.Array) {
  614. delete updatedField.max_capacity;
  615. delete updatedField.element_type;
  616. }
  617. // remove varchar params, if not varchar
  618. if (
  619. updatedField.data_type !== DataTypeEnum.VarChar &&
  620. updatedField.data_type !== DataTypeEnum.VarCharBM25 &&
  621. updatedField.element_type !== DataTypeEnum.VarChar
  622. ) {
  623. delete updatedField.max_length;
  624. }
  625. // remove dimension, if not vector
  626. if (
  627. !VectorTypes.includes(updatedField.data_type) ||
  628. updatedField.data_type === DataTypeEnum.SparseFloatVector
  629. ) {
  630. delete updatedField.dim;
  631. } else {
  632. // add dimension if not exist
  633. updatedField.dim = Number(updatedField.dim || DEFAULT_ATTU_DIM);
  634. }
  635. return updatedField;
  636. });
  637. setFields(newFields);
  638. };
  639. const handleAddNewField = (index: number, type = DataTypeEnum.Int16) => {
  640. const id = generateId();
  641. const newDefaultItem: FieldType = {
  642. name: '',
  643. data_type: type,
  644. is_primary_key: false,
  645. description: '',
  646. isDefault: false,
  647. dim: DEFAULT_ATTU_DIM,
  648. max_length: DEFAULT_ATTU_VARCHAR_MAX_LENGTH,
  649. enable_analyzer: type === DataTypeEnum.VarCharBM25,
  650. id,
  651. };
  652. const newValidation = {
  653. id,
  654. name: false,
  655. dim: true,
  656. };
  657. fields.splice(index + 1, 0, newDefaultItem);
  658. setFields([...fields]);
  659. setFieldsValidation(v => [...v, newValidation]);
  660. };
  661. const handleRemoveField = (id: string) => {
  662. const newFields = fields.filter(f => f.id !== id);
  663. setFields(newFields);
  664. setFieldsValidation(v => v.filter(item => item.id !== id));
  665. };
  666. const generatePrimaryKeyRow = (
  667. field: FieldType,
  668. autoID: boolean
  669. ): ReactElement => {
  670. const isVarChar = field.data_type === DataTypeEnum.VarChar;
  671. return (
  672. <div className={`${classes.rowWrapper}`}>
  673. {generateFieldName(field, collectionTrans('idFieldName'))}
  674. {getSelector(
  675. 'primaryKey',
  676. `${collectionTrans('idType')} `,
  677. field.data_type,
  678. (value: DataTypeEnum) => {
  679. changeFields(field.id!, { data_type: value });
  680. if (value === DataTypeEnum.VarChar) {
  681. setAutoID(false);
  682. }
  683. }
  684. )}
  685. {generateDesc(field)}
  686. {isVarChar && generateMaxLength(field)}
  687. <FormControlLabel
  688. control={
  689. <Switch
  690. checked={autoID}
  691. disabled={isVarChar}
  692. size="small"
  693. onChange={() => {
  694. changeFields(field.id!, { autoID: !autoID });
  695. setAutoID(!autoID);
  696. }}
  697. />
  698. }
  699. label={
  700. <CustomToolTip
  701. title={collectionTrans('autoIdToggleTip')}
  702. placement="top"
  703. >
  704. <>{collectionTrans('autoId')}</>
  705. </CustomToolTip>
  706. }
  707. className={classes.toggle}
  708. />
  709. </div>
  710. );
  711. };
  712. const generateDefaultVectorRow = (
  713. field: FieldType,
  714. index: number
  715. ): ReactElement => {
  716. return (
  717. <div className={`${classes.rowWrapper}`}>
  718. {generateFieldName(field)}
  719. {getSelector(
  720. 'vector',
  721. `${collectionTrans('vectorType')} `,
  722. field.data_type,
  723. (value: DataTypeEnum) => changeFields(field.id!, { data_type: value })
  724. )}
  725. {generateDimension(field)}
  726. {generateDesc(field)}
  727. <IconButton
  728. onClick={() => handleAddNewField(index, field.data_type)}
  729. classes={{ root: classes.iconBtn }}
  730. aria-label="add"
  731. size="large"
  732. >
  733. <AddIcon />
  734. </IconButton>
  735. </div>
  736. );
  737. };
  738. const generateScalarFieldRow = (
  739. field: FieldType,
  740. index: number,
  741. fields: FieldType[]
  742. ): ReactElement => {
  743. const isVarChar = field.data_type === DataTypeEnum.VarChar;
  744. const isInt64 = field.data_type === DataTypeEnum.Int64;
  745. const isArray = field.data_type === DataTypeEnum.Array;
  746. const isElementVarChar = field.element_type === DataTypeEnum.VarChar;
  747. const showDefaultValue =
  748. field.data_type !== DataTypeEnum.Array &&
  749. field.data_type !== DataTypeEnum.JSON;
  750. // handle default values
  751. if (isArray && typeof field.element_type === 'undefined') {
  752. changeFields(field.id!, { element_type: DEFAULT_ATTU_ELEMENT_TYPE });
  753. }
  754. if (isArray && typeof field.max_capacity === 'undefined') {
  755. changeFields(field.id!, { max_capacity: DEFAULT_ATTU_MAX_CAPACITY });
  756. }
  757. return (
  758. <div className={`${classes.rowWrapper}`}>
  759. {generateFieldName(field)}
  760. {getSelector(
  761. 'scalar',
  762. collectionTrans('fieldType'),
  763. field.data_type,
  764. (value: DataTypeEnum) =>
  765. changeFields(field.id!, { data_type: value }),
  766. classes.smallSelect
  767. )}
  768. {isArray
  769. ? getSelector(
  770. 'element',
  771. collectionTrans('elementType'),
  772. field.element_type || DEFAULT_ATTU_ELEMENT_TYPE,
  773. (value: DataTypeEnum) =>
  774. changeFields(field.id!, { element_type: value }),
  775. classes.smallSelect
  776. )
  777. : null}
  778. {isArray ? generateMaxCapacity(field) : null}
  779. {isVarChar || isElementVarChar ? generateMaxLength(field) : null}
  780. {showDefaultValue && generateDefaultValue(field)}
  781. {generateDesc(field)}
  782. <div className={classes.paramsGrp}>
  783. {isInt64 ? generatePartitionKeyCheckbox(field, fields) : null}
  784. {isVarChar ? (
  785. <>
  786. {generateAnalyzerCheckBox(field, fields)}
  787. {generateTextMatchCheckBox(field, fields)}
  788. {generatePartitionKeyCheckbox(field, fields)}
  789. </>
  790. ) : null}
  791. {generateNullableCheckbox(field, fields)}
  792. </div>
  793. <IconButton
  794. onClick={() => {
  795. handleAddNewField(index, field.data_type);
  796. }}
  797. classes={{ root: classes.iconBtn }}
  798. aria-label="add"
  799. size="large"
  800. >
  801. <AddIcon />
  802. </IconButton>
  803. <IconButton
  804. onClick={() => {
  805. const id = field.id || '';
  806. handleRemoveField(id);
  807. }}
  808. classes={{ root: classes.iconBtn }}
  809. aria-label="delete"
  810. size="large"
  811. >
  812. <RemoveIcon />
  813. </IconButton>
  814. </div>
  815. );
  816. };
  817. const generateFunctionRow = (
  818. field: FieldType,
  819. index: number,
  820. fields: FieldType[],
  821. requiredFields: FieldType[]
  822. ) => {
  823. return (
  824. <div className={`${classes.rowWrapper}`}>
  825. {generateFieldName(field)}
  826. {getSelector(
  827. 'vector',
  828. collectionTrans('fieldType'),
  829. field.data_type,
  830. (value: DataTypeEnum) => changeFields(field.id!, { data_type: value })
  831. )}
  832. {generateMaxLength(field)}
  833. {generateDefaultValue(field)}
  834. {generateDesc(field)}
  835. <div className={classes.paramsGrp}>
  836. {generateAnalyzerCheckBox(field, fields)}
  837. {generateTextMatchCheckBox(field, fields)}
  838. {generatePartitionKeyCheckbox(field, fields)}
  839. {generateNullableCheckbox(field, fields)}
  840. </div>
  841. <IconButton
  842. onClick={() => {
  843. handleAddNewField(index, field.data_type);
  844. }}
  845. classes={{ root: classes.iconBtn }}
  846. aria-label="add"
  847. size="large"
  848. >
  849. <AddIcon />
  850. </IconButton>
  851. {requiredFields.length !== 2 && (
  852. <IconButton
  853. onClick={() => {
  854. const id = field.id || '';
  855. handleRemoveField(id);
  856. }}
  857. classes={{ root: classes.iconBtn }}
  858. aria-label="delete"
  859. size="large"
  860. >
  861. <RemoveIcon />
  862. </IconButton>
  863. )}
  864. </div>
  865. );
  866. };
  867. const generateVectorRow = (field: FieldType, index: number) => {
  868. return (
  869. <div className={`${classes.rowWrapper}`}>
  870. {generateFieldName(field)}
  871. {getSelector(
  872. 'vector',
  873. `${collectionTrans('vectorType')} `,
  874. field.data_type,
  875. (value: DataTypeEnum) => changeFields(field.id!, { data_type: value })
  876. )}
  877. {generateDimension(field)}
  878. {generateDesc(field)}
  879. <IconButton
  880. onClick={() => handleAddNewField(index, field.data_type)}
  881. classes={{ root: classes.iconBtn }}
  882. aria-label="add"
  883. size="large"
  884. >
  885. <AddIcon />
  886. </IconButton>
  887. {requiredFields.length !== 2 && (
  888. <IconButton
  889. onClick={() => {
  890. const id = field.id || '';
  891. handleRemoveField(id);
  892. }}
  893. classes={{ root: classes.iconBtn }}
  894. aria-label="delete"
  895. size="large"
  896. >
  897. <RemoveIcon />
  898. </IconButton>
  899. )}
  900. </div>
  901. );
  902. };
  903. const generateRequiredFieldRow = (
  904. field: FieldType,
  905. autoID: boolean,
  906. index: number,
  907. fields: FieldType[],
  908. requiredFields: FieldType[]
  909. ) => {
  910. // required type is primaryKey or defaultVector
  911. if (field.createType === 'primaryKey') {
  912. return generatePrimaryKeyRow(field, autoID);
  913. }
  914. if (field.data_type === DataTypeEnum.VarCharBM25) {
  915. return generateFunctionRow(field, index, fields, requiredFields);
  916. }
  917. if (field.createType === 'defaultVector') {
  918. return generateDefaultVectorRow(field, index);
  919. }
  920. // generate other vector rows
  921. return generateVectorRow(field, index);
  922. };
  923. return (
  924. <>
  925. <h4 className={classes.title}>
  926. {`${collectionTrans('idAndVectorFields')}(${requiredFields.length})`}
  927. </h4>
  928. {requiredFields.map((field, index) => (
  929. <Fragment key={field.id}>
  930. {generateRequiredFieldRow(
  931. field,
  932. autoID,
  933. index,
  934. fields,
  935. requiredFields
  936. )}
  937. </Fragment>
  938. ))}
  939. <h4 className={classes.title}>
  940. {`${collectionTrans('scalarFields')}(${scalarFields.length})`}
  941. <IconButton
  942. onClick={() => {
  943. handleAddNewField(requiredFields.length + 1);
  944. }}
  945. classes={{ root: classes.iconBtn }}
  946. aria-label="add"
  947. size="large"
  948. >
  949. <AddIcon />
  950. </IconButton>
  951. </h4>
  952. <div className={classes.scalarFieldsWrapper}>
  953. {scalarFields.map((field, index) => (
  954. <Fragment key={field.id}>
  955. {generateScalarFieldRow(
  956. field,
  957. index + requiredFields.length,
  958. fields
  959. )}
  960. </Fragment>
  961. ))}
  962. </div>
  963. </>
  964. );
  965. };
  966. export default CreateFields;