diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx index 0af2e945f3..1a39441136 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/annotations/page.tsx @@ -2,10 +2,6 @@ import React from 'react' import Main from '@/app/components/app/log-annotation' import { PageType } from '@/app/components/base/features/new-feature-panel/annotation-reply/type' -export type IProps = { - params: { appId: string } -} - const Logs = async () => { return (
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx index 4101120703..3397232dcb 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/develop/page.tsx @@ -1,15 +1,14 @@ import React from 'react' -import { type Locale } from '@/i18n' import DevelopMain from '@/app/components/develop' export type IDevelopProps = { - params: { locale: Locale; appId: string } + params: Promise<{ appId: string }> } const Develop = async ({ - params: { appId }, + params, }: IDevelopProps) => { - return + return } export default Develop diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx new file mode 100644 index 0000000000..762b43d4da --- /dev/null +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout-main.tsx @@ -0,0 +1,174 @@ +'use client' +import type { FC } from 'react' +import { useUnmount } from 'ahooks' +import React, { useCallback, useEffect, useState } from 'react' +import { usePathname, useRouter } from 'next/navigation' +import { + RiDashboard2Fill, + RiDashboard2Line, + RiFileList3Fill, + RiFileList3Line, + RiTerminalBoxFill, + RiTerminalBoxLine, + RiTerminalWindowFill, + RiTerminalWindowLine, +} from '@remixicon/react' +import { useTranslation } from 'react-i18next' +import { useShallow } from 'zustand/react/shallow' +import { useContextSelector } from 'use-context-selector' +import s from './style.module.css' +import cn from '@/utils/classnames' +import { useStore } from '@/app/components/app/store' +import AppSideBar from '@/app/components/app-sidebar' +import type { NavIcon } from '@/app/components/app-sidebar/navLink' +import { fetchAppDetail, fetchAppSSO } from '@/service/apps' +import AppContext, { useAppContext } from '@/context/app-context' +import Loading from '@/app/components/base/loading' +import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' +import type { App } from '@/types/app' + +export type IAppDetailLayoutProps = { + children: React.ReactNode + appId: string +} + +const AppDetailLayout: FC = (props) => { + const { + children, + appId, + } = props + const { t } = useTranslation() + const router = useRouter() + const pathname = usePathname() + const media = useBreakpoints() + const isMobile = media === MediaType.mobile + const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext() + const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({ + appDetail: state.appDetail, + setAppDetail: state.setAppDetail, + setAppSiderbarExpand: state.setAppSiderbarExpand, + }))) + const [isLoadingAppDetail, setIsLoadingAppDetail] = useState(false) + const [appDetailRes, setAppDetailRes] = useState(null) + const [navigation, setNavigation] = useState>([]) + const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) + + const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => { + const navs = [ + ...(isCurrentWorkspaceEditor + ? [{ + name: t('common.appMenus.promptEng'), + href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`, + icon: RiTerminalWindowLine, + selectedIcon: RiTerminalWindowFill, + }] + : [] + ), + { + name: t('common.appMenus.apiAccess'), + href: `/app/${appId}/develop`, + icon: RiTerminalBoxLine, + selectedIcon: RiTerminalBoxFill, + }, + ...(isCurrentWorkspaceEditor + ? [{ + name: mode !== 'workflow' + ? t('common.appMenus.logAndAnn') + : t('common.appMenus.logs'), + href: `/app/${appId}/logs`, + icon: RiFileList3Line, + selectedIcon: RiFileList3Fill, + }] + : [] + ), + { + name: t('common.appMenus.overview'), + href: `/app/${appId}/overview`, + icon: RiDashboard2Line, + selectedIcon: RiDashboard2Fill, + }, + ] + return navs + }, [t]) + + useEffect(() => { + if (appDetail) { + document.title = `${(appDetail.name || 'App')} - Dify` + const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' + const mode = isMobile ? 'collapse' : 'expand' + setAppSiderbarExpand(isMobile ? mode : localeMode) + // TODO: consider screen size and mode + // if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow')) + // setAppSiderbarExpand('collapse') + } + }, [appDetail, isMobile]) + + useEffect(() => { + setAppDetail() + setIsLoadingAppDetail(true) + fetchAppDetail({ url: '/apps', id: appId }).then((res) => { + setAppDetailRes(res) + }).catch((e: any) => { + if (e.status === 404) + router.replace('/apps') + }).finally(() => { + setIsLoadingAppDetail(false) + }) + }, [appId, router, setAppDetail]) + + useEffect(() => { + if (!appDetailRes || isLoadingCurrentWorkspace || isLoadingAppDetail) + return + const res = appDetailRes + // redirection + const canIEditApp = isCurrentWorkspaceEditor + if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) { + router.replace(`/app/${appId}/overview`) + return + } + if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) { + router.replace(`/app/${appId}/workflow`) + } + else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) { + router.replace(`/app/${appId}/configuration`) + } + else { + setAppDetail({ ...res, enable_sso: false }) + setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode)) + if (systemFeatures.enable_web_sso_switch_component && canIEditApp) { + fetchAppSSO({ appId }).then((ssoRes) => { + setAppDetail({ ...res, enable_sso: ssoRes.enabled }) + }) + } + } + }, [appDetailRes, appId, getNavigations, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, pathname, router, setAppDetail, systemFeatures.enable_web_sso_switch_component]) + + useUnmount(() => { + setAppDetail() + }) + + if (!appDetail) { + return ( +
+ +
+ ) + } + + return ( +
+ {appDetail && ( + + )} +
+ {children} +
+
+ ) +} +export default React.memo(AppDetailLayout) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx index 1d96320309..491a046e7d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx @@ -1,174 +1,14 @@ -'use client' -import type { FC } from 'react' -import { useUnmount } from 'ahooks' -import React, { useCallback, useEffect, useState } from 'react' -import { usePathname, useRouter } from 'next/navigation' -import { - RiDashboard2Fill, - RiDashboard2Line, - RiFileList3Fill, - RiFileList3Line, - RiTerminalBoxFill, - RiTerminalBoxLine, - RiTerminalWindowFill, - RiTerminalWindowLine, -} from '@remixicon/react' -import { useTranslation } from 'react-i18next' -import { useShallow } from 'zustand/react/shallow' -import { useContextSelector } from 'use-context-selector' -import s from './style.module.css' -import cn from '@/utils/classnames' -import { useStore } from '@/app/components/app/store' -import AppSideBar from '@/app/components/app-sidebar' -import type { NavIcon } from '@/app/components/app-sidebar/navLink' -import { fetchAppDetail, fetchAppSSO } from '@/service/apps' -import AppContext, { useAppContext } from '@/context/app-context' -import Loading from '@/app/components/base/loading' -import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' -import type { App } from '@/types/app' +import Main from './layout-main' -export type IAppDetailLayoutProps = { +const AppDetailLayout = async (props: { children: React.ReactNode - params: { appId: string } -} - -const AppDetailLayout: FC = (props) => { + params: Promise<{ appId: string }> +}) => { const { children, - params: { appId }, // get appId in path + params, } = props - const { t } = useTranslation() - const router = useRouter() - const pathname = usePathname() - const media = useBreakpoints() - const isMobile = media === MediaType.mobile - const { isCurrentWorkspaceEditor, isLoadingCurrentWorkspace } = useAppContext() - const { appDetail, setAppDetail, setAppSiderbarExpand } = useStore(useShallow(state => ({ - appDetail: state.appDetail, - setAppDetail: state.setAppDetail, - setAppSiderbarExpand: state.setAppSiderbarExpand, - }))) - const [isLoadingAppDetail, setIsLoadingAppDetail] = useState(false) - const [appDetailRes, setAppDetailRes] = useState(null) - const [navigation, setNavigation] = useState>([]) - const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) - const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => { - const navs = [ - ...(isCurrentWorkspaceEditor - ? [{ - name: t('common.appMenus.promptEng'), - href: `/app/${appId}/${(mode === 'workflow' || mode === 'advanced-chat') ? 'workflow' : 'configuration'}`, - icon: RiTerminalWindowLine, - selectedIcon: RiTerminalWindowFill, - }] - : [] - ), - { - name: t('common.appMenus.apiAccess'), - href: `/app/${appId}/develop`, - icon: RiTerminalBoxLine, - selectedIcon: RiTerminalBoxFill, - }, - ...(isCurrentWorkspaceEditor - ? [{ - name: mode !== 'workflow' - ? t('common.appMenus.logAndAnn') - : t('common.appMenus.logs'), - href: `/app/${appId}/logs`, - icon: RiFileList3Line, - selectedIcon: RiFileList3Fill, - }] - : [] - ), - { - name: t('common.appMenus.overview'), - href: `/app/${appId}/overview`, - icon: RiDashboard2Line, - selectedIcon: RiDashboard2Fill, - }, - ] - return navs - }, [t]) - - useEffect(() => { - if (appDetail) { - document.title = `${(appDetail.name || 'App')} - Dify` - const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' - const mode = isMobile ? 'collapse' : 'expand' - setAppSiderbarExpand(isMobile ? mode : localeMode) - // TODO: consider screen size and mode - // if ((appDetail.mode === 'advanced-chat' || appDetail.mode === 'workflow') && (pathname).endsWith('workflow')) - // setAppSiderbarExpand('collapse') - } - }, [appDetail, isMobile]) - - useEffect(() => { - setAppDetail() - setIsLoadingAppDetail(true) - fetchAppDetail({ url: '/apps', id: appId }).then((res) => { - setAppDetailRes(res) - }).catch((e: any) => { - if (e.status === 404) - router.replace('/apps') - }).finally(() => { - setIsLoadingAppDetail(false) - }) - }, [appId, router, setAppDetail]) - - useEffect(() => { - if (!appDetailRes || isLoadingCurrentWorkspace || isLoadingAppDetail) - return - const res = appDetailRes - // redirection - const canIEditApp = isCurrentWorkspaceEditor - if (!canIEditApp && (pathname.endsWith('configuration') || pathname.endsWith('workflow') || pathname.endsWith('logs'))) { - router.replace(`/app/${appId}/overview`) - return - } - if ((res.mode === 'workflow' || res.mode === 'advanced-chat') && (pathname).endsWith('configuration')) { - router.replace(`/app/${appId}/workflow`) - } - else if ((res.mode !== 'workflow' && res.mode !== 'advanced-chat') && (pathname).endsWith('workflow')) { - router.replace(`/app/${appId}/configuration`) - } - else { - setAppDetail({ ...res, enable_sso: false }) - setNavigation(getNavigations(appId, isCurrentWorkspaceEditor, res.mode)) - if (systemFeatures.enable_web_sso_switch_component && canIEditApp) { - fetchAppSSO({ appId }).then((ssoRes) => { - setAppDetail({ ...res, enable_sso: ssoRes.enabled }) - }) - } - } - }, [appDetailRes, appId, getNavigations, isCurrentWorkspaceEditor, isLoadingAppDetail, isLoadingCurrentWorkspace, pathname, router, setAppDetail, systemFeatures.enable_web_sso_switch_component]) - - useUnmount(() => { - setAppDetail() - }) - - if (!appDetail) { - return ( -
- -
- ) - } - - return ( -
- {appDetail && ( - - )} -
- {children} -
-
- ) + return
{children}
} -export default React.memo(AppDetailLayout) +export default AppDetailLayout diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx index 137c2c36ee..a7e6735e42 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/page.tsx @@ -5,12 +5,13 @@ import TracingPanel from './tracing/panel' import ApikeyInfoPanel from '@/app/components/app/overview/apikey-info-panel' export type IDevelopProps = { - params: { appId: string } + params: Promise<{ appId: string }> } const Overview = async ({ - params: { appId }, + params, }: IDevelopProps) => { + const appId = (await params).appId return (
diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx index 2bd2a356cc..5a6894bc63 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/page.tsx @@ -2,14 +2,14 @@ import React from 'react' import MainDetail from '@/app/components/datasets/documents/detail' export type IDocumentDetailProps = { - params: { datasetId: string; documentId: string } + params: Promise<{ datasetId: string; documentId: string }> } const DocumentDetail = async ({ - params: { datasetId, documentId }, + params, }: IDocumentDetailProps) => { return ( - + ) } diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx index 2194934ad1..72f3ede729 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/[documentId]/settings/page.tsx @@ -2,14 +2,14 @@ import React from 'react' import Settings from '@/app/components/datasets/documents/detail/settings' export type IProps = { - params: { datasetId: string; documentId: string } + params: Promise<{ datasetId: string; documentId: string }> } const DocumentSettings = async ({ - params: { datasetId, documentId }, + params, }: IProps) => { return ( - + ) } diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx index e249632bfa..3479b45168 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/create/page.tsx @@ -2,14 +2,14 @@ import React from 'react' import DatasetUpdateForm from '@/app/components/datasets/create' export type IProps = { - params: { datasetId: string } + params: Promise<{ datasetId: string }> } const Create = async ({ - params: { datasetId }, + params, }: IProps) => { return ( - + ) } diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx index 545e9ed378..260afd0b83 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/documents/page.tsx @@ -2,14 +2,14 @@ import React from 'react' import Main from '@/app/components/datasets/documents' export type IProps = { - params: { datasetId: string } + params: Promise<{ datasetId: string }> } const Documents = async ({ - params: { datasetId }, + params, }: IProps) => { return ( -
+
) } diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx index bec07e41b9..972ca7083f 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/hitTesting/page.tsx @@ -2,14 +2,14 @@ import React from 'react' import Main from '@/app/components/datasets/hit-testing' type Props = { - params: { datasetId: string } + params: Promise<{ datasetId: string }> } -const HitTesting = ({ - params: { datasetId }, +const HitTesting = async ({ + params, }: Props) => { return ( -
+
) } diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx new file mode 100644 index 0000000000..2e212ebb01 --- /dev/null +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout-main.tsx @@ -0,0 +1,228 @@ +'use client' +import type { FC, SVGProps } from 'react' +import React, { useEffect, useMemo } from 'react' +import { usePathname } from 'next/navigation' +import useSWR from 'swr' +import { useTranslation } from 'react-i18next' +import { useBoolean } from 'ahooks' +import { + Cog8ToothIcon, + DocumentTextIcon, + PaperClipIcon, +} from '@heroicons/react/24/outline' +import { + Cog8ToothIcon as Cog8ToothSolidIcon, + // CommandLineIcon as CommandLineSolidIcon, + DocumentTextIcon as DocumentTextSolidIcon, +} from '@heroicons/react/24/solid' +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 { RelatedAppResponse } from '@/models/datasets' +import AppSideBar from '@/app/components/app-sidebar' +import Loading from '@/app/components/base/loading' +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 { 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 + datasetId: string +} + +const TargetIcon = ({ className }: SVGProps) => { + return + + + + + + + + + +} + +const TargetSolidIcon = ({ className }: SVGProps) => { + return + + + + +} + +const BookOpenIcon = ({ className }: SVGProps) => { + return + + + +} + +type IExtraInfoProps = { + isMobile: boolean + relatedApps?: RelatedAppResponse + expand: boolean +} + +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
+ {hasRelatedApps && ( + <> + {!isMobile && ( + + } + > +
+ {relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')} + +
+
+ )} + + {isMobile &&
+ {relatedAppsTotal || '--'} + +
} + + )} + {!hasRelatedApps && !expand && ( + +
+ +
+
{t('common.datasetMenus.emptyTip')}
+ + + {t('common.datasetMenus.viewDoc')} + +
+ } + > +
+ {t('common.datasetMenus.noRelatedApp')} + +
+ + )} +
+} + +const DatasetDetailLayout: FC = (props) => { + const { + children, + datasetId, + } = props + const pathname = usePathname() + const hideSideBar = /documents\/create$/.test(pathname) + const { t } = useTranslation() + const { isCurrentWorkspaceDatasetOperator } = useAppContext() + + const media = useBreakpoints() + const isMobile = media === MediaType.mobile + + const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({ + url: 'fetchDatasetDetail', + datasetId, + }, apiParams => fetchDatasetDetail(apiParams.datasetId)) + + const { data: relatedApps } = useSWR({ + action: 'fetchDatasetRelatedApps', + datasetId, + }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId)) + + 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) + document.title = `${datasetRes.name || 'Dataset'} - Dify` + }, [datasetRes]) + + const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand) + + useEffect(() => { + const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' + const mode = isMobile ? 'collapse' : 'expand' + setAppSiderbarExpand(isMobile ? mode : localeMode) + }, [isMobile, setAppSiderbarExpand]) + + if (!datasetRes && !error) + return + + return ( +
+ {!hideSideBar && : undefined} + iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} + />} + mutateDatasetRes(), + }}> +
{children}
+
+
+ ) +} +export default React.memo(DatasetDetailLayout) diff --git a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx index a6fb116fa8..039a0280f7 100644 --- a/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx +++ b/web/app/(commonLayout)/datasets/(datasetDetailLayout)/[datasetId]/layout.tsx @@ -1,228 +1,12 @@ -'use client' -import type { FC, SVGProps } from 'react' -import React, { useEffect, useMemo } from 'react' -import { usePathname } from 'next/navigation' -import useSWR from 'swr' -import { useTranslation } from 'react-i18next' -import { useBoolean } from 'ahooks' -import { - Cog8ToothIcon, - DocumentTextIcon, - PaperClipIcon, -} from '@heroicons/react/24/outline' -import { - Cog8ToothIcon as Cog8ToothSolidIcon, - // CommandLineIcon as CommandLineSolidIcon, - DocumentTextIcon as DocumentTextSolidIcon, -} from '@heroicons/react/24/solid' -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 { RelatedAppResponse } from '@/models/datasets' -import AppSideBar from '@/app/components/app-sidebar' -import Loading from '@/app/components/base/loading' -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 { 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' +import Main from './layout-main' -export type IAppDetailLayoutProps = { +const DatasetDetailLayout = async ({ + children, + params, +}: { children: React.ReactNode - params: { datasetId: string } + params: Promise<{ datasetId: string }> +}) => { + return
{children}
} - -const TargetIcon = ({ className }: SVGProps) => { - return - - - - - - - - - -} - -const TargetSolidIcon = ({ className }: SVGProps) => { - return - - - - -} - -const BookOpenIcon = ({ className }: SVGProps) => { - return - - - -} - -type IExtraInfoProps = { - isMobile: boolean - relatedApps?: RelatedAppResponse - expand: boolean -} - -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
- {hasRelatedApps && ( - <> - {!isMobile && ( - - } - > -
- {relatedAppsTotal || '--'} {t('common.datasetMenus.relatedApp')} - -
-
- )} - - {isMobile &&
- {relatedAppsTotal || '--'} - -
} - - )} - {!hasRelatedApps && !expand && ( - -
- -
-
{t('common.datasetMenus.emptyTip')}
- - - {t('common.datasetMenus.viewDoc')} - -
- } - > -
- {t('common.datasetMenus.noRelatedApp')} - -
- - )} - -} - -const DatasetDetailLayout: FC = (props) => { - const { - children, - params: { datasetId }, - } = props - const pathname = usePathname() - const hideSideBar = /documents\/create$/.test(pathname) - const { t } = useTranslation() - const { isCurrentWorkspaceDatasetOperator } = useAppContext() - - const media = useBreakpoints() - const isMobile = media === MediaType.mobile - - const { data: datasetRes, error, mutate: mutateDatasetRes } = useSWR({ - url: 'fetchDatasetDetail', - datasetId, - }, apiParams => fetchDatasetDetail(apiParams.datasetId)) - - const { data: relatedApps } = useSWR({ - action: 'fetchDatasetRelatedApps', - datasetId, - }, apiParams => fetchDatasetRelatedApps(apiParams.datasetId)) - - 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) - document.title = `${datasetRes.name || 'Dataset'} - Dify` - }, [datasetRes]) - - const setAppSiderbarExpand = useStore(state => state.setAppSiderbarExpand) - - useEffect(() => { - const localeMode = localStorage.getItem('app-detail-collapse-or-expand') || 'expand' - const mode = isMobile ? 'collapse' : 'expand' - setAppSiderbarExpand(isMobile ? mode : localeMode) - }, [isMobile, setAppSiderbarExpand]) - - if (!datasetRes && !error) - return - - return ( -
- {!hideSideBar && : undefined} - iconType={datasetRes?.data_source_type === DataSourceType.NOTION ? 'notion' : 'dataset'} - />} - mutateDatasetRes(), - }}> -
{children}
-
-
- ) -} -export default React.memo(DatasetDetailLayout) +export default DatasetDetailLayout diff --git a/web/app/(commonLayout)/explore/installed/[appId]/page.tsx b/web/app/(commonLayout)/explore/installed/[appId]/page.tsx index c22645cab3..938a03992b 100644 --- a/web/app/(commonLayout)/explore/installed/[appId]/page.tsx +++ b/web/app/(commonLayout)/explore/installed/[appId]/page.tsx @@ -3,14 +3,14 @@ import React from 'react' import Main from '@/app/components/explore/installed-app' export type IInstalledAppProps = { - params: { + params: Promise<{ appId: string - } + }> } -const InstalledApp: FC = ({ params: { appId } }) => { +const InstalledApp: FC = async ({ params }) => { return ( -
+
) } export default React.memo(InstalledApp) diff --git a/web/package.json b/web/package.json index 064285ff56..3969c568b8 100644 --- a/web/package.json +++ b/web/package.json @@ -3,7 +3,7 @@ "version": "0.15.0", "private": true, "engines": { - "node": ">=18.17.0" + "node": ">=18.18.0" }, "scripts": { "dev": "next dev",