SearchParams.tsx 8.1 KB

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