From a430284aa21e3ae1f0d5654e55b2ad2852519cc2 Mon Sep 17 00:00:00 2001 From: wwf <yearningwang@iqtogether.com> Date: 星期三, 04 六月 2025 15:17:49 +0800 Subject: [PATCH] 初始化 --- app/(commonLayout)/app/(appDetailLayout)/[appId]/layout.tsx | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 167 insertions(+), 7 deletions(-) diff --git "a/app/\050commonLayout\051/app/\050appDetailLayout\051/\133appId\135/layout.tsx" "b/app/\050commonLayout\051/app/\050appDetailLayout\051/\133appId\135/layout.tsx" index 491a046..1d96320 100644 --- "a/app/\050commonLayout\051/app/\050appDetailLayout\051/\133appId\135/layout.tsx" +++ "b/app/\050commonLayout\051/app/\050appDetailLayout\051/\133appId\135/layout.tsx" @@ -1,14 +1,174 @@ -import Main from './layout-main' +'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' -const AppDetailLayout = async (props: { +export type IAppDetailLayoutProps = { children: React.ReactNode - params: Promise<{ appId: string }> -}) => { + params: { appId: string } +} + +const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => { const { children, - params, + params: { appId }, // get appId in path } = 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<App | null>(null) + const [navigation, setNavigation] = useState<Array<{ + name: string + href: string + icon: NavIcon + selectedIcon: NavIcon + }>>([]) + const systemFeatures = useContextSelector(AppContext, state => state.systemFeatures) - return <Main appId={(await params).appId}>{children}</Main> + 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 ( + <div className='flex h-full items-center justify-center bg-background-body'> + <Loading /> + </div> + ) + } + + return ( + <div className={cn(s.app, 'flex', 'overflow-hidden')}> + {appDetail && ( + <AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background} desc={appDetail.mode} navigation={navigation} /> + )} + <div className="bg-components-panel-bg grow overflow-hidden"> + {children} + </div> + </div> + ) } -export default AppDetailLayout +export default React.memo(AppDetailLayout) -- Gitblit v1.8.0