|
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 |
|
|