You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

textarea.tsx 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import React, { useState } from 'react'
  2. import { useTranslation } from 'react-i18next'
  3. import {
  4. RiEqualizer2Line,
  5. } from '@remixicon/react'
  6. import Image from 'next/image'
  7. import Button from '../../base/button'
  8. import { getIcon } from '../common/retrieval-method-info'
  9. import ModifyExternalRetrievalModal from './modify-external-retrieval-modal'
  10. import Tooltip from '@/app/components/base/tooltip'
  11. import cn from '@/utils/classnames'
  12. import type { ExternalKnowledgeBaseHitTestingResponse, HitTestingResponse } from '@/models/datasets'
  13. import { externalKnowledgeBaseHitTesting, hitTesting } from '@/service/datasets'
  14. import { asyncRunSafe } from '@/utils'
  15. import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
  16. type TextAreaWithButtonIProps = {
  17. datasetId: string
  18. onUpdateList: () => void
  19. setHitResult: (res: HitTestingResponse) => void
  20. setExternalHitResult: (res: ExternalKnowledgeBaseHitTestingResponse) => void
  21. loading: boolean
  22. setLoading: (v: boolean) => void
  23. text: string
  24. setText: (v: string) => void
  25. isExternal?: boolean
  26. onClickRetrievalMethod: () => void
  27. retrievalConfig: RetrievalConfig
  28. isEconomy: boolean
  29. onSubmit?: () => void
  30. }
  31. const TextAreaWithButton = ({
  32. datasetId,
  33. onUpdateList,
  34. setHitResult,
  35. setExternalHitResult,
  36. setLoading,
  37. loading,
  38. text,
  39. setText,
  40. isExternal = false,
  41. onClickRetrievalMethod,
  42. retrievalConfig,
  43. isEconomy,
  44. onSubmit: _onSubmit,
  45. }: TextAreaWithButtonIProps) => {
  46. const { t } = useTranslation()
  47. const [isSettingsOpen, setIsSettingsOpen] = useState(false)
  48. const [externalRetrievalSettings, setExternalRetrievalSettings] = useState({
  49. top_k: 2,
  50. score_threshold: 0.5,
  51. score_threshold_enabled: false,
  52. })
  53. const handleSaveExternalRetrievalSettings = (data: { top_k: number; score_threshold: number; score_threshold_enabled: boolean }) => {
  54. setExternalRetrievalSettings(data)
  55. setIsSettingsOpen(false)
  56. }
  57. function handleTextChange(event: any) {
  58. setText(event.target.value)
  59. }
  60. const onSubmit = async () => {
  61. setLoading(true)
  62. const [e, res] = await asyncRunSafe<HitTestingResponse>(
  63. hitTesting({
  64. datasetId,
  65. queryText: text,
  66. retrieval_model: {
  67. ...retrievalConfig,
  68. search_method: isEconomy ? RETRIEVE_METHOD.keywordSearch : retrievalConfig.search_method,
  69. },
  70. }) as Promise<HitTestingResponse>,
  71. )
  72. if (!e) {
  73. setHitResult(res)
  74. onUpdateList?.()
  75. }
  76. setLoading(false)
  77. _onSubmit && _onSubmit()
  78. }
  79. const externalRetrievalTestingOnSubmit = async () => {
  80. setLoading(true)
  81. const [e, res] = await asyncRunSafe<ExternalKnowledgeBaseHitTestingResponse>(
  82. externalKnowledgeBaseHitTesting({
  83. datasetId,
  84. query: text,
  85. external_retrieval_model: {
  86. top_k: externalRetrievalSettings.top_k,
  87. score_threshold: externalRetrievalSettings.score_threshold,
  88. score_threshold_enabled: externalRetrievalSettings.score_threshold_enabled,
  89. },
  90. }) as Promise<ExternalKnowledgeBaseHitTestingResponse>,
  91. )
  92. if (!e) {
  93. setExternalHitResult(res)
  94. onUpdateList?.()
  95. }
  96. setLoading(false)
  97. }
  98. const retrievalMethod = isEconomy ? RETRIEVE_METHOD.invertedIndex : retrievalConfig.search_method
  99. const icon = <Image className='size-3.5 text-util-colors-purple-purple-600' src={getIcon(retrievalMethod)} alt='' />
  100. return (
  101. <>
  102. <div className={cn('relative rounded-xl bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2 p-0.5 shadow-xs')}>
  103. <div className='relative rounded-t-xl bg-background-section-burn pt-1.5'>
  104. <div className="flex h-8 items-center justify-between pb-1 pl-4 pr-1.5">
  105. <span className="text-[13px] font-semibold uppercase leading-4 text-text-secondary">
  106. {t('datasetHitTesting.input.title')}
  107. </span>
  108. {isExternal
  109. ? <Button
  110. variant='secondary'
  111. size='small'
  112. onClick={() => setIsSettingsOpen(!isSettingsOpen)}
  113. >
  114. <RiEqualizer2Line className='h-3.5 w-3.5 text-components-button-secondary-text' />
  115. <div className='flex items-center justify-center gap-1 px-[3px]'>
  116. <span className='system-xs-medium text-components-button-secondary-text'>{t('datasetHitTesting.settingTitle')}</span>
  117. </div>
  118. </Button>
  119. : <div
  120. onClick={onClickRetrievalMethod}
  121. className='flex h-7 cursor-pointer items-center space-x-0.5 rounded-lg border-[0.5px] border-components-button-secondary-bg bg-components-button-secondary-bg px-1.5 shadow-xs backdrop-blur-[5px] hover:bg-components-button-secondary-bg-hover'
  122. >
  123. {icon}
  124. <div className='text-xs font-medium uppercase text-text-secondary'>{t(`dataset.retrieval.${retrievalMethod}.title`)}</div>
  125. <RiEqualizer2Line className='size-4 text-components-menu-item-text'></RiEqualizer2Line>
  126. </div>
  127. }
  128. </div>
  129. {
  130. isSettingsOpen && (
  131. <ModifyExternalRetrievalModal
  132. onClose={() => setIsSettingsOpen(false)}
  133. onSave={handleSaveExternalRetrievalSettings}
  134. initialTopK={externalRetrievalSettings.top_k}
  135. initialScoreThreshold={externalRetrievalSettings.score_threshold}
  136. initialScoreThresholdEnabled={externalRetrievalSettings.score_threshold_enabled}
  137. />
  138. )
  139. }
  140. <div className='h-2 rounded-t-xl bg-background-default'></div>
  141. </div>
  142. <div className='rounded-b-xl bg-background-default px-4 pb-11'>
  143. <textarea
  144. className='h-[220px] w-full resize-none border-none bg-transparent text-sm font-normal text-text-secondary caret-[#295EFF] placeholder:text-sm placeholder:font-normal placeholder:text-components-input-text-placeholder focus-visible:outline-none'
  145. value={text}
  146. onChange={handleTextChange}
  147. placeholder={t('datasetHitTesting.input.placeholder') as string}
  148. />
  149. <div className="absolute inset-x-0 bottom-0 mx-4 mb-2 mt-2 flex items-center justify-between">
  150. {text?.length > 200
  151. ? (
  152. <Tooltip
  153. popupContent={t('datasetHitTesting.input.countWarning')}
  154. >
  155. <div
  156. className={cn('flex h-5 items-center rounded-md bg-background-section-burn px-1 text-xs font-medium text-red-600', !text?.length && 'opacity-50')}
  157. >
  158. {text?.length}
  159. <span className="mx-0.5 text-red-300">/</span>
  160. 200
  161. </div>
  162. </Tooltip>
  163. )
  164. : (
  165. <div
  166. className={cn('flex h-5 items-center rounded-md bg-background-section-burn px-1 text-xs font-medium text-text-tertiary', !text?.length && 'opacity-50')}
  167. >
  168. {text?.length}
  169. <span className="mx-0.5 text-divider-deep">/</span>
  170. 200
  171. </div>
  172. )}
  173. <div>
  174. <Button
  175. onClick={isExternal ? externalRetrievalTestingOnSubmit : onSubmit}
  176. variant="primary"
  177. loading={loading}
  178. disabled={(!text?.length || text?.length > 200)}
  179. className='w-[88px]'
  180. >
  181. {t('datasetHitTesting.input.testing')}
  182. </Button>
  183. </div>
  184. </div>
  185. </div>
  186. </div>
  187. </>
  188. )
  189. }
  190. export default TextAreaWithButton