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