Browse Source

feat: refactor create collection dialog (#860)

* refact: descriptionField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: fieldname input

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refact: extract defaultvaluefield

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract nullable checkbox

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract textMatchCheckboxField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract DimensionField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract maxLengthField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract maxCapapcityField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract paritionkeycheckboxField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract AnalyzerCheckboxField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: extract getSelector to 4 components

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* use sx for AnalyzerCheckboxField

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactors: refactor rows

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor: use sx

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove className

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* fix row styles and checkbox styles

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* fix styles

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* fix first load error

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* validation part1

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* validation part2

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* use ref

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update styles

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update style

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update validation

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove bm25 varchar

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* refactor

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* finish bm25 function

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* remove className

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* better ux

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 4 days ago
parent
commit
c27d1da98a
33 changed files with 2315 additions and 1109 deletions
  1. 2 0
      client/src/components/customDialog/DialogTemplate.tsx
  2. 3 0
      client/src/components/customDialog/Types.ts
  3. 2 0
      client/src/components/customInput/CustomInput.tsx
  4. 2 0
      client/src/components/customInput/Types.ts
  5. 0 1
      client/src/consts/Milvus.ts
  6. 9 1
      client/src/i18n/cn/collection.ts
  7. 12 1
      client/src/i18n/en/collection.ts
  8. 3 5
      client/src/pages/databases/collections/Types.ts
  9. 207 202
      client/src/pages/dialogs/CreateCollectionDialog.tsx
  10. 132 0
      client/src/pages/dialogs/create/AnalyzerCheckboxField.tsx
  11. 248 0
      client/src/pages/dialogs/create/BM25FunctionSection.tsx
  12. 0 4
      client/src/pages/dialogs/create/Constants.ts
  13. 86 894
      client/src/pages/dialogs/create/CreateFields.tsx
  14. 65 0
      client/src/pages/dialogs/create/DefaultValueField.tsx
  15. 47 0
      client/src/pages/dialogs/create/DescriptionField.tsx
  16. 89 0
      client/src/pages/dialogs/create/DimensionField.tsx
  17. 56 0
      client/src/pages/dialogs/create/ElementTypeSelector.tsx
  18. 122 0
      client/src/pages/dialogs/create/ExtraInfoSection.tsx
  19. 105 0
      client/src/pages/dialogs/create/MaxCapacityField.tsx
  20. 105 0
      client/src/pages/dialogs/create/MaxLengthField.tsx
  21. 94 0
      client/src/pages/dialogs/create/NameField.tsx
  22. 47 0
      client/src/pages/dialogs/create/NullableCheckboxField.tsx
  23. 56 0
      client/src/pages/dialogs/create/PartitionKeyCheckboxField.tsx
  24. 63 0
      client/src/pages/dialogs/create/PrimaryKeyTypeSelector.tsx
  25. 48 0
      client/src/pages/dialogs/create/ScalarTypeSelector.tsx
  26. 55 0
      client/src/pages/dialogs/create/TextMatchCheckboxField.tsx
  27. 48 0
      client/src/pages/dialogs/create/VectorTypeSelector.tsx
  28. 174 0
      client/src/pages/dialogs/create/rows/FunctionFieldRow.tsx
  29. 98 0
      client/src/pages/dialogs/create/rows/PrimaryKeyFieldRow.tsx
  30. 210 0
      client/src/pages/dialogs/create/rows/ScalarFieldRow.tsx
  31. 100 0
      client/src/pages/dialogs/create/rows/VectorFieldRow.tsx
  32. 25 0
      client/src/pages/dialogs/create/rows/styles.ts
  33. 2 1
      client/src/styles/theme.ts

+ 2 - 0
client/src/components/customDialog/DialogTemplate.tsx

@@ -28,6 +28,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   showCode = false,
   codeBlocksData = [],
   dialogClass = '',
+  sx = {},
 }) => {
   const [confirming, setConfirming] = useState(false);
   const { t } = useTranslation('btn');
@@ -67,6 +68,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
             minWidth: 540,
             transition: 'width 0.2s',
             width: showCode ? 540 : 'auto',
+            ...sx,
           }}
         >
           <CustomDialogTitle

+ 3 - 0
client/src/components/customDialog/Types.ts

@@ -1,6 +1,8 @@
 import { ReactElement } from 'react';
 import { DialogType } from '@/context';
 import { CodeViewData } from '../code/Types';
+import { SxProps, Theme } from '@mui/material';
+
 export type CustomDialogType = DialogType & {
   onClose: () => void;
   containerClass?: string;
@@ -41,4 +43,5 @@ export type DialogContainerProps = {
   codeBlocksData?: CodeViewData[];
   children: ReactElement;
   dialogClass?: string;
+  sx?: SxProps<Theme>;
 };

+ 2 - 0
client/src/components/customInput/CustomInput.tsx

@@ -198,6 +198,7 @@ const getTextfield = (
     inputProps,
     InputProps,
     value,
+    sx,
     ...others
   } = config;
 
@@ -239,6 +240,7 @@ const getTextfield = (
       }}
       type={others.type || 'text'}
       value={value}
+      sx={sx}
       onChange={event => {
         handleOnChange({
           event,

+ 2 - 0
client/src/components/customInput/Types.ts

@@ -2,6 +2,7 @@ import { ReactElement } from 'react';
 import { InputLabelProps } from '@mui/material';
 import { IValidationItem } from '@/hooks';
 import { IExtraParam, ValidType } from '@/utils';
+import { SxProps, Theme } from '@mui/material';
 
 export type InputType = 'icon' | 'adornment' | 'text' | undefined;
 export type VariantType = 'filled' | 'outlined' | 'standard';
@@ -82,6 +83,7 @@ export interface ITextfieldConfig {
   onChange?: (event: any) => void;
   onKeyDown?: (event: any) => void;
   InputLabelProps?: Partial<InputLabelProps>;
+  sx?: SxProps<Theme>;
 }
 
 export interface IAdornmentConfig {

+ 0 - 1
client/src/consts/Milvus.ts

@@ -27,7 +27,6 @@ export enum DataTypeEnum {
   Float16Vector = 102,
   BFloat16Vector = 103,
   SparseFloatVector = 104,
-  VarCharBM25 = 1000,
 }
 
 export const VectorTypes = [

+ 9 - 1
client/src/i18n/cn/collection.ts

@@ -15,6 +15,7 @@ const collectionTrans = {
   mmapSettings: 'MMap 设置',
   collectionMMapSettingsLabel: '全局原始数据 MMap 设置',
   rawData: '全局内存映射 (MMap) 的原始数据配置',
+  functions: 'Functions',
 
   // table
   id: 'ID',
@@ -41,7 +42,7 @@ const collectionTrans = {
 
   // create dialog
   createTitle: '创建 Collection {{name}}',
-  idAndVectorFields: 'ID、向量或可用 BM25 算法处理的文本字段',
+  idAndVectorFields: 'ID、向量字段',
   scalarFields: '标量字段',
   schema: 'Schema',
   consistency: '一致性',
@@ -81,6 +82,13 @@ const collectionTrans = {
   loadCollectionAfterCreate: '创建后加载 Collection',
   loadCollectionAfterCreateTooltip:
     'Attu 将使用 AUTOINDEX 为所有字段创建 Index,然后加载 Collection。',
+  addBm25Function: '添加 BM25 Function',
+  bm25NoVarcharAndSparse: '要创建 BM25 Function,请至少添加一个 VarChar 字段和一个 SparseFloatVector 字段。',
+  bm25NoVarchar: '要创建 BM25 Function,请至少添加一个 VarChar 字段。',
+  bm25NoSparse: '要创建 BM25 Function,请至少添加一个 SparseFloatVector 字段。',
+  bm25InputVarChar: '输入 VarChar 字段',
+  bm25OutputSparse: '输出 SparseFloatVector 字段',
+  noAvailableSparse: '没有可用的字段',
 
   // load dialog
   loadTitle: '加载 Collection',

+ 12 - 1
client/src/i18n/en/collection.ts

@@ -15,6 +15,7 @@ const collectionTrans = {
   mmapSettings: 'MMap Settings',
   collectionMMapSettingsLabel: 'Collection Raw Data MMap Settings',
   rawData: 'Raw Data',
+  functions: 'Functions',
 
   // table
   id: 'ID',
@@ -42,7 +43,7 @@ const collectionTrans = {
 
   // create dialog
   createTitle: 'Create Collection {{name}}',
-  idAndVectorFields: 'Primary Key, Vector, or BM25(VarChar) Fields',
+  idAndVectorFields: 'Primary Key, Vector Fields',
   scalarFields: 'Scalar Fields',
   schema: 'Schema',
   consistency: 'Consistency',
@@ -87,6 +88,16 @@ const collectionTrans = {
   loadCollectionAfterCreate: 'Load collection immediately after creation',
   loadCollectionAfterCreateTip:
     'Attu will create indexes for fields using AUTOINDEX and then load the collection.',
+  addBm25Function: 'Add BM25 Function',
+  bm25NoVarcharAndSparse:
+    'To create a BM25 function, please add at least one VarChar field and one SparseFloatVector field.',
+  bm25NoVarchar:
+    'To create a BM25 function, please add at least one VarChar field.',
+  bm25NoSparse:
+    'To create a BM25 function, please add at least one SparseFloatVector field.',
+  bm25InputVarChar: 'Input VarChar',
+  bm25OutputSparse: 'Output SparseFloatVector',
+  noAvailableSparse: 'No available field',
 
   // load dialog
   loadTitle: 'Load Collection',

+ 3 - 5
client/src/pages/databases/collections/Types.ts

@@ -26,7 +26,7 @@ export interface CollectionCreateParam {
 export type AnalyzerType = 'standard' | 'english' | 'chinese';
 
 export interface CreateField {
-  name: string | null;
+  name: string;
   data_type: DataTypeEnum;
   is_primary_key: boolean;
   is_partition_key?: boolean;
@@ -53,7 +53,7 @@ export type CreateFieldType =
   | 'number';
 
 export type FieldType = {
-  name: string | null;
+  name: string;
   data_type: DataTypeEnum;
   element_type?: DataTypeEnum;
   is_primary_key: boolean;
@@ -80,11 +80,9 @@ export type FieldType = {
 export interface CreateFieldsProps {
   fields: CreateField[];
   setFields: Dispatch<SetStateAction<CreateField[]>>;
-  setFieldsValidation: Dispatch<
-    SetStateAction<{ [x: string]: string | boolean }[]>
-  >;
   autoID: boolean;
   setAutoID: (value: boolean) => void;
+  onValidationChange: (isValid: boolean) => void;
 }
 
 export interface InsertDataParam {

+ 207 - 202
client/src/pages/dialogs/CreateCollectionDialog.tsx

@@ -1,11 +1,16 @@
-import { Theme, Checkbox } from '@mui/material';
-import { FC, useContext, useMemo, useState, ChangeEvent } from 'react';
+import { Box, Typography } from '@mui/material';
+import {
+  FC,
+  useContext,
+  useMemo,
+  useState,
+  ChangeEvent,
+  useEffect,
+} from 'react';
 import { useTranslation } from 'react-i18next';
 import DialogTemplate from '@/components/customDialog/DialogTemplate';
 import CustomInput from '@/components/customInput/CustomInput';
-import CustomSelector from '@/components/customSelector/CustomSelector';
 import { ITextfieldConfig } from '@/components/customInput/Types';
-import CustomToolTip from '@/components/customToolTip/CustomToolTip';
 import { rootContext, dataContext } from '@/context';
 import { useFormValidation } from '@/hooks';
 import { formatForm, getAnalyzerParams, TypeEnum } from '@/utils';
@@ -16,84 +21,55 @@ import {
   FunctionType,
 } from '@/consts';
 import CreateFields from './create/CreateFields';
-import { CONSISTENCY_LEVEL_OPTIONS } from './create/Constants';
-import { makeStyles } from '@mui/styles';
+import ExtraInfoSection from './create/ExtraInfoSection';
+import BM25FunctionSection from './create/BM25FunctionSection';
 import type {
   CollectionCreateParam,
   CollectionCreateProps,
   CreateField,
 } from '../databases/collections/Types';
 
-const useStyles = makeStyles((theme: Theme) => ({
-  dialog: {
-    minWidth: 720,
-  },
-  container: {
-    display: 'flex',
-    flexDirection: 'column',
-
-    '& section': {
-      display: 'flex',
-      '& fieldset': {},
-    },
-  },
-  generalInfo: {
-    '& fieldset': {
-      gap: 16,
-      display: 'flex',
-      width: '100%',
-    },
-  },
-  schemaInfo: {
-    background: theme.palette.background.grey,
-    padding: '16px',
-    borderRadius: 8,
-  },
-  extraInfo: {
-    marginTop: theme.spacing(2),
-    display: 'flex',
-    flexDirection: 'column',
-    gap: 8,
-
-    '& input': {
-      marginLeft: 0,
-    },
-  },
-  input: {
-    width: '100%',
-  },
-  chexBoxArea: {
-    paddingTop: 8,
-    fontSize: 14,
-    marginLeft: -8,
-    '& label': {
-      display: 'inline-block',
-    },
-    borderTop: `1px solid ${theme.palette.divider}`,
-  },
-  consistencySelect: {
-    width: '50%',
-    marginBottom: 16,
-  },
-}));
+// Add this type at the top of your file or in a relevant types file
+interface BM25Function {
+  name: string;
+  description: string;
+  type: FunctionType;
+  input_field_names: string[];
+  output_field_names: string[];
+  params: Record<string, any>;
+}
 
 const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
   const { createCollection } = useContext(dataContext);
-  const classes = useStyles();
   const { handleCloseDialog, openSnackBar } = useContext(rootContext);
   const { t: collectionTrans } = useTranslation('collection');
   const { t: btnTrans } = useTranslation('btn');
   const { t: successTrans } = useTranslation('success');
   const { t: warningTrans } = useTranslation('warning');
 
-  const [form, setForm] = useState({
+  const [form, setForm] = useState<{
+    collection_name: string;
+    description: string;
+    autoID: boolean;
+    enableDynamicField: boolean;
+    loadAfterCreate: boolean;
+    functions: BM25Function[];
+  }>({
     collection_name: '',
     description: '',
     autoID: true,
     enableDynamicField: false,
     loadAfterCreate: true,
+    functions: [],
   });
 
+  const [fieldsValidation, setFieldsValidation] = useState(true);
+
+  // State for BM25 selection UI
+  const [showBm25Selection, setShowBm25Selection] = useState<boolean>(false);
+  const [selectedBm25Input, setSelectedBm25Input] = useState<string>('');
+  const [selectedBm25Output, setSelectedBm25Output] = useState<string>('');
+
   const [consistencyLevel, setConsistencyLevel] =
     useState<ConsistencyLevelEnum>(ConsistencyLevelEnum.Bounded); // Bounded is the default value of consistency level
 
@@ -101,7 +77,7 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     {
       data_type: DataTypeEnum.Int64,
       is_primary_key: true,
-      name: null, // we need hide helpertext at first time, so we use null to detect user enter input or not.
+      name: 'id', // we need hide helpertext at first time, so we use null to detect user enter input or not.
       description: '',
       isDefault: true,
       id: '1',
@@ -109,7 +85,7 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     {
       data_type: DataTypeEnum.FloatVector,
       is_primary_key: false,
-      name: null,
+      name: 'vector',
       dim: DEFAULT_ATTU_DIM,
       description: '',
       isDefault: true,
@@ -117,19 +93,6 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     },
   ]);
 
-  const [fieldsValidation, setFieldsValidation] = useState<
-    {
-      [x: string]: string | boolean;
-    }[]
-  >([
-    { id: '1', name: false },
-    { id: '2', name: false, dim: true },
-  ]);
-
-  const allFieldsValid = useMemo(() => {
-    return fieldsValidation.every(v => Object.keys(v).every(key => !!v[key]));
-  }, [fieldsValidation]);
-
   const checkedForm = useMemo(() => {
     const { collection_name } = form;
     return formatForm({ collection_name });
@@ -199,7 +162,9 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
         shrink: true,
       },
       size: 'small',
-      className: classes.input,
+      sx: {
+        width: '100%',
+      },
     },
     {
       label: collectionTrans('description'),
@@ -209,16 +174,16 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
       variant: 'filled',
       validations: [],
       size: 'small',
-      className: classes.input,
       InputLabelProps: {
         shrink: true,
       },
+      sx: {
+        width: '100%',
+      },
     },
   ];
 
   const handleCreateCollection = async () => {
-    // function output fields
-    const fnOutputFields: CreateField[] = [];
     const param: CollectionCreateParam = {
       ...form,
       fields: fields.map(v => {
@@ -249,23 +214,6 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
           data.max_capacity = Number(v.max_capacity);
         }
 
-        // handle BM25 row
-        if (data.data_type === DataTypeEnum.VarCharBM25) {
-          data.data_type = DataTypeEnum.VarChar;
-          data.enable_analyzer = true;
-          data.analyzer_params = data.analyzer_params || 'standard';
-          // create sparse field
-          const sparseField = {
-            name: `${data.name}_embeddings`,
-            is_primary_key: false,
-            data_type: DataTypeEnum.SparseFloatVector,
-            description: `fn BM25(${data.name}) -> embeddings`,
-            is_function_output: true,
-          };
-          // push sparse field to fields
-          fnOutputFields.push(sparseField);
-        }
-
         if (data.analyzer_params) {
           // if analyzer_params is string, we need to use default value
           data.analyzer_params = getAnalyzerParams(data.analyzer_params);
@@ -277,6 +225,7 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
         if (data.data_type === DataTypeEnum.SparseFloatVector) {
           delete data.dim;
         }
+
         // delete analyzer if not varchar
         if (
           data.data_type !== DataTypeEnum.VarChar &&
@@ -290,27 +239,10 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
 
         return data;
       }),
-      functions: [],
+      functions: form.functions || [],
       consistency_level: consistencyLevel,
     };
 
-    // push sparse fields to param.fields
-    param.fields.push(...fnOutputFields);
-
-    // build functions
-    fnOutputFields.forEach((field, index) => {
-      const [input] = (field.name as string).split('_');
-      const functionParam = {
-        name: `BM25_${index}`,
-        description: `${input} BM25 function`,
-        type: FunctionType.BM25,
-        input_field_names: [input],
-        output_field_names: [field.name as string],
-        params: {},
-      };
-      param.functions.push(functionParam);
-    });
-
     // create collection
     await createCollection({
       ...param,
@@ -327,6 +259,107 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
     handleCloseDialog();
   };
 
+  // Filter available fields for BM25 selectors
+  const varcharFields = useMemo(
+    () => fields.filter(f => f.data_type === DataTypeEnum.VarChar && f.name),
+    [fields]
+  );
+  const sparseFields = useMemo(
+    () =>
+      fields.filter(
+        f => f.data_type === DataTypeEnum.SparseFloatVector && f.name
+      ),
+    [fields]
+  );
+
+  const handleAddBm25Click = () => {
+    setShowBm25Selection(true);
+  };
+
+  const handleConfirmAddBm25 = () => {
+    if (!selectedBm25Input || !selectedBm25Output) {
+      openSnackBar(collectionTrans('bm25SelectFieldsWarning'), 'warning');
+      return;
+    }
+
+    const inputField = fields.find(f => f.name === selectedBm25Input);
+    const outputField = fields.find(f => f.name === selectedBm25Output);
+
+    if (!inputField || !outputField) {
+      // Should not happen if state is managed correctly, but good to check
+      console.error('Selected BM25 fields not found');
+      return;
+    }
+
+    // Generate a unique name for the function
+    const functionName = `BM25_${inputField.name}_${
+      outputField.name
+    }_${Math.floor(Math.random() * 1000)}`;
+
+    // Create a new function with the selected fields
+    const newFunction: BM25Function = {
+      name: functionName,
+      description: `BM25 function: ${inputField.name} → ${outputField.name}`,
+      type: FunctionType.BM25,
+      input_field_names: [inputField.name],
+      output_field_names: [outputField.name],
+      params: {}, // Add default params if needed, e.g., { k1: 1.2, b: 0.75 }
+    };
+
+    // Update form with the new function
+    setForm(prev => ({
+      ...prev,
+      functions: [...(prev.functions || []), newFunction],
+    }));
+
+    // Hide selection UI
+    setShowBm25Selection(false);
+
+    // need to update field.is_function_output to true
+    const updatedFields = fields.map(field => {
+      if (field.name === outputField.name) {
+        return {
+          ...field,
+          is_function_output: true,
+        };
+      }
+      return field;
+    });
+    setFields(updatedFields);
+  };
+
+  const handleCancelAddBm25 = () => {
+    setShowBm25Selection(false);
+  };
+
+  // Effect to reset selection when fields change or selection UI is hidden
+  useEffect(() => {
+    if (!showBm25Selection) {
+      setSelectedBm25Input('');
+      setSelectedBm25Output('');
+    } else {
+      // Pre-select first available fields when opening selection
+      setSelectedBm25Input(varcharFields[0]?.name || '');
+      setSelectedBm25Output(sparseFields[0]?.name || '');
+    }
+  }, [showBm25Selection, varcharFields, sparseFields]);
+
+  // Effect to filter out functions with invalid field names
+  useEffect(() => {
+    setForm(prevForm => {
+      const fieldNames = fields.map(f => f.name);
+      const filteredFunctions = (prevForm.functions || []).filter(
+        fn =>
+          fn.input_field_names.every(name => fieldNames.includes(name)) &&
+          fn.output_field_names.every(name => fieldNames.includes(name))
+      );
+      if (filteredFunctions.length !== (prevForm.functions || []).length) {
+        return { ...prevForm, functions: filteredFunctions };
+      }
+      return prevForm;
+    });
+  }, [fields]);
+
   return (
     <DialogTemplate
       title={collectionTrans('createTitle', { name: form.collection_name })}
@@ -335,96 +368,68 @@ const CreateCollectionDialog: FC<CollectionCreateProps> = ({ onCreate }) => {
       }}
       confirmLabel={btnTrans('create')}
       handleConfirm={handleCreateCollection}
-      confirmDisabled={disabled || !allFieldsValid}
-      dialogClass={classes.dialog}
+      confirmDisabled={disabled || !fieldsValidation}
+      sx={{ width: 900 }}
     >
-      <div className={classes.container}>
-        <section className={classes.generalInfo}>
-          <fieldset>
-            {generalInfoConfigs.map(config => (
-              <CustomInput
-                key={config.key}
-                type="text"
-                textConfig={config}
-                checkValid={checkIsValid}
-                validInfo={validation}
-              />
-            ))}
-          </fieldset>
-        </section>
-
-        <section className={classes.schemaInfo}>
-          <fieldset>
-            {/* <legend>{collectionTrans('schema')}</legend> */}
-            <CreateFields
-              fields={fields}
-              setFields={setFields}
-              setFieldsValidation={setFieldsValidation}
-              autoID={form.autoID}
-              setAutoID={changeIsAutoID}
-            />
-          </fieldset>
-        </section>
-
-        <section className={classes.extraInfo}>
-          <fieldset>
-            <CustomSelector
-              wrapperClass={classes.consistencySelect}
-              size="small"
-              options={CONSISTENCY_LEVEL_OPTIONS}
-              onChange={e => {
-                setConsistencyLevel(e.target.value as ConsistencyLevelEnum);
-              }}
-              label={collectionTrans('consistency')}
-              value={consistencyLevel}
-              variant="filled"
+      <Box sx={{ display: 'flex', gap: 2, flexDirection: 'column' }}>
+        <Box sx={{ display: 'flex', gap: 2, width: '100%' }}>
+          {generalInfoConfigs.map(config => (
+            <CustomInput
+              key={config.key}
+              type="text"
+              textConfig={{ ...config }}
+              checkValid={checkIsValid}
+              validInfo={validation}
             />
-          </fieldset>
-          <fieldset className={classes.chexBoxArea}>
-            <div>
-              <CustomToolTip title={collectionTrans('partitionKeyTooltip')}>
-                <label htmlFor="enableDynamicField">
-                  <Checkbox
-                    id="enableDynamicField"
-                    checked={!!form.enableDynamicField}
-                    size="small"
-                    onChange={event => {
-                      updateCheckBox(
-                        event,
-                        'enableDynamicField',
-                        !form.enableDynamicField
-                      );
-                    }}
-                  />
-                  {collectionTrans('enableDynamicSchema')}
-                </label>
-              </CustomToolTip>
-            </div>
-
-            <div>
-              <CustomToolTip
-                title={collectionTrans('loadCollectionAfterCreateTip')}
-              >
-                <label htmlFor="loadAfterCreate">
-                  <Checkbox
-                    id="loadAfterCreate"
-                    checked={!!form.loadAfterCreate}
-                    size="small"
-                    onChange={event => {
-                      updateCheckBox(
-                        event,
-                        'loadAfterCreate',
-                        !form.loadAfterCreate
-                      );
-                    }}
-                  />
-                  {collectionTrans('loadCollectionAfterCreate')}
-                </label>
-              </CustomToolTip>
-            </div>
-          </fieldset>
-        </section>
-      </div>
+          ))}
+        </Box>
+
+        <Box
+          sx={{
+            background: theme => theme.palette.background.lightGrey,
+            padding: '16px',
+            borderRadius: 0.5,
+          }}
+        >
+          <CreateFields
+            fields={fields}
+            setFields={setFields}
+            autoID={form.autoID}
+            setAutoID={changeIsAutoID}
+            onValidationChange={setFieldsValidation}
+          />
+        </Box>
+
+        <Box sx={{ mt: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
+          <Typography variant="h4" sx={{ fontSize: 16 }}>
+            {collectionTrans('functions')}
+          </Typography>
+          <BM25FunctionSection
+            showBm25Selection={showBm25Selection}
+            varcharFields={varcharFields}
+            sparseFields={sparseFields}
+            selectedBm25Input={selectedBm25Input}
+            selectedBm25Output={selectedBm25Output}
+            setSelectedBm25Input={setSelectedBm25Input}
+            setSelectedBm25Output={setSelectedBm25Output}
+            handleAddBm25Click={handleAddBm25Click}
+            handleConfirmAddBm25={handleConfirmAddBm25}
+            handleCancelAddBm25={handleCancelAddBm25}
+            formFunctions={form.functions}
+            setForm={setForm}
+            collectionTrans={collectionTrans}
+            btnTrans={btnTrans}
+          />
+        </Box>
+
+        <ExtraInfoSection
+          consistencyLevel={consistencyLevel}
+          setConsistencyLevel={setConsistencyLevel}
+          form={form}
+          updateCheckBox={updateCheckBox}
+          collectionTrans={collectionTrans}
+        />
+      </Box>
     </DialogTemplate>
   );
 };

+ 132 - 0
client/src/pages/dialogs/create/AnalyzerCheckboxField.tsx

@@ -0,0 +1,132 @@
+import { FC, useContext } from 'react';
+import { Checkbox, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import CustomSelector from '@/components/customSelector/CustomSelector';
+import CustomIconButton from '@/components/customButton/CustomIconButton';
+import icons from '@/components/icons/Icons';
+import { ANALYZER_OPTIONS } from './Constants';
+import { DataTypeEnum } from '@/consts';
+import { getAnalyzerParams } from '@/utils';
+import { rootContext } from '@/context';
+import EditJSONDialog from '@/pages/dialogs/EditJSONDialog';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface AnalyzerCheckboxFieldProps {
+  field: FieldType;
+  onChange: (id: string, changes: Partial<FieldType>) => void;
+  localFieldAnalyzers: React.MutableRefObject<Map<string, Record<string, {}>>>;
+  sx?: SxProps<Theme>;
+}
+
+const AnalyzerCheckboxField: FC<AnalyzerCheckboxFieldProps> = ({
+  field,
+  onChange,
+  localFieldAnalyzers,
+  sx,
+}) => {
+  const { setDialog2, handleCloseDialog2 } = useContext(rootContext);
+  const { t: collectionTrans } = useTranslation('collection');
+  const { t: dialogTrans } = useTranslation('dialog');
+
+  // Determine which analyzer type is selected
+  let analyzer = 'standard';
+  if (typeof field.analyzer_params === 'string') {
+    analyzer = field.analyzer_params;
+  } else if (!field.analyzer_params) {
+    analyzer = 'standard';
+  } else {
+    analyzer = 'custom';
+  }
+
+  const localAnalyzer = localFieldAnalyzers.current.get(field.id!) || {
+    tokenizer: 'standard',
+    filter: ['lowercase'],
+  };
+
+  const handleCheckboxChange = () => {
+    onChange(field.id!, {
+      enable_analyzer: !field.enable_analyzer,
+    });
+  };
+
+  const handleAnalyzerChange = (selectedAnalyzer: string) => {
+    if (selectedAnalyzer === 'custom') {
+      // If custom, set the analyzer_params to a JSON editable format
+      onChange(field.id!, {
+        analyzer_params: localAnalyzer,
+      });
+    } else {
+      // If standard, chinese, or english, set the analyzer_params to the selected type
+      onChange(field.id!, {
+        analyzer_params: selectedAnalyzer,
+      });
+    }
+  };
+
+  const handleSettingsClick = () => {
+    setDialog2({
+      open: true,
+      type: 'custom',
+      params: {
+        component: (
+          <EditJSONDialog
+            data={getAnalyzerParams(field.analyzer_params || 'standard')}
+            dialogTitle={dialogTrans('editAnalyzerTitle')}
+            dialogTip={dialogTrans('editAnalyzerInfo')}
+            handleConfirm={data => {
+              localFieldAnalyzers.current.set(field.id!, data);
+              onChange(field.id!, { analyzer_params: data });
+            }}
+            handleCloseDialog={handleCloseDialog2}
+          />
+        ),
+      },
+    });
+  };
+
+  return (
+    <Box
+      sx={{
+        paddingTop: 1, // 8px
+        '& .select': {
+          width: '110px',
+        },
+        ...sx,
+      }}
+    >
+      <Checkbox
+        checked={
+          !!field.enable_analyzer ||
+          field.data_type === DataTypeEnum.VarCharBM25
+        }
+        size="small"
+        onChange={handleCheckboxChange}
+        disabled={field.data_type === DataTypeEnum.VarCharBM25}
+        style={{ padding: '8px' }}
+      />
+      <CustomSelector
+        wrapperClass="select"
+        options={ANALYZER_OPTIONS}
+        size="small"
+        onChange={e => handleAnalyzerChange(e.target.value as string)}
+        disabled={
+          !field.enable_analyzer && field.data_type !== DataTypeEnum.VarCharBM25
+        }
+        value={analyzer}
+        variant="filled"
+        label={collectionTrans('analyzer')}
+      />
+      <CustomIconButton
+        disabled={
+          !field.enable_analyzer && field.data_type !== DataTypeEnum.VarCharBM25
+        }
+        onClick={handleSettingsClick}
+      >
+        <icons.settings sx={{ fontSize: '14px', marginLeft: '4px' }} />
+      </CustomIconButton>
+    </Box>
+  );
+};
+
+export default AnalyzerCheckboxField;

+ 248 - 0
client/src/pages/dialogs/create/BM25FunctionSection.tsx

@@ -0,0 +1,248 @@
+import { FC } from 'react';
+import {
+  Typography,
+  Button,
+  Box,
+  List,
+  IconButton,
+  Paper,
+  Stack,
+  Chip,
+  Tooltip,
+  Select,
+  MenuItem,
+  InputLabel,
+  FormControl,
+} from '@mui/material';
+import Icons from '@/components/icons/Icons';
+import type { CreateField } from '../../databases/collections/Types';
+export interface BM25Function {
+  name: string;
+  description: string;
+  type: any;
+  input_field_names: string[];
+  output_field_names: string[];
+  params: Record<string, any>;
+}
+
+interface BM25FunctionSectionProps {
+  showBm25Selection: boolean;
+  varcharFields: CreateField[];
+  sparseFields: CreateField[];
+  selectedBm25Input: string;
+  selectedBm25Output: string;
+  setSelectedBm25Input: (val: string) => void;
+  setSelectedBm25Output: (val: string) => void;
+  handleAddBm25Click: () => void;
+  handleConfirmAddBm25: () => void;
+  handleCancelAddBm25: () => void;
+  formFunctions: BM25Function[];
+  setForm: (updater: (prev: any) => any) => void;
+  collectionTrans: any;
+  btnTrans: any;
+}
+
+const BM25FunctionSection: FC<BM25FunctionSectionProps> = ({
+  showBm25Selection,
+  varcharFields,
+  sparseFields,
+  selectedBm25Input,
+  selectedBm25Output,
+  setSelectedBm25Input,
+  setSelectedBm25Output,
+  handleAddBm25Click,
+  handleConfirmAddBm25,
+  handleCancelAddBm25,
+  formFunctions,
+  setForm,
+  collectionTrans,
+  btnTrans,
+}) => {
+  const usedSparseOutputs = new Set(
+    formFunctions.reduce<string[]>(
+      (acc, f) => acc.concat(f.output_field_names),
+      []
+    )
+  );
+
+  const availableSparseFields = sparseFields.filter(
+    field => !usedSparseOutputs.has(field.name)
+  );
+  const sparseOptions =
+    availableSparseFields.length > 0
+      ? availableSparseFields.map(field => ({
+          label: field.name,
+          value: field.name,
+          disabled: false,
+        }))
+      : [
+          {
+            label: collectionTrans('noAvailableSparse'),
+            value: '',
+            disabled: true,
+          },
+        ];
+
+  return (
+    <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
+      {!showBm25Selection && (
+        <Tooltip
+          title={
+            varcharFields.length === 0 && sparseFields.length === 0
+              ? collectionTrans('bm25NoVarcharAndSparse')
+              : varcharFields.length === 0
+                ? collectionTrans('bm25NoVarchar')
+                : sparseFields.length === 0
+                  ? collectionTrans('bm25NoSparse')
+                  : ''
+          }
+          placement="top"
+          arrow
+          disableHoverListener={
+            !(varcharFields.length === 0 || sparseFields.length === 0)
+          }
+        >
+          <span>
+            <Button
+              variant="outlined"
+              size="small"
+              startIcon={<Icons.add />}
+              onClick={handleAddBm25Click}
+              sx={{
+                width: '100%',
+              }}
+              disabled={varcharFields.length === 0 || sparseFields.length === 0}
+            >
+              {collectionTrans('addBm25Function')}
+            </Button>
+          </span>
+        </Tooltip>
+      )}
+
+      {showBm25Selection && (
+        <Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
+          <FormControl variant="filled" size="small" sx={{ minWidth: 200 }}>
+            <InputLabel shrink={true}>
+              {collectionTrans('bm25InputVarChar')}
+            </InputLabel>
+            <Select
+              value={selectedBm25Input}
+              onChange={e => setSelectedBm25Input(e.target.value as string)}
+              label={collectionTrans('bm25InputVarChar')}
+            >
+              {varcharFields.map(field => (
+                <MenuItem key={field.name} value={field.name}>
+                  {field.name}
+                </MenuItem>
+              ))}
+            </Select>
+          </FormControl>
+
+          <Typography sx={{ mx: 1 }}>→</Typography>
+
+          <FormControl variant="filled" size="small" sx={{ minWidth: 200 }}>
+            <InputLabel shrink={true}>
+              {collectionTrans('bm25OutputSparse')}
+            </InputLabel>
+            <Select
+              value={selectedBm25Output}
+              onChange={e => setSelectedBm25Output(e.target.value as string)}
+              label={collectionTrans('bm25OutputSparse')}
+            >
+              {sparseOptions.map(option => (
+                <MenuItem
+                  key={option.label}
+                  value={option.value}
+                  disabled={!!option.disabled}
+                >
+                  {option.label}
+                </MenuItem>
+              ))}
+            </Select>
+          </FormControl>
+
+          <Button
+            variant="contained"
+            size="small"
+            onClick={handleConfirmAddBm25}
+            disabled={!selectedBm25Input || !selectedBm25Output}
+            sx={{ ml: 1 }}
+          >
+            {btnTrans('confirm')}
+          </Button>
+          <Button variant="text" size="small" onClick={handleCancelAddBm25}>
+            {btnTrans('cancel')}
+          </Button>
+        </Box>
+      )}
+
+      {formFunctions && formFunctions.length > 0 && (
+        <Box>
+          <List dense>
+            {formFunctions.map((func, index) => (
+              <Paper
+                key={index}
+                elevation={0}
+                variant="outlined"
+                sx={{
+                  mb: 2,
+                  p: 1.5,
+                  position: 'relative',
+                  borderRadius: 1,
+                }}
+              >
+                <IconButton
+                  size="small"
+                  sx={{
+                    position: 'absolute',
+                    top: 8,
+                    right: 8,
+                  }}
+                  onClick={() => {
+                    setForm(prev => ({
+                      ...prev,
+                      functions: prev.functions?.filter(
+                        (_: any, i: number) => i !== index
+                      ),
+                    }));
+                  }}
+                >
+                  <Icons.delete fontSize="small" />
+                </IconButton>
+                <Stack spacing={0.5}>
+                  <Typography variant="subtitle1" fontWeight="bold">
+                    {func.name}
+                  </Typography>
+                  <Typography variant="body2" color="text.secondary">
+                    {func.description}
+                  </Typography>
+                  <Stack direction="row" spacing={1} alignItems="center">
+                    {func.input_field_names.map(name => (
+                      <Chip
+                        key={name}
+                        label={name}
+                        size="small"
+                        color="primary"
+                      />
+                    ))}
+                    <Typography variant="body2">→</Typography>
+                    {func.output_field_names.map(name => (
+                      <Chip
+                        key={name}
+                        label={name}
+                        size="small"
+                        color="success"
+                      />
+                    ))}
+                  </Stack>
+                </Stack>
+              </Paper>
+            ))}
+          </List>
+        </Box>
+      )}
+    </Box>
+  );
+};
+
+export default BM25FunctionSection;

+ 0 - 4
client/src/pages/dialogs/create/Constants.ts

@@ -41,10 +41,6 @@ export const VECTOR_FIELDS_OPTIONS: LabelValuePair[] = [
     label: 'Sparse Vector',
     value: DataTypeEnum.SparseFloatVector,
   },
-  {
-    label: 'BM25(VarChar)',
-    value: DataTypeEnum.VarCharBM25,
-  },
 ];
 
 export const ALL_OPTIONS: LabelValuePair[] = [

File diff suppressed because it is too large
+ 86 - 894
client/src/pages/dialogs/create/CreateFields.tsx


+ 65 - 0
client/src/pages/dialogs/create/DefaultValueField.tsx

@@ -0,0 +1,65 @@
+import { FC } from 'react';
+import { TextField } from '@mui/material';
+import { DataTypeEnum } from '@/consts';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface DefaultValueFieldProps {
+  field: FieldType;
+  onChange: (id: string, defaultValue: string) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+}
+
+const DefaultValueField: FC<DefaultValueFieldProps> = ({
+  field,
+  onChange,
+  label = 'Default Value',
+  sx,
+}) => {
+  const getInputType = (dataType: DataTypeEnum): 'number' | 'text' => {
+    switch (dataType) {
+      case DataTypeEnum.Int8:
+      case DataTypeEnum.Int16:
+      case DataTypeEnum.Int32:
+      case DataTypeEnum.Int64:
+      case DataTypeEnum.Float:
+      case DataTypeEnum.Double:
+        return 'number';
+      case DataTypeEnum.Bool:
+      default:
+        return 'text';
+    }
+  };
+
+  return (
+    <TextField
+      label={label}
+      defaultValue={field.default_value}
+      onChange={e => {
+        onChange(field.id!, e.target.value);
+      }}
+      variant="filled"
+      InputLabelProps={{
+        shrink: true,
+      }}
+      helperText={' '}
+      size="small"
+      type={getInputType(field.data_type)}
+      sx={sx}
+      style={{
+        width: 120,
+      }}
+      FormHelperTextProps={{
+        style: {
+          lineHeight: '20px',
+          fontSize: '10px',
+          margin: 0,
+          marginLeft: '11px',
+        },
+      }}
+    />
+  );
+};
+
+export default DefaultValueField;

+ 47 - 0
client/src/pages/dialogs/create/DescriptionField.tsx

@@ -0,0 +1,47 @@
+import { FC } from 'react';
+import { TextField } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import type { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface DescriptionFieldProps {
+  field: FieldType;
+  onChange: (id: string, description: string) => void;
+  sx?: SxProps<Theme>;
+}
+
+const DescriptionField: FC<DescriptionFieldProps> = ({
+  field,
+  onChange,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  return (
+    <TextField
+      label={collectionTrans('description')}
+      defaultValue={field.description}
+      onChange={e => onChange(field.id!, e.target.value)}
+      variant="filled"
+      size="small"
+      InputLabelProps={{
+        shrink: true,
+      }}
+      helperText={' '}
+      style={{ width: 120 }}
+      sx={{
+        ...sx,
+      }}
+      FormHelperTextProps={{
+        style: {
+          lineHeight: '20px',
+          fontSize: '10px',
+          margin: 0,
+          marginLeft: '11px',
+        },
+      }}
+    />
+  );
+};
+
+export default DescriptionField;

+ 89 - 0
client/src/pages/dialogs/create/DimensionField.tsx

@@ -0,0 +1,89 @@
+import { FC } from 'react';
+import { TextField } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { getCheckResult } from '@/utils';
+import { DataTypeEnum } from '@/consts';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface DimensionFieldProps {
+  field: FieldType;
+  onChange: (id: string, changes: Partial<FieldType>) => void;
+  sx?: SxProps<Theme>;
+}
+
+const DimensionField: FC<DimensionFieldProps> = ({ field, onChange, sx }) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  // Return null for sparse vectors as they don't support dimension
+  if (field.data_type === DataTypeEnum.SparseFloatVector) {
+    return null;
+  }
+
+  const validateDimension = (value: string) => {
+    const isPositive = getCheckResult({
+      value,
+      rule: 'positiveNumber',
+    });
+    const isMultiple = getCheckResult({
+      value,
+      rule: 'multiple',
+      extraParam: {
+        multipleNumber: 8,
+      },
+    });
+    if (field.data_type === DataTypeEnum.BinaryVector) {
+      return {
+        isMultiple,
+        isPositive,
+      };
+    }
+    return {
+      isPositive,
+    };
+  };
+
+  const handleChange = (value: string) => {
+    onChange(field.id!, { dim: value });
+  };
+
+  const getValidationMessage = (value: string) => {
+    const { isPositive, isMultiple } = validateDimension(value);
+
+    if (isMultiple === false) {
+      return collectionTrans('dimensionMultipleWarning');
+    }
+
+    return isPositive ? ' ' : collectionTrans('dimensionPositiveWarning');
+  };
+
+  return (
+    <TextField
+      label={collectionTrans('dimension')}
+      defaultValue={field.dim}
+      onChange={e => handleChange(e.target.value)}
+      variant="filled"
+      InputLabelProps={{
+        shrink: true,
+      }}
+      size="small"
+      type="number"
+      error={getValidationMessage(field.dim as string) !== ' '}
+      helperText={getValidationMessage(field.dim as string) || ' '}
+      style={{
+        width: 120,
+      }}
+      FormHelperTextProps={{
+        style: {
+          lineHeight: '20px',
+          fontSize: '10px',
+          margin: 0,
+          marginLeft: '11px',
+        },
+      }}
+      sx={sx}
+    />
+  );
+};
+
+export default DimensionField;

+ 56 - 0
client/src/pages/dialogs/create/ElementTypeSelector.tsx

@@ -0,0 +1,56 @@
+import { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Box, FormHelperText, SxProps, Theme } from '@mui/material';
+import CustomSelector from '@/components/customSelector/CustomSelector';
+import { ALL_OPTIONS } from './Constants';
+import { DataTypeEnum } from '@/consts';
+
+interface ElementTypeSelectorProps {
+  value: number;
+  onChange: (value: DataTypeEnum) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+}
+
+const ElementTypeSelector: FC<ElementTypeSelectorProps> = ({
+  value,
+  onChange,
+  label,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const elementOptions = ALL_OPTIONS.filter(
+    d =>
+      d.label !== 'Array' &&
+      d.label !== 'JSON' &&
+      d.label !== 'VarChar(BM25)' &&
+      !d.label.includes('Vector')
+  );
+
+  return (
+    <Box
+      sx={{
+        display: 'flex',
+        flexDirection: 'column',
+        width: 120,
+        ...sx,
+      }}
+    >
+      <CustomSelector
+        options={elementOptions}
+        size="small"
+        onChange={e => {
+          onChange(e.target.value as DataTypeEnum);
+        }}
+        value={value}
+        variant="filled"
+        label={label || collectionTrans('elementType')}
+        sx={{ width: '100%' }}
+      />
+      <FormHelperText sx={{ marginTop: 0 }}> </FormHelperText>
+    </Box>
+  );
+};
+
+export default ElementTypeSelector;

+ 122 - 0
client/src/pages/dialogs/create/ExtraInfoSection.tsx

@@ -0,0 +1,122 @@
+import React, { ChangeEvent } from 'react';
+import { Checkbox, Box } from '@mui/material';
+import CustomSelector from '@/components/customSelector/CustomSelector';
+import CustomToolTip from '@/components/customToolTip/CustomToolTip';
+import { ConsistencyLevelEnum } from '@/consts';
+import { CONSISTENCY_LEVEL_OPTIONS } from './Constants';
+
+interface ExtraInfoSectionProps {
+  consistencyLevel: ConsistencyLevelEnum;
+  setConsistencyLevel: (level: ConsistencyLevelEnum) => void;
+  form: {
+    enableDynamicField: boolean;
+    loadAfterCreate: boolean;
+  };
+  updateCheckBox: (
+    event: ChangeEvent<any>,
+    key: string,
+    value: boolean
+  ) => void;
+  collectionTrans: (key: string) => string;
+}
+
+const ExtraInfoSection: React.FC<ExtraInfoSectionProps> = ({
+  consistencyLevel,
+  setConsistencyLevel,
+  form,
+  updateCheckBox,
+  collectionTrans,
+}) => {
+  return (
+    <Box
+      component="section"
+      sx={{
+        mt: 2,
+        display: 'flex',
+        flexDirection: 'column',
+        gap: 1, // Corresponds to gap: 8 in makeStyles
+        '& fieldset': {
+          border: 'none',
+          padding: 0,
+          margin: 0,
+        },
+        '& input': {
+          ml: 0, // Corresponds to marginLeft: 0 in makeStyles
+        },
+      }}
+    >
+      <fieldset>
+        <CustomSelector
+          wrapperClass="consistency-select" // Keep class for potential external styling if needed, or remove
+          sx={{ width: '400px', mb: 2 }} // Corresponds to consistencySelect style
+          size="small"
+          options={CONSISTENCY_LEVEL_OPTIONS}
+          onChange={e => {
+            setConsistencyLevel(e.target.value as ConsistencyLevelEnum);
+          }}
+          label={collectionTrans('consistency')}
+          value={consistencyLevel}
+          variant="filled"
+        />
+      </fieldset>
+      <Box
+        component="fieldset"
+        sx={{
+          pt: 1, // Corresponds to paddingTop: 8
+          fontSize: 14,
+          ml: -1, // Corresponds to marginLeft: -8
+          borderTop: theme => `1px solid ${theme.palette.divider}`, // Corresponds to borderTop style
+          '& label': {
+            display: 'inline-block',
+          },
+        }}
+      >
+        <div>
+          <CustomToolTip title={collectionTrans('partitionKeyTooltip')}>
+            <label htmlFor="enableDynamicField">
+              <Checkbox
+                id="enableDynamicField"
+                checked={!!form.enableDynamicField}
+                size="small"
+                onChange={event => {
+                  updateCheckBox(
+                    event,
+                    'enableDynamicField',
+                    !form.enableDynamicField
+                  );
+                }}
+                sx={{ py: 0.5 }} // Adjust padding if needed
+              />
+              {collectionTrans('enableDynamicSchema')}
+            </label>
+          </CustomToolTip>
+        </div>
+
+        <div>
+          <CustomToolTip
+            title={collectionTrans('loadCollectionAfterCreateTip')}
+          >
+            <label htmlFor="loadAfterCreate">
+              <Checkbox
+                id="loadAfterCreate"
+                checked={!!form.loadAfterCreate}
+                size="small"
+                onChange={event => {
+                  updateCheckBox(
+                    event,
+                    'loadAfterCreate',
+                    !form.loadAfterCreate
+                  );
+                }}
+                sx={{ py: 0.5 }} // Adjust padding if needed
+              />
+              {collectionTrans('loadCollectionAfterCreate')}
+            </label>
+          </CustomToolTip>
+        </div>
+      </Box>
+    </Box>
+  );
+};
+
+export default ExtraInfoSection;

+ 105 - 0
client/src/pages/dialogs/create/MaxCapacityField.tsx

@@ -0,0 +1,105 @@
+import { FC } from 'react';
+import { TextField } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { checkEmptyValid, checkRange } from '@/utils';
+import { DEFAULT_ATTU_MAX_CAPACITY } from '@/consts';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface MaxCapacityFieldProps {
+  field: FieldType;
+  onChange: (id: string, max_capacity: number, isValid?: boolean) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+}
+
+const MaxCapacityField: FC<MaxCapacityFieldProps> = ({
+  field,
+  onChange,
+  label = 'Max Capacity',
+  sx,
+}) => {
+  const { t: warningTrans } = useTranslation('warning');
+
+  // Initialize max_capacity if not defined
+  if (typeof field.max_capacity === 'undefined') {
+    onChange(field.id!, DEFAULT_ATTU_MAX_CAPACITY, true);
+  }
+
+  // Common validation function to avoid duplicating the validation logic
+  const validateField = (value: any) => {
+    if (value === null) {
+      return { isValid: false, isEmptyValid: false, isRangeValid: false };
+    }
+
+    const isEmptyValid = checkEmptyValid(value);
+    const isRangeValid = checkRange({
+      value,
+      min: 1,
+      max: 4096,
+      type: 'number',
+    });
+
+    return {
+      isValid: isEmptyValid && isRangeValid,
+      isEmptyValid,
+      isRangeValid,
+    };
+  };
+
+  const handleChange = (value: string) => {
+    const numValue = Number(value);
+    const { isValid } = validateField(numValue);
+    onChange(field.id!, numValue, isValid);
+  };
+
+  const getError = (value: any) => {
+    const { isValid } = validateField(value);
+    return !isValid;
+  };
+
+  const getHelperText = (value: any) => {
+    if (value === null) return ' ';
+
+    const { isEmptyValid, isRangeValid } = validateField(value);
+
+    return !isEmptyValid
+      ? warningTrans('requiredOnly')
+      : !isRangeValid
+        ? warningTrans('range', {
+            min: 1,
+            max: 4096,
+          })
+        : ' ';
+  };
+
+  return (
+    <TextField
+      label={label}
+      defaultValue={field.max_capacity || DEFAULT_ATTU_MAX_CAPACITY}
+      onChange={e => handleChange(e.target.value)}
+      variant="filled"
+      InputLabelProps={{
+        shrink: true,
+      }}
+      size="small"
+      type="number"
+      error={getError(field.max_capacity)}
+      helperText={getHelperText(field.max_capacity)}
+      style={{
+        width: 100,
+      }}
+      FormHelperTextProps={{
+        style: {
+          lineHeight: '20px',
+          fontSize: '10px',
+          margin: 0,
+          marginLeft: '11px',
+        },
+      }}
+      sx={sx}
+    />
+  );
+};
+
+export default MaxCapacityField;

+ 105 - 0
client/src/pages/dialogs/create/MaxLengthField.tsx

@@ -0,0 +1,105 @@
+import { FC } from 'react';
+import { TextField } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { checkEmptyValid, checkRange } from '@/utils';
+import { DEFAULT_ATTU_VARCHAR_MAX_LENGTH } from '@/consts';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface MaxLengthFieldProps {
+  field: FieldType;
+  onChange: (id: string, max_length: number, isValid?: boolean) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+}
+
+const MaxLengthField: FC<MaxLengthFieldProps> = ({
+  field,
+  onChange,
+  label = 'Max Length',
+  sx,
+}) => {
+  const { t: warningTrans } = useTranslation('warning');
+
+  // Initialize max_length if not defined
+  if (typeof field.max_length === 'undefined') {
+    onChange(field.id!, DEFAULT_ATTU_VARCHAR_MAX_LENGTH, true);
+  }
+
+  // Common validation function to avoid duplicating the validation logic
+  const validateField = (value: any) => {
+    if (value === null) {
+      return { isValid: false, isEmptyValid: false, isRangeValid: false };
+    }
+
+    const isEmptyValid = checkEmptyValid(value);
+    const isRangeValid = checkRange({
+      value,
+      min: 1,
+      max: 65535,
+      type: 'number',
+    });
+
+    return {
+      isValid: isEmptyValid && isRangeValid,
+      isEmptyValid,
+      isRangeValid,
+    };
+  };
+
+  const handleChange = (value: string) => {
+    const numValue = Number(value);
+    const { isValid } = validateField(numValue);
+    onChange(field.id!, numValue, isValid);
+  };
+
+  const getError = (value: any) => {
+    const { isValid } = validateField(value);
+    return !isValid;
+  };
+
+  const getHelperText = (value: any) => {
+    if (value === null) return ' ';
+
+    const { isEmptyValid, isRangeValid } = validateField(value);
+
+    return !isEmptyValid
+      ? warningTrans('requiredOnly')
+      : !isRangeValid
+        ? warningTrans('range', {
+            min: 1,
+            max: 65535,
+          })
+        : ' ';
+  };
+
+  return (
+    <TextField
+      label={label}
+      defaultValue={field.max_length || DEFAULT_ATTU_VARCHAR_MAX_LENGTH}
+      onChange={e => handleChange(e.target.value)}
+      variant="filled"
+      InputLabelProps={{
+        shrink: true,
+      }}
+      size="small"
+      type="number"
+      error={getError(field.max_length)}
+      helperText={getHelperText(field.max_length)}
+      style={{
+        width: 120,
+      }}
+      FormHelperTextProps={{
+        style: {
+          lineHeight: '20px',
+          fontSize: '10px',
+          margin: 0,
+          marginLeft: '11px',
+        },
+      }}
+      sx={sx}
+    />
+  );
+};
+
+export default MaxLengthField;

+ 94 - 0
client/src/pages/dialogs/create/NameField.tsx

@@ -0,0 +1,94 @@
+import { TextField } from '@mui/material';
+import { FC, useState, useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { checkEmptyValid } from '@/utils';
+import { VectorTypes } from '@/consts';
+import type { FieldType } from '../../databases/collections/Types';
+
+interface NameFieldProps {
+  field: FieldType;
+  onChange: (id: string, name: string, isValid: boolean) => void;
+  label?: string;
+  isReadOnly?: boolean;
+}
+
+const NameField: FC<NameFieldProps> = ({
+  field,
+  onChange,
+  label,
+  isReadOnly = false,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+  const { t: warningTrans } = useTranslation('warning');
+  const [touched, setTouched] = useState<boolean>(false);
+
+  // Determine the label based on field type
+  const defaultLabel = collectionTrans(
+    VectorTypes.includes(field.data_type) ? 'vectorFieldName' : 'fieldName'
+  );
+
+  // Initialize name if not defined
+  useEffect(() => {
+    if (field.name === undefined && field.id) {
+      onChange(field.id, '', false);
+    }
+  }, []);
+
+  // Common validation function
+  const validateField = (name: string) => {
+    const isValid = checkEmptyValid(name);
+    return { isValid };
+  };
+
+  const handleChange = (newValue: string) => {
+    setTouched(true);
+    if (field.id) {
+      const { isValid } = validateField(newValue);
+      onChange(field.id, newValue, isValid);
+    }
+  };
+
+  const handleBlur = () => {
+    setTouched(true);
+  };
+
+  const getError = (name: string) => {
+    if (!touched) return false;
+    const { isValid } = validateField(name);
+    return !isValid;
+  };
+
+  const getHelperText = (name: string) => {
+    if (!touched) return ' ';
+    const { isValid } = validateField(name);
+    return isValid ? ' ' : warningTrans('requiredOnly');
+  };
+
+  return (
+    <TextField
+      label={label || defaultLabel}
+      defaultValue={field.name || ''}
+      onChange={e => handleChange(e.target.value)}
+      onBlur={handleBlur}
+      variant="filled"
+      InputLabelProps={{
+        shrink: true,
+      }}
+      size="small"
+      disabled={isReadOnly}
+      error={getError(field.name || '')}
+      helperText={getHelperText(field.name || '')}
+      style={{ width: 128 }}
+      FormHelperTextProps={{
+        style: {
+          lineHeight: '20px',
+          fontSize: '10px',
+          margin: 0,
+          marginLeft: '11px',
+        },
+      }}
+    />
+  );
+};
+
+export default NameField;

+ 47 - 0
client/src/pages/dialogs/create/NullableCheckboxField.tsx

@@ -0,0 +1,47 @@
+import { FC } from 'react';
+import { Checkbox, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import CustomToolTip from '@/components/customToolTip/CustomToolTip';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface NullableCheckboxFieldProps {
+  field: FieldType;
+  onChange: (id: string, changes: Partial<FieldType>) => void;
+  sx?: SxProps<Theme>;
+}
+
+const NullableCheckboxField: FC<NullableCheckboxFieldProps> = ({
+  field,
+  onChange,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  return (
+    <Box sx={sx}>
+      <label htmlFor={`nullable-${field.id}`}>
+        <Checkbox
+          id={`nullable-${field.id}`}
+          checked={!!field.nullable}
+          size="small"
+          onChange={() => {
+            onChange(field.id!, {
+              nullable: !field.nullable,
+              is_partition_key: false,
+            });
+          }}
+          style={{ padding: '8px' }}
+        />
+        <CustomToolTip
+          title={collectionTrans('nullableTooltip')}
+          placement="top"
+        >
+          <>{collectionTrans('nullable')}</>
+        </CustomToolTip>
+      </label>
+    </Box>
+  );
+};
+
+export default NullableCheckboxField;

+ 56 - 0
client/src/pages/dialogs/create/PartitionKeyCheckboxField.tsx

@@ -0,0 +1,56 @@
+import { FC } from 'react';
+import { Checkbox, SxProps, Theme, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import CustomToolTip from '@/components/customToolTip/CustomToolTip';
+import { FieldType } from '../../databases/collections/Types';
+
+interface PartitionKeyCheckboxFieldProps {
+  field: FieldType;
+  fields: FieldType[];
+  onChange: (id: string, changes: Partial<FieldType>) => void;
+  sx?: SxProps<Theme>;
+}
+
+const PartitionKeyCheckboxField: FC<PartitionKeyCheckboxFieldProps> = ({
+  field,
+  fields,
+  onChange,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const disabled =
+    (fields.some(f => f.is_partition_key) && !field.is_partition_key) ||
+    field.nullable;
+
+  const handleChange = () => {
+    onChange(field.id!, {
+      is_partition_key: !field.is_partition_key,
+    });
+  };
+
+  return (
+    <Box sx={sx}>
+      <label htmlFor={`partitionKey-${field.id}`}>
+        <Checkbox
+          id={`partitionKey-${field.id}`}
+          checked={!!field.is_partition_key}
+          size="small"
+          disabled={disabled}
+          onChange={handleChange}
+          style={{ padding: '8px' }}
+        />
+        <CustomToolTip
+          title={collectionTrans(
+            disabled ? 'paritionKeyDisabledTooltip' : 'partitionKeyTooltip'
+          )}
+          placement="top"
+        >
+          <>{collectionTrans('partitionKey')}</>
+        </CustomToolTip>
+      </label>
+    </Box>
+  );
+};
+
+export default PartitionKeyCheckboxField;

+ 63 - 0
client/src/pages/dialogs/create/PrimaryKeyTypeSelector.tsx

@@ -0,0 +1,63 @@
+import { FC, ReactNode } from 'react';
+import { useTranslation } from 'react-i18next';
+import CustomSelector from '@/components/customSelector/CustomSelector';
+import { PRIMARY_FIELDS_OPTIONS } from './Constants';
+import { DataTypeEnum } from '@/consts';
+import { SxProps, Theme, Box, InputLabel, FormHelperText } from '@mui/material';
+
+interface PrimaryKeyTypeSelectorProps {
+  value: number;
+  onChange: (value: DataTypeEnum) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+  required?: boolean;
+  error?: boolean;
+  helperText?: ReactNode;
+  disabled?: boolean;
+  id?: string;
+}
+
+const PrimaryKeyTypeSelector: FC<PrimaryKeyTypeSelectorProps> = ({
+  value,
+  onChange,
+  label,
+  sx,
+  required = false,
+  error = false,
+  helperText = ' ',
+  disabled = false,
+  id = 'primary-key-type-selector',
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+  const displayLabel = label || `${collectionTrans('idType')}`;
+
+  return (
+    <Box
+      sx={{
+        display: 'flex',
+        flexDirection: 'column',
+        width: 140,
+        ...sx,
+      }}
+    >
+      <CustomSelector
+        id={id}
+        labelId={`${id}-label`}
+        options={PRIMARY_FIELDS_OPTIONS}
+        size="small"
+        onChange={e => {
+          onChange(e.target.value as DataTypeEnum);
+        }}
+        disabled={disabled}
+        required={required}
+        error={error}
+        value={value}
+        variant="filled"
+        label={displayLabel}
+      />
+      <FormHelperText sx={{ marginTop: 0 }}>{helperText}</FormHelperText>
+    </Box>
+  );
+};
+
+export default PrimaryKeyTypeSelector;

+ 48 - 0
client/src/pages/dialogs/create/ScalarTypeSelector.tsx

@@ -0,0 +1,48 @@
+import { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+import CustomSelector from '@/components/customSelector/CustomSelector';
+import { ALL_OPTIONS } from './Constants';
+import { DataTypeEnum } from '@/consts';
+import { SxProps, Theme, Box, FormHelperText } from '@mui/material';
+
+interface ScalarTypeSelectorProps {
+  value: number;
+  onChange: (value: DataTypeEnum) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+}
+
+const ScalarTypeSelector: FC<ScalarTypeSelectorProps> = ({
+  value,
+  onChange,
+  label,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  return (
+    <Box
+      sx={{
+        display: 'flex',
+        flexDirection: 'column',
+        width: 140,
+        ...sx,
+      }}
+    >
+      <CustomSelector
+        options={ALL_OPTIONS}
+        size="small"
+        onChange={e => {
+          onChange(e.target.value as DataTypeEnum);
+        }}
+        value={value}
+        variant="filled"
+        label={label || collectionTrans('fieldType')}
+        sx={sx}
+      />
+      <FormHelperText sx={{ marginTop: 0 }}> </FormHelperText>
+    </Box>
+  );
+};
+
+export default ScalarTypeSelector;

+ 55 - 0
client/src/pages/dialogs/create/TextMatchCheckboxField.tsx

@@ -0,0 +1,55 @@
+import { FC } from 'react';
+import { Checkbox, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import CustomToolTip from '@/components/customToolTip/CustomToolTip';
+import { FieldType } from '../../databases/collections/Types';
+import { SxProps, Theme } from '@mui/material';
+
+interface TextMatchCheckboxFieldProps {
+  field: FieldType;
+  onChange: (id: string, changes: Partial<FieldType>) => void;
+  sx?: SxProps<Theme>;
+}
+
+const TextMatchCheckboxField: FC<TextMatchCheckboxFieldProps> = ({
+  field,
+  onChange,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const handleChange = () => {
+    const update: Partial<FieldType> = {
+      enable_match: !field.enable_match,
+    };
+
+    // If enabling text match, also enable analyzer
+    if (!field.enable_match) {
+      update.enable_analyzer = true;
+    }
+
+    onChange(field.id!, update);
+  };
+
+  return (
+    <Box sx={sx}>
+      <label htmlFor={`enableMatch-${field.id}`}>
+        <Checkbox
+          id={`enableMatch-${field.id}`}
+          checked={!!field.enable_match}
+          size="small"
+          onChange={handleChange}
+          style={{ padding: '8px' }}
+        />
+        <CustomToolTip
+          title={collectionTrans('textMatchTooltip')}
+          placement="top"
+        >
+          <>{collectionTrans('enableMatch')}</>
+        </CustomToolTip>
+      </label>
+    </Box>
+  );
+};
+
+export default TextMatchCheckboxField;

+ 48 - 0
client/src/pages/dialogs/create/VectorTypeSelector.tsx

@@ -0,0 +1,48 @@
+import { FC } from 'react';
+import { useTranslation } from 'react-i18next';
+import CustomSelector from '@/components/customSelector/CustomSelector';
+import { VECTOR_FIELDS_OPTIONS } from './Constants';
+import { DataTypeEnum } from '@/consts';
+import { SxProps, Theme, Box, FormHelperText } from '@mui/material';
+
+interface VectorTypeSelectorProps {
+  value: number;
+  onChange: (value: DataTypeEnum) => void;
+  label?: string;
+  sx?: SxProps<Theme>;
+}
+
+const VectorTypeSelector: FC<VectorTypeSelectorProps> = ({
+  value,
+  onChange,
+  label,
+  sx,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  return (
+    <Box
+      sx={{
+        display: 'flex',
+        flexDirection: 'column',
+        width: 140,
+        ...sx,
+      }}
+    >
+      <CustomSelector
+        options={VECTOR_FIELDS_OPTIONS}
+        size="small"
+        onChange={e => {
+          onChange(e.target.value as DataTypeEnum);
+        }}
+        value={value}
+        variant="filled"
+        label={label || `${collectionTrans('vectorType')} `}
+        sx={sx}
+      />
+      <FormHelperText sx={{ marginTop: 0 }}> </FormHelperText>
+    </Box>
+  );
+};
+
+export default VectorTypeSelector;

+ 174 - 0
client/src/pages/dialogs/create/rows/FunctionFieldRow.tsx

@@ -0,0 +1,174 @@
+import { FC, MutableRefObject } from 'react';
+import { IconButton, Theme, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import icons from '@/components/icons/Icons';
+import { DataTypeEnum } from '@/consts';
+import { FieldType } from '../../../databases/collections/Types';
+import NameField from '../NameField';
+import VectorTypeSelector from '../VectorTypeSelector';
+import MaxLengthField from '../MaxLengthField';
+import DefaultValueField from '../DefaultValueField';
+import DescriptionField from '../DescriptionField';
+import AnalyzerCheckboxField from '../AnalyzerCheckboxField';
+import TextMatchCheckboxField from '../TextMatchCheckboxField';
+import PartitionKeyCheckboxField from '../PartitionKeyCheckboxField';
+import NullableCheckboxField from '../NullableCheckboxField';
+import { rowStyles } from './styles';
+
+interface FunctionFieldRowProps {
+  field: FieldType;
+  index: number;
+  fields: FieldType[];
+  requiredFields: FieldType[];
+  onFieldChange: (
+    id: string,
+    changes: Partial<FieldType>,
+    isValid?: boolean
+  ) => void;
+  onAddField: (index: number, type: DataTypeEnum) => void;
+  onRemoveField: (id: string) => void;
+  localFieldAnalyzers: MutableRefObject<Map<string, Record<string, {}>>>;
+}
+
+const FunctionFieldRow: FC<FunctionFieldRowProps> = ({
+  field,
+  index,
+  fields,
+  requiredFields,
+  onFieldChange,
+  onAddField,
+  onRemoveField,
+  localFieldAnalyzers,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const AddIcon = icons.addOutline;
+  const RemoveIcon = icons.remove;
+
+  const selectStyles = {
+    width: '150px',
+  };
+
+  const maxLengthStyles = {
+    maxWidth: '80px',
+  };
+
+  const iconBtnStyles = {
+    padding: 0,
+    position: 'relative',
+    top: '-8px',
+    '& svg': {
+      width: 15,
+    },
+  };
+
+  const paramsGrpStyles = (theme: Theme) => ({
+    border: `1px dashed ${theme.palette.divider}`,
+    borderRadius: 4,
+    display: 'flex',
+    flexDirection: 'column',
+    paddingLeft: 0,
+    paddingTop: 0,
+    paddingRight: 2,
+    minHeight: 44,
+    alignSelf: 'flex-start',
+    alignItems: 'flex-start',
+    justifyContent: 'center',
+  });
+
+  const settingStyles = {
+    fontSize: 12,
+    alignItems: 'center',
+    display: 'flex',
+  };
+
+  return (
+    <Box sx={rowStyles}>
+      <NameField
+        field={field}
+        onChange={(id, name, isValid) =>
+          onFieldChange(field.id!, { name }, isValid)
+        }
+      />
+
+      <VectorTypeSelector
+        value={field.data_type}
+        onChange={(value: DataTypeEnum) =>
+          onFieldChange(field.id!, { data_type: value })
+        }
+        sx={selectStyles}
+      />
+
+      <MaxLengthField
+        field={field}
+        onChange={(id, max_length, isValid) =>
+          onFieldChange(id, { max_length }, isValid)
+        }
+        sx={maxLengthStyles}
+      />
+
+      <DefaultValueField
+        field={field}
+        onChange={(id, defaultValue) =>
+          onFieldChange(id, { default_value: defaultValue })
+        }
+        label={collectionTrans('defaultValue')}
+      />
+
+      <DescriptionField
+        field={field}
+        onChange={(id, description) => onFieldChange(id, { description })}
+      />
+
+      <Box sx={paramsGrpStyles}>
+        <AnalyzerCheckboxField
+          field={field}
+          onChange={onFieldChange}
+          localFieldAnalyzers={localFieldAnalyzers}
+          sx={settingStyles}
+        />
+
+        <TextMatchCheckboxField
+          field={field}
+          onChange={onFieldChange}
+          sx={settingStyles}
+        />
+
+        <PartitionKeyCheckboxField
+          field={field}
+          fields={fields}
+          onChange={onFieldChange}
+          sx={settingStyles}
+        />
+
+        <NullableCheckboxField
+          field={field}
+          onChange={onFieldChange}
+          sx={settingStyles}
+        />
+      </Box>
+
+      <IconButton
+        onClick={() => onAddField(index, field.data_type)}
+        sx={iconBtnStyles}
+        aria-label="add"
+        size="large"
+      >
+        <AddIcon />
+      </IconButton>
+
+      {requiredFields.length !== 2 && (
+        <IconButton
+          onClick={() => onRemoveField(field.id || '')}
+          sx={iconBtnStyles}
+          aria-label="delete"
+          size="large"
+        >
+          <RemoveIcon />
+        </IconButton>
+      )}
+    </Box>
+  );
+};
+
+export default FunctionFieldRow;

+ 98 - 0
client/src/pages/dialogs/create/rows/PrimaryKeyFieldRow.tsx

@@ -0,0 +1,98 @@
+import { FC } from 'react';
+import { FormControlLabel, Switch, Theme, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import CustomToolTip from '@/components/customToolTip/CustomToolTip';
+import { DataTypeEnum } from '@/consts';
+import { FieldType } from '../../../databases/collections/Types';
+import NameField from '../NameField';
+import DescriptionField from '../DescriptionField';
+import MaxLengthField from '../MaxLengthField';
+import PrimaryKeyTypeSelector from '../PrimaryKeyTypeSelector';
+import { rowStyles } from './styles';
+
+interface PrimaryKeyFieldRowProps {
+  field: FieldType;
+  autoID: boolean;
+  onFieldChange: (
+    id: string,
+    changes: Partial<FieldType>,
+    isValid?: boolean
+  ) => void;
+  setAutoID: (value: boolean) => void;
+}
+
+const PrimaryKeyFieldRow: FC<PrimaryKeyFieldRowProps> = ({
+  field,
+  autoID,
+  onFieldChange,
+  setAutoID,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const isVarChar = field.data_type === DataTypeEnum.VarChar;
+
+  const toggleStyles = (theme: Theme) => ({
+    marginLeft: theme.spacing(0.5),
+    marginRight: theme.spacing(0.5),
+  });
+
+  return (
+    <Box sx={rowStyles}>
+      <NameField
+        field={field}
+        onChange={(id, name, isValid) => onFieldChange(id, { name }, isValid)}
+        label={collectionTrans('idFieldName')}
+      />
+
+      <PrimaryKeyTypeSelector
+        value={field.data_type}
+        onChange={(value: DataTypeEnum) => {
+          onFieldChange(field.id!, { data_type: value });
+          if (value === DataTypeEnum.VarChar) {
+            setAutoID(false);
+          }
+        }}
+      />
+
+      {isVarChar && (
+        <MaxLengthField
+          field={field}
+          onChange={(id, max_length, isValid) =>
+            onFieldChange(id, { max_length }, isValid)
+          }
+        />
+      )}
+
+      <DescriptionField
+        field={field}
+        onChange={(id, description) => onFieldChange(id, { description }, true)}
+        sx={{ width: '64px' }}
+      />
+
+      <FormControlLabel
+        control={
+          <Switch
+            checked={autoID}
+            disabled={isVarChar}
+            size="small"
+            onChange={() => {
+              onFieldChange(field.id!, { autoID: !autoID });
+              setAutoID(!autoID);
+            }}
+          />
+        }
+        label={
+          <CustomToolTip
+            title={collectionTrans('autoIdToggleTip')}
+            placement="top"
+          >
+            <>{collectionTrans('autoId')}</>
+          </CustomToolTip>
+        }
+        sx={toggleStyles}
+      />
+    </Box>
+  );
+};
+
+export default PrimaryKeyFieldRow;

+ 210 - 0
client/src/pages/dialogs/create/rows/ScalarFieldRow.tsx

@@ -0,0 +1,210 @@
+import { FC, MutableRefObject } from 'react';
+import { IconButton, Theme, Box } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import icons from '@/components/icons/Icons';
+import { DataTypeEnum } from '@/consts';
+import { DEFAULT_ATTU_ELEMENT_TYPE, DEFAULT_ATTU_MAX_CAPACITY } from '@/consts';
+import { FieldType } from '../../../databases/collections/Types';
+import NameField from '../NameField';
+import ScalarTypeSelector from '../ScalarTypeSelector';
+import ElementTypeSelector from '../ElementTypeSelector';
+import MaxCapacityField from '../MaxCapacityField';
+import MaxLengthField from '../MaxLengthField';
+import DefaultValueField from '../DefaultValueField';
+import DescriptionField from '../DescriptionField';
+import PartitionKeyCheckboxField from '../PartitionKeyCheckboxField';
+import AnalyzerCheckboxField from '../AnalyzerCheckboxField';
+import TextMatchCheckboxField from '../TextMatchCheckboxField';
+import NullableCheckboxField from '../NullableCheckboxField';
+import { rowStyles } from './styles';
+
+interface ScalarFieldRowProps {
+  field: FieldType;
+  index: number;
+  fields: FieldType[];
+  onFieldChange: (
+    id: string,
+    changes: Partial<FieldType>,
+    isValid?: boolean
+  ) => void;
+  onAddField: (index: number, type: DataTypeEnum) => void;
+  onRemoveField: (id: string) => void;
+  localFieldAnalyzers: MutableRefObject<Map<string, Record<string, {}>>>;
+}
+
+const ScalarFieldRow: FC<ScalarFieldRowProps> = ({
+  field,
+  index,
+  fields,
+  onFieldChange,
+  onAddField,
+  onRemoveField,
+  localFieldAnalyzers,
+}) => {
+  const { t: collectionTrans } = useTranslation('collection');
+
+  const AddIcon = icons.addOutline;
+  const RemoveIcon = icons.remove;
+
+  const isVarChar = field.data_type === DataTypeEnum.VarChar;
+  const isInt64 = field.data_type === DataTypeEnum.Int64;
+  const isArray = field.data_type === DataTypeEnum.Array;
+  const isElementVarChar = field.element_type === DataTypeEnum.VarChar;
+  const showDefaultValue =
+    field.data_type !== DataTypeEnum.Array &&
+    field.data_type !== DataTypeEnum.JSON;
+
+  // handle default values for Array type
+  if (isArray && typeof field.element_type === 'undefined') {
+    onFieldChange(field.id!, { element_type: DEFAULT_ATTU_ELEMENT_TYPE });
+  }
+  if (isArray && typeof field.max_capacity === 'undefined') {
+    onFieldChange(field.id!, { max_capacity: DEFAULT_ATTU_MAX_CAPACITY });
+  }
+
+  const iconBtnStyles = {
+    padding: 0,
+    position: 'relative',
+    top: '-8px',
+    '& svg': {
+      width: 15,
+    },
+  };
+
+  const paramsGrpStyles = (theme: Theme) => ({
+    border: `1px dashed ${theme.palette.divider}`,
+    borderRadius: 4,
+    display: 'flex',
+    flexDirection: 'column',
+    paddingLeft: 0,
+    paddingTop: 0,
+    paddingRight: 2,
+    minHeight: 44,
+    alignSelf: 'flex-start',
+    alignItems: 'flex-start',
+    justifyContent: 'center',
+  });
+
+  const settingStyles = {
+    fontSize: 12,
+    alignItems: 'center',
+    display: 'flex',
+  };
+
+  return (
+    <Box sx={rowStyles}>
+      <NameField
+        field={field}
+        onChange={(id, name, isValid) => onFieldChange(id, { name }, isValid)}
+      />
+
+      <ScalarTypeSelector
+        value={field.data_type}
+        onChange={(value: DataTypeEnum) =>
+          onFieldChange(field.id!, { data_type: value }, true)
+        }
+      />
+
+      {isArray && (
+        <ElementTypeSelector
+          value={field.element_type || DEFAULT_ATTU_ELEMENT_TYPE}
+          onChange={(value: DataTypeEnum) =>
+            onFieldChange(field.id!, { element_type: value }, true)
+          }
+        />
+      )}
+
+      {isArray && (
+        <MaxCapacityField
+          field={field}
+          onChange={(id, max_capacity, isValid) => {
+            onFieldChange(id, { max_capacity }, isValid);
+          }}
+        />
+      )}
+
+      {(isVarChar || isElementVarChar) && (
+        <MaxLengthField
+          field={field}
+          onChange={(id, max_length, isValid) =>
+            onFieldChange(id, { max_length }, isValid)
+          }
+        />
+      )}
+
+      {showDefaultValue && (
+        <DefaultValueField
+          field={field}
+          onChange={(id, defaultValue) =>
+            onFieldChange(id, { default_value: defaultValue }, true)
+          }
+          label={collectionTrans('defaultValue')}
+        />
+      )}
+
+      <DescriptionField
+        field={field}
+        onChange={(id, description) => onFieldChange(id, { description }, true)}
+      />
+
+      <Box sx={paramsGrpStyles}>
+        {isInt64 && (
+          <PartitionKeyCheckboxField
+            field={field}
+            fields={fields}
+            onChange={onFieldChange}
+            sx={settingStyles}
+          />
+        )}
+
+        {isVarChar && (
+          <>
+            <AnalyzerCheckboxField
+              field={field}
+              onChange={onFieldChange}
+              localFieldAnalyzers={localFieldAnalyzers}
+              sx={settingStyles}
+            />
+            <TextMatchCheckboxField
+              field={field}
+              onChange={onFieldChange}
+              sx={settingStyles}
+            />
+            <PartitionKeyCheckboxField
+              field={field}
+              fields={fields}
+              onChange={onFieldChange}
+              sx={settingStyles}
+            />
+          </>
+        )}
+
+        <NullableCheckboxField
+          field={field}
+          onChange={onFieldChange}
+          sx={settingStyles}
+        />
+      </Box>
+
+      <IconButton
+        onClick={() => onAddField(index, field.data_type)}
+        sx={iconBtnStyles}
+        aria-label="add"
+        size="large"
+      >
+        <AddIcon />
+      </IconButton>
+
+      <IconButton
+        onClick={() => onRemoveField(field.id || '')}
+        sx={iconBtnStyles}
+        aria-label="delete"
+        size="large"
+      >
+        <RemoveIcon />
+      </IconButton>
+    </Box>
+  );
+};
+
+export default ScalarFieldRow;

+ 100 - 0
client/src/pages/dialogs/create/rows/VectorFieldRow.tsx

@@ -0,0 +1,100 @@
+import { FC } from 'react';
+import { IconButton, Box } from '@mui/material';
+import icons from '@/components/icons/Icons';
+import { DataTypeEnum } from '@/consts';
+import { FieldType } from '../../../databases/collections/Types';
+import NameField from '../NameField';
+import VectorTypeSelector from '../VectorTypeSelector';
+import DimensionField from '../DimensionField';
+import DescriptionField from '../DescriptionField';
+import { rowStyles } from './styles';
+
+interface VectorFieldRowProps {
+  field: FieldType;
+  index: number;
+  requiredFields?: FieldType[];
+  onFieldChange: (
+    id: string,
+    changes: Partial<FieldType>,
+    isValid?: boolean
+  ) => void;
+  onAddField: (index: number, type: DataTypeEnum) => void;
+  onRemoveField?: (id: string) => void;
+  showDeleteButton?: boolean;
+}
+
+const VectorFieldRow: FC<VectorFieldRowProps> = ({
+  field,
+  index,
+  requiredFields = [],
+  onFieldChange,
+  onAddField,
+  onRemoveField,
+  showDeleteButton = false,
+}) => {
+  const AddIcon = icons.addOutline;
+  const RemoveIcon = icons.remove;
+
+  const showDelete =
+    showDeleteButton && onRemoveField && requiredFields.length !== 2;
+
+  return (
+    <Box sx={rowStyles}>
+      <NameField
+        field={field}
+        onChange={(id, name, isValid) =>
+          onFieldChange(field.id!, { name }, isValid)
+        }
+      />
+
+      <VectorTypeSelector
+        value={field.data_type}
+        onChange={(value: DataTypeEnum) =>
+          onFieldChange(field.id!, { data_type: value })
+        }
+      />
+
+      <DimensionField field={field} onChange={onFieldChange} />
+
+      <DescriptionField
+        field={field}
+        onChange={(id, description) => onFieldChange(id, { description })}
+      />
+
+      <IconButton
+        onClick={() => onAddField(index, field.data_type)}
+        sx={{
+          padding: 0,
+          position: 'relative',
+          top: '-8px',
+          '& svg': { width: 15 },
+        }}
+        aria-label="add"
+        size="large"
+      >
+        <AddIcon />
+      </IconButton>
+
+      {showDelete && (
+        <IconButton
+          onClick={() => {
+            const id = field.id || '';
+            onRemoveField(id);
+          }}
+          sx={{
+            padding: 0,
+            position: 'relative',
+            top: '-8px',
+            '& svg': { width: 15 },
+          }}
+          aria-label="delete"
+          size="large"
+        >
+          <RemoveIcon />
+        </IconButton>
+      )}
+    </Box>
+  );
+};
+
+export default VectorFieldRow;

+ 25 - 0
client/src/pages/dialogs/create/rows/styles.ts

@@ -0,0 +1,25 @@
+import { SxProps } from '@mui/material/styles';
+import { Theme } from '@mui/material/styles/createTheme';
+
+export const rowStyles = {
+  display: 'flex',
+  flexWrap: 'nowrap',
+  alignItems: 'center',
+  gap: '8px',
+  marginBottom: 1,
+  '& .MuiFormLabel-root': {
+    fontSize: 14,
+  },
+  '& .MuiInputBase-root': {
+    fontSize: 14,
+  },
+  '& .MuiSelect-filled': {
+    fontSize: 14,
+  },
+  '& .MuiCheckbox-root': {
+    padding: 4,
+  },
+  '& .MuiFormControlLabel-label': {
+    fontSize: 14,
+  },
+} as SxProps<Theme>;

+ 2 - 1
client/src/styles/theme.ts

@@ -11,7 +11,7 @@ declare module '@mui/material/Typography' {
 
 declare module '@mui/material/styles' {
   interface TypeBackground {
-    light?: string; // Adding the light property to the TypeBackground interface
+    lightGrey?: string; // Adding the light property to the TypeBackground interface
     grey?: string;
   }
   interface Palette {
@@ -55,6 +55,7 @@ const getCommonThemes = (mode: PaletteMode) => ({
       default: mode === 'light' ? '#f5f5f5' : '#121212',
       paper: mode === 'light' ? '#ffffff' : '#1e1e1e',
       grey: mode === 'light' ? grey[200] : grey[800],
+      lightGrey: mode === 'light' ? grey[100] : grey[800],
     },
   },
   spacing: (factor: number) => `${8 * factor}px`,

Some files were not shown because too many files changed in this diff