SearchParams.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. import { FC, useCallback, useContext, useEffect, useMemo } from 'react';
  2. import Box from '@mui/material/Box';
  3. import { useTranslation } from 'react-i18next';
  4. import CustomInput from '@/components/customInput/CustomInput';
  5. import { ITextfieldConfig } from '@/components/customInput/Types';
  6. import {
  7. DEFAULT_NLIST_VALUE,
  8. DEFAULT_SEARCH_PARAM_VALUE_MAP,
  9. INDEX_CONFIG,
  10. searchKeywordsType,
  11. } from '@/consts';
  12. import { rootContext } from '@/context';
  13. import { useFormValidation } from '@/hooks';
  14. import { formatForm } from '@/utils';
  15. import type { SearchParamInputConfig, SearchParamsProps } from './Types';
  16. const SearchParams: FC<SearchParamsProps> = ({
  17. indexType = '',
  18. indexParams = [],
  19. searchParamsForm,
  20. handleFormChange,
  21. topK,
  22. setParamsDisabled,
  23. sx = {},
  24. }) => {
  25. const { t: warningTrans } = useTranslation('warning');
  26. const { openSnackBar } = useContext(rootContext);
  27. // search params key list, depends on index type
  28. // e.g. ['nprobe']
  29. const searchParams = useMemo((): searchKeywordsType[] => {
  30. const isSupportedType = Object.keys(INDEX_CONFIG).includes(indexType);
  31. // show warning snackbar for unsupported type
  32. if (!isSupportedType) {
  33. indexType !== '' &&
  34. openSnackBar(
  35. warningTrans('noSupportIndexType', { type: indexType }),
  36. 'warning'
  37. );
  38. }
  39. const commonParams: searchKeywordsType[] = ['radius', 'range_filter'];
  40. return indexType !== '' && isSupportedType
  41. ? [...INDEX_CONFIG[indexType!].search, ...commonParams]
  42. : commonParams;
  43. }, [indexType, openSnackBar, warningTrans]);
  44. const handleInputChange = useCallback(
  45. (key: string, value: number | string | typeof NaN) => {
  46. let form = { ...searchParamsForm };
  47. if (value === '' || isNaN(value as any)) {
  48. delete form[key];
  49. } else {
  50. form = { ...searchParamsForm, [key]: value };
  51. }
  52. handleFormChange(form);
  53. },
  54. [handleFormChange, searchParamsForm]
  55. );
  56. /**
  57. * function to transfer search params to CustomInput need config type
  58. */
  59. const getInputConfig = useCallback(
  60. (params: SearchParamInputConfig): ITextfieldConfig => {
  61. const {
  62. label,
  63. key,
  64. min,
  65. max,
  66. value,
  67. handleChange,
  68. isInt = true,
  69. type = 'number',
  70. required = true,
  71. } = params;
  72. // search_k range is special compared to others,need to be handled separately
  73. // range: {-1} ∪ [top_k, n × n_trees]
  74. const isSearchK = label === 'search_k';
  75. const config: ITextfieldConfig = {
  76. label,
  77. key,
  78. onChange: value => {
  79. handleChange(value);
  80. },
  81. className: 'inline-input',
  82. variant: 'filled',
  83. type: type,
  84. value,
  85. validations: [],
  86. };
  87. if (required) {
  88. config.validations?.push({
  89. rule: 'require',
  90. errorText: warningTrans('required', { name: label }),
  91. });
  92. }
  93. if (isInt) {
  94. config.validations?.push({
  95. rule: 'integer',
  96. errorText: warningTrans('integer', { name: label }),
  97. });
  98. }
  99. if (typeof min === 'number' && typeof max === 'number') {
  100. config.validations?.push({
  101. rule: 'range',
  102. errorText: warningTrans('range', { name: label, min, max }),
  103. extraParam: { min, max, type: 'number' },
  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]
  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. filter: {
  138. label: 'filter',
  139. key: 'filter',
  140. value: searchParamsForm['filter'] ?? '',
  141. isInt: false,
  142. type: 'text',
  143. required: false,
  144. handleChange: value => {
  145. handleInputChange('filter', value);
  146. },
  147. className: 'inline-input',
  148. },
  149. round_decimal: {
  150. label: 'round',
  151. key: 'round_decimal',
  152. type: 'number',
  153. value: searchParamsForm['round_decimal'] ?? '',
  154. min: -1,
  155. max: 10,
  156. isInt: true,
  157. required: false,
  158. handleChange: value => {
  159. handleInputChange('round_decimal', value);
  160. },
  161. className: 'inline-input',
  162. },
  163. nprobe: {
  164. label: 'nprobe',
  165. key: 'nprobe',
  166. type: 'number',
  167. value: searchParamsForm['nprobe'] ?? '',
  168. min: 1,
  169. max: nlist,
  170. isInt: true,
  171. handleChange: value => {
  172. handleInputChange('nprobe', value);
  173. },
  174. className: 'inline-input',
  175. },
  176. radius: {
  177. label: 'radius',
  178. key: 'radius',
  179. type: 'number',
  180. value: searchParamsForm['radius'] ?? '',
  181. isInt: false,
  182. required: false,
  183. handleChange: value => {
  184. handleInputChange('radius', value);
  185. },
  186. className: 'inline-input',
  187. },
  188. range_filter: {
  189. label: 'range filter',
  190. key: 'range_filter',
  191. value: searchParamsForm['range_filter'] ?? '',
  192. isInt: false,
  193. required: false,
  194. type: 'number',
  195. handleChange: value => {
  196. handleInputChange('range_filter', value);
  197. },
  198. className: 'inline-input',
  199. },
  200. ef: {
  201. label: 'ef',
  202. key: 'ef',
  203. value: searchParamsForm['ef'] ?? '',
  204. isInt: true,
  205. type: 'number',
  206. handleChange: value => {
  207. handleInputChange('ef', value);
  208. },
  209. },
  210. level: {
  211. label: 'level',
  212. key: 'level',
  213. value: searchParamsForm['level'] ?? 1,
  214. min: 1,
  215. max: 5,
  216. isInt: true,
  217. required: false,
  218. type: 'number',
  219. handleChange: value => {
  220. handleInputChange('level', value);
  221. },
  222. },
  223. search_k: {
  224. label: 'search_k',
  225. key: 'search_k',
  226. value: searchParamsForm['search_k'] ?? topK,
  227. min: topK,
  228. // n * n_trees can be infinity
  229. max: Infinity,
  230. isInt: true,
  231. type: 'number',
  232. handleChange: value => {
  233. handleInputChange('search_k', value);
  234. },
  235. },
  236. search_length: {
  237. label: 'search_length',
  238. key: 'search_length',
  239. value: searchParamsForm['search_length'] ?? '',
  240. min: 10,
  241. max: 300,
  242. isInt: true,
  243. type: 'number',
  244. handleChange: value => {
  245. handleInputChange('search_length', value);
  246. },
  247. },
  248. search_list: {
  249. label: 'search_list',
  250. key: 'search_list',
  251. value: searchParamsForm['search_list'] ?? '',
  252. min: 150,
  253. max: 65535,
  254. isInt: true,
  255. type: 'number',
  256. handleChange: value => {
  257. handleInputChange('search_list', value);
  258. },
  259. },
  260. drop_ratio_search: {
  261. label: 'drop_ratio_search',
  262. key: 'drop_ratio_search',
  263. value: searchParamsForm['drop_ratio_search'] ?? '',
  264. min: 0,
  265. max: 1,
  266. isInt: false,
  267. type: 'number',
  268. required: false,
  269. handleChange: value => {
  270. handleInputChange('drop_ratio_search', value);
  271. },
  272. },
  273. itopk_size: {
  274. label: 'itopk_size',
  275. key: 'itopk_size',
  276. value: searchParamsForm['itopk_size'] ?? '',
  277. isInt: true,
  278. type: 'number',
  279. required: false,
  280. handleChange: value => {
  281. handleInputChange('itopk_size', value);
  282. },
  283. },
  284. search_width: {
  285. label: 'search_width',
  286. key: 'search_width',
  287. value: searchParamsForm['search_width'] ?? '',
  288. isInt: true,
  289. type: 'number',
  290. required: false,
  291. handleChange: value => {
  292. handleInputChange('search_width', value);
  293. },
  294. },
  295. min_iterations: {
  296. label: 'min_iterations',
  297. key: 'min_iterations',
  298. value: searchParamsForm['min_iterations'] ?? '0',
  299. isInt: true,
  300. type: 'number',
  301. required: false,
  302. handleChange: value => {
  303. handleInputChange('min_iterations', value);
  304. },
  305. },
  306. max_iterations: {
  307. label: 'max_iterations',
  308. key: 'max_iterations',
  309. value: searchParamsForm['max_iterations'] ?? '0',
  310. isInt: true,
  311. type: 'number',
  312. required: false,
  313. handleChange: value => {
  314. handleInputChange('max_iterations', value);
  315. },
  316. },
  317. team_size: {
  318. label: 'team_size',
  319. key: 'team_size',
  320. value: searchParamsForm['team_size'] ?? '0',
  321. min: 2,
  322. max: 32,
  323. isInt: true,
  324. type: 'number',
  325. required: false,
  326. handleChange: value => {
  327. handleInputChange('team_size', value);
  328. },
  329. },
  330. };
  331. const param = configParamMap[paramKey];
  332. return getInputConfig(param);
  333. },
  334. [indexParams, searchParamsForm, topK, getInputConfig, handleInputChange]
  335. );
  336. useEffect(() => {
  337. // generate different form according to search params
  338. const form = searchParams.reduce(
  339. (paramsForm, param) => ({
  340. ...paramsForm,
  341. [param]: DEFAULT_SEARCH_PARAM_VALUE_MAP[param],
  342. }),
  343. {}
  344. );
  345. handleFormChange(form);
  346. }, []);
  347. const checkedForm = useMemo(() => {
  348. const { ...needCheckItems } = searchParamsForm;
  349. return formatForm(needCheckItems);
  350. }, [searchParamsForm]);
  351. const { validation, checkIsValid, disabled } = useFormValidation(checkedForm);
  352. useEffect(() => {
  353. setParamsDisabled(disabled);
  354. }, [disabled, setParamsDisabled]);
  355. return (
  356. <Box
  357. sx={{
  358. ...sx,
  359. display: 'flex',
  360. flexDirection: 'column',
  361. gap: 2,
  362. '& .inline-input': {
  363. marginBottom: '20px',
  364. },
  365. }}
  366. >
  367. {/* dynamic params, now every type only has one param except metric type */}
  368. {searchParams.map(param => (
  369. <CustomInput
  370. key={param}
  371. type="text"
  372. textConfig={{
  373. ...getSearchInputConfig(param),
  374. className: 'inline-input',
  375. sx: {
  376. width: '100%',
  377. '& .MuiFormHelperText-root': {
  378. position: 'absolute',
  379. margin: 0,
  380. padding: '0 14px',
  381. fontSize: '0.75rem',
  382. lineHeight: '1.66',
  383. letterSpacing: '0.03333em',
  384. textAlign: 'left',
  385. marginTop: '3px',
  386. marginRight: '14px',
  387. marginBottom: '0',
  388. marginLeft: '14px',
  389. },
  390. },
  391. variant: 'outlined',
  392. InputLabelProps: { shrink: true },
  393. }}
  394. checkValid={checkIsValid}
  395. validInfo={validation}
  396. />
  397. ))}
  398. </Box>
  399. );
  400. };
  401. export default SearchParams;