diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index e691cc05f6..a58027bcd1 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -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 = (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 = (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 => : undefined} iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} diff --git a/web/app/(commonLayout)/datasets/DatasetCard.tsx b/web/app/(commonLayout)/datasets/DatasetCard.tsx index 5542ddd0d6..e3014f08d0 100644 --- a/web/app/(commonLayout)/datasets/DatasetCard.tsx +++ b/web/app/(commonLayout)/datasets/DatasetCard.tsx @@ -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(dataset.tags) @@ -40,6 +41,7 @@ const DatasetCard = ({ const [showRenameModal, setShowRenameModal] = useState(false) const [showConfirmDelete, setShowConfirmDelete] = useState(false) const [confirmMessage, setConfirmMessage] = useState('') + 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' && } + {isExternalProvider(dataset.provider) && }
, } -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 (
{icon && icon_background && iconType === 'app' && ( @@ -83,6 +84,7 @@ export default function AppBasic({ icon, icon_background, name, type, hoverTip, }
{type}
+
{isExternal ? 'External' : ''}
}
) diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 5d5d407dc0..5ee063ad64 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -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} /> )} diff --git a/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx b/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx index c65b244f6d..f5512838ab 100644 --- a/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx +++ b/web/app/components/datasets/documents/detail/completed/SegmentCard.tsx @@ -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 @@ -48,6 +54,8 @@ type ISegmentCardProps = { const SegmentCard: FC = ({ detail = {}, + contentExternal, + refSource, score, onClick, onChangeSwitch, @@ -88,6 +96,9 @@ const SegmentCard: FC = ({ ) } + if (contentExternal) + return contentExternal + return content } @@ -201,8 +212,8 @@ const SegmentCard: FC = ({
{ + 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 + return } export default ExternalKnowledgeBaseConnector diff --git a/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx b/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx index b0a8566a23..42ddebdfa3 100644 --- a/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/KnowledgeBaseInfo.tsx @@ -16,7 +16,7 @@ const KnowledgeBaseInfo: React.FC = ({ name, description onChange({ name: e.target.value }) } - const handleDescriptionChange = (e: React.ChangeEvent) => { + const handleDescriptionChange = (e: React.ChangeEvent) => { onChange({ description: e.target.value }) } @@ -38,11 +38,11 @@ const KnowledgeBaseInfo: React.FC = ({ name, description
- 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' />
diff --git a/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx b/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx index 4b804caef2..56793bb2fb 100644 --- a/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/RetrievalSettings.tsx @@ -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 = ({ topK, scoreThreshold, onChange }) => { +const RetrievalSettings: FC = ({ topK, scoreThreshold, onChange, isInHitTesting = false, isInRetrievalSetting = false }) => { const [scoreThresholdEnabled, setScoreThresholdEnabled] = useState(false) const { t } = useTranslation() return ( -
-
+
+ {!isInHitTesting && !isInRetrievalSetting &&
-
-
+
} +
void + loading: boolean } -const ExternalKnowledgeBaseCreate: React.FC = ({ onConnect }) => { +const ExternalKnowledgeBaseCreate: React.FC = ({ onConnect, loading }) => { const { t } = useTranslation() const router = useRouter() const [formData, setFormData] = useState({ @@ -24,7 +25,7 @@ const ExternalKnowledgeBaseCreate: React.FC = 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 = 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 (
@@ -79,12 +80,12 @@ const ExternalKnowledgeBaseCreate: React.FC = })} /> 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 = onClick={() => { onConnect(formData) navBackHandle() - }} disabled={!isFormValid}> + }} + disabled={!isFormValid} + loading={loading} + >
{t('dataset.externalKnowledgeForm.connect')}
diff --git a/web/app/components/datasets/hit-testing/hit-detail.tsx b/web/app/components/datasets/hit-testing/hit-detail.tsx index 70e43176d9..a1c6b10e53 100644 --- a/web/app/components/datasets/hit-testing/hit-detail.tsx +++ b/web/app/components/datasets/hit-testing/hit-detail.tsx @@ -30,36 +30,40 @@ const HitDetail: FC = ({ segInfo }) => { } return ( -
-
-
- -
- - {segInfo?.word_count} {t('datasetDocuments.segment.characters')} - -
- - {segInfo?.hit_count} {t('datasetDocuments.segment.hitCount')} - -
- + segInfo?.id === 'external' + ?
{renderContent()}
-
- {t('datasetDocuments.segment.keywords')} -
-
- {!segInfo?.keywords?.length - ? '-' - : segInfo?.keywords?.map((word, index) => { - return
{word}
- })} +
+ :
+
+
+ +
+ + {segInfo?.word_count} {t('datasetDocuments.segment.characters')} + +
+ + {segInfo?.hit_count} {t('datasetDocuments.segment.hitCount')} + +
+ +
{renderContent()}
+
+ {t('datasetDocuments.segment.keywords')} +
+
+ {!segInfo?.keywords?.length + ? '-' + : segInfo?.keywords?.map((word, index) => { + return
{word}
+ })} +
-
) } diff --git a/web/app/components/datasets/hit-testing/index.tsx b/web/app/components/datasets/hit-testing/index.tsx index 505cd98fa7..cb345f4fc2 100644 --- a/web/app/components/datasets/hit-testing/index.tsx +++ b/web/app/components/datasets/hit-testing/index.tsx @@ -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 = ({ datasetId }: Props) => { const isMobile = media === MediaType.mobile const [hitResult, setHitResult] = useState() // 初始化记录为空数组 + const [externalHitResult, setExternalHitResult] = useState() 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(0) @@ -66,12 +68,50 @@ const HitTesting: FC = ({ 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) => ( + <> +
{t('datasetHitTesting.hit.title')}
+
+
+ {results.map((record, idx) => ( + onClickCard(record)} + /> + ))} +
+
+ + ) + + const renderEmptyState = () => ( +
+
+
+ {t('datasetHitTesting.hit.emptyTip')} +
+
+ ) + useEffect(() => { setShowRightPanel(!isMobile) }, [isMobile, setShowRightPanel]) @@ -86,12 +126,14 @@ const HitTesting: FC = ({ datasetId }: Props) => {