index.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback } from 'react'
  4. import { useTranslation } from 'react-i18next'
  5. import Image from 'next/image'
  6. import ProgressIndicator from '../../create/assets/progress-indicator.svg'
  7. import Reranking from '../../create/assets/rerank.svg'
  8. import cn from '@/utils/classnames'
  9. import TopKItem from '@/app/components/base/param-item/top-k-item'
  10. import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item'
  11. import { RETRIEVE_METHOD } from '@/types/app'
  12. import Switch from '@/app/components/base/switch'
  13. import Tooltip from '@/app/components/base/tooltip'
  14. import type { RetrievalConfig } from '@/types/app'
  15. import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector'
  16. import { useCurrentProviderAndModel, useModelListAndDefaultModel } from '@/app/components/header/account-setting/model-provider-page/hooks'
  17. import { ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations'
  18. import {
  19. DEFAULT_WEIGHTED_SCORE,
  20. RerankingModeEnum,
  21. WeightedScoreEnum,
  22. } from '@/models/datasets'
  23. import WeightedScore from '@/app/components/app/configuration/dataset-config/params-config/weighted-score'
  24. import Toast from '@/app/components/base/toast'
  25. import RadioCard from '@/app/components/base/radio-card'
  26. type Props = {
  27. type: RETRIEVE_METHOD
  28. value: RetrievalConfig
  29. onChange: (value: RetrievalConfig) => void
  30. }
  31. const RetrievalParamConfig: FC<Props> = ({
  32. type,
  33. value,
  34. onChange,
  35. }) => {
  36. const { t } = useTranslation()
  37. const canToggleRerankModalEnable = type !== RETRIEVE_METHOD.hybrid
  38. const isEconomical = type === RETRIEVE_METHOD.invertedIndex
  39. const {
  40. defaultModel: rerankDefaultModel,
  41. modelList: rerankModelList,
  42. } = useModelListAndDefaultModel(ModelTypeEnum.rerank)
  43. const {
  44. currentModel,
  45. } = useCurrentProviderAndModel(
  46. rerankModelList,
  47. rerankDefaultModel
  48. ? {
  49. ...rerankDefaultModel,
  50. provider: rerankDefaultModel.provider.provider,
  51. }
  52. : undefined,
  53. )
  54. const handleDisabledSwitchClick = useCallback(() => {
  55. if (!currentModel)
  56. Toast.notify({ type: 'error', message: t('workflow.errorMsg.rerankModelRequired') })
  57. }, [currentModel, rerankDefaultModel, t])
  58. const isHybridSearch = type === RETRIEVE_METHOD.hybrid
  59. const rerankModel = (() => {
  60. if (value.reranking_model) {
  61. return {
  62. provider_name: value.reranking_model.reranking_provider_name,
  63. model_name: value.reranking_model.reranking_model_name,
  64. }
  65. }
  66. else if (rerankDefaultModel) {
  67. return {
  68. provider_name: rerankDefaultModel.provider.provider,
  69. model_name: rerankDefaultModel.model,
  70. }
  71. }
  72. })()
  73. const handleChangeRerankMode = (v: RerankingModeEnum) => {
  74. if (v === value.reranking_mode)
  75. return
  76. const result = {
  77. ...value,
  78. reranking_mode: v,
  79. }
  80. if (!result.weights && v === RerankingModeEnum.WeightedScore) {
  81. result.weights = {
  82. weight_type: WeightedScoreEnum.Customized,
  83. vector_setting: {
  84. vector_weight: DEFAULT_WEIGHTED_SCORE.other.semantic,
  85. embedding_provider_name: '',
  86. embedding_model_name: '',
  87. },
  88. keyword_setting: {
  89. keyword_weight: DEFAULT_WEIGHTED_SCORE.other.keyword,
  90. },
  91. }
  92. }
  93. onChange(result)
  94. }
  95. const rerankingModeOptions = [
  96. {
  97. value: RerankingModeEnum.WeightedScore,
  98. label: t('dataset.weightedScore.title'),
  99. tips: t('dataset.weightedScore.description'),
  100. },
  101. {
  102. value: RerankingModeEnum.RerankingModel,
  103. label: t('common.modelProvider.rerankModel.key'),
  104. tips: t('common.modelProvider.rerankModel.tip'),
  105. },
  106. ]
  107. return (
  108. <div>
  109. {!isEconomical && !isHybridSearch && (
  110. <div>
  111. <div className='flex items-center space-x-2 mb-2'>
  112. {canToggleRerankModalEnable && (
  113. <div
  114. className='flex items-center'
  115. onClick={handleDisabledSwitchClick}
  116. >
  117. <Switch
  118. size='md'
  119. defaultValue={currentModel ? value.reranking_enable : false}
  120. onChange={(v) => {
  121. onChange({
  122. ...value,
  123. reranking_enable: v,
  124. })
  125. }}
  126. disabled={!currentModel}
  127. />
  128. </div>
  129. )}
  130. <div className='flex items-center'>
  131. <span className='mr-0.5 system-sm-semibold text-text-secondary'>{t('common.modelProvider.rerankModel.key')}</span>
  132. <Tooltip
  133. popupContent={
  134. <div className="w-[200px]">{t('common.modelProvider.rerankModel.tip')}</div>
  135. }
  136. />
  137. </div>
  138. </div>
  139. <ModelSelector
  140. triggerClassName={`${!value.reranking_enable && '!opacity-60 !cursor-not-allowed'}`}
  141. defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
  142. modelList={rerankModelList}
  143. readonly={!value.reranking_enable}
  144. onSelect={(v) => {
  145. onChange({
  146. ...value,
  147. reranking_model: {
  148. reranking_provider_name: v.provider,
  149. reranking_model_name: v.model,
  150. },
  151. })
  152. }}
  153. />
  154. </div>
  155. )}
  156. {
  157. !isHybridSearch && (
  158. <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-4')}>
  159. <TopKItem
  160. className='grow'
  161. value={value.top_k}
  162. onChange={(_key, v) => {
  163. onChange({
  164. ...value,
  165. top_k: v,
  166. })
  167. }}
  168. enable={true}
  169. />
  170. {(!isEconomical && !(value.search_method === RETRIEVE_METHOD.fullText && !value.reranking_enable)) && (
  171. <ScoreThresholdItem
  172. className='grow'
  173. value={value.score_threshold}
  174. onChange={(_key, v) => {
  175. onChange({
  176. ...value,
  177. score_threshold: v,
  178. })
  179. }}
  180. enable={value.score_threshold_enabled}
  181. hasSwitch={true}
  182. onSwitchChange={(_key, v) => {
  183. onChange({
  184. ...value,
  185. score_threshold_enabled: v,
  186. })
  187. }}
  188. />
  189. )}
  190. </div>
  191. )
  192. }
  193. {
  194. isHybridSearch && (
  195. <>
  196. <div className='flex gap-2 mb-4'>
  197. {
  198. rerankingModeOptions.map(option => (
  199. <RadioCard
  200. key={option.value}
  201. isChosen={value.reranking_mode === option.value}
  202. onChosen={() => handleChangeRerankMode(option.value)}
  203. icon={<Image src={
  204. option.value === RerankingModeEnum.WeightedScore
  205. ? ProgressIndicator
  206. : Reranking
  207. } alt=''/>}
  208. title={option.label}
  209. description={option.tips}
  210. className='flex-1'
  211. />
  212. ))
  213. }
  214. </div>
  215. {
  216. value.reranking_mode === RerankingModeEnum.WeightedScore && (
  217. <WeightedScore
  218. value={{
  219. value: [
  220. value.weights!.vector_setting.vector_weight,
  221. value.weights!.keyword_setting.keyword_weight,
  222. ],
  223. }}
  224. onChange={(v) => {
  225. onChange({
  226. ...value,
  227. weights: {
  228. ...value.weights!,
  229. vector_setting: {
  230. ...value.weights!.vector_setting,
  231. vector_weight: v.value[0],
  232. },
  233. keyword_setting: {
  234. ...value.weights!.keyword_setting,
  235. keyword_weight: v.value[1],
  236. },
  237. },
  238. })
  239. }}
  240. />
  241. )
  242. }
  243. {
  244. value.reranking_mode !== RerankingModeEnum.WeightedScore && (
  245. <ModelSelector
  246. triggerClassName={`${!value.reranking_enable && '!opacity-60 !cursor-not-allowed'}`}
  247. defaultModel={rerankModel && { provider: rerankModel.provider_name, model: rerankModel.model_name }}
  248. modelList={rerankModelList}
  249. readonly={!value.reranking_enable}
  250. onSelect={(v) => {
  251. onChange({
  252. ...value,
  253. reranking_model: {
  254. reranking_provider_name: v.provider,
  255. reranking_model_name: v.model,
  256. },
  257. })
  258. }}
  259. />
  260. )
  261. }
  262. <div className={cn(!isEconomical && 'mt-4', 'flex space-between space-x-6')}>
  263. <TopKItem
  264. className='grow'
  265. value={value.top_k}
  266. onChange={(_key, v) => {
  267. onChange({
  268. ...value,
  269. top_k: v,
  270. })
  271. }}
  272. enable={true}
  273. />
  274. <ScoreThresholdItem
  275. className='grow'
  276. value={value.score_threshold}
  277. onChange={(_key, v) => {
  278. onChange({
  279. ...value,
  280. score_threshold: v,
  281. })
  282. }}
  283. enable={value.score_threshold_enabled}
  284. hasSwitch={true}
  285. onSwitchChange={(_key, v) => {
  286. onChange({
  287. ...value,
  288. score_threshold_enabled: v,
  289. })
  290. }}
  291. />
  292. </div>
  293. </>
  294. )
  295. }
  296. </div>
  297. )
  298. }
  299. export default React.memo(RetrievalParamConfig)