SearchParams.tsx 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { makeStyles, Theme } from '@material-ui/core';
  2. import { FC, useCallback, useEffect, useMemo } from 'react';
  3. import { useTranslation } from 'react-i18next';
  4. import CustomInput from '../../components/customInput/CustomInput';
  5. import { ITextfieldConfig } from '../../components/customInput/Types';
  6. import CustomSelector from '../../components/customSelector/CustomSelector';
  7. import { Option } from '../../components/customSelector/Types';
  8. import {
  9. DEFAULT_NLIST_VALUE,
  10. DEFAULT_SEARCH_PARAM_VALUE_MAP,
  11. INDEX_CONFIG,
  12. METRIC_OPTIONS_MAP,
  13. searchKeywordsType,
  14. } from '../../consts/Milvus';
  15. import { useFormValidation } from '../../hooks/Form';
  16. import { formatForm } from '../../utils/Form';
  17. import { SearchParamInputConfig, SearchParamsProps } from './Types';
  18. const getStyles = makeStyles((theme: Theme) => ({
  19. selector: {
  20. width: '100%',
  21. marginTop: theme.spacing(2),
  22. },
  23. input: {
  24. marginTop: theme.spacing(2),
  25. },
  26. }));
  27. const SearchParams: FC<SearchParamsProps> = ({
  28. indexType,
  29. indexParams,
  30. searchParamsForm,
  31. handleFormChange,
  32. embeddingType,
  33. metricType,
  34. topK,
  35. setParamsDisabled,
  36. wrapperClass = '',
  37. }) => {
  38. const { t: indexTrans } = useTranslation('index');
  39. const { t: warningTrans } = useTranslation('warning');
  40. const classes = getStyles();
  41. const metricOptions: Option[] = METRIC_OPTIONS_MAP[embeddingType];
  42. // search params key list, depends on index type
  43. // e.g. ['nprobe']
  44. const searchParams = useMemo(
  45. () => (indexType !== '' ? INDEX_CONFIG[indexType].search : []),
  46. [indexType]
  47. );
  48. const handleInputChange = useCallback(
  49. (key: string, value: number) => {
  50. const form = { ...searchParamsForm, [key]: value };
  51. handleFormChange(form);
  52. },
  53. [handleFormChange, searchParamsForm]
  54. );
  55. /**
  56. * function to transfer search params to CustomInput need config type
  57. */
  58. const getNumberInputConfig = useCallback(
  59. (params: SearchParamInputConfig): ITextfieldConfig => {
  60. const {
  61. label,
  62. key,
  63. min,
  64. max,
  65. value,
  66. handleChange,
  67. isInt = true,
  68. } = params;
  69. // search_k range is special compared to others,need to be handled separately
  70. // range: {-1} ∪ [top_k, n × n_trees]
  71. const isSearchK = label === 'search_k';
  72. const config: ITextfieldConfig = {
  73. label,
  74. key,
  75. onChange: value => {
  76. handleChange(value);
  77. },
  78. className: classes.input,
  79. variant: 'filled',
  80. type: 'number',
  81. value,
  82. validations: [
  83. {
  84. rule: 'require',
  85. errorText: warningTrans('required', { name: label }),
  86. },
  87. ],
  88. };
  89. if (!isSearchK && min && max) {
  90. config.validations?.push({
  91. rule: 'range',
  92. errorText: warningTrans('range', { min, max }),
  93. extraParam: {
  94. min,
  95. max,
  96. type: 'number',
  97. },
  98. });
  99. }
  100. if (isInt) {
  101. config.validations?.push({
  102. rule: 'integer',
  103. errorText: warningTrans('integer', { name: label }),
  104. });
  105. }
  106. // search_k
  107. if (isSearchK) {
  108. config.validations?.push({
  109. rule: 'specValueOrRange',
  110. errorText: warningTrans('specValueOrRange', {
  111. name: label,
  112. min,
  113. max,
  114. specValue: -1,
  115. }),
  116. extraParam: {
  117. min,
  118. max,
  119. compareValue: -1,
  120. type: 'number',
  121. },
  122. });
  123. }
  124. return config;
  125. },
  126. [warningTrans, classes.input]
  127. );
  128. const getSearchInputConfig = useCallback(
  129. (paramKey: searchKeywordsType): ITextfieldConfig => {
  130. const nlist = Number(
  131. // nlist range is [1, 65536], if user didn't create index, we set 1024 as default nlist value
  132. indexParams.find(p => p.key === 'nlist')?.value || DEFAULT_NLIST_VALUE
  133. );
  134. const configParamMap: {
  135. [key in searchKeywordsType]: SearchParamInputConfig;
  136. } = {
  137. nprobe: {
  138. label: 'nprobe',
  139. key: 'nprobe',
  140. value: searchParamsForm['nprobe'] || '',
  141. min: 1,
  142. max: nlist,
  143. isInt: true,
  144. handleChange: value => {
  145. handleInputChange('nprobe', value);
  146. },
  147. },
  148. ef: {
  149. label: 'ef',
  150. key: 'ef',
  151. value: searchParamsForm['ef'] || '',
  152. min: topK,
  153. max: 32768,
  154. isInt: true,
  155. handleChange: value => {
  156. handleInputChange('ef', value);
  157. },
  158. },
  159. search_k: {
  160. label: 'search_k',
  161. key: 'search_k',
  162. value: searchParamsForm['search_k'] || '',
  163. min: topK,
  164. // n * n_trees can be infinity
  165. max: Infinity,
  166. isInt: true,
  167. handleChange: value => {
  168. handleInputChange('search_k', value);
  169. },
  170. },
  171. search_length: {
  172. label: 'search_length',
  173. key: 'search_length',
  174. value: searchParamsForm['search_length'] || '',
  175. min: 10,
  176. max: 300,
  177. isInt: true,
  178. handleChange: value => {
  179. handleInputChange('search_length', value);
  180. },
  181. },
  182. };
  183. const param = configParamMap[paramKey];
  184. return getNumberInputConfig(param);
  185. },
  186. [
  187. indexParams,
  188. topK,
  189. searchParamsForm,
  190. getNumberInputConfig,
  191. handleInputChange,
  192. ]
  193. );
  194. useEffect(() => {
  195. // generate different form according to search params
  196. const form = searchParams.reduce(
  197. (paramsForm, param) => ({
  198. ...paramsForm,
  199. [param]: DEFAULT_SEARCH_PARAM_VALUE_MAP[param],
  200. }),
  201. {}
  202. );
  203. handleFormChange(form);
  204. }, [searchParams, handleFormChange]);
  205. const checkedForm = useMemo(() => {
  206. const { ...needCheckItems } = searchParamsForm;
  207. return formatForm(needCheckItems);
  208. }, [searchParamsForm]);
  209. const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
  210. useEffect(() => {
  211. setParamsDisabled(disabled);
  212. }, [disabled, setParamsDisabled]);
  213. return (
  214. <div className={wrapperClass}>
  215. {/* metric type */}
  216. <CustomSelector
  217. options={metricOptions}
  218. value={metricType}
  219. label={indexTrans('metric')}
  220. wrapperClass={classes.selector}
  221. variant="filled"
  222. onChange={(e: { target: { value: unknown } }) => {
  223. // not selectable now, so not set onChange event
  224. }}
  225. // not selectable now
  226. // readOnly can't avoid all events, so we use disabled instead
  227. disabled={true}
  228. />
  229. {/* dynamic params, now every type only has one param except metric type */}
  230. {searchParams.map(param => (
  231. <CustomInput
  232. key={param}
  233. type="text"
  234. textConfig={getSearchInputConfig(param)}
  235. checkValid={checkIsValid}
  236. validInfo={validation}
  237. />
  238. ))}
  239. </div>
  240. );
  241. };
  242. export default SearchParams;