Browse Source

Merge pull request #8 from zilliztech/uxd-624

[UXD-624] add create / release / load collection dialog ui
nameczz 4 years ago
parent
commit
549a95f4f1

+ 21 - 2
client/src/components/customDialog/CustomDialog.tsx

@@ -22,6 +22,15 @@ const useStyles = makeStyles((theme: Theme) =>
       borderRadius: '8px',
       padding: 0,
     },
+    noticePaper: {
+      maxWidth: '480px',
+    },
+    paperSm: {
+      maxWidth: '80%',
+    },
+    dialogContent: {
+      marginTop: theme.spacing(4),
+    },
     title: {
       // padding: theme.spacing(4),
       '& p': {
@@ -80,7 +89,13 @@ const CustomDialog: FC<CustomDialogType> = props => {
 
   return (
     <Dialog
-      classes={{ paper: classes.paper, container: `${containerClass}` }}
+      classes={{
+        paper: `${classes.paper} ${
+          type === 'notice' ? classes.noticePaper : ''
+        }`,
+        paperWidthSm: type === 'notice' ? '' : classes.paperSm,
+        container: `${containerClass}`,
+      }}
       open={open}
       onClose={handleCancel}
     >
@@ -92,7 +107,11 @@ const CustomDialog: FC<CustomDialogType> = props => {
           >
             <Typography variant="body1">{title}</Typography>
           </CustomDialogTitle>
-          {component && <DialogContent>{component}</DialogContent>}
+          {component && (
+            <DialogContent classes={{ root: classes.dialogContent }}>
+              {component}
+            </DialogContent>
+          )}
           <DialogActions classes={{ spacing: classes.padding }}>
             <CustomButton
               onClick={() => handleCancel()}

+ 116 - 0
client/src/components/customDialog/DeleteDialogTemplate.tsx

@@ -0,0 +1,116 @@
+import {
+  DialogActions,
+  DialogContent,
+  makeStyles,
+  TextField,
+  Theme,
+  Typography,
+} from '@material-ui/core';
+import { ChangeEvent, FC, useContext, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import CustomButton from '../../components/customButton/CustomButton';
+import CustomDialogTitle from '../../components/customDialog/CustomDialogTitle';
+import { DeleteDialogContentType } from '../../components/customDialog/Types';
+import { rootContext } from '../../context/Root';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  root: {
+    maxWidth: '480px',
+  },
+  mb: {
+    marginBottom: theme.spacing(2.5),
+  },
+  btnWrapper: {
+    display: 'flex',
+  },
+  label: {
+    display: 'none',
+  },
+  btnLabel: {
+    fontWeight: 'bold',
+  },
+  input: {
+    padding: '10px 12px',
+  },
+  cancelBtn: {
+    color: '#82838e',
+  },
+}));
+
+const DeleteTemplate: FC<DeleteDialogContentType> = props => {
+  const { title, text, label, handleDelete, handleCancel = () => {} } = props;
+  const { handleCloseDialog } = useContext(rootContext);
+  const classes = useStyles();
+  const { t: dialogTrans } = useTranslation('dialog');
+  const { t: btnTrans } = useTranslation('btn');
+
+  const [value, setValue] = useState<string>('');
+  const [deleteReady, setDeleteReady] = useState<boolean>(false);
+
+  const onCancelClick = () => {
+    handleCloseDialog();
+    handleCancel();
+  };
+
+  const onDeleteClick = () => {
+    handleDelete();
+  };
+
+  const onChange = (event: ChangeEvent<HTMLInputElement>) => {
+    const value = event.target.value;
+    setValue(value);
+
+    setDeleteReady(value.toLowerCase() === label.toLowerCase());
+  };
+
+  return (
+    <div className={classes.root}>
+      <CustomDialogTitle classes={{ root: classes.mb }} onClose={onCancelClick}>
+        {title}
+      </CustomDialogTitle>
+
+      <DialogContent>
+        <Typography variant="body1">{text}</Typography>
+        <Typography variant="body1" className={classes.mb}>
+          {dialogTrans('deleteTipAction')}
+          <strong
+            className={classes.btnLabel}
+          >{` ${label.toLowerCase()} `}</strong>
+          {dialogTrans('deleteTipPurpose')}
+        </Typography>
+        <TextField
+          value={value}
+          onChange={onChange}
+          InputLabelProps={{
+            classes: {
+              root: classes.label,
+            },
+          }}
+          InputProps={{
+            classes: {
+              input: classes.input,
+            },
+          }}
+          variant="filled"
+          fullWidth={true}
+        />
+      </DialogContent>
+
+      <DialogActions className={classes.btnWrapper}>
+        <CustomButton onClick={onCancelClick} className={classes.cancelBtn}>
+          {btnTrans('cancel')}
+        </CustomButton>
+        <CustomButton
+          variant="contained"
+          onClick={onDeleteClick}
+          color="secondary"
+          disabled={!deleteReady}
+        >
+          {label}
+        </CustomButton>
+      </DialogActions>
+    </div>
+  );
+};
+
+export default DeleteTemplate;

+ 2 - 0
client/src/components/icons/Icons.tsx

@@ -17,6 +17,7 @@ import ExpandMore from '@material-ui/icons/ExpandMore';
 import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos';
 import ExitToAppIcon from '@material-ui/icons/ExitToApp';
 import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos';
+import RemoveCircleOutlineIcon from '@material-ui/icons/RemoveCircleOutline';
 import { SvgIcon } from '@material-ui/core';
 import { ReactComponent as MilvusIcon } from '../../assets/icons/milvus.svg';
 import { ReactComponent as OverviewIcon } from '../../assets/icons/overview.svg';
@@ -44,6 +45,7 @@ const icons: { [x in IconsType]: (props?: any) => React.ReactElement } = {
   back: (props = {}) => <ArrowBackIosIcon {...props} />,
   logout: (props = {}) => <ExitToAppIcon {...props} />,
   rightArrow: (props = {}) => <ArrowForwardIosIcon {...props} />,
+  remove: (props = {}) => <RemoveCircleOutlineIcon {...props} />,
 
   milvus: (props = {}) => (
     <SvgIcon viewBox="0 0 44 31" component={MilvusIcon} {...props} />

+ 2 - 1
client/src/components/icons/Types.ts

@@ -22,4 +22,5 @@ export type IconsType =
   | 'rightArrow'
   | 'info'
   | 'release'
-  | 'load';
+  | 'load'
+  | 'remove';

+ 24 - 0
client/src/i18n/cn/collection.ts

@@ -8,6 +8,8 @@ const collectionTrans = {
   create: 'Create Collection',
   delete: 'delete',
 
+  collection: 'Collection',
+
   // table
   id: 'ID',
   name: 'Name',
@@ -19,6 +21,28 @@ const collectionTrans = {
   general: '1. General Info',
   structure: '2. Define Structure',
   description: 'Description (Optional)',
+  fieldType: 'Field Type',
+  vectorFieldType: 'Vector Field Type',
+  fieldName: 'Field Name',
+  autoId: 'Auto ID',
+  dimension: 'Dimension',
+  newBtn: 'add new field',
+
+  // load dialog
+  loadTitle: 'Load Collection',
+  loadContent:
+    'You are trying to load a collection with data. Only loaded collection can be searched.',
+  loadConfirmLabel: 'Load',
+
+  // release dialog
+  releaseTitle: 'Release Collection',
+  releaseContent:
+    'You are trying to release a collection with data. Please be aware that the data will no longer be available for search.',
+  releaseConfirmLabel: 'Release',
+
+  // delete dialog
+  deleteWarning:
+    'You are trying to delete a collection with data. This action cannot be undone.',
 };
 
 export default collectionTrans;

+ 7 - 0
client/src/i18n/cn/dialog.ts

@@ -0,0 +1,7 @@
+const dialogTrans = {
+  deleteTipAction: 'Type',
+  deleteTipPurpose: 'to confirm.',
+  deleteTitle: `Delete {{type}}`,
+};
+
+export default dialogTrans;

+ 24 - 0
client/src/i18n/en/collection.ts

@@ -8,6 +8,8 @@ const collectionTrans = {
   create: 'Create Collection',
   delete: 'delete',
 
+  collection: 'Collection',
+
   // table
   id: 'ID',
   name: 'Name',
@@ -19,6 +21,28 @@ const collectionTrans = {
   general: '1. General Info',
   structure: '2. Define Structure',
   description: 'Description (Optional)',
+  fieldType: 'Field Type',
+  vectorFieldType: 'Vector Field Type',
+  fieldName: 'Field Name',
+  autoId: 'Auto ID',
+  dimension: 'Dimension',
+  newBtn: 'add new field',
+
+  // load dialog
+  loadTitle: 'Load Collection',
+  loadContent:
+    'You are trying to load a collection with data. Only loaded collection can be searched.',
+  loadConfirmLabel: 'Load',
+
+  // release dialog
+  releaseTitle: 'Release Collection',
+  releaseContent:
+    'You are trying to release a collection with data. Please be aware that the data will no longer be available for search.',
+  releaseConfirmLabel: 'Release',
+
+  // delete dialog
+  deleteWarning:
+    'You are trying to delete a collection with data. This action cannot be undone.',
 };
 
 export default collectionTrans;

+ 7 - 0
client/src/i18n/en/dialog.ts

@@ -0,0 +1,7 @@
+const dialogTrans = {
+  deleteTipAction: 'Type',
+  deleteTipPurpose: 'to confirm.',
+  deleteTitle: `Delete {{type}}`,
+};
+
+export default dialogTrans;

+ 4 - 0
client/src/i18n/index.ts

@@ -13,6 +13,8 @@ import overviewCn from './cn/overview';
 import overviewEn from './en/overview';
 import collectionCn from './cn/collection';
 import collectionEn from './en/collection';
+import dialogCn from './cn/dialog';
+import dialogEn from './en/dialog';
 
 export const resources = {
   cn: {
@@ -22,6 +24,7 @@ export const resources = {
     nav: navCn,
     overview: overviewCn,
     collection: collectionCn,
+    dialog: dialogCn,
   },
   en: {
     translation: commonEn,
@@ -30,6 +33,7 @@ export const resources = {
     nav: navEn,
     overview: overviewEn,
     collection: collectionEn,
+    dialog: dialogEn,
   },
 };
 

+ 75 - 5
client/src/pages/collections/Collections.tsx

@@ -11,11 +11,12 @@ import EmptyCard from '../../components/cards/EmptyCard';
 import Status from '../../components/status/Status';
 import { useTranslation } from 'react-i18next';
 import { StatusEnum } from '../../components/status/Types';
-import { makeStyles, Theme, Link } from '@material-ui/core';
+import { makeStyles, Theme, Link, Typography } from '@material-ui/core';
 import StatusIcon from '../../components/status/StatusIcon';
 import CustomToolTip from '../../components/customToolTip/CustomToolTip';
 import { rootContext } from '../../context/Root';
 import CreateCollection from './Create';
+import DeleteTemplate from '../../components/customDialog/DeleteDialogTemplate';
 
 const useStyles = makeStyles((theme: Theme) => ({
   emptyWrapper: {
@@ -26,6 +27,11 @@ const useStyles = makeStyles((theme: Theme) => ({
     fontSize: '20px',
     marginLeft: theme.spacing(0.5),
   },
+
+  dialogContent: {
+    lineHeight: '24px',
+    fontSize: '16px',
+  },
 }));
 
 const Collections = () => {
@@ -44,8 +50,10 @@ const Collections = () => {
     CollectionView[]
   >([]);
 
-  const { setDialog } = useContext(rootContext);
+  const { setDialog, handleCloseDialog } = useContext(rootContext);
   const { t } = useTranslation('collection');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: dialogTrans } = useTranslation('dialog');
 
   const classes = useStyles();
 
@@ -90,7 +98,56 @@ const Collections = () => {
   }, []);
 
   const handleCreateCollection = (param: CollectionCreateParam) => {
-    console.log('===== param', param);
+    handleCloseDialog();
+  };
+
+  const handleRelease = async (data: CollectionView) => {};
+
+  const handleLoad = async (data: CollectionView) => {};
+
+  const handleDelete = async () => {
+    console.log('selected', selectedCollections);
+  };
+
+  const handleAction = (data: CollectionView) => {
+    const actionType: 'release' | 'load' =
+      data.status === StatusEnum.loaded ? 'release' : 'load';
+
+    const actionsMap = {
+      release: {
+        title: t('releaseTitle'),
+        component: (
+          <Typography className={classes.dialogContent}>
+            {t('releaseContent')}
+          </Typography>
+        ),
+        confirmLabel: t('releaseConfirmLabel'),
+        confirm: () => handleRelease(data),
+      },
+      load: {
+        title: t('loadTitle'),
+        component: (
+          <Typography className={classes.dialogContent}>
+            {t('loadContent')}
+          </Typography>
+        ),
+        confirmLabel: t('loadConfirmLabel'),
+        confirm: () => handleLoad(data),
+      },
+    };
+
+    const { title, component, confirmLabel, confirm } = actionsMap[actionType];
+
+    setDialog({
+      open: true,
+      type: 'notice',
+      params: {
+        title,
+        component,
+        confirmLabel,
+        confirm,
+      },
+    });
   };
 
   const toolbarConfigs: ToolBarConfig[] = [
@@ -112,7 +169,20 @@ const Collections = () => {
     {
       type: 'iconBtn',
       onClick: () => {
-        console.log('delete collections');
+        setDialog({
+          open: true,
+          type: 'custom',
+          params: {
+            component: (
+              <DeleteTemplate
+                label={btnTrans('delete')}
+                title={dialogTrans('deleteTitle', { type: t('collection') })}
+                text={t('deleteWarning')}
+                handleDelete={handleDelete}
+              />
+            ),
+          },
+        });
       },
       label: t('delete'),
       icon: 'delete',
@@ -173,7 +243,7 @@ const Collections = () => {
       actionBarConfigs: [
         {
           onClick: (e: React.MouseEvent, row: CollectionView) => {
-            console.log('action row', row);
+            handleAction(row);
           },
           icon: 'load',
           label: 'load',

+ 11 - 0
client/src/pages/collections/Constants.ts

@@ -39,3 +39,14 @@ export const ALL_OPTIONS: KeyValuePair[] = [
     value: DataTypeEnum.Double,
   },
 ];
+
+export const AUTO_ID_OPTIONS: KeyValuePair[] = [
+  {
+    label: 'On',
+    value: 'true',
+  },
+  {
+    label: 'Off',
+    value: 'false',
+  },
+];

+ 38 - 3
client/src/pages/collections/Create.tsx

@@ -6,9 +6,15 @@ import CustomInput from '../../components/customInput/CustomInput';
 import { ITextfieldConfig } from '../../components/customInput/Types';
 import { rootContext } from '../../context/Root';
 import { useFormValidation } from '../../hooks/Form';
+import { generateId } from '../../utils/Common';
 import { formatForm } from '../../utils/Form';
 import CreateFields from './CreateFields';
-import { CollectionCreateProps, DataTypeEnum, Field } from './Types';
+import {
+  CollectionCreateParam,
+  CollectionCreateProps,
+  DataTypeEnum,
+  Field,
+} from './Types';
 
 const useStyles = makeStyles((theme: Theme) => ({
   fieldset: {
@@ -17,6 +23,11 @@ const useStyles = makeStyles((theme: Theme) => ({
     justifyContent: 'space-between',
     alignItems: 'center',
 
+    '&:last-child': {
+      flexDirection: 'column',
+      alignItems: 'flex-start',
+    },
+
     '& legend': {
       marginBottom: theme.spacing(2),
       color: `#82838e`,
@@ -24,6 +35,9 @@ const useStyles = makeStyles((theme: Theme) => ({
       fontSize: '14px',
     },
   },
+  input: {
+    width: '48%',
+  },
 }));
 
 const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
@@ -45,6 +59,7 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
       name: '',
       desc: '',
       isDefault: true,
+      id: generateId(),
     },
     {
       type: DataTypeEnum.FloatVector,
@@ -53,9 +68,10 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
       dimension: '',
       desc: '',
       isDefault: true,
+      id: generateId(),
     },
   ]);
-  const [fieldsAllValid, setFieldsAllValid] = useState<boolean>(false);
+  const [fieldsAllValid, setFieldsAllValid] = useState<boolean>(true);
 
   const checkedForm = useMemo(() => {
     const { name } = form;
@@ -63,6 +79,13 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
   }, [form]);
   const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
 
+  const changeIsAutoID = (value: boolean) => {
+    setForm({
+      ...form,
+      autoID: value,
+    });
+  };
+
   const handleInputChange = (key: string, value: string) => {
     setForm(v => ({ ...v, [key]: value }));
   };
@@ -80,6 +103,7 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
           errorText: warningTrans('required', { name: t('name') }),
         },
       ],
+      className: classes.input,
     },
     {
       label: t('description'),
@@ -88,15 +112,24 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
       onChange: (value: string) => handleInputChange('desc', value),
       variant: 'filled',
       validations: [],
+      className: classes.input,
     },
   ];
 
+  const handleCreateCollection = () => {
+    const param: CollectionCreateParam = {
+      ...form,
+      fields,
+    };
+    handleCreate(param);
+  };
+
   return (
     <DialogTemplate
       title={t('createTitle')}
       handleCancel={handleCloseDialog}
       confirmLabel={btnTrans('create')}
-      handleConfirm={handleCreate}
+      handleConfirm={handleCreateCollection}
       confirmDisabled={disabled || !fieldsAllValid}
     >
       <form>
@@ -119,6 +152,8 @@ const CreateCollection: FC<CollectionCreateProps> = ({ handleCreate }) => {
             fields={fields}
             setFields={setFields}
             setfieldsAllValid={setFieldsAllValid}
+            autoID={form.autoID}
+            setAutoID={changeIsAutoID}
           />
         </fieldset>
       </form>

+ 318 - 0
client/src/pages/collections/CreateFields.tsx

@@ -0,0 +1,318 @@
+import { makeStyles, Theme, TextField, IconButton } from '@material-ui/core';
+import { FC, Fragment, ReactElement } from 'react';
+import { useTranslation } from 'react-i18next';
+import CustomButton from '../../components/customButton/CustomButton';
+import CustomSelector from '../../components/customSelector/CustomSelector';
+import icons from '../../components/icons/Icons';
+import { generateId } from '../../utils/Common';
+import { getCreateFieldType } from '../../utils/Format';
+import {
+  ALL_OPTIONS,
+  AUTO_ID_OPTIONS,
+  VECTOR_FIELDS_OPTIONS,
+} from './Constants';
+import {
+  CreateFieldsProps,
+  CreateFieldType,
+  DataTypeEnum,
+  Field,
+} from './Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  rowWrapper: {
+    display: 'flex',
+    flexWrap: 'nowrap',
+    alignItems: 'center',
+    gap: '10px',
+    width: '100%',
+
+    '& .dimension': {
+      maxWidth: '160px',
+    },
+  },
+  input: {
+    fontSize: '14px',
+  },
+  primaryInput: {
+    maxWidth: '160px',
+  },
+  select: {
+    width: '160px',
+  },
+  descInput: {
+    minWidth: '270px',
+    flexGrow: 1,
+  },
+  btnTxt: {
+    textTransform: 'uppercase',
+  },
+  iconBtn: {
+    padding: 0,
+    width: '20px',
+    height: '20px',
+  },
+  mb3: {
+    marginBottom: theme.spacing(3),
+  },
+  mb2: {
+    marginBottom: theme.spacing(2),
+  },
+}));
+
+const CreateFields: FC<CreateFieldsProps> = ({
+  fields,
+  setFields,
+  // @TODO validation
+  setfieldsAllValid,
+  setAutoID,
+  autoID,
+}) => {
+  const { t } = useTranslation('collection');
+  const classes = useStyles();
+
+  const primaryInt64Value = 'INT64 (Primary key)';
+  const AddIcon = icons.add;
+  const RemoveIcon = icons.remove;
+
+  const getSelector = (
+    type: 'all' | 'vector',
+    label: string,
+    value: number,
+    onChange: (value: DataTypeEnum) => void
+  ) => (
+    <CustomSelector
+      options={type === 'all' ? ALL_OPTIONS : VECTOR_FIELDS_OPTIONS}
+      onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
+        onChange(e.target.value as DataTypeEnum);
+      }}
+      value={value}
+      variant="filled"
+      label={label}
+      classes={{ root: classes.select }}
+    />
+  );
+
+  const getInput = (
+    label: string,
+    value: string | number,
+    handleChange: (value: string) => void,
+    className = '',
+    inputClassName = '',
+    isReadOnly = false
+  ) => (
+    <TextField
+      label={label}
+      value={value}
+      onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
+        handleChange(e.target.value as string);
+      }}
+      variant="filled"
+      className={className}
+      InputProps={{
+        classes: {
+          input: inputClassName,
+        },
+      }}
+      disabled={isReadOnly}
+    />
+  );
+
+  const changeFields = (
+    id: string,
+    key: string,
+    value: string | DataTypeEnum
+  ) => {
+    const newFields = fields.map(f => {
+      if (f.id !== id) {
+        return f;
+      }
+      return {
+        ...f,
+        [key]: value,
+      };
+    });
+
+    setFields(newFields);
+  };
+
+  const handleAddNewField = () => {
+    const newDefaultItem: Field = {
+      name: '',
+      type: DataTypeEnum.Int16,
+      isPrimaryKey: false,
+      desc: '',
+      isDefault: false,
+      id: generateId(),
+    };
+    setFields([...fields, newDefaultItem]);
+  };
+
+  const handleRemoveField = (field: Field) => {
+    const newFields = fields.filter(f => f.id !== field.id);
+    setFields(newFields);
+  };
+
+  const generatePrimaryKeyRow = (
+    field: Field,
+    autoID: boolean
+  ): ReactElement => {
+    return (
+      <div className={`${classes.rowWrapper} ${classes.mb3}`}>
+        {getInput(
+          t('fieldType'),
+          primaryInt64Value,
+          () => {},
+          classes.primaryInput,
+          classes.input,
+          true
+        )}
+
+        {getInput(t('fieldName'), field.name, (value: string) =>
+          changeFields(field.id, 'name', value)
+        )}
+
+        <CustomSelector
+          label={t('autoId')}
+          options={AUTO_ID_OPTIONS}
+          value={autoID ? 'true' : 'false'}
+          onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
+            const autoId = e.target.value === 'true';
+            setAutoID(autoId);
+          }}
+          variant="filled"
+          classes={{ root: classes.select }}
+        />
+
+        {getInput(
+          t('description'),
+          field.desc,
+          (value: string) => changeFields(field.id, 'desc', value),
+          classes.descInput
+        )}
+      </div>
+    );
+  };
+
+  const generateDefaultVectorRow = (field: Field): ReactElement => {
+    return (
+      <>
+        <div className={`${classes.rowWrapper} ${classes.mb2}`}>
+          {getSelector(
+            'vector',
+            t('fieldType'),
+            field.type,
+            (value: DataTypeEnum) => changeFields(field.id, 'type', value)
+          )}
+
+          {getInput(t('fieldName'), field.name, (value: string) =>
+            changeFields(field.id, 'name', value)
+          )}
+
+          {getInput(
+            t('dimension'),
+            field.dimension as number,
+            (value: string) => changeFields(field.id, 'dimension', value),
+            'dimension'
+          )}
+
+          {getInput(
+            t('description'),
+            field.desc,
+            (value: string) => changeFields(field.id, 'desc', value),
+            classes.descInput
+          )}
+        </div>
+
+        <CustomButton onClick={handleAddNewField} className={classes.mb2}>
+          <AddIcon />
+          <span className={classes.btnTxt}>{t('newBtn')}</span>
+        </CustomButton>
+      </>
+    );
+  };
+
+  const generateNumberRow = (field: Field): ReactElement => {
+    return (
+      <div className={`${classes.rowWrapper} ${classes.mb2}`}>
+        <IconButton
+          onClick={() => handleRemoveField(field)}
+          classes={{ root: classes.iconBtn }}
+          aria-label="delete"
+        >
+          <RemoveIcon />
+        </IconButton>
+        {getInput(t('fieldName'), field.name, (value: string) =>
+          changeFields(field.id, 'name', value)
+        )}
+        {getSelector('all', t('fieldType'), field.type, (value: DataTypeEnum) =>
+          changeFields(field.id, 'type', value)
+        )}
+
+        {getInput(
+          t('description'),
+          field.desc,
+          (value: string) => changeFields(field.id, 'desc', value),
+          classes.descInput
+        )}
+      </div>
+    );
+  };
+
+  const generateVectorRow = (field: Field) => {
+    return (
+      <div className={`${classes.rowWrapper} ${classes.mb2}`}>
+        <IconButton classes={{ root: classes.iconBtn }} aria-label="delete">
+          <RemoveIcon />
+        </IconButton>
+        {getInput(t('fieldName'), field.name, (value: string) =>
+          changeFields(field.id, 'name', value)
+        )}
+        {getSelector('all', t('fieldType'), field.type, (value: DataTypeEnum) =>
+          changeFields(field.id, 'type', value)
+        )}
+        {getInput(
+          t('dimension'),
+          field.dimension as number,
+          (value: string) => changeFields(field.id, 'dimension', value),
+          'dimension'
+        )}
+
+        {getInput(
+          t('description'),
+          field.desc,
+          (value: string) => changeFields(field.id, 'desc', value),
+          classes.descInput
+        )}
+      </div>
+    );
+  };
+
+  const generateFieldRow = (field: Field, autoID: boolean) => {
+    const createType: CreateFieldType = getCreateFieldType(field);
+    switch (createType) {
+      case 'primaryKey': {
+        return generatePrimaryKeyRow(field, autoID);
+      }
+      case 'defaultVector': {
+        return generateDefaultVectorRow(field);
+      }
+      case 'vector': {
+        return generateVectorRow(field);
+      }
+      // use number as default createType
+      default: {
+        return generateNumberRow(field);
+      }
+    }
+  };
+
+  return (
+    <>
+      {fields.map((field, index) => (
+        <Fragment key={index}>{generateFieldRow(field, autoID)}</Fragment>
+      ))}
+    </>
+  );
+};
+
+export default CreateFields;

+ 8 - 1
client/src/pages/collections/Types.ts

@@ -41,12 +41,19 @@ export interface Field {
   desc: string;
   dimension?: number | string;
   isDefault?: boolean;
+  id: string;
 }
 
-export type CreateFieldType = 'primaryKey' | 'vector' | 'number';
+export type CreateFieldType =
+  | 'primaryKey'
+  | 'defaultVector'
+  | 'vector'
+  | 'number';
 
 export interface CreateFieldsProps {
   fields: Field[];
   setFields: Dispatch<SetStateAction<Field[]>>;
   setfieldsAllValid: Dispatch<SetStateAction<boolean>>;
+  autoID: boolean;
+  setAutoID: (value: boolean) => void;
 }

+ 4 - 1
client/src/utils/Format.ts

@@ -4,7 +4,6 @@ import {
   DataTypeEnum,
   Field,
 } from '../pages/collections/Types';
-import { KeyValuePair } from '../types/Common';
 
 /**
  * transform large capacity to capacity in b.
@@ -104,6 +103,10 @@ export const getCreateFieldType = (config: Field): CreateFieldType => {
     return 'primaryKey';
   }
 
+  if (config.isDefault) {
+    return 'defaultVector';
+  }
+
   const vectorTypes = [DataTypeEnum.BinaryVector, DataTypeEnum.FloatVector];
   if (vectorTypes.includes(config.type)) {
     return 'vector';