feat: external knowledge base
This commit is contained in:
parent
ff0260e564
commit
1c7cb3fbc0
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { FC, SVGProps } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -203,12 +203,23 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
datasetId,
|
||||
}, apiParams => fetchDatasetRelatedApps(apiParams.datasetId))
|
||||
|
||||
const navigation = [
|
||||
{ name: t('common.datasetMenus.documents'), href: `/datasets/${datasetId}/documents`, icon: DocumentTextIcon, selectedIcon: DocumentTextSolidIcon },
|
||||
{ name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
|
||||
// { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
|
||||
{ name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
|
||||
]
|
||||
const navigation = useMemo(() => {
|
||||
const baseNavigation = [
|
||||
{ name: t('common.datasetMenus.hitTesting'), href: `/datasets/${datasetId}/hitTesting`, icon: TargetIcon, selectedIcon: TargetSolidIcon },
|
||||
// { name: 'api & webhook', href: `/datasets/${datasetId}/api`, icon: CommandLineIcon, selectedIcon: CommandLineSolidIcon },
|
||||
{ name: t('common.datasetMenus.settings'), href: `/datasets/${datasetId}/settings`, icon: Cog8ToothIcon, selectedIcon: Cog8ToothSolidIcon },
|
||||
]
|
||||
|
||||
if (datasetRes?.provider !== 'external') {
|
||||
baseNavigation.unshift({
|
||||
name: t('common.datasetMenus.documents'),
|
||||
href: `/datasets/${datasetId}/documents`,
|
||||
icon: DocumentTextIcon,
|
||||
selectedIcon: DocumentTextSolidIcon,
|
||||
})
|
||||
}
|
||||
return baseNavigation
|
||||
}, [datasetRes?.provider, datasetId, t])
|
||||
|
||||
useEffect(() => {
|
||||
if (datasetRes)
|
||||
@ -233,6 +244,7 @@ const DatasetDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
|
||||
icon={datasetRes?.icon || 'https://static.dify.ai/images/dataset-default-icon.png'}
|
||||
icon_background={datasetRes?.icon_background || '#F5F5F5'}
|
||||
desc={datasetRes?.description || '--'}
|
||||
isExternal={datasetRes?.provider === 'external'}
|
||||
navigation={navigation}
|
||||
extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => <ExtraInfo isMobile={mode === 'collapse'} relatedApps={relatedApps} /> : undefined}
|
||||
iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'}
|
||||
|
@ -33,6 +33,7 @@ const DatasetCard = ({
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useContext(ToastContext)
|
||||
const { push } = useRouter()
|
||||
const EXTERNAL_PROVIDER = 'external' as const
|
||||
|
||||
const { isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const [tags, setTags] = useState<Tag[]>(dataset.tags)
|
||||
@ -40,6 +41,7 @@ const DatasetCard = ({
|
||||
const [showRenameModal, setShowRenameModal] = useState(false)
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [confirmMessage, setConfirmMessage] = useState<string>('')
|
||||
const isExternalProvider = (provider: string): boolean => provider === EXTERNAL_PROVIDER
|
||||
const detectIsUsedByApp = useCallback(async () => {
|
||||
try {
|
||||
const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id)
|
||||
@ -113,10 +115,12 @@ const DatasetCard = ({
|
||||
data-disable-nprogress={true}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
push(`/datasets/${dataset.id}/documents`)
|
||||
isExternalProvider(dataset.provider)
|
||||
? push(`/datasets/${dataset.id}/hitTesting`)
|
||||
: push(`/datasets/${dataset.id}/documents`)
|
||||
}}
|
||||
>
|
||||
{dataset.provider === 'external' && <CornerLabel label='External' className='absolute right-0' labelClassName='rounded-tr-xl' />}
|
||||
{isExternalProvider(dataset.provider) && <CornerLabel label='External' className='absolute right-0' labelClassName='rounded-tr-xl' />}
|
||||
<div className='flex pt-[14px] px-[14px] pb-3 h-[66px] items-center gap-3 grow-0 shrink-0'>
|
||||
<div className={cn(
|
||||
'shrink-0 flex items-center justify-center p-2.5 bg-[#F5F8FF] rounded-md border-[0.5px] border-[#E0EAFF]',
|
||||
|
@ -6,6 +6,7 @@ export type IAppBasicProps = {
|
||||
iconType?: 'app' | 'api' | 'dataset' | 'webapp' | 'notion'
|
||||
icon?: string
|
||||
icon_background?: string | null
|
||||
isExternal?: boolean
|
||||
name: string
|
||||
type: string | React.ReactNode
|
||||
hoverTip?: string
|
||||
@ -52,7 +53,7 @@ const ICON_MAP = {
|
||||
notion: <AppIcon innerIcon={NotionSvg} className='!border-[0.5px] !border-indigo-100 !bg-white' />,
|
||||
}
|
||||
|
||||
export default function AppBasic({ icon, icon_background, name, type, hoverTip, textStyle, mode = 'expand', iconType = 'app' }: IAppBasicProps) {
|
||||
export default function AppBasic({ icon, icon_background, name, isExternal, type, hoverTip, textStyle, mode = 'expand', iconType = 'app' }: IAppBasicProps) {
|
||||
return (
|
||||
<div className="flex items-start p-1">
|
||||
{icon && icon_background && iconType === 'app' && (
|
||||
@ -83,6 +84,7 @@ export default function AppBasic({ icon, icon_background, name, type, hoverTip,
|
||||
}
|
||||
</div>
|
||||
<div className={`text-xs font-normal text-gray-500 group-hover:text-gray-700 break-all ${textStyle?.extra ?? ''}`}>{type}</div>
|
||||
<div className='text-text-tertiary system-2xs-medium-uppercase'>{isExternal ? 'External' : ''}</div>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
|
@ -15,6 +15,7 @@ export type IAppDetailNavProps = {
|
||||
iconType?: 'app' | 'dataset' | 'notion'
|
||||
title: string
|
||||
desc: string
|
||||
isExternal?: boolean
|
||||
icon: string
|
||||
icon_background: string
|
||||
navigation: Array<{
|
||||
@ -26,7 +27,7 @@ export type IAppDetailNavProps = {
|
||||
extraInfo?: (modeState: string) => React.ReactNode
|
||||
}
|
||||
|
||||
const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
|
||||
const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigation, extraInfo, iconType = 'app' }: IAppDetailNavProps) => {
|
||||
const { appSidebarExpand, setAppSiderbarExpand } = useAppStore(useShallow(state => ({
|
||||
appSidebarExpand: state.appSidebarExpand,
|
||||
setAppSiderbarExpand: state.setAppSiderbarExpand,
|
||||
@ -70,6 +71,7 @@ const AppDetailNav = ({ title, desc, icon, icon_background, navigation, extraInf
|
||||
icon_background={icon_background}
|
||||
name={title}
|
||||
type={desc}
|
||||
isExternal={isExternal}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -36,6 +36,12 @@ export type UsageScene = 'doc' | 'hitTesting'
|
||||
type ISegmentCardProps = {
|
||||
loading: boolean
|
||||
detail?: SegmentDetailModel & { document: { name: string } }
|
||||
contentExternal?: string
|
||||
refSource?: {
|
||||
title: string
|
||||
uri: string
|
||||
}
|
||||
isExternal?: boolean
|
||||
score?: number
|
||||
onClick?: () => void
|
||||
onChangeSwitch?: (segId: string, enabled: boolean) => Promise<void>
|
||||
@ -48,6 +54,8 @@ type ISegmentCardProps = {
|
||||
|
||||
const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
detail = {},
|
||||
contentExternal,
|
||||
refSource,
|
||||
score,
|
||||
onClick,
|
||||
onChangeSwitch,
|
||||
@ -88,6 +96,9 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
if (contentExternal)
|
||||
return contentExternal
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
@ -201,8 +212,8 @@ const SegmentCard: FC<ISegmentCardProps> = ({
|
||||
<Divider />
|
||||
<div className="relative flex items-center w-full">
|
||||
<DocumentTitle
|
||||
name={detail?.document?.name || ''}
|
||||
extension={(detail?.document?.name || '').split('.').pop() || 'txt'}
|
||||
name={detail?.document?.name || refSource?.title || ''}
|
||||
extension={(detail?.document?.name || refSource?.title || '').split('.').pop() || 'txt'}
|
||||
wrapperCls='w-full'
|
||||
iconCls="!h-4 !w-4 !bg-contain"
|
||||
textCls="text-xs text-gray-700 !font-normal overflow-hidden whitespace-nowrap text-ellipsis"
|
||||
|
@ -1,20 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useToastContext } from '@/app/components/base/toast'
|
||||
import ExternalKnowledgeBaseCreate from '@/app/components/datasets/external-knowledge-base/create'
|
||||
import type { CreateKnowledgeBaseReq } from '@/app/components/datasets/external-knowledge-base/create/declarations'
|
||||
import { createExternalKnowledgeBase } from '@/service/datasets'
|
||||
|
||||
const ExternalKnowledgeBaseConnector = () => {
|
||||
const { notify } = useToastContext()
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const handleConnect = async (formValue: CreateKnowledgeBaseReq) => {
|
||||
try {
|
||||
setLoading(true)
|
||||
const result = await createExternalKnowledgeBase({ body: formValue })
|
||||
if (result && result.id)
|
||||
notify({ type: 'success', message: 'External Knowledge Base Connected Successfully' })
|
||||
else
|
||||
throw new Error('Failed to create external knowledge base')
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error creating external knowledge base:', error)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
return <ExternalKnowledgeBaseCreate onConnect={handleConnect} />
|
||||
return <ExternalKnowledgeBaseCreate onConnect={handleConnect} loading={loading} />
|
||||
}
|
||||
|
||||
export default ExternalKnowledgeBaseConnector
|
||||
|
@ -16,7 +16,7 @@ const KnowledgeBaseInfo: React.FC<KnowledgeBaseInfoProps> = ({ name, description
|
||||
onChange({ name: e.target.value })
|
||||
}
|
||||
|
||||
const handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
onChange({ description: e.target.value })
|
||||
}
|
||||
|
||||
@ -38,11 +38,11 @@ const KnowledgeBaseInfo: React.FC<KnowledgeBaseInfoProps> = ({ name, description
|
||||
<label className='text-text-secondary system-sm-semibold'>{t('dataset.externalKnowledgeDescription')}</label>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1 self-stretch'>
|
||||
<Input
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={handleDescriptionChange}
|
||||
onChange={ e => handleDescriptionChange(e)}
|
||||
placeholder={t('dataset.externalKnowledgeDescriptionPlaceholder') ?? ''}
|
||||
className='flex h-20 p-2 self-stretch items-start'
|
||||
className='flex h-20 p-2 self-stretch items-start rounded-lg bg-components-input-bg-normal text-components-input-text-placeholder system-sm-regular'
|
||||
/>
|
||||
<div className='flex py-0.5 gap-1 self-stretch'>
|
||||
<div className='flex p-0.5 items-center gap-2'>
|
||||
|
@ -3,22 +3,32 @@ import React, { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import TopKItem from '@/app/components/base/param-item/top-k-item'
|
||||
import ScoreThresholdItem from '@/app/components/base/param-item/score-threshold-item'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type RetrievalSettingsProps = {
|
||||
topK: number
|
||||
scoreThreshold: number
|
||||
isInHitTesting?: boolean
|
||||
isInRetrievalSetting?: boolean
|
||||
onChange: (data: { top_k?: number; score_threshold?: number }) => void
|
||||
}
|
||||
|
||||
const RetrievalSettings: FC<RetrievalSettingsProps> = ({ topK, scoreThreshold, onChange }) => {
|
||||
const RetrievalSettings: FC<RetrievalSettingsProps> = ({ topK, scoreThreshold, onChange, isInHitTesting = false, isInRetrievalSetting = false }) => {
|
||||
const [scoreThresholdEnabled, setScoreThresholdEnabled] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className='flex flex-col gap-2 self-stretch'>
|
||||
<div className='flex h-7 pt-1 flex-col gap-2 self-stretch'>
|
||||
<div className={cn('flex flex-col gap-2 self-stretch', isInRetrievalSetting && 'w-full max-w-[480px]')}>
|
||||
{!isInHitTesting && !isInRetrievalSetting && <div className='flex h-7 pt-1 flex-col gap-2 self-stretch'>
|
||||
<label className='text-text-secondary system-sm-semibold'>{t('dataset.retrievalSettings')}</label>
|
||||
</div>
|
||||
<div className='flex gap-4 self-stretch'>
|
||||
</div>}
|
||||
<div className={cn(
|
||||
'flex gap-4 self-stretch',
|
||||
{
|
||||
'flex-col': isInHitTesting,
|
||||
'flex-row': isInRetrievalSetting,
|
||||
'flex-col sm:flex-row': !isInHitTesting && !isInRetrievalSetting,
|
||||
},
|
||||
)}>
|
||||
<div className='flex flex-col gap-1 flex-grow'>
|
||||
<TopKItem
|
||||
className='grow'
|
||||
|
@ -4,7 +4,7 @@ export type CreateKnowledgeBaseReq = {
|
||||
external_knowledge_api_id: string
|
||||
provider: 'external'
|
||||
external_knowledge_id: string
|
||||
external_retrieval_modal: {
|
||||
external_retrieval_model: {
|
||||
top_k: number
|
||||
score_threshold: number
|
||||
}
|
||||
|
@ -14,9 +14,10 @@ import Button from '@/app/components/base/button'
|
||||
|
||||
type ExternalKnowledgeBaseCreateProps = {
|
||||
onConnect: (formValue: CreateKnowledgeBaseReq) => void
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = ({ onConnect }) => {
|
||||
const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> = ({ onConnect, loading }) => {
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
const [formData, setFormData] = useState<CreateKnowledgeBaseReq>({
|
||||
@ -24,7 +25,7 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
|
||||
description: '',
|
||||
external_knowledge_api_id: '',
|
||||
external_knowledge_id: '',
|
||||
external_retrieval_modal: {
|
||||
external_retrieval_model: {
|
||||
top_k: 2,
|
||||
score_threshold: 0.5,
|
||||
},
|
||||
@ -43,8 +44,8 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
|
||||
const isFormValid = formData.name !== ''
|
||||
&& formData.external_knowledge_api_id !== ''
|
||||
&& formData.external_knowledge_id !== ''
|
||||
&& formData.external_retrieval_modal.top_k !== undefined
|
||||
&& formData.external_retrieval_modal.score_threshold !== undefined
|
||||
&& formData.external_retrieval_model.top_k !== undefined
|
||||
&& formData.external_retrieval_model.score_threshold !== undefined
|
||||
|
||||
return (
|
||||
<div className='flex flex-col flex-grow self-stretch rounded-t-2xl border-t border-effects-highlight bg-components-panel-bg'>
|
||||
@ -79,12 +80,12 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
|
||||
})}
|
||||
/>
|
||||
<RetrievalSettings
|
||||
topK={formData.external_retrieval_modal.top_k}
|
||||
scoreThreshold={formData.external_retrieval_modal.score_threshold}
|
||||
topK={formData.external_retrieval_model.top_k}
|
||||
scoreThreshold={formData.external_retrieval_model.score_threshold}
|
||||
onChange={data => handleFormChange({
|
||||
...formData,
|
||||
external_retrieval_modal: {
|
||||
...formData.external_retrieval_modal,
|
||||
external_retrieval_model: {
|
||||
...formData.external_retrieval_model,
|
||||
...data,
|
||||
},
|
||||
})}
|
||||
@ -98,7 +99,10 @@ const ExternalKnowledgeBaseCreate: React.FC<ExternalKnowledgeBaseCreateProps> =
|
||||
onClick={() => {
|
||||
onConnect(formData)
|
||||
navBackHandle()
|
||||
}} disabled={!isFormValid}>
|
||||
}}
|
||||
disabled={!isFormValid}
|
||||
loading={loading}
|
||||
>
|
||||
<div className='text-components-button-primary-text system-sm-medium'>{t('dataset.externalKnowledgeForm.connect')}</div>
|
||||
<RiArrowRightLine className='w-4 h-4 text-components-button-primary-text' />
|
||||
</Button>
|
||||
|
@ -30,36 +30,40 @@ const HitDetail: FC<IHitDetailProps> = ({ segInfo }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='overflow-x-auto'>
|
||||
<div className="bg-gray-25 p-6">
|
||||
<div className="flex items-center">
|
||||
<SegmentIndexTag
|
||||
positionId={segInfo?.position || ''}
|
||||
className="w-fit mr-6"
|
||||
/>
|
||||
<div className={cn(s.commonIcon, s.typeSquareIcon)} />
|
||||
<span className={cn('mr-6', s.numberInfo)}>
|
||||
{segInfo?.word_count} {t('datasetDocuments.segment.characters')}
|
||||
</span>
|
||||
<div className={cn(s.commonIcon, s.targetIcon)} />
|
||||
<span className={s.numberInfo}>
|
||||
{segInfo?.hit_count} {t('datasetDocuments.segment.hitCount')}
|
||||
</span>
|
||||
</div>
|
||||
<Divider />
|
||||
segInfo?.id === 'external'
|
||||
? <div className='bg-gray-25 p-10'>
|
||||
<div className={s.segModalContent}>{renderContent()}</div>
|
||||
<div className={s.keywordTitle}>
|
||||
{t('datasetDocuments.segment.keywords')}
|
||||
</div>
|
||||
<div className={s.keywordWrapper}>
|
||||
{!segInfo?.keywords?.length
|
||||
? '-'
|
||||
: segInfo?.keywords?.map((word, index) => {
|
||||
return <div key={index} className={s.keyword}>{word}</div>
|
||||
})}
|
||||
</div>
|
||||
: <div className='overflow-x-auto'>
|
||||
<div className="bg-gray-25 p-6">
|
||||
<div className="flex items-center">
|
||||
<SegmentIndexTag
|
||||
positionId={segInfo?.position || ''}
|
||||
className="w-fit mr-6"
|
||||
/>
|
||||
<div className={cn(s.commonIcon, s.typeSquareIcon)} />
|
||||
<span className={cn('mr-6', s.numberInfo)}>
|
||||
{segInfo?.word_count} {t('datasetDocuments.segment.characters')}
|
||||
</span>
|
||||
<div className={cn(s.commonIcon, s.targetIcon)} />
|
||||
<span className={s.numberInfo}>
|
||||
{segInfo?.hit_count} {t('datasetDocuments.segment.hitCount')}
|
||||
</span>
|
||||
</div>
|
||||
<Divider />
|
||||
<div className={s.segModalContent}>{renderContent()}</div>
|
||||
<div className={s.keywordTitle}>
|
||||
{t('datasetDocuments.segment.keywords')}
|
||||
</div>
|
||||
<div className={s.keywordWrapper}>
|
||||
{!segInfo?.keywords?.length
|
||||
? '-'
|
||||
: segInfo?.keywords?.map((word, index) => {
|
||||
return <div key={index} className={s.keyword}>{word}</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import s from './style.module.css'
|
||||
import HitDetail from './hit-detail'
|
||||
import ModifyRetrievalModal from './modify-retrieval-modal'
|
||||
import cn from '@/utils/classnames'
|
||||
import type { HitTestingResponse, HitTesting as HitTestingType } from '@/models/datasets'
|
||||
import type { ExternalKnowledgeBaseHitTestingResponse, ExternalKnowledgeBaseHitTesting as ExternalKnowledgeBaseHitTestingType, HitTestingResponse, HitTesting as HitTestingType } from '@/models/datasets'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Drawer from '@/app/components/base/drawer'
|
||||
@ -49,8 +49,10 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
const isMobile = media === MediaType.mobile
|
||||
|
||||
const [hitResult, setHitResult] = useState<HitTestingResponse | undefined>() // 初始化记录为空数组
|
||||
const [externalHitResult, setExternalHitResult] = useState<ExternalKnowledgeBaseHitTestingResponse | undefined>()
|
||||
const [submitLoading, setSubmitLoading] = useState(false)
|
||||
const [currParagraph, setCurrParagraph] = useState<{ paraInfo?: HitTestingType; showModal: boolean }>({ showModal: false })
|
||||
const [externalCurrParagraph, setExternalCurrParagraph] = useState<{ paraInfo?: ExternalKnowledgeBaseHitTestingType; showModal: boolean }>({ showModal: false })
|
||||
const [text, setText] = useState('')
|
||||
|
||||
const [currPage, setCurrPage] = React.useState<number>(0)
|
||||
@ -66,12 +68,50 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
setCurrParagraph({ paraInfo: detail, showModal: true })
|
||||
}
|
||||
|
||||
const onClickExternalCard = (detail: ExternalKnowledgeBaseHitTestingType) => {
|
||||
setExternalCurrParagraph({ paraInfo: detail, showModal: true })
|
||||
}
|
||||
const { dataset: currentDataset } = useContext(DatasetDetailContext)
|
||||
|
||||
const [retrievalConfig, setRetrievalConfig] = useState(currentDataset?.retrieval_model_dict as RetrievalConfig)
|
||||
const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false)
|
||||
const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile)
|
||||
|
||||
const renderHitResults = (results: any[], onClickCard: (record: any) => void) => (
|
||||
<>
|
||||
<div className='text-gray-600 font-semibold mb-4'>{t('datasetHitTesting.hit.title')}</div>
|
||||
<div className='overflow-auto flex-1'>
|
||||
<div className={s.cardWrapper}>
|
||||
{results.map((record, idx) => (
|
||||
<SegmentCard
|
||||
key={idx}
|
||||
loading={false}
|
||||
refSource= {{
|
||||
title: record.title,
|
||||
uri: record.metadata ? record.metadata['x-amz-bedrock-kb-source-uri'] : '',
|
||||
}}
|
||||
detail={record.segment}
|
||||
contentExternal={record.content}
|
||||
score={record.score}
|
||||
scene='hitTesting'
|
||||
className='h-[216px] mb-4'
|
||||
onClick={() => onClickCard(record)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
const renderEmptyState = () => (
|
||||
<div className='h-full flex flex-col justify-center items-center'>
|
||||
<div className={cn(docStyle.commonIcon, docStyle.targetIcon, '!bg-gray-200 !h-14 !w-14')} />
|
||||
<div className='text-gray-300 text-[13px] mt-3'>
|
||||
{t('datasetHitTesting.hit.emptyTip')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setShowRightPanel(!isMobile)
|
||||
}, [isMobile, setShowRightPanel])
|
||||
@ -86,12 +126,14 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
<Textarea
|
||||
datasetId={datasetId}
|
||||
setHitResult={setHitResult}
|
||||
setExternalHitResult={setExternalHitResult}
|
||||
onSubmit={showRightPanel}
|
||||
onUpdateList={recordsMutate}
|
||||
loading={submitLoading}
|
||||
setLoading={setSubmitLoading}
|
||||
setText={setText}
|
||||
text={text}
|
||||
isExternal={currentDataset?.provider === 'external'}
|
||||
onClickRetrievalMethod={() => setIsShowModifyRetrievalModal(true)}
|
||||
retrievalConfig={retrievalConfig}
|
||||
isEconomy={currentDataset?.indexing_technique === 'economy'}
|
||||
@ -159,47 +201,42 @@ const HitTesting: FC<Props> = ({ datasetId }: Props) => {
|
||||
className='h-[216px]'
|
||||
/>
|
||||
</div>
|
||||
: !hitResult?.records.length
|
||||
? (
|
||||
<div className='h-full flex flex-col justify-center items-center'>
|
||||
<div className={cn(docStyle.commonIcon, docStyle.targetIcon, '!bg-gray-200 !h-14 !w-14')} />
|
||||
<div className='text-gray-300 text-[13px] mt-3'>
|
||||
{t('datasetHitTesting.hit.emptyTip')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<div className='text-gray-600 font-semibold mb-4'>{t('datasetHitTesting.hit.title')}</div>
|
||||
<div className='overflow-auto flex-1'>
|
||||
<div className={s.cardWrapper}>
|
||||
{hitResult?.records.map((record, idx) => {
|
||||
return <SegmentCard
|
||||
key={idx}
|
||||
loading={false}
|
||||
detail={record.segment as any}
|
||||
score={record.score}
|
||||
scene='hitTesting'
|
||||
className='h-[216px] mb-4'
|
||||
onClick={() => onClickCard(record as any)}
|
||||
/>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
: (
|
||||
(() => {
|
||||
if (!hitResult?.records.length && !externalHitResult?.records.length)
|
||||
return renderEmptyState()
|
||||
|
||||
if (hitResult?.records.length)
|
||||
return renderHitResults(hitResult.records, onClickCard)
|
||||
|
||||
return renderHitResults(externalHitResult?.records || [], onClickExternalCard)
|
||||
})()
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FloatRightContainer>
|
||||
<Modal
|
||||
className='w-[520px] p-0'
|
||||
className='w-[520px] px-8 py-6'
|
||||
closable
|
||||
onClose={() => setCurrParagraph({ showModal: false })}
|
||||
isShow={currParagraph.showModal}
|
||||
onClose={() => {
|
||||
setCurrParagraph({ showModal: false })
|
||||
setExternalCurrParagraph({ showModal: false })
|
||||
}}
|
||||
isShow={currParagraph.showModal || externalCurrParagraph.showModal}
|
||||
>
|
||||
{currParagraph.showModal && <HitDetail
|
||||
segInfo={currParagraph.paraInfo?.segment}
|
||||
/>}
|
||||
{currParagraph.showModal && (
|
||||
<HitDetail
|
||||
segInfo={currParagraph.paraInfo?.segment}
|
||||
/>
|
||||
)}
|
||||
{externalCurrParagraph.showModal && (
|
||||
<HitDetail
|
||||
segInfo={{
|
||||
id: 'external',
|
||||
content: externalCurrParagraph.paraInfo?.content,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
<Drawer isOpen={isShowModifyRetrievalModal} onClose={() => setIsShowModifyRetrievalModal(false)} footer={null} mask={isMobile} panelClassname='mt-16 mx-2 sm:mr-2 mb-3 !p-0 !max-w-[640px] rounded-xl'>
|
||||
<ModifyRetrievalModal
|
||||
|
@ -0,0 +1,65 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
RiCloseLine,
|
||||
} from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import RetrievalSettings from '../external-knowledge-base/create/RetrievalSettings'
|
||||
import Button from '@/app/components/base/button'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
|
||||
type ModifyExternalRetrievalModalProps = {
|
||||
onClose: () => void
|
||||
onSave: (data: { top_k: number; score_threshold: number }) => void
|
||||
initialTopK: number
|
||||
initialScoreThreshold: number
|
||||
}
|
||||
|
||||
const ModifyExternalRetrievalModal: React.FC<ModifyExternalRetrievalModalProps> = ({
|
||||
onClose,
|
||||
onSave,
|
||||
initialTopK,
|
||||
initialScoreThreshold,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [topK, setTopK] = useState(initialTopK)
|
||||
const [scoreThreshold, setScoreThreshold] = useState(initialScoreThreshold)
|
||||
|
||||
const handleSettingsChange = (data: { top_k?: number; score_threshold?: number }) => {
|
||||
if (data.top_k !== undefined)
|
||||
setTopK(data.top_k)
|
||||
if (data.score_threshold !== undefined)
|
||||
setScoreThreshold(data.score_threshold)
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
onSave({ top_k: topK, score_threshold: scoreThreshold })
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='absolute z-10 top-[36px] right-[14px] flex w-[320px] flex-col items-start rounded-2xl border-[0.5px]
|
||||
border-components-panel-border bg-components-panel-bg shadows-shadow-2xl'
|
||||
>
|
||||
<div className='flex p-4 pb-2 items-center justify-between self-stretch'>
|
||||
<div className='text-text-primary system-xl-semibold flex-grow'>{t('datasetHitTesting.settingTitle')}</div>
|
||||
<ActionButton className='ml-auto' onClick={onClose}>
|
||||
<RiCloseLine className='w-4 h-4 flex-shrink-0' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='flex p-4 pt-2 flex-col justify-center items-start gap-4 self-stretch'>
|
||||
<RetrievalSettings
|
||||
topK={topK}
|
||||
scoreThreshold={scoreThreshold}
|
||||
onChange={handleSettingsChange}
|
||||
isInHitTesting={true}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex p-4 pt-2 justify-end items-end gap-1 w-full'>
|
||||
<Button className='flex-shrink-0 min-w-[72px]' onClick={onClose}>{t('common.operation.cancel')}</Button>
|
||||
<Button variant='primary' className='flex-shrink-0 min-w-[72px]' onClick={handleSave}>{t('common.operation.save')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModifyExternalRetrievalModal
|
@ -1,12 +1,17 @@
|
||||
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 { HitTestingResponse } from '@/models/datasets'
|
||||
import { hitTesting } from '@/service/datasets'
|
||||
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'
|
||||
|
||||
@ -14,10 +19,12 @@ 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
|
||||
@ -28,16 +35,28 @@ 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,
|
||||
})
|
||||
|
||||
const handleSaveExternalRetrievalSettings = (data: { top_k: number; score_threshold: number }) => {
|
||||
setExternalRetrievalSettings(data)
|
||||
setIsSettingsOpen(false)
|
||||
}
|
||||
|
||||
function handleTextChange(event: any) {
|
||||
setText(event.target.value)
|
||||
@ -63,28 +82,70 @@ const TextAreaWithButton = ({
|
||||
_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,
|
||||
},
|
||||
}) as Promise<ExternalKnowledgeBaseHitTestingResponse>,
|
||||
)
|
||||
if (!e) {
|
||||
setExternalHitResult(res)
|
||||
onUpdateList?.()
|
||||
}
|
||||
setLoading(false)
|
||||
_onSubmit && _onSubmit()
|
||||
}
|
||||
|
||||
const retrievalMethod = isEconomy ? RETRIEVE_METHOD.invertedIndex : retrievalConfig.search_method
|
||||
const Icon = getIcon(retrievalMethod)
|
||||
return (
|
||||
<>
|
||||
<div className={s.wrapper}>
|
||||
<div className='pt-2 rounded-tl-xl rounded-tr-xl bg-[#EEF4FF]'>
|
||||
<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>
|
||||
<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]'
|
||||
{isExternal
|
||||
? <Button
|
||||
variant='secondary'
|
||||
size='small'
|
||||
onClick={() => setIsSettingsOpen(!isSettingsOpen)}
|
||||
>
|
||||
<Icon className='w-3.5 h-3.5'></Icon>
|
||||
<div className='text-xs font-medium'>{t(`dataset.retrieval.${retrievalMethod}.title`)}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<div className='h-2 rounded-tl-xl rounded-tr-xl bg-white'></div>
|
||||
</div>
|
||||
<div className='px-4 pb-11'>
|
||||
@ -122,7 +183,7 @@ const TextAreaWithButton = ({
|
||||
|
||||
<div>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
onClick={isExternal ? externalRetrievalTestingOnSubmit : onSubmit}
|
||||
variant="primary"
|
||||
loading={loading}
|
||||
disabled={(!text?.length || text?.length > 200)}
|
||||
@ -132,7 +193,6 @@ const TextAreaWithButton = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
@ -8,11 +8,14 @@ import { useSWRConfig } from 'swr'
|
||||
import { unstable_serialize } from 'swr/infinite'
|
||||
import PermissionSelector from '../permission-selector'
|
||||
import IndexMethodRadio from '../index-method-radio'
|
||||
import RetrievalSettings from '../../external-knowledge-base/create/RetrievalSettings'
|
||||
import cn from '@/utils/classnames'
|
||||
import RetrievalMethodConfig from '@/app/components/datasets/common/retrieval-method-config'
|
||||
import EconomicalRetrievalMethodConfig from '@/app/components/datasets/common/economical-retrieval-method-config'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import { ApiConnectionMod } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { updateDatasetSetting } from '@/service/datasets'
|
||||
import type { DataSetListResponse } from '@/models/datasets'
|
||||
import DatasetDetailContext from '@/context/dataset-detail'
|
||||
@ -55,6 +58,8 @@ const Form = () => {
|
||||
const [name, setName] = useState(currentDataset?.name ?? '')
|
||||
const [description, setDescription] = useState(currentDataset?.description ?? '')
|
||||
const [permission, setPermission] = useState(currentDataset?.permission)
|
||||
const [topK, setTopK] = useState(currentDataset?.external_retrieval_model.top_k ?? 2)
|
||||
const [scoreThreshold, setScoreThreshold] = useState(currentDataset?.external_retrieval_model.score_threshold ?? 0.5)
|
||||
const [selectedMemberIDs, setSelectedMemberIDs] = useState<string[]>(currentDataset?.partial_member_list || [])
|
||||
const [memberList, setMemberList] = useState<Member[]>([])
|
||||
const [indexMethod, setIndexMethod] = useState(currentDataset?.indexing_technique)
|
||||
@ -85,6 +90,13 @@ const Form = () => {
|
||||
setMemberList(accounts)
|
||||
}
|
||||
|
||||
const handleSettingsChange = (data: { top_k?: number; score_threshold?: number }) => {
|
||||
if (data.top_k !== undefined)
|
||||
setTopK(data.top_k)
|
||||
if (data.score_threshold !== undefined)
|
||||
setScoreThreshold(data.score_threshold)
|
||||
}
|
||||
|
||||
useMount(() => {
|
||||
getMembers()
|
||||
})
|
||||
@ -126,10 +138,16 @@ const Form = () => {
|
||||
description,
|
||||
permission,
|
||||
indexing_technique: indexMethod,
|
||||
external_retrieval_model: {
|
||||
top_k: topK,
|
||||
score_threshold: scoreThreshold,
|
||||
},
|
||||
retrieval_model: {
|
||||
...postRetrievalConfig,
|
||||
score_threshold: postRetrievalConfig.score_threshold_enabled ? postRetrievalConfig.score_threshold : 0,
|
||||
},
|
||||
external_knowledge_id: currentDataset!.external_knowledge_info.external_knowledge_id,
|
||||
external_knowledge_api_id: currentDataset!.external_knowledge_info.external_knowledge_api_id,
|
||||
embedding_model: embeddingModel.model,
|
||||
embedding_model_provider: embeddingModel.provider,
|
||||
},
|
||||
@ -161,7 +179,7 @@ const Form = () => {
|
||||
<div className='w-full sm:w-[800px] p-4 sm:px-16 sm:py-6'>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.name')}</div>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.name')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<input
|
||||
@ -174,7 +192,7 @@ const Form = () => {
|
||||
</div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.desc')}</div>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.desc')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<textarea
|
||||
@ -192,7 +210,7 @@ const Form = () => {
|
||||
</div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.permissions')}</div>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.permissions')}</div>
|
||||
</div>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<PermissionSelector
|
||||
@ -210,7 +228,7 @@ const Form = () => {
|
||||
<div className='w-full h-0 border-b-[0.5px] border-b-gray-200 my-2' />
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.indexMethod')}</div>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.indexMethod')}</div>
|
||||
</div>
|
||||
<div className='w-full sm:w-[480px]'>
|
||||
<IndexMethodRadio
|
||||
@ -225,7 +243,7 @@ const Form = () => {
|
||||
{indexMethod === 'high_quality' && (
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>{t('datasetSettings.form.embeddingModel')}</div>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.embeddingModel')}</div>
|
||||
</div>
|
||||
<div className='w-[480px]'>
|
||||
<ModelSelector
|
||||
@ -240,32 +258,75 @@ const Form = () => {
|
||||
</div>
|
||||
)}
|
||||
{/* Retrieval Method Config */}
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>
|
||||
<div>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
<div className='leading-[18px] text-xs font-normal text-gray-500'>
|
||||
<a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-[#155eef]'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
|
||||
{t('datasetSettings.form.retrievalSetting.description')}
|
||||
{currentDataset?.provider === 'external'
|
||||
? <>
|
||||
<div className={rowClass}><Divider/></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
</div>
|
||||
<RetrievalSettings
|
||||
topK={topK}
|
||||
scoreThreshold={scoreThreshold}
|
||||
onChange={handleSettingsChange}
|
||||
isInRetrievalSetting={true}
|
||||
/>
|
||||
</div>
|
||||
<div className={rowClass}><Divider/></div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.externalKnowledgeAPI')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<div className='flex h-full px-3 py-2 items-center gap-1 rounded-lg bg-components-input-bg-normal'>
|
||||
<ApiConnectionMod className='w-4 h-4 text-text-secondary' />
|
||||
<div className='overflow-hidden text-text-secondary text-ellipsis system-sm-medium'>
|
||||
{currentDataset?.external_knowledge_info.external_knowledge_api_name}
|
||||
</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>·</div>
|
||||
<div className='text-text-tertiary system-xs-regular'>{currentDataset?.external_knowledge_info.external_knowledge_api_endpoint}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.externalKnowledgeID')}</div>
|
||||
</div>
|
||||
<div className='w-full max-w-[480px]'>
|
||||
<div className='flex h-full px-3 py-2 items-center gap-1 rounded-lg bg-components-input-bg-normal'>
|
||||
<div className='text-text-tertiary system-xs-regular'>{currentDataset?.external_knowledge_info.external_knowledge_id}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={rowClass}><Divider/></div>
|
||||
</>
|
||||
: <div className={rowClass}>
|
||||
<div className={labelClass}>
|
||||
<div>
|
||||
<div className='text-text-secondary system-sm-semibold'>{t('datasetSettings.form.retrievalSetting.title')}</div>
|
||||
<div className='leading-[18px] text-xs font-normal text-gray-500'>
|
||||
<a target='_blank' rel='noopener noreferrer' href='https://docs.dify.ai/guides/knowledge-base/create-knowledge-and-upload-documents#id-4-retrieval-settings' className='text-[#155eef]'>{t('datasetSettings.form.retrievalSetting.learnMore')}</a>
|
||||
{t('datasetSettings.form.retrievalSetting.description')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-[480px]'>
|
||||
{indexMethod === 'high_quality'
|
||||
? (
|
||||
<RetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={setRetrievalConfig}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<EconomicalRetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={setRetrievalConfig}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-[480px]'>
|
||||
{indexMethod === 'high_quality'
|
||||
? (
|
||||
<RetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={setRetrievalConfig}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<EconomicalRetrievalMethodConfig
|
||||
value={retrievalConfig}
|
||||
onChange={setRetrievalConfig}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className={rowClass}>
|
||||
<div className={labelClass} />
|
||||
<div className='w-[480px]'>
|
||||
|
@ -51,7 +51,7 @@ const DatasetNav = () => {
|
||||
navs={datasetItems.map(dataset => ({
|
||||
id: dataset.id,
|
||||
name: dataset.name,
|
||||
link: `/datasets/${dataset.id}/documents`,
|
||||
link: dataset.provider === 'external' ? `/datasets/${dataset.id}/hitTesting` : `/datasets/${dataset.id}/documents`,
|
||||
icon: dataset.icon,
|
||||
icon_background: dataset.icon_background,
|
||||
})) as NavItem[]}
|
||||
|
@ -1,6 +1,7 @@
|
||||
const translation = {
|
||||
title: 'Retrieval Testing',
|
||||
desc: 'Test the hitting effect of the Knowledge based on the given query text.',
|
||||
settingTitle: 'Retrieval Setting',
|
||||
desc: 'Test the hitting effect of the Knowledge based on the given query text',
|
||||
dateTimeFormat: 'MM/DD/YYYY hh:mm A',
|
||||
recents: 'Recents',
|
||||
table: {
|
||||
|
@ -1,13 +1,13 @@
|
||||
const translation = {
|
||||
title: 'Knowledge settings',
|
||||
desc: 'Here you can modify the properties and working methods of the Knowledge.',
|
||||
desc: 'Here you can modify the properties and retrieval settings of this Knowledge.',
|
||||
form: {
|
||||
name: 'Knowledge Name',
|
||||
namePlaceholder: 'Please enter the Knowledge name',
|
||||
nameError: 'Name cannot be empty',
|
||||
desc: 'Knowledge description',
|
||||
desc: 'Knowledge Description',
|
||||
descInfo: 'Please write a clear textual description to outline the content of the Knowledge. This description will be used as a basis for matching when selecting from multiple Knowledge for inference.',
|
||||
descPlaceholder: 'Describe what is in this Knowledge. A detailed description allows AI to access the content of the Knowledge in a timely manner. If empty, Dify will use the default hit strategy.',
|
||||
descPlaceholder: 'Describe what\'s in this Knowledge (optional)',
|
||||
descWrite: 'Learn how to write a good Knowledge description.',
|
||||
permissions: 'Permissions',
|
||||
permissionsOnlyMe: 'Only me',
|
||||
@ -23,11 +23,14 @@ const translation = {
|
||||
embeddingModelTip: 'Change the embedded model, please go to ',
|
||||
embeddingModelTipLink: 'Settings',
|
||||
retrievalSetting: {
|
||||
title: 'Retrieval setting',
|
||||
title: 'Retrieval Setting',
|
||||
learnMore: 'Learn more',
|
||||
description: ' about retrieval method.',
|
||||
longDescription: ' about retrieval method, you can change this at any time in the Knowledge settings.',
|
||||
},
|
||||
externalKnowledgeAPI: 'External Knowledge API',
|
||||
externalKnowledgeID: 'External Knowledge ID',
|
||||
retrievalSettings: 'Retrieval Settings',
|
||||
save: 'Save',
|
||||
},
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
const translation = {
|
||||
title: '召回测试',
|
||||
desc: '基于给定的查询文本测试知识库的召回效果。',
|
||||
settingTitle: '召回设置',
|
||||
desc: '基于给定的查询文本测试知识库的召回效果',
|
||||
dateTimeFormat: 'YYYY-MM-DD HH:mm',
|
||||
recents: '最近查询',
|
||||
table: {
|
||||
|
@ -1,13 +1,13 @@
|
||||
const translation = {
|
||||
title: '知识库设置',
|
||||
desc: '在这里您可以修改知识库的工作方式以及其它设置。',
|
||||
desc: '在这里,您可以修改此知识库的属性和检索设置',
|
||||
form: {
|
||||
name: '知识库名称',
|
||||
namePlaceholder: '请输入知识库名称',
|
||||
nameError: '名称不能为空',
|
||||
desc: '知识库描述',
|
||||
descInfo: '请写出清楚的文字描述来概述知识库的内容。当从多个知识库中进行选择匹配时,该描述将用作匹配的基础。',
|
||||
descPlaceholder: '描述这个知识库中的内容。详细的描述可以让 AI 及时访问知识库的内容。如果为空,Dify 将使用默认的命中策略。',
|
||||
descPlaceholder: '请描述这个知识库包含的内容(可选)',
|
||||
descWrite: '了解如何编写更好的知识库描述。',
|
||||
permissions: '可见权限',
|
||||
permissionsOnlyMe: '只有我',
|
||||
@ -28,6 +28,8 @@ const translation = {
|
||||
description: '关于检索方法。',
|
||||
longDescription: '关于检索方法,您可以随时在知识库设置中更改此设置。',
|
||||
},
|
||||
externalKnowledgeAPI: '外部知识 API',
|
||||
externalKnowledgeID: '外部知识库 ID',
|
||||
save: '保存',
|
||||
},
|
||||
}
|
||||
|
@ -33,6 +33,16 @@ export type DataSet = {
|
||||
retrieval_model: RetrievalConfig
|
||||
tags: Tag[]
|
||||
partial_member_list?: any[]
|
||||
external_knowledge_info: {
|
||||
external_knowledge_id: string
|
||||
external_knowledge_api_id: string
|
||||
external_knowledge_api_name: string
|
||||
external_knowledge_api_endpoint: string
|
||||
}
|
||||
external_retrieval_model: {
|
||||
top_k: number
|
||||
score_threshold: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ExternalAPIItem = {
|
||||
@ -434,6 +444,16 @@ export type HitTesting = {
|
||||
tsne_position: TsnePosition
|
||||
}
|
||||
|
||||
export type ExternalKnowledgeBaseHitTesting = {
|
||||
content: string
|
||||
title: string
|
||||
score: number
|
||||
metadata: {
|
||||
'x-amz-bedrock-kb-source-uri': string
|
||||
'x-amz-bedrock-kb-data-source-id': string
|
||||
}
|
||||
}
|
||||
|
||||
export type Segment = {
|
||||
id: string
|
||||
document: Document
|
||||
@ -474,6 +494,13 @@ export type HitTestingResponse = {
|
||||
records: Array<HitTesting>
|
||||
}
|
||||
|
||||
export type ExternalKnowledgeBaseHitTestingResponse = {
|
||||
query: {
|
||||
content: string
|
||||
}
|
||||
records: Array<ExternalKnowledgeBaseHitTesting>
|
||||
}
|
||||
|
||||
export type RelatedApp = {
|
||||
id: string
|
||||
name: string
|
||||
|
@ -12,6 +12,7 @@ import type {
|
||||
ExternalAPIItem,
|
||||
ExternalAPIListResponse,
|
||||
ExternalAPIUsage,
|
||||
ExternalKnowledgeBaseHitTestingResponse,
|
||||
ExternalKnowledgeItem,
|
||||
FileIndexingEstimateResponse,
|
||||
HitTestingRecordsResponse,
|
||||
@ -244,6 +245,10 @@ export const hitTesting: Fetcher<HitTestingResponse, { datasetId: string; queryT
|
||||
return post<HitTestingResponse>(`/datasets/${datasetId}/hit-testing`, { body: { query: queryText, retrieval_model } })
|
||||
}
|
||||
|
||||
export const externalKnowledgeBaseHitTesting: Fetcher<ExternalKnowledgeBaseHitTestingResponse, { datasetId: string; query: string; external_retrieval_model: { top_k: number; score_threshold: number } }> = ({ datasetId, query, external_retrieval_model }) => {
|
||||
return post<ExternalKnowledgeBaseHitTestingResponse>(`/datasets/${datasetId}/external-hit-testing`, { body: { query, external_retrieval_model } })
|
||||
}
|
||||
|
||||
export const fetchTestingRecords: Fetcher<HitTestingRecordsResponse, { datasetId: string; params: { page: number; limit: number } }> = ({ datasetId, params }) => {
|
||||
return get<HitTestingRecordsResponse>(`/datasets/${datasetId}/queries`, { params })
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user