mirror of
				https://github.com/langgenius/dify.git
				synced 2025-10-31 19:03:09 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			201 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			201 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import type { ChangeEvent } from 'react'
 | |
| import React, { useState } from 'react'
 | |
| import { useTranslation } from 'react-i18next'
 | |
| import {
 | |
|   RiEqualizer2Line,
 | |
| } from '@remixicon/react'
 | |
| import Image from 'next/image'
 | |
| import Button from '../../base/button'
 | |
| import { getIcon } from '../common/retrieval-method-info'
 | |
| import ModifyExternalRetrievalModal from './modify-external-retrieval-modal'
 | |
| import Tooltip from '@/app/components/base/tooltip'
 | |
| import cn from '@/utils/classnames'
 | |
| import type { ExternalKnowledgeBaseHitTestingResponse, HitTestingResponse } from '@/models/datasets'
 | |
| import { externalKnowledgeBaseHitTesting, hitTesting } from '@/service/datasets'
 | |
| import { asyncRunSafe } from '@/utils'
 | |
| import { RETRIEVE_METHOD, type RetrievalConfig } from '@/types/app'
 | |
| 
 | |
| type TextAreaWithButtonIProps = {
 | |
|   datasetId: string
 | |
|   onUpdateList: () => void
 | |
|   setHitResult: (res: HitTestingResponse) => void
 | |
|   setExternalHitResult: (res: ExternalKnowledgeBaseHitTestingResponse) => void
 | |
|   loading: boolean
 | |
|   setLoading: (v: boolean) => void
 | |
|   text: string
 | |
|   setText: (v: string) => void
 | |
|   isExternal?: boolean
 | |
|   onClickRetrievalMethod: () => void
 | |
|   retrievalConfig: RetrievalConfig
 | |
|   isEconomy: boolean
 | |
|   onSubmit?: () => void
 | |
| }
 | |
| 
 | |
| const TextAreaWithButton = ({
 | |
|   datasetId,
 | |
|   onUpdateList,
 | |
|   setHitResult,
 | |
|   setExternalHitResult,
 | |
|   setLoading,
 | |
|   loading,
 | |
|   text,
 | |
|   setText,
 | |
|   isExternal = false,
 | |
|   onClickRetrievalMethod,
 | |
|   retrievalConfig,
 | |
|   isEconomy,
 | |
|   onSubmit: _onSubmit,
 | |
| }: TextAreaWithButtonIProps) => {
 | |
|   const { t } = useTranslation()
 | |
|   const [isSettingsOpen, setIsSettingsOpen] = useState(false)
 | |
|   const [externalRetrievalSettings, setExternalRetrievalSettings] = useState({
 | |
|     top_k: 2,
 | |
|     score_threshold: 0.5,
 | |
|     score_threshold_enabled: false,
 | |
|   })
 | |
| 
 | |
|   const handleSaveExternalRetrievalSettings = (data: { top_k: number; score_threshold: number; score_threshold_enabled: boolean }) => {
 | |
|     setExternalRetrievalSettings(data)
 | |
|     setIsSettingsOpen(false)
 | |
|   }
 | |
| 
 | |
|   function handleTextChange(event: ChangeEvent<HTMLTextAreaElement>) {
 | |
|     setText(event.target.value)
 | |
|   }
 | |
| 
 | |
|   const onSubmit = async () => {
 | |
|     setLoading(true)
 | |
|     const [e, res] = await asyncRunSafe<HitTestingResponse>(
 | |
|       hitTesting({
 | |
|         datasetId,
 | |
|         queryText: text,
 | |
|         retrieval_model: {
 | |
|           ...retrievalConfig,
 | |
|           search_method: isEconomy ? RETRIEVE_METHOD.keywordSearch : retrievalConfig.search_method,
 | |
|         },
 | |
|       }) as Promise<HitTestingResponse>,
 | |
|     )
 | |
|     if (!e) {
 | |
|       setHitResult(res)
 | |
|       onUpdateList?.()
 | |
|     }
 | |
|     setLoading(false)
 | |
|     _onSubmit && _onSubmit()
 | |
|   }
 | |
| 
 | |
|   const externalRetrievalTestingOnSubmit = async () => {
 | |
|     setLoading(true)
 | |
|     const [e, res] = await asyncRunSafe<ExternalKnowledgeBaseHitTestingResponse>(
 | |
|       externalKnowledgeBaseHitTesting({
 | |
|         datasetId,
 | |
|         query: text,
 | |
|         external_retrieval_model: {
 | |
|           top_k: externalRetrievalSettings.top_k,
 | |
|           score_threshold: externalRetrievalSettings.score_threshold,
 | |
|           score_threshold_enabled: externalRetrievalSettings.score_threshold_enabled,
 | |
|         },
 | |
|       }) as Promise<ExternalKnowledgeBaseHitTestingResponse>,
 | |
|     )
 | |
|     if (!e) {
 | |
|       setExternalHitResult(res)
 | |
|       onUpdateList?.()
 | |
|     }
 | |
|     setLoading(false)
 | |
|   }
 | |
| 
 | |
|   const retrievalMethod = isEconomy ? RETRIEVE_METHOD.invertedIndex : retrievalConfig.search_method
 | |
|   const icon = <Image className='size-3.5 text-util-colors-purple-purple-600' src={getIcon(retrievalMethod)} alt='' />
 | |
|   return (
 | |
|     <>
 | |
|       <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')}>
 | |
|         <div className='relative rounded-t-xl bg-background-section-burn pt-1.5'>
 | |
|           <div className="flex h-8 items-center justify-between pb-1 pl-4 pr-1.5">
 | |
|             <span className="text-[13px] font-semibold uppercase leading-4 text-text-secondary">
 | |
|               {t('datasetHitTesting.input.title')}
 | |
|             </span>
 | |
|             {isExternal
 | |
|               ? <Button
 | |
|                 variant='secondary'
 | |
|                 size='small'
 | |
|                 onClick={() => setIsSettingsOpen(!isSettingsOpen)}
 | |
|               >
 | |
|                 <RiEqualizer2Line className='h-3.5 w-3.5 text-components-button-secondary-text' />
 | |
|                 <div className='flex items-center justify-center gap-1 px-[3px]'>
 | |
|                   <span className='system-xs-medium text-components-button-secondary-text'>{t('datasetHitTesting.settingTitle')}</span>
 | |
|                 </div>
 | |
|               </Button>
 | |
|               : <div
 | |
|                 onClick={onClickRetrievalMethod}
 | |
|                 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'
 | |
|               >
 | |
|                 {icon}
 | |
|                 <div className='text-xs font-medium uppercase text-text-secondary'>{t(`dataset.retrieval.${retrievalMethod}.title`)}</div>
 | |
|                 <RiEqualizer2Line className='size-4 text-components-menu-item-text'></RiEqualizer2Line>
 | |
|               </div>
 | |
|             }
 | |
|           </div>
 | |
|           {
 | |
|             isSettingsOpen && (
 | |
|               <ModifyExternalRetrievalModal
 | |
|                 onClose={() => setIsSettingsOpen(false)}
 | |
|                 onSave={handleSaveExternalRetrievalSettings}
 | |
|                 initialTopK={externalRetrievalSettings.top_k}
 | |
|                 initialScoreThreshold={externalRetrievalSettings.score_threshold}
 | |
|                 initialScoreThresholdEnabled={externalRetrievalSettings.score_threshold_enabled}
 | |
|               />
 | |
|             )
 | |
|           }
 | |
|           <div className='h-2 rounded-t-xl bg-background-default'></div>
 | |
|         </div>
 | |
|         <div className='rounded-b-xl bg-background-default px-4 pb-11'>
 | |
|           <textarea
 | |
|             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'
 | |
|             value={text}
 | |
|             onChange={handleTextChange}
 | |
|             placeholder={t('datasetHitTesting.input.placeholder') as string}
 | |
|           />
 | |
|           <div className="absolute inset-x-0 bottom-0 mx-4 mb-2 mt-2 flex items-center justify-between">
 | |
|             {text?.length > 200
 | |
|               ? (
 | |
|                 <Tooltip
 | |
|                   popupContent={t('datasetHitTesting.input.countWarning')}
 | |
|                 >
 | |
|                   <div
 | |
|                     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')}
 | |
|                   >
 | |
|                     {text?.length}
 | |
|                     <span className="mx-0.5 text-red-300">/</span>
 | |
|                     200
 | |
|                   </div>
 | |
|                 </Tooltip>
 | |
|               )
 | |
|               : (
 | |
|                 <div
 | |
|                   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')}
 | |
|                 >
 | |
|                   {text?.length}
 | |
|                   <span className="mx-0.5 text-divider-deep">/</span>
 | |
|                   200
 | |
|                 </div>
 | |
|               )}
 | |
| 
 | |
|             <div>
 | |
|               <Button
 | |
|                 onClick={isExternal ? externalRetrievalTestingOnSubmit : onSubmit}
 | |
|                 variant="primary"
 | |
|                 loading={loading}
 | |
|                 disabled={(!text?.length || text?.length > 200)}
 | |
|                 className='w-[88px]'
 | |
|               >
 | |
|                 {t('datasetHitTesting.input.testing')}
 | |
|               </Button>
 | |
|             </div>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
|     </>
 | |
|   )
 | |
| }
 | |
| 
 | |
| export default TextAreaWithButton
 | 
