diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index a58027bcd1..1b327185e5 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -7,85 +7,36 @@ import { useTranslation } from 'react-i18next' import { useBoolean } from 'ahooks' import { Cog8ToothIcon, - // CommandLineIcon, - Squares2X2Icon, - // eslint-disable-next-line sort-imports - PuzzlePieceIcon, DocumentTextIcon, PaperClipIcon, - QuestionMarkCircleIcon, } from '@heroicons/react/24/outline' import { Cog8ToothIcon as Cog8ToothSolidIcon, // CommandLineIcon as CommandLineSolidIcon, DocumentTextIcon as DocumentTextSolidIcon, } from '@heroicons/react/24/solid' -import Link from 'next/link' +import { RiApps2AddLine, RiInformation2Line } from '@remixicon/react' import s from './style.module.css' import classNames from '@/utils/classnames' import { fetchDatasetDetail, fetchDatasetRelatedApps } from '@/service/datasets' -import type { RelatedApp, RelatedAppResponse } from '@/models/datasets' +import type { RelatedAppResponse } from '@/models/datasets' import AppSideBar from '@/app/components/app-sidebar' -import Divider from '@/app/components/base/divider' -import AppIcon from '@/app/components/base/app-icon' import Loading from '@/app/components/base/loading' -import FloatPopoverContainer from '@/app/components/base/float-popover-container' import DatasetDetailContext from '@/context/dataset-detail' import { DataSourceType } from '@/models/datasets' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { LanguagesSupported } from '@/i18n/language' import { useStore } from '@/app/components/app/store' -import { AiText, ChatBot, CuteRobot } from '@/app/components/base/icons/src/vender/solid/communication' -import { Route } from '@/app/components/base/icons/src/vender/solid/mapsAndTravel' import { getLocaleOnClient } from '@/i18n' import { useAppContext } from '@/context/app-context' +import Tooltip from '@/app/components/base/tooltip' +import LinkedAppsPanel from '@/app/components/base/linked-apps-panel' export type IAppDetailLayoutProps = { children: React.ReactNode params: { datasetId: string } } -type ILikedItemProps = { - type?: 'plugin' | 'app' - appStatus?: boolean - detail: RelatedApp - isMobile: boolean -} - -const LikedItem = ({ - type = 'app', - detail, - isMobile, -}: ILikedItemProps) => { - return ( - -
- - {type === 'app' && ( - - {detail.mode === 'advanced-chat' && ( - - )} - {detail.mode === 'agent-chat' && ( - - )} - {detail.mode === 'chat' && ( - - )} - {detail.mode === 'completion' && ( - - )} - {detail.mode === 'workflow' && ( - - )} - - )} -
- {!isMobile &&
{detail?.name || '--'}
} - - ) -} - const TargetIcon = ({ className }: SVGProps) => { return @@ -117,65 +68,80 @@ const BookOpenIcon = ({ className }: SVGProps) => { type IExtraInfoProps = { isMobile: boolean relatedApps?: RelatedAppResponse + expand: boolean } -const ExtraInfo = ({ isMobile, relatedApps }: IExtraInfoProps) => { +const ExtraInfo = ({ isMobile, relatedApps, expand }: IExtraInfoProps) => { const locale = getLocaleOnClient() const [isShowTips, { toggle: toggleTips, set: setShowTips }] = useBoolean(!isMobile) const { t } = useTranslation() + const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 + const relatedAppsTotal = relatedApps?.data?.length || 0 + useEffect(() => { setShowTips(!isMobile) }, [isMobile, setShowTips]) - return
- - {(relatedApps?.data && relatedApps?.data?.length > 0) && ( + return
+ {hasRelatedApps && ( <> - {!isMobile &&
{relatedApps?.total || '--'} {t('common.datasetMenus.relatedApp')}
} + {!isMobile && ( + + } + > +
+ {relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')} + +
+
+ )} + {isMobile &&
- {relatedApps?.total || '--'} + {relatedAppsTotal || '--'}
} - {relatedApps?.data?.map((item, index) => ())} )} - {!relatedApps?.data?.length && ( - - + {!hasRelatedApps && !expand && ( + +
+ +
+
{t('common.datasetMenus.emptyTip')}
+ + + {t('common.datasetMenus.viewDoc')} +
} > -
-
-
- -
-
- -
-
-
{t('common.datasetMenus.emptyTip')}
- - - {t('common.datasetMenus.viewDoc')} - +
+ {t('common.datasetMenus.noRelatedApp')} +
- + )}
} @@ -246,7 +212,7 @@ const DatasetDetailLayout: FC = (props) => { desc={datasetRes?.description || '--'} isExternal={datasetRes?.provider === 'external'} navigation={navigation} - extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => : undefined} + extraInfo={!isCurrentWorkspaceDatasetOperator ? mode => : undefined} iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} />} + + + +type Props = { + isExternal?: boolean + name: string + description: string + expand: boolean + extraInfo?: React.ReactNode +} + +const DatasetInfo: FC = ({ + name, + description, + isExternal, + expand, + extraInfo, +}) => { + const { t } = useTranslation() + return ( +
+
+ +
+ {expand && ( +
+
+ {name} +
+
{isExternal ? t('dataset.externalTag') : t('dataset.localDocs')}
+
{description}
+
+ )} + {extraInfo} +
+ ) +} +export default React.memo(DatasetInfo) diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index 5ee063ad64..dc5bf5b17a 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -4,12 +4,14 @@ import NavLink from './navLink' import type { NavIcon } from './navLink' import AppBasic from './basic' import AppInfo from './app-info' +import DatasetInfo from './dataset-info' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { AlignLeft01, AlignRight01, } from '@/app/components/base/icons/src/vender/line/layout' import { useStore as useAppStore } from '@/app/components/app/store' +import cn from '@/utils/classnames' export type IAppDetailNavProps = { iconType?: 'app' | 'dataset' | 'notion' @@ -63,7 +65,16 @@ const AppDetailNav = ({ title, desc, isExternal, icon, icon_background, navigati {iconType === 'app' && ( )} - {iconType !== 'app' && ( + {iconType === 'dataset' && ( + + )} + {!['app', 'dataset'].includes(iconType) && ( )}
- {!expand && ( -
- )} +
+
+
{ !isMobile && ( diff --git a/web/app/components/base/linked-apps-panel/index.tsx b/web/app/components/base/linked-apps-panel/index.tsx new file mode 100644 index 0000000000..4320cb0fc6 --- /dev/null +++ b/web/app/components/base/linked-apps-panel/index.tsx @@ -0,0 +1,62 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import Link from 'next/link' +import { useTranslation } from 'react-i18next' +import { RiArrowRightUpLine } from '@remixicon/react' +import cn from '@/utils/classnames' +import AppIcon from '@/app/components/base/app-icon' +import type { RelatedApp } from '@/models/datasets' + +type ILikedItemProps = { + appStatus?: boolean + detail: RelatedApp + isMobile: boolean +} + +const appTypeMap = { + 'chat': 'Chatbot', + 'completion': 'Completion', + 'agent-chat': 'Agent', + 'advanced-chat': 'Chatflow', + 'workflow': 'Workflow', +} + +const LikedItem = ({ + detail, + isMobile, +}: ILikedItemProps) => { + return ( + +
+
+ +
+ {!isMobile &&
{detail?.name || '--'}
} +
+
{appTypeMap[detail.mode]}
+ + + ) +} + +type Props = { + relatedApps: RelatedApp[] + isMobile: boolean +} + +const LinkedAppsPanel: FC = ({ + relatedApps, + isMobile, +}) => { + const { t } = useTranslation() + return ( +
+
{relatedApps.length || '--'} {t('common.datasetMenus.relatedApp')}
+ {relatedApps.map((item, index) => ( + + ))} +
+ ) +} +export default React.memo(LinkedAppsPanel) diff --git a/web/app/components/base/tooltip/index.tsx b/web/app/components/base/tooltip/index.tsx index e5f27fdd8c..de0a9aacfa 100644 --- a/web/app/components/base/tooltip/index.tsx +++ b/web/app/components/base/tooltip/index.tsx @@ -14,6 +14,7 @@ export type TooltipProps = { popupContent?: React.ReactNode children?: React.ReactNode popupClassName?: string + noDecoration?: boolean offset?: OffsetOptions needsDelay?: boolean asChild?: boolean @@ -27,6 +28,7 @@ const Tooltip: FC = ({ popupContent, children, popupClassName, + noDecoration, offset, asChild = true, needsDelay = false, @@ -96,7 +98,7 @@ const Tooltip: FC = ({ > {popupContent && (
triggerMethod === 'hover' && setHoverPopup()} diff --git a/web/app/components/datasets/common/retrieval-method-config/index.tsx b/web/app/components/datasets/common/retrieval-method-config/index.tsx index 6509ed995c..986135e3c6 100644 --- a/web/app/components/datasets/common/retrieval-method-config/index.tsx +++ b/web/app/components/datasets/common/retrieval-method-config/index.tsx @@ -64,7 +64,7 @@ const RetrievalMethodConfig: FC = ({ isActive={ value.search_method === RETRIEVE_METHOD.semantic } - onClick={() => onChange({ + onSwitched={() => onChange({ ...value, search_method: RETRIEVE_METHOD.semantic, })} @@ -85,7 +85,7 @@ const RetrievalMethodConfig: FC = ({ isActive={ value.search_method === RETRIEVE_METHOD.fullText } - onClick={() => onChange({ + onSwitched={() => onChange({ ...value, search_method: RETRIEVE_METHOD.fullText, })} @@ -104,7 +104,7 @@ const RetrievalMethodConfig: FC = ({ title={
{t('dataset.retrieval.hybrid_search.title')}
- +
} description={t('dataset.retrieval.hybrid_search.description')} isActive={ diff --git a/web/app/components/datasets/settings/form/index.tsx b/web/app/components/datasets/settings/form/index.tsx index 7ce31986c1..22b52bb8f2 100644 --- a/web/app/components/datasets/settings/form/index.tsx +++ b/web/app/components/datasets/settings/form/index.tsx @@ -17,7 +17,7 @@ import Textarea from '@/app/components/base/textarea' 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 { type DataSetListResponse } from '@/models/datasets' import DatasetDetailContext from '@/context/dataset-detail' import { type RetrievalConfig } from '@/types/app' import { useAppContext } from '@/context/app-context' @@ -234,6 +234,7 @@ const Form = () => { disable={!currentDataset?.embedding_available} value={indexMethod} onChange={v => setIndexMethod(v)} + docForm={currentDataset.doc_form} />
diff --git a/web/app/components/datasets/settings/index-method-radio/index.tsx b/web/app/components/datasets/settings/index-method-radio/index.tsx index 2bf6f36ce1..e563b96256 100644 --- a/web/app/components/datasets/settings/index-method-radio/index.tsx +++ b/web/app/components/datasets/settings/index-method-radio/index.tsx @@ -1,8 +1,10 @@ 'use client' import { useTranslation } from 'react-i18next' +import { IndexingType } from '../../create/step-two' import s from './index.module.css' import classNames from '@/utils/classnames' import type { DataSet } from '@/models/datasets' +import { ChuckingMode } from '@/models/datasets' const itemClass = ` w-full sm:w-[234px] p-3 rounded-xl bg-gray-25 border border-gray-100 cursor-pointer @@ -15,6 +17,7 @@ type IIndexMethodRadioProps = { onChange: (v?: DataSet['indexing_technique']) => void disable?: boolean itemClassName?: string + docForm?: ChuckingMode } const IndexMethodRadio = ({ @@ -22,6 +25,7 @@ const IndexMethodRadio = ({ onChange, disable, itemClassName, + docForm, }: IIndexMethodRadioProps) => { const { t } = useTranslation() const options = [ @@ -42,29 +46,35 @@ const IndexMethodRadio = ({ return (
{ - options.map(option => ( -
{ - if (!disable) - onChange(option.key as DataSet['indexing_technique']) - }} - > -
-
-
{option.text}
-
+ options.map((option) => { + const isParentChild = docForm === ChuckingMode.parentChild + return ( +
{ + if (isParentChild && option.key === IndexingType.ECONOMICAL) + return + if (!disable) + onChange(option.key as DataSet['indexing_technique']) + }} + > +
+
+
{option.text}
+
+
+
{option.desc}
-
{option.desc}
-
- )) + ) + }) }
) diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index ce341a9148..6b60a7a5cc 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -474,9 +474,10 @@ const translation = { documents: 'Documents', hitTesting: 'Retrieval Testing', settings: 'Settings', - emptyTip: 'The Knowledge has not been associated, please go to the application or plug-in to complete the association.', + emptyTip: 'This Knowledge has not been integrated within any application. Please refer to the document for guidance.', viewDoc: 'View documentation', relatedApp: 'linked apps', + noRelatedApp: 'No linked apps', }, voiceInput: { speaking: 'Speak now...', diff --git a/web/i18n/en-US/dataset.ts b/web/i18n/en-US/dataset.ts index 503c44f312..9c6855929c 100644 --- a/web/i18n/en-US/dataset.ts +++ b/web/i18n/en-US/dataset.ts @@ -16,6 +16,7 @@ const translation = { learnHowToWriteGoodKnowledgeDescription: 'Learn how to write a good knowledge description', externalAPIPanelDescription: 'The external knowledge API is used to connect to a knowledge base outside of Dify and retrieve knowledge from that knowledge base.', externalAPIPanelDocumentation: 'Learn how to create an External Knowledge API', + localDocs: 'Local Docs', documentCount: ' docs', wordCount: ' k words', appCount: ' linked apps', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index 70caaa976a..ec4b0acf9f 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -474,9 +474,10 @@ const translation = { documents: '文档', hitTesting: '召回测试', settings: '设置', - emptyTip: ' 知识库尚未关联,请前往应用程序或插件完成关联。', + emptyTip: '此知识尚未集成到任何应用程序中。请参阅文档以获取指导。', viewDoc: '查看文档', relatedApp: '个关联应用', + noRelatedApp: '无关联应用', }, voiceInput: { speaking: '现在讲...', diff --git a/web/i18n/zh-Hans/dataset.ts b/web/i18n/zh-Hans/dataset.ts index 94db7713c7..475088d84f 100644 --- a/web/i18n/zh-Hans/dataset.ts +++ b/web/i18n/zh-Hans/dataset.ts @@ -16,6 +16,7 @@ const translation = { learnHowToWriteGoodKnowledgeDescription: '了解如何编写良好的知识库描述', externalAPIPanelDescription: '外部知识库 API 用于连接到 Dify 之外的知识库并从中检索知识。', externalAPIPanelDocumentation: '了解如何创建外部知识库 API', + localDocs: '本地文档', documentCount: ' 文档', wordCount: ' 千字符', appCount: ' 关联应用', @@ -119,8 +120,10 @@ const translation = { change: '更改', changeRetrievalMethod: '更改检索方法', }, - docsFailedNotice: '文档无法被索引', + docsFailedNotice: '文档索引失败', retry: '重试', + documentsDisabled: '{{num}} 个文档已禁用 - 未活动超过 30 天', + enable: '启用', indexingTechnique: { high_quality: '高质量', economy: '经济',