nameczz 4 years ago
parent
commit
850acf1f83

+ 25 - 51
client/src/consts/Milvus.tsx

@@ -1,33 +1,11 @@
-export const VECTOR_TYPE_OPTIONS = [
-  {
-    label: 'Vector float',
-    value: 'VECTOR_FLOAT',
-  },
-  {
-    label: 'Vector binary',
-    value: 'VECTOR_BINARY',
-  },
-];
-
-export const NON_VECTOR_TYPE_OPTIONS = [
-  {
-    label: 'Number',
-    value: 'number',
-  },
-  {
-    label: 'Float',
-    value: 'float',
-  },
-];
-
 export enum METRIC_TYPES_VALUES {
-  L2 = 1,
-  IP,
-  HAMMING,
-  JACCARD,
-  TANIMOTO,
-  SUBSTRUCTURE,
-  SUPERSTRUCTURE,
+  L2 = 'L2',
+  IP = 'IP',
+  HAMMING = 'Hamming',
+  JACCARD = 'Jaccard',
+  TANIMOTO = 'Tanimoto',
+  SUBSTRUCTURE = 'Substructure',
+  SUPERSTRUCTURE = 'Superstructure',
 }
 
 export const METRIC_TYPES = [
@@ -39,10 +17,6 @@ export const METRIC_TYPES = [
     value: METRIC_TYPES_VALUES.IP,
     label: 'IP',
   },
-  {
-    value: METRIC_TYPES_VALUES.HAMMING,
-    label: 'Hamming',
-  },
   {
     value: METRIC_TYPES_VALUES.SUBSTRUCTURE,
     label: 'Substructure',
@@ -51,6 +25,10 @@ export const METRIC_TYPES = [
     value: METRIC_TYPES_VALUES.SUPERSTRUCTURE,
     label: 'Superstructure',
   },
+  {
+    value: METRIC_TYPES_VALUES.HAMMING,
+    label: 'Hamming',
+  },
   {
     value: METRIC_TYPES_VALUES.JACCARD,
     label: 'Jaccard',
@@ -61,13 +39,14 @@ export const METRIC_TYPES = [
   },
 ];
 
-export const BINARY_METRIC_TYPES = [
-  'HAMMING',
-  'JACCARD',
-  'TANIMOTO',
-  'SUBSTRUCTURE',
-  'SUPERSTRUCTURE',
-];
+export type MetricType =
+  | 'L2'
+  | 'IP'
+  | 'Hamming'
+  | 'Substructure'
+  | 'Superstructure'
+  | 'Jaccard'
+  | 'Tanimoto';
 
 export type searchKeywordsType = 'nprobe' | 'ef' | 'search_k' | 'search_length';
 
@@ -124,20 +103,15 @@ export const m_OPTIONS = [
 
 export const INDEX_OPTIONS_MAP = {
   FLOAT_POINT: Object.keys(INDEX_CONFIG).map(v => ({ label: v, value: v })),
-  BINARY_ONE: [{ label: 'FLAT', value: 'FLAT' }],
-  BINARY_TWO: [
+  BINARY: [
     { label: 'FLAT', value: 'FLAT' },
     { label: 'IVF_FLAT', value: 'IVF_FLAT' },
   ],
 };
 
-export const FIELD_TYPES = {
-  VECTOR_FLOAT: 'vector_float',
-  VECTOR_BINARY: 'vector_binary',
-  Float: 'float',
-  Double: 'double',
-  INT32: 'int32',
-  INT64: 'int64',
-};
-
 export const PRIMARY_KEY_FIELD = 'INT64 (Primary key)';
+
+export enum EmbeddingTypeEnum {
+  float = 'FLOAT_POINT',
+  binary = 'BINARY',
+}

+ 15 - 2
client/src/http/Index.ts

@@ -1,9 +1,13 @@
-import { IndexView } from '../pages/structure/Types';
+import {
+  IndexCreateParam,
+  IndexView,
+  ParamPair,
+} from '../pages/structure/Types';
 import { IndexState } from '../types/Milvus';
 import BaseModel from './BaseModel';
 
 export class IndexHttp extends BaseModel implements IndexView {
-  params!: { key: string; value: string }[];
+  params!: ParamPair[];
   field_name!: string;
 
   constructor(props: {}) {
@@ -34,6 +38,15 @@ export class IndexHttp extends BaseModel implements IndexView {
     return res.index_descriptions.map((index: any) => new this(index));
   }
 
+  static async createIndex(param: IndexCreateParam) {
+    const path = this.BASE_URL;
+
+    return super.create({
+      path,
+      data: { ...param },
+    });
+  }
+
   get _indexType() {
     return this.params.find(p => p.key === 'index_type')?.value || '';
   }

+ 1 - 0
client/src/i18n/cn/common.ts

@@ -21,6 +21,7 @@ const commonTrans = {
     copy: 'Copy',
     copied: 'Copied',
   },
+  param: 'Parameter',
 };
 
 export default commonTrans;

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

@@ -8,6 +8,8 @@ const dialogTrans = {
 
   loadContent: `You are trying to load a {{type}} with data. Only loaded {{type}} can be searched.`,
   releaseContent: `You are trying to release a {{type}} with data. Please be aware that the data will no longer be available for search.`,
+
+  createTitle: `Create {{type}} in "{{name}}"`,
 };
 
 export default dialogTrans;

+ 5 - 0
client/src/i18n/cn/index.ts

@@ -2,6 +2,11 @@ const indexTrans = {
   type: 'Index Type',
   param: 'Index Parameters',
   create: 'Create Index',
+
+  index: 'Index',
+  metric: 'Metric Type',
+
+  createSuccess: 'Creating index successfully',
 };
 
 export default indexTrans;

+ 1 - 0
client/src/i18n/en/common.ts

@@ -21,6 +21,7 @@ const commonTrans = {
     copy: 'Copy',
     copied: 'Copied',
   },
+  param: 'Parameter',
 };
 
 export default commonTrans;

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

@@ -7,6 +7,8 @@ const dialogTrans = {
 
   loadContent: `You are trying to load a {{type}} with data. Only loaded {{type}} can be searched.`,
   releaseContent: `You are trying to release a {{type}} with data. Please be aware that the data will no longer be available for search.`,
+
+  createTitle: `Create {{type}} in "{{name}}"`,
 };
 
 export default dialogTrans;

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

@@ -3,6 +3,10 @@ const indexTrans = {
   param: 'Index Parameters',
 
   create: 'Create Index',
+  index: 'Index',
+
+  metric: 'Metric Type',
+  createSuccess: 'Creating index successfully',
 };
 
 export default indexTrans;

+ 160 - 0
client/src/pages/structure/Create.tsx

@@ -0,0 +1,160 @@
+import { useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import DialogTemplate from '../../components/customDialog/DialogTemplate';
+import {
+  EmbeddingTypeEnum,
+  INDEX_CONFIG,
+  INDEX_OPTIONS_MAP,
+  MetricType,
+} from '../../consts/Milvus';
+import { useFormValidation } from '../../hooks/Form';
+import { formatForm, getMetricOptions } from '../../utils/Form';
+import { DataType } from '../collections/Types';
+import CreateForm from './CreateForm';
+import { IndexType, ParamPair } from './Types';
+
+const CreateIndex = (props: {
+  collectionName: string;
+  fieldType: DataType;
+  handleCreate: (params: ParamPair[]) => void;
+  handleCancel: () => void;
+}) => {
+  const { collectionName, fieldType, handleCreate, handleCancel } = props;
+
+  const { t: indexTrans } = useTranslation('index');
+  const { t: dialogTrans } = useTranslation('dialog');
+  const { t: btnTrans } = useTranslation('btn');
+
+  const defaultMetricType = fieldType === 'BinaryVector' ? 'Hamming' : 'L2';
+
+  const [indexSetting, setIndexSetting] = useState<{
+    index_type: IndexType;
+    metric_type: MetricType;
+    [x: string]: string;
+  }>({
+    index_type: 'IVF_FLAT',
+    metric_type: defaultMetricType,
+    M: '',
+    m: '4',
+    efConstruction: '',
+    nlist: '',
+    n_trees: '',
+    outDegree: '',
+    candidatePoolSize: '',
+    searchLength: '',
+    knng: '',
+  });
+
+  const indexCreateParams = useMemo(() => {
+    if (!INDEX_CONFIG[indexSetting.index_type]) {
+      return [];
+    }
+    return INDEX_CONFIG[indexSetting.index_type].create;
+  }, [indexSetting.index_type]);
+
+  const metricOptions = useMemo(
+    () => getMetricOptions(indexSetting.index_type, fieldType),
+    [indexSetting.index_type, fieldType]
+  );
+
+  const indexOptions = useMemo(() => {
+    const type =
+      fieldType === 'BinaryVector'
+        ? EmbeddingTypeEnum.binary
+        : EmbeddingTypeEnum.float;
+    return INDEX_OPTIONS_MAP[type];
+  }, [fieldType]);
+
+  const checkedForm = useMemo(() => {
+    const paramsForm: any = { metric_type: indexSetting.metric_type };
+    indexCreateParams.forEach(v => {
+      paramsForm[v] = indexSetting[v];
+    });
+    const form = formatForm(paramsForm);
+    return form;
+  }, [indexSetting, indexCreateParams]);
+
+  const { validation, checkIsValid, disabled, setDisabled, resetValidation } =
+    useFormValidation(checkedForm);
+
+  useEffect(() => {
+    setDisabled(true);
+    setIndexSetting(v => ({
+      ...v,
+      metric_type: defaultMetricType,
+      M: '',
+      m: '4',
+      efConstruction: '',
+      nlist: '',
+      n_trees: '',
+      out_degree: '',
+      candidate_pool_size: '',
+      search_length: '',
+      knng: '',
+    }));
+  }, [indexCreateParams, setDisabled, defaultMetricType]);
+
+  const updateStepTwoForm = (type: string, value: string) => {
+    setIndexSetting(v => ({ ...v, [type]: value }));
+  };
+
+  const onIndexTypeChange = (type: string) => {
+    let paramsForm: { [key in string]: string } = {};
+    // m is selector not input
+    (INDEX_CONFIG[type].create || [])
+      .filter(t => t !== 'm')
+      .forEach(item => {
+        paramsForm[item] = '';
+      });
+
+    const form = formatForm(paramsForm);
+    resetValidation(form);
+  };
+
+  const handleCreateIndex = () => {
+    const { index_type, metric_type } = indexSetting;
+
+    const params: ParamPair[] = [
+      {
+        key: 'index_type',
+        value: index_type,
+      },
+      {
+        key: 'metric_type',
+        value: metric_type,
+      },
+      ...indexCreateParams.map(p => ({
+        key: p,
+        value: indexSetting[p],
+      })),
+    ];
+
+    handleCreate(params);
+  };
+
+  return (
+    <DialogTemplate
+      title={dialogTrans('createTitle', {
+        type: indexTrans('index'),
+        name: collectionName,
+      })}
+      handleCancel={handleCancel}
+      confirmLabel={btnTrans('create')}
+      handleConfirm={handleCreateIndex}
+      confirmDisabled={disabled}
+    >
+      <CreateForm
+        updateForm={updateStepTwoForm}
+        metricOptions={metricOptions}
+        indexOptions={indexOptions}
+        formValue={indexSetting}
+        checkIsValid={checkIsValid}
+        validation={validation}
+        indexParams={indexCreateParams}
+        indexTypeChange={onIndexTypeChange}
+      />
+    </DialogTemplate>
+  );
+};
+
+export default CreateIndex;

+ 203 - 0
client/src/pages/structure/CreateForm.tsx

@@ -0,0 +1,203 @@
+import { makeStyles, Theme, Typography } from '@material-ui/core';
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { ITextfieldConfig } from '../../components/customInput/Types';
+import CustomInput from '../../components/customInput/CustomInput';
+import CustomSelector from '../../components/customSelector/CustomSelector';
+import { m_OPTIONS } from '../../consts/Milvus';
+import { FormHelperType } from '../../types/Common';
+import { Option } from '../../components/customSelector/Types';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  wrapper: {
+    maxWidth: '480px',
+  },
+  select: {
+    width: '100%',
+    marginBottom: theme.spacing(2),
+  },
+  paramTitle: {
+    margin: theme.spacing(2, 0),
+    color: '#82838e',
+    lineHeight: '20px',
+    fontSize: '14px',
+  },
+}));
+
+const CreateForm = (
+  props: FormHelperType & {
+    metricOptions: Option[];
+    indexOptions: Option[];
+    indexParams: string[];
+    indexTypeChange?: (type: string) => void;
+  }
+) => {
+  const classes = useStyles();
+  const {
+    updateForm,
+    formValue,
+    checkIsValid,
+    validation,
+    indexParams,
+    indexTypeChange,
+    indexOptions,
+    metricOptions,
+  } = props;
+
+  const { t } = useTranslation();
+  const { t: indexTrans } = useTranslation('index');
+  const { t: warningTrans } = useTranslation('warning');
+
+  const paramsConfig: ITextfieldConfig[] = useMemo(() => {
+    const result = [];
+    const generateNumberConfig = (
+      label: string,
+      key: string,
+      min: number,
+      max: number
+    ) => {
+      const config: ITextfieldConfig = {
+        label,
+        key,
+        onChange: value => {
+          updateForm(key, value);
+        },
+        variant: 'filled',
+        fullWidth: true,
+        type: 'number',
+        value: formValue[key],
+        validations: [
+          {
+            rule: 'require',
+            errorText: warningTrans('required', { name: label }),
+          },
+          {
+            rule: 'range',
+            errorText: warningTrans('range', { min, max }),
+            extraParam: {
+              min,
+              max,
+              type: 'number',
+            },
+          },
+        ],
+      };
+      return config;
+    };
+
+    const nlist = generateNumberConfig('nlist', 'nlist', 1, 65536);
+    const nTrees = generateNumberConfig('nTrees', 'n_trees', 1, 1024);
+
+    const M = generateNumberConfig('M', 'M', 4, 64);
+    const efConstruction = generateNumberConfig(
+      'Ef Construction',
+      'efConstruction',
+      8,
+      512
+    );
+
+    const outDegree = generateNumberConfig('out_degree', 'out_degree', 5, 300);
+    const candidatePoolSize = generateNumberConfig(
+      'candidate_pool_size',
+      'candidate_pool_size',
+      50,
+      1000
+    );
+    const searchLength = generateNumberConfig(
+      'search_length',
+      'search_length',
+      10,
+      300
+    );
+    const knng = generateNumberConfig('knng', 'knng', 5, 300);
+
+    if (indexParams.includes('nlist')) {
+      result.push(nlist);
+    }
+
+    if (indexParams.includes('M')) {
+      result.push(M);
+    }
+
+    if (indexParams.includes('efConstruction')) {
+      result.push(efConstruction);
+    }
+
+    if (indexParams.includes('n_trees')) {
+      result.push(nTrees);
+    }
+
+    if (indexParams.includes('out_degree')) {
+      result.push(outDegree);
+    }
+
+    if (indexParams.includes('candidate_pool_size')) {
+      result.push(candidatePoolSize);
+    }
+
+    if (indexParams.includes('search_length')) {
+      result.push(searchLength);
+    }
+
+    if (indexParams.includes('knng')) {
+      result.push(knng);
+    }
+
+    return result;
+  }, [updateForm, warningTrans, indexParams, formValue]);
+  return (
+    <div className={classes.wrapper}>
+      <CustomSelector
+        label={indexTrans('type')}
+        value={formValue.index_type}
+        options={indexOptions}
+        onChange={(e: { target: { value: unknown } }) => {
+          const type = e.target.value;
+          updateForm('index_type', type as string);
+          // reset metric type value
+          updateForm('metric_type', 'L2');
+          indexTypeChange && indexTypeChange(type as string);
+        }}
+        variant="filled"
+        classes={{ root: classes.select }}
+      />
+
+      <Typography className={classes.paramTitle}>{t('param')}</Typography>
+      <CustomSelector
+        label={indexTrans('metric')}
+        value={formValue.metric_type}
+        options={metricOptions}
+        onChange={(e: { target: { value: unknown } }) => {
+          const type = e.target.value;
+          updateForm('metric_type', type as string);
+        }}
+        variant="filled"
+        classes={{ root: classes.select }}
+      />
+
+      {indexParams.includes('m') && (
+        <CustomSelector
+          label="m"
+          value={Number(formValue.m)}
+          options={m_OPTIONS}
+          onChange={(e: { target: { value: unknown } }) =>
+            updateForm('m', e.target.value as string)
+          }
+          variant="filled"
+          classes={{ root: classes.select }}
+        />
+      )}
+
+      {paramsConfig.map(v => (
+        <CustomInput
+          type="text"
+          textConfig={v}
+          checkValid={checkIsValid}
+          validInfo={validation}
+          key={v.label}
+        />
+      ))}
+    </div>
+  );
+};
+export default CreateForm;

+ 51 - 13
client/src/pages/structure/IndexTypeElement.tsx

@@ -1,14 +1,17 @@
-import { FC, useCallback, useEffect, useState } from 'react';
+import { FC, useCallback, useContext, useEffect, useState } from 'react';
 import Chip from '@material-ui/core/Chip';
 import CustomButton from '../../components/customButton/CustomButton';
 import { IndexHttp } from '../../http/Index';
 import { IndexState } from '../../types/Milvus';
-import { FieldView } from './Types';
+import { FieldView, IndexCreateParam, ParamPair } from './Types';
 import StatusIcon from '../../components/status/StatusIcon';
 import { ChildrenStatusType } from '../../components/status/Types';
 import { useTranslation } from 'react-i18next';
 import { makeStyles, Theme } from '@material-ui/core';
 import icons from '../../components/icons/Icons';
+import { rootContext } from '../../context/Root';
+import CreateIndex from './Create';
+import { ManageRequestMethods } from '../../types/Common';
 
 const useStyles = makeStyles((theme: Theme) => ({
   item: {
@@ -17,6 +20,7 @@ const useStyles = makeStyles((theme: Theme) => ({
   btn: {
     '& span': {
       textTransform: 'uppercase',
+      whiteSpace: 'nowrap',
     },
   },
   chip: {
@@ -24,31 +28,65 @@ const useStyles = makeStyles((theme: Theme) => ({
   },
 }));
 
-const IndexTypeElement: FC<{ data: FieldView; collectionName: string }> = ({
-  data,
-  collectionName,
-}) => {
+const IndexTypeElement: FC<{
+  data: FieldView;
+  collectionName: string;
+  createCb: (collectionName: string) => void;
+}> = ({ data, collectionName, createCb }) => {
   const classes = useStyles();
 
   const [status, setStatus] = useState<string>('');
   const { t } = useTranslation('index');
 
+  const { setDialog, handleCloseDialog, openSnackBar } =
+    useContext(rootContext);
+
   const AddIcon = icons.add;
   const DeleteIcon = icons.delete;
 
   const fetchStatus = useCallback(async () => {
-    const status = await IndexHttp.getIndexStatus(
-      collectionName,
-      data._fieldName
-    );
-    setStatus(status);
-  }, [collectionName, data._fieldName]);
+    if (data._indexType !== '') {
+      const status = await IndexHttp.getIndexStatus(
+        collectionName,
+        data._fieldName
+      );
+      setStatus(status);
+    }
+  }, [collectionName, data._fieldName, data._indexType]);
 
   useEffect(() => {
     fetchStatus();
   }, [fetchStatus]);
 
-  const handleCreate = () => {};
+  const requestCreateIndex = async (params: ParamPair[]) => {
+    const indexCreateParam: IndexCreateParam = {
+      type: ManageRequestMethods.CREATE,
+      collection_name: collectionName,
+      field_name: data._fieldName,
+      extra_params: params,
+    };
+    await IndexHttp.createIndex(indexCreateParam);
+    handleCloseDialog();
+    openSnackBar(t('createSuccess'));
+    createCb(collectionName);
+  };
+
+  const handleCreate = () => {
+    setDialog({
+      open: true,
+      type: 'custom',
+      params: {
+        component: (
+          <CreateIndex
+            collectionName={collectionName}
+            fieldType={data._fieldType}
+            handleCancel={handleCloseDialog}
+            handleCreate={requestCreateIndex}
+          />
+        ),
+      },
+    });
+  };
 
   const handleDelete = () => {};
 

+ 21 - 15
client/src/pages/structure/Structure.tsx

@@ -31,19 +31,21 @@ const useStyles = makeStyles((theme: Theme) => ({
     },
   },
 
-  param: {
-    padding: theme.spacing(0.5),
+  paramWrapper: {
+    '& .param': {
+      padding: theme.spacing(0.5),
 
-    marginRight: theme.spacing(2),
+      marginRight: theme.spacing(2),
 
-    '& .key': {
-      color: '#82838e',
-      display: 'inline-block',
-      marginRight: theme.spacing(0.5),
-    },
+      '& .key': {
+        color: '#82838e',
+        display: 'inline-block',
+        marginRight: theme.spacing(0.5),
+      },
 
-    '& .value': {
-      color: '#010e29',
+      '& .value': {
+        color: '#010e29',
+      },
     },
   },
 }));
@@ -107,10 +109,10 @@ const Structure: FC<{
               </div>
             ),
             _indexParamElement: (
-              <>
+              <div className={classes.paramWrapper}>
                 {f._indexParameterPairs?.length > 0 ? (
                   f._indexParameterPairs.map(p => (
-                    <span key={p.key} className={classes.param}>
+                    <span key={p.key} className="param">
                       <Typography variant="caption" className="key">
                         {`${p.key}:`}
                       </Typography>
@@ -122,10 +124,14 @@ const Structure: FC<{
                 ) : (
                   <>--</>
                 )}
-              </>
+              </div>
             ),
             _indexTypeElement: (
-              <IndexTypeElement data={f} collectionName={collectionName} />
+              <IndexTypeElement
+                data={f}
+                collectionName={collectionName}
+                createCb={fetchFields}
+              />
             ),
           })
         );
@@ -137,7 +143,7 @@ const Structure: FC<{
         throw err;
       }
     },
-    [classes.nameWrapper, classes.param]
+    [classes.nameWrapper, classes.paramWrapper]
   );
 
   useEffect(() => {

+ 23 - 1
client/src/pages/structure/Types.ts

@@ -1,5 +1,5 @@
 import { ReactElement } from 'react';
-import { IndexState } from '../../types/Milvus';
+import { ManageRequestMethods } from '../../types/Common';
 import { DataType } from '../collections/Types';
 
 export enum INDEX_TYPES_ENUM {
@@ -37,3 +37,25 @@ export interface IndexView {
   _indexParameterPairs: { key: string; value: string }[];
   _indexParamElement?: ReactElement;
 }
+
+export type IndexType =
+  | 'FLAT'
+  | 'IVF_FLAT'
+  | 'IVF_SQ8'
+  // | 'IVF_SQ8_HYBRID'
+  | 'IVF_PQ'
+  | 'RNSG'
+  | 'HNSW'
+  | 'ANNOY';
+
+export interface IndexCreateParam {
+  type: ManageRequestMethods;
+  collection_name: string;
+  field_name: string;
+  extra_params: ParamPair[];
+}
+
+export interface ParamPair {
+  key: string;
+  value: string;
+}

+ 9 - 0
client/src/types/Common.ts

@@ -1,3 +1,5 @@
+import { IValidationItem } from '../hooks/Form';
+
 export interface KeyValuePair {
   label: string;
   value: string | number;
@@ -25,3 +27,10 @@ export enum ManageRequestMethods {
   DELETE = 'delete',
   CREATE = 'create',
 }
+
+export type FormHelperType = {
+  formValue: { [x: string]: any };
+  updateForm: (type: string, value: string) => void;
+  validation: { [key: string]: IValidationItem };
+  checkIsValid: Function;
+};

+ 62 - 3
client/src/utils/Form.ts

@@ -1,7 +1,11 @@
-import { IForm } from "../hooks/Form";
+import { Option } from '../components/customSelector/Types';
+import { METRIC_TYPES_VALUES } from '../consts/Milvus';
+import { IForm } from '../hooks/Form';
+import { DataType } from '../pages/collections/Types';
+import { IndexType } from '../pages/structure/Types';
 
 interface IInfo {
-  [key: string]: any
+  [key: string]: any;
 }
 
 export const formatForm = (info: IInfo): IForm[] => {
@@ -14,4 +18,59 @@ export const formatForm = (info: IInfo): IForm[] => {
     };
   });
   return form;
-}
+};
+
+export const getMetricOptions = (
+  indexType: IndexType,
+  fieldType: DataType
+): Option[] => {
+  const baseFloatOptions = [
+    {
+      value: METRIC_TYPES_VALUES.L2,
+      label: 'L2',
+    },
+    {
+      value: METRIC_TYPES_VALUES.IP,
+      label: 'IP',
+    },
+  ];
+
+  const baseBinaryOptions = [
+    {
+      value: METRIC_TYPES_VALUES.HAMMING,
+      label: 'Hamming',
+    },
+    {
+      value: METRIC_TYPES_VALUES.JACCARD,
+      label: 'Jaccard',
+    },
+    {
+      value: METRIC_TYPES_VALUES.TANIMOTO,
+      label: 'Tanimoto',
+    },
+  ];
+
+  const type = fieldType === 'FloatVector' ? 'ALL' : indexType;
+
+  const baseOptionsMap: { [key: string]: any } = {
+    BinaryVector: {
+      FLAT: [
+        ...baseBinaryOptions,
+        {
+          value: METRIC_TYPES_VALUES.SUBSTRUCTURE,
+          label: 'Substructure',
+        },
+        {
+          value: METRIC_TYPES_VALUES.SUPERSTRUCTURE,
+          label: 'Superstructure',
+        },
+      ],
+      IVF_FLAT: baseBinaryOptions,
+    },
+    FloatVector: {
+      ALL: baseFloatOptions,
+    },
+  };
+
+  return baseOptionsMap[fieldType][type];
+};

+ 3 - 3
client/src/utils/Validation.ts

@@ -1,4 +1,4 @@
-import { METRIC_TYPES_VALUES } from '../consts/Milvus';
+import { MetricType, METRIC_TYPES_VALUES } from '../consts/Milvus';
 
 export type ValidType =
   | 'email'
@@ -28,7 +28,7 @@ export interface IExtraParam {
   type?: 'string' | 'number';
 
   // used for dimension
-  metricType?: number;
+  metricType?: MetricType;
   multipleNumber?: number;
 }
 export type CheckMap = {
@@ -133,7 +133,7 @@ export const checkMultiple = (param: {
 
 export const checkDimension = (param: {
   value: string;
-  metricType?: number;
+  metricType?: MetricType;
   multipleNumber?: number;
 }): boolean => {
   const { value, metricType, multipleNumber } = param;

+ 3 - 0
server/src/schema/schema.service.ts

@@ -25,6 +25,9 @@ export class SchemaService {
 
   async describeIndex(data: DescribeIndexReq) {
     const res = await this.milvusClient.describeIndex(data);
+    if (res.status.error_code === 'IndexNotExist') {
+      return res;
+    }
     throwErrorFromSDK(res.status);
     return res;
   }