| import React, { useState } from 'react' | |
| import { useTranslation } from 'react-i18next' | |
| import { | |
| RiEqualizer2Line, | |
| } from '@remixicon/react' | |
| import Button from '../../base/button' | |
| import Tag from '../../base/tag' | |
| import { getIcon } from '../common/retrieval-method-info' | |
| import s from './style.module.css' | |
| 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: any) { | |
| 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 () => { | |
| 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 = getIcon(retrievalMethod) | |
| return ( | |
| <> | |
| <div className={s.wrapper}> | |
| <div className='relative pt-2 rounded-tl-xl rounded-tr-xl bg-[#EEF4FF]'> | |
| <div className="px-4 pb-2 flex justify-between h-8 items-center"> | |
| <span className="text-gray-800 font-semibold text-sm"> | |
| {t('datasetHitTesting.input.title')} | |
| </span> | |
| {isExternal | |
| ? <Button | |
| variant='secondary' | |
| size='small' | |
| onClick={() => setIsSettingsOpen(!isSettingsOpen)} | |
| > | |
| <RiEqualizer2Line className='text-components-button-secondary-text w-3.5 h-3.5' /> | |
| <div className='flex px-[3px] justify-center items-center gap-1'> | |
| <span className='text-components-button-secondary-text system-xs-medium'>{t('datasetHitTesting.settingTitle')}</span> | |
| </div> | |
| </Button> | |
| : <Tooltip | |
| popupContent={t('dataset.retrieval.changeRetrievalMethod')} | |
| > | |
| <div | |
| onClick={onClickRetrievalMethod} | |
| className='flex px-2 h-7 items-center space-x-1 bg-white hover:bg-[#ECE9FE] rounded-md shadow-sm cursor-pointer text-[#6927DA]' | |
| > | |
| <Icon className='w-3.5 h-3.5'></Icon> | |
| <div className='text-xs font-medium'>{t(`dataset.retrieval.${retrievalMethod}.title`)}</div> | |
| </div> | |
| </Tooltip> | |
| } | |
| </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-tl-xl rounded-tr-xl bg-white'></div> | |
| </div> | |
| <div className='px-4 pb-11'> | |
| <textarea | |
| className='h-[220px] border-none resize-none font-normal caret-primary-600 text-gray-700 text-sm w-full focus-visible:outline-none placeholder:text-gray-300 placeholder:text-sm placeholder:font-normal' | |
| value={text} | |
| onChange={handleTextChange} | |
| placeholder={t('datasetHitTesting.input.placeholder') as string} | |
| /> | |
| <div className="absolute inset-x-0 bottom-0 flex items-center justify-between mx-4 mt-2 mb-2"> | |
| {text?.length > 200 | |
| ? ( | |
| <Tooltip | |
| popupContent={t('datasetHitTesting.input.countWarning')} | |
| > | |
| <div> | |
| <Tag color="red" className="!text-red-600"> | |
| {text?.length} | |
| <span className="text-red-300 mx-0.5">/</span> | |
| 200 | |
| </Tag> | |
| </div> | |
| </Tooltip> | |
| ) | |
| : ( | |
| <Tag | |
| color="gray" | |
| className={cn('!text-gray-500', text?.length ? '' : 'opacity-50')} | |
| > | |
| {text?.length} | |
| <span className="text-gray-300 mx-0.5">/</span> | |
| 200 | |
| </Tag> | |
| )} | |
| <div> | |
| <Button | |
| onClick={isExternal ? externalRetrievalTestingOnSubmit : onSubmit} | |
| variant="primary" | |
| loading={loading} | |
| disabled={(!text?.length || text?.length > 200)} | |
| > | |
| {t('datasetHitTesting.input.testing')} | |
| </Button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </> | |
| ) | |
| } | |
| export default TextAreaWithButton | |