From e7d394f160edb3beecce94ded6062cdac01d3cc9 Mon Sep 17 00:00:00 2001 From: twwu Date: Wed, 9 Jul 2025 15:13:02 +0800 Subject: [PATCH] feat: Add DatasetSidebarDropdown component and integrate ExtraInfo for dataset details --- .../[datasetId]/layout-main.tsx | 71 +------- .../app-sidebar/dataset-info/dropdown.tsx | 2 +- .../app-sidebar/dataset-info/menu-item.tsx | 8 +- .../app-sidebar/dataset-info/menu.tsx | 24 +-- .../app-sidebar/dataset-sidebar-dropdown.tsx | 160 ++++++++++++++++++ web/app/components/app-sidebar/index.tsx | 10 ++ web/app/components/datasets/extra-info.tsx | 69 ++++++++ .../list/dataset-card/operation-item.tsx | 8 +- .../datasets/list/dataset-card/operations.tsx | 24 +-- web/app/components/header/header-wrapper.tsx | 3 +- web/app/components/workflow/header/index.tsx | 3 +- 11 files changed, 265 insertions(+), 117 deletions(-) create mode 100644 web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx create mode 100644 web/app/components/datasets/extra-info.tsx diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx index 5794852a49..7084c8d3da 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -12,8 +12,6 @@ import { RiFocus2Fill, RiFocus2Line, } from '@remixicon/react' -import { RiInformation2Line } from '@remixicon/react' -import type { RelatedAppResponse } from '@/models/datasets' import AppSideBar from '@/app/components/app-sidebar' import Loading from '@/app/components/base/loading' import DatasetDetailContext from '@/context/dataset-detail' @@ -21,79 +19,16 @@ import { DataSourceType } from '@/models/datasets' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import { useStore } from '@/app/components/app/store' import { useAppContext } from '@/context/app-context' -import Tooltip from '@/app/components/base/tooltip' -import LinkedAppsPanel from '@/app/components/base/linked-apps-panel' import { PipelineFill, PipelineLine } from '@/app/components/base/icons/src/vender/pipeline' -import { Divider } from '@/app/components/base/icons/src/vender/knowledge' -import NoLinkedAppsPanel from '@/app/components/datasets/no-linked-apps-panel' import { useDatasetDetail, useDatasetRelatedApps } from '@/service/knowledge/use-dataset' import useDocumentTitle from '@/hooks/use-document-title' +import ExtraInfo from '@/app/components/datasets/extra-info' export type IAppDetailLayoutProps = { children: React.ReactNode params: { datasetId: string } } -type IExtraInfoProps = { - relatedApps?: RelatedAppResponse - documentCount?: number - expand: boolean -} - -const ExtraInfo = React.memo(({ - relatedApps, - documentCount, - expand, -}: IExtraInfoProps) => { - const { t } = useTranslation() - - const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 - const relatedAppsTotal = relatedApps?.data?.length || 0 - - return ( - <> - {!expand && ( -
-
-
- {documentCount ?? '--'} -
-
- {t('common.datasetMenus.documents')} -
-
-
- -
-
-
- {relatedAppsTotal ?? '--'} -
- - ) : - } - > -
- {t('common.datasetMenus.relatedApp')} - -
-
-
-
- )} - - ) -}) - const DatasetDetailLayout: FC = (props) => { const { children, @@ -186,13 +121,13 @@ const DatasetDetailLayout: FC = (props) => { navigation={navigation} extraInfo={ !isCurrentWorkspaceDatasetOperator - ? mode => + ? mode => : undefined } iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} /> )} -
{children}
+
{children}
) diff --git a/web/app/components/app-sidebar/dataset-info/dropdown.tsx b/web/app/components/app-sidebar/dataset-info/dropdown.tsx index f944922cc1..2bc64c8f56 100644 --- a/web/app/components/app-sidebar/dataset-info/dropdown.tsx +++ b/web/app/components/app-sidebar/dataset-info/dropdown.tsx @@ -118,7 +118,7 @@ const DropDown = ({ - + ) => void + handleClick?: () => void } const MenuItem = ({ @@ -15,7 +15,11 @@ const MenuItem = ({ return (
{ + e.preventDefault() + e.stopPropagation() + handleClick?.() + }} > {name} diff --git a/web/app/components/app-sidebar/dataset-info/menu.tsx b/web/app/components/app-sidebar/dataset-info/menu.tsx index b016763227..fd560ce643 100644 --- a/web/app/components/app-sidebar/dataset-info/menu.tsx +++ b/web/app/components/app-sidebar/dataset-info/menu.tsx @@ -19,36 +19,18 @@ const Menu = ({ }: MenuProps) => { const { t } = useTranslation() - const onClickRename = async (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - openRenameModal() - } - - const onClickExport = async (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - handleExportPipeline() - } - - const onClickDelete = async (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - detectIsUsedByApp() - } - return (
{showDelete && ( @@ -58,7 +40,7 @@ const Menu = ({
diff --git a/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx new file mode 100644 index 0000000000..73a25d9ab9 --- /dev/null +++ b/web/app/components/app-sidebar/dataset-sidebar-dropdown.tsx @@ -0,0 +1,160 @@ +import React, { useCallback, useRef, useState } from 'react' +import { + RiMenuLine, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import AppIcon from '../base/app-icon' +import Divider from '../base/divider' +import NavLink from './navLink' +import type { NavIcon } from './navLink' +import cn from '@/utils/classnames' +import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail' +import Effect from '../base/effect' +import Dropdown from './dataset-info/dropdown' +import type { DataSet } from '@/models/datasets' +import { DOC_FORM_TEXT } from '@/models/datasets' +import { useKnowledge } from '@/hooks/use-knowledge' +import { useTranslation } from 'react-i18next' +import { useDatasetRelatedApps } from '@/service/knowledge/use-dataset' +import ExtraInfo from '../datasets/extra-info' + +type DatasetSidebarDropdownProps = { + navigation: Array<{ + name: string + href: string + icon: NavIcon + selectedIcon: NavIcon + disabled?: boolean + }> +} + +const DatasetSidebarDropdown = ({ + navigation, +}: DatasetSidebarDropdownProps) => { + const { t } = useTranslation() + const dataset = useDatasetDetailContextWithSelector(state => state.dataset) as DataSet + + const { data: relatedApps } = useDatasetRelatedApps(dataset.id) + + const [open, doSetOpen] = useState(false) + const openRef = useRef(open) + const setOpen = useCallback((v: boolean) => { + doSetOpen(v) + openRef.current = v + }, [doSetOpen]) + const handleTrigger = useCallback(() => { + setOpen(!openRef.current) + }, [setOpen]) + + const iconInfo = dataset.icon_info || { + icon: '📙', + icon_type: 'emoji', + icon_background: '#FFF4ED', + icon_url: '', + } + const isExternalProvider = dataset.provider === 'external' + const { formatIndexingTechniqueAndMethod } = useKnowledge() + + if (!dataset) + return null + + return ( + <> +
+ + +
+ + +
+
+ +
+ +
+
+ + +
+
+
+ {dataset.name} +
+
+ {isExternalProvider && t('dataset.externalTag')} + {!isExternalProvider && dataset.doc_form && dataset.indexing_technique && ( +
+ {t(`dataset.chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`)} + {formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method)} +
+ )} +
+
+ {!!dataset.description && ( +

+ {dataset.description} +

+ )} +
+
+ +
+ + +
+
+
+
+ + ) +} + +export default DatasetSidebarDropdown diff --git a/web/app/components/app-sidebar/index.tsx b/web/app/components/app-sidebar/index.tsx index cc30797b9e..8bfd04b914 100644 --- a/web/app/components/app-sidebar/index.tsx +++ b/web/app/components/app-sidebar/index.tsx @@ -14,6 +14,7 @@ import Divider from '../base/divider' import { useHover, useKeyPress } from 'ahooks' import ToggleButton from './toggle-button' import { getKeyboardKeyCodeBySystem } from '../workflow/utils' +import DatasetSidebarDropdown from './dataset-sidebar-dropdown' export type IAppDetailNavProps = { iconType?: 'app' | 'dataset' | 'notion' @@ -50,6 +51,7 @@ const AppDetailNav = ({ // Check if the current path is a workflow canvas & fullscreen const pathname = usePathname() const inWorkflowCanvas = pathname.endsWith('/workflow') + const isPipelineCanvas = pathname.endsWith('/pipeline') const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true' const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize) const { eventEmitter } = useEventEmitterContextContext() @@ -79,6 +81,14 @@ const AppDetailNav = ({ ) } + if (isPipelineCanvas && hideHeader) { + return ( +
+ +
+ ) + } + return (
{ + const { t } = useTranslation() + + const hasRelatedApps = relatedApps?.data && relatedApps?.data?.length > 0 + const relatedAppsTotal = relatedApps?.data?.length || 0 + + if (!expand) + return null + + return ( +
+
+
+ {documentCount ?? '--'} +
+
+ {t('common.datasetMenus.documents')} +
+
+
+ +
+
+
+ {relatedAppsTotal ?? '--'} +
+ + ) : + } + > +
+ {t('common.datasetMenus.relatedApp')} + +
+
+
+
+ ) +} + +export default React.memo(ExtraInfo) diff --git a/web/app/components/datasets/list/dataset-card/operation-item.tsx b/web/app/components/datasets/list/dataset-card/operation-item.tsx index 4a7e7e0cf6..8a0eb86ec0 100644 --- a/web/app/components/datasets/list/dataset-card/operation-item.tsx +++ b/web/app/components/datasets/list/dataset-card/operation-item.tsx @@ -4,7 +4,7 @@ import type { RemixiconComponentType } from '@remixicon/react' type OperationItemProps = { Icon: RemixiconComponentType name: string - handleClick?: (e: React.MouseEvent) => void + handleClick?: () => void } const OperationItem = ({ @@ -15,7 +15,11 @@ const OperationItem = ({ return (
{ + e.preventDefault() + e.stopPropagation() + handleClick?.() + }} > diff --git a/web/app/components/datasets/list/dataset-card/operations.tsx b/web/app/components/datasets/list/dataset-card/operations.tsx index 2901ebc98d..56b32d2f17 100644 --- a/web/app/components/datasets/list/dataset-card/operations.tsx +++ b/web/app/components/datasets/list/dataset-card/operations.tsx @@ -19,36 +19,18 @@ const Operations = ({ }: OperationsProps) => { const { t } = useTranslation() - const onClickRename = async (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - openRenameModal() - } - - const onClickExport = async (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - handleExportPipeline() - } - - const onClickDelete = async (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - detectIsUsedByApp() - } - return (
{showDelete && ( @@ -58,7 +40,7 @@ const Operations = ({
diff --git a/web/app/components/header/header-wrapper.tsx b/web/app/components/header/header-wrapper.tsx index 6486e3707a..e7d37d53ed 100644 --- a/web/app/components/header/header-wrapper.tsx +++ b/web/app/components/header/header-wrapper.tsx @@ -16,6 +16,7 @@ const HeaderWrapper = ({ const isBordered = ['/apps', '/datasets', '/datasets/create', '/tools'].includes(pathname) // Check if the current path is a workflow canvas & fullscreen const inWorkflowCanvas = pathname.endsWith('/workflow') + const isPipelineCanvas = pathname.endsWith('/pipeline') const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true' const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize) const { eventEmitter } = useEventEmitterContextContext() @@ -30,7 +31,7 @@ const HeaderWrapper = ({ 'sticky left-0 right-0 top-0 z-[15] flex min-h-[56px] shrink-0 grow-0 basis-auto flex-col', s.header, isBordered ? 'border-b border-divider-regular' : '', - hideHeader && inWorkflowCanvas && 'hidden', + hideHeader && (inWorkflowCanvas || isPipelineCanvas) && 'hidden', )} > {children} diff --git a/web/app/components/workflow/header/index.tsx b/web/app/components/workflow/header/index.tsx index dcb7000e94..f8f3cc1fb0 100644 --- a/web/app/components/workflow/header/index.tsx +++ b/web/app/components/workflow/header/index.tsx @@ -21,6 +21,7 @@ const Header = ({ }: HeaderProps) => { const pathname = usePathname() const inWorkflowCanvas = pathname.endsWith('/workflow') + const isPipelineCanvas = pathname.endsWith('/pipeline') const { normal, restoring, @@ -32,7 +33,7 @@ const Header = ({
- {inWorkflowCanvas && maximizeCanvas &&
} + {(inWorkflowCanvas || isPipelineCanvas) && maximizeCanvas &&
} { normal && (