diff --git a/package.json b/package.json index 2475e5b06a502..1290878c7dbc3 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,6 @@ "@fal-ai/client": "^1.6.2", "@formkit/auto-animate": "^0.9.0", "@google/genai": "^1.24.0", - "@huggingface/inference": "^4.11.3", "@icons-pack/react-simple-icons": "^13.8.0", "@khmyznikov/pwa-install": "0.3.9", "@langchain/community": "^0.3.57", @@ -262,6 +261,7 @@ "react-lazy-load": "^4.0.1", "react-pdf": "^9.2.1", "react-rnd": "^10.5.2", + "react-router-dom": "^7.9.4", "react-scan": "^0.4.3", "react-virtuoso": "^4.14.1", "react-wrap-balancer": "^1.1.1", diff --git a/src/app/[variants]/(main)/profile/(home)/[[...slugs]]/page.tsx b/src/app/[variants]/(main)/profile/(home)/[[...slugs]]/page.tsx deleted file mode 100644 index 9020cbc30954d..0000000000000 --- a/src/app/[variants]/(main)/profile/(home)/[[...slugs]]/page.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Skeleton } from 'antd'; -import dynamic from 'next/dynamic'; - -import { enableClerk } from '@/const/auth'; -import { metadataModule } from '@/server/metadata'; -import { translation } from '@/server/translation'; -import { DynamicLayoutProps } from '@/types/next'; -import { RouteVariants } from '@/utils/server/routeVariants'; - -import Client from '../Client'; - -// 为了兼容 ClerkProfile, 需要使用 [[...slug]] - -const ClerkProfile = dynamic(() => import('../../features/ClerkProfile'), { - loading: () => ( -
- -
- ), -}); - -export const generateMetadata = async (props: DynamicLayoutProps) => { - const locale = await RouteVariants.getLocale(props); - const { t } = await translation('auth', locale); - return metadataModule.generate({ - description: t('header.desc'), - title: t('tab.profile'), - url: '/profile', - }); -}; - -const Page = async (props: DynamicLayoutProps) => { - const mobile = await RouteVariants.getIsMobile(props); - - if (enableClerk) return ; - - return ; -}; - -export default Page; diff --git a/src/app/[variants]/(main)/profile/@category/default.tsx b/src/app/[variants]/(main)/profile/@category/default.tsx deleted file mode 100644 index 6177d698b5044..0000000000000 --- a/src/app/[variants]/(main)/profile/@category/default.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Suspense } from 'react'; - -import SkeletonLoading from '@/components/Loading/SkeletonLoading'; - -import CategoryContent from './features/CategoryContent'; - -const Category = () => { - return ( - }> - - - ); -}; - -Category.displayName = 'SettingCategory'; - -export default Category; diff --git a/src/app/[variants]/(main)/profile/@category/features/CategoryContent.tsx b/src/app/[variants]/(main)/profile/@category/features/CategoryContent.tsx deleted file mode 100644 index 85f99f8e02711..0000000000000 --- a/src/app/[variants]/(main)/profile/@category/features/CategoryContent.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client'; - -import { usePathname } from 'next/navigation'; -import { memo } from 'react'; -import urlJoin from 'url-join'; - -import Menu from '@/components/Menu'; -import { useQueryRoute } from '@/hooks/useQueryRoute'; -import { ProfileTabs } from '@/store/global/initialState'; - -import { useCategory } from '../../hooks/useCategory'; - -const CategoryContent = memo(() => { - const pathname = usePathname(); - const activeTab = pathname.split('/').at(-1); - const cateItems = useCategory(); - const router = useQueryRoute(); - - return ( - { - const activeKey = key === ProfileTabs.Profile ? '/' : key; - - router.push(urlJoin('/profile', activeKey)); - }} - selectable - selectedKeys={activeTab ? [activeTab] : []} - /> - ); -}); - -export default CategoryContent; diff --git a/src/app/[variants]/(main)/profile/ProfileRouter.tsx b/src/app/[variants]/(main)/profile/ProfileRouter.tsx new file mode 100644 index 0000000000000..becacc13619bf --- /dev/null +++ b/src/app/[variants]/(main)/profile/ProfileRouter.tsx @@ -0,0 +1,90 @@ +'use client'; + +import { useResponsive } from 'antd-style'; +import { memo, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Flexbox } from 'react-layout-kit'; +import { BrowserRouter, Route, Routes, useLocation } from 'react-router-dom'; + +import InitClientDB from '@/features/InitClientDB'; +import Footer from '@/features/Setting/Footer'; +import SettingContainer from '@/features/Setting/SettingContainer'; + +import Header from './_layout/Desktop/Header'; +import SideBar from './_layout/Desktop/SideBar'; +import CategoryContent from './features/CategoryContent'; +import ApiKeyPage from './routes/ApiKeyPage'; +import ProfilePage from './routes/ProfilePage'; +import SecurityPage from './routes/SecurityPage'; +import StatsPage from './routes/StatsPage'; + +interface ProfileLayoutProps { + mobile?: boolean; +} + +const ProfileLayout = memo(({ mobile }) => { + const ref = useRef(null); + const { md = true } = useResponsive(); + const { t } = useTranslation('auth'); + const location = useLocation(); + + // Get active tab from pathname + const getActiveTab = () => { + const path = location.pathname.replace('/profile', '').replace(/^\//, ''); + if (!path) return 'profile'; + return path; + }; + + const activeKey = getActiveTab(); + + return ( + <> + + {md ? ( + + + + ) : ( +
ref.current} title={<>{t(`tab.${activeKey}` as any)}}> + +
+ )} + } + style={{ + paddingBlock: 24, + paddingInline: 32, + }} + > + + } path="/" /> + } path="/stats" /> + } path="/apikey" /> + } path="/security" /> + + +
+ + + ); +}); + +ProfileLayout.displayName = 'ProfileLayout'; + +const ProfileRouter = memo(({ mobile }) => { + return ( + + + + ); +}); + +ProfileRouter.displayName = 'ProfileRouter'; + +export default ProfileRouter; diff --git a/src/app/[variants]/(main)/profile/[[...slugs]]/page.tsx b/src/app/[variants]/(main)/profile/[[...slugs]]/page.tsx new file mode 100644 index 0000000000000..313985c202b57 --- /dev/null +++ b/src/app/[variants]/(main)/profile/[[...slugs]]/page.tsx @@ -0,0 +1,19 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +import BrandTextLoading from '@/components/Loading/BrandTextLoading'; +import { useIsMobile } from '@/hooks/useIsMobile'; + +const ProfileRouter = dynamic(() => import('../ProfileRouter'), { + loading: BrandTextLoading, + ssr: false, +}); + +const Page = () => { + const mobile = useIsMobile(); + + return ; +}; + +export default Page; diff --git a/src/app/[variants]/(main)/profile/apikey/page.tsx b/src/app/[variants]/(main)/profile/apikey/page.tsx deleted file mode 100644 index b3ba54640c403..0000000000000 --- a/src/app/[variants]/(main)/profile/apikey/page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { notFound } from 'next/navigation'; - -import { serverFeatureFlags } from '@/config/featureFlags'; -import { metadataModule } from '@/server/metadata'; -import { translation } from '@/server/translation'; -import { DynamicLayoutProps } from '@/types/next'; -import { RouteVariants } from '@/utils/server/routeVariants'; - -import Page from '../../settings/system-agent'; -import Client from './Client'; - -export const generateMetadata = async (props: DynamicLayoutProps) => { - const locale = await RouteVariants.getLocale(props); - const { t } = await translation('auth', locale); - return metadataModule.generate({ - description: t('header.desc'), - title: t('tab.apikey'), - url: '/profile/apikey', - }); -}; - -const page = () => { - const { showApiKeyManage } = serverFeatureFlags(); - - if (!showApiKeyManage) return notFound(); - - return ; -}; - -Page.displayName = 'ApiKey'; - -export default page; diff --git a/src/app/[variants]/(main)/profile/features/CategoryContent.tsx b/src/app/[variants]/(main)/profile/features/CategoryContent.tsx new file mode 100644 index 0000000000000..1b5d7f93d9355 --- /dev/null +++ b/src/app/[variants]/(main)/profile/features/CategoryContent.tsx @@ -0,0 +1,31 @@ +'use client'; + +import { memo } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; + +import Menu from '@/components/Menu'; +import { ProfileTabs } from '@/store/global/initialState'; + +import { useCategory } from '../hooks/useCategory'; + +const CategoryContent = memo(() => { + const location = useLocation(); + const navigate = useNavigate(); + const activeTab = location.pathname.replace(/^\//, '') || 'profile'; + const cateItems = useCategory(); + + return ( + { + const activeKey = key === ProfileTabs.Profile ? '/' : `/${key}`; + navigate(activeKey); + }} + selectable + selectedKeys={activeTab === '' ? ['profile'] : [activeTab]} + /> + ); +}); + +export default CategoryContent; diff --git a/src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx b/src/app/[variants]/(main)/profile/features/SSOProvidersList/index.tsx similarity index 100% rename from src/app/[variants]/(main)/profile/(home)/features/SSOProvidersList/index.tsx rename to src/app/[variants]/(main)/profile/features/SSOProvidersList/index.tsx diff --git a/src/app/[variants]/(main)/profile/hooks/useCategory.tsx b/src/app/[variants]/(main)/profile/hooks/useCategory.tsx index 70f651919d380..219733c93f4a4 100644 --- a/src/app/[variants]/(main)/profile/hooks/useCategory.tsx +++ b/src/app/[variants]/(main)/profile/hooks/useCategory.tsx @@ -1,6 +1,5 @@ import { Icon } from '@lobehub/ui'; import { ChartColumnBigIcon, KeyIcon, ShieldCheck, UserCircle } from 'lucide-react'; -import Link from 'next/link'; import { useTranslation } from 'react-i18next'; import type { MenuProps } from '@/components/Menu'; @@ -20,39 +19,23 @@ export const useCategory = () => { { icon: , key: ProfileTabs.Profile, - label: ( - e.preventDefault()}> - {t('tab.profile')} - - ), + label: t('tab.profile'), }, enableAuth && isLoginWithClerk && { icon: , key: ProfileTabs.Security, - label: ( - e.preventDefault()}> - {t('tab.security')} - - ), + label: t('tab.security'), }, !isDeprecatedEdition && { icon: , key: ProfileTabs.Stats, - label: ( - e.preventDefault()}> - {t('tab.stats')} - - ), - }, + label: t('tab.stats'), + }, !!showApiKeyManage && { icon: , key: ProfileTabs.APIKey, - label: ( - e.preventDefault()}> - {t('tab.apikey')} - - ), + label: t('tab.apikey'), }, ].filter(Boolean) as MenuProps['items']; diff --git a/src/app/[variants]/(main)/profile/layout.tsx b/src/app/[variants]/(main)/profile/layout.tsx index 238eb0c0a5d14..504076fdb420c 100644 --- a/src/app/[variants]/(main)/profile/layout.tsx +++ b/src/app/[variants]/(main)/profile/layout.tsx @@ -1,10 +1,8 @@ -import ServerLayout from '@/components/server/ServerLayout'; +import { PropsWithChildren } from 'react'; -import Desktop from './_layout/Desktop'; -import Mobile from './_layout/Mobile'; -import { LayoutProps } from './_layout/type'; - -const ProfileLayout = ServerLayout({ Desktop, Mobile }); +const ProfileLayout = ({ children }: PropsWithChildren) => { + return children; +}; ProfileLayout.displayName = 'ProfileLayout'; diff --git a/src/app/[variants]/(main)/profile/apikey/Client.tsx b/src/app/[variants]/(main)/profile/routes/ApiKeyPage.tsx similarity index 97% rename from src/app/[variants]/(main)/profile/apikey/Client.tsx rename to src/app/[variants]/(main)/profile/routes/ApiKeyPage.tsx index c325ac13987d9..6f3a677bf265a 100644 --- a/src/app/[variants]/(main)/profile/apikey/Client.tsx +++ b/src/app/[variants]/(main)/profile/routes/ApiKeyPage.tsx @@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next'; import { lambdaClient } from '@/libs/trpc/client'; import { ApiKeyItem, CreateApiKeyParams, UpdateApiKeyParams } from '@/types/apiKey'; -import { ApiKeyDisplay, ApiKeyModal, EditableCell } from './features'; +import { ApiKeyDisplay, ApiKeyModal, EditableCell } from '../apikey/features'; const useStyles = createStyles(({ css, token }) => ({ container: css` @@ -35,7 +35,7 @@ const useStyles = createStyles(({ css, token }) => ({ `, })); -const Client: FC = () => { +const ApiKeyPage: FC = () => { const { styles } = useStyles(); const { t } = useTranslation('auth'); const [modalOpen, setModalOpen] = useState(false); @@ -206,4 +206,4 @@ const Client: FC = () => { ); }; -export default Client; +export default ApiKeyPage; diff --git a/src/app/[variants]/(main)/profile/(home)/Client.tsx b/src/app/[variants]/(main)/profile/routes/ProfilePage.tsx similarity index 93% rename from src/app/[variants]/(main)/profile/(home)/Client.tsx rename to src/app/[variants]/(main)/profile/routes/ProfilePage.tsx index 3d57c56dc25c3..7396362a743ab 100644 --- a/src/app/[variants]/(main)/profile/(home)/Client.tsx +++ b/src/app/[variants]/(main)/profile/routes/ProfilePage.tsx @@ -12,9 +12,9 @@ import UserAvatar from '@/features/User/UserAvatar'; import { useUserStore } from '@/store/user'; import { authSelectors, userProfileSelectors } from '@/store/user/selectors'; -import SSOProvidersList from './features/SSOProvidersList'; +import SSOProvidersList from '../features/SSOProvidersList'; -const Client = memo<{ mobile?: boolean }>(({ mobile }) => { +const ProfilePage = memo<{ mobile?: boolean }>(({ mobile }) => { const [isLoginWithNextAuth, isLogin] = useUserStore((s) => [ authSelectors.isLoginWithNextAuth(s), authSelectors.isLogin(s), @@ -83,4 +83,4 @@ const Client = memo<{ mobile?: boolean }>(({ mobile }) => { ); }); -export default Client; +export default ProfilePage; diff --git a/src/app/[variants]/(main)/profile/routes/SecurityPage.tsx b/src/app/[variants]/(main)/profile/routes/SecurityPage.tsx new file mode 100644 index 0000000000000..592596a6fc6f7 --- /dev/null +++ b/src/app/[variants]/(main)/profile/routes/SecurityPage.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { Skeleton } from 'antd'; +import dynamic from 'next/dynamic'; +import { memo } from 'react'; + +import { enableClerk } from '@/const/auth'; + +const ClerkProfile = dynamic(() => import('../features/ClerkProfile'), { + loading: () => ( +
+ +
+ ), +}); + +const SecurityPage = memo<{ mobile?: boolean }>(({ mobile }) => { + if (!enableClerk) return null; + + return ; +}); + +export default SecurityPage; diff --git a/src/app/[variants]/(main)/profile/stats/Client.tsx b/src/app/[variants]/(main)/profile/routes/StatsPage.tsx similarity index 62% rename from src/app/[variants]/(main)/profile/stats/Client.tsx rename to src/app/[variants]/(main)/profile/routes/StatsPage.tsx index 5ce5896b08c36..e910d2e4b7729 100644 --- a/src/app/[variants]/(main)/profile/stats/Client.tsx +++ b/src/app/[variants]/(main)/profile/routes/StatsPage.tsx @@ -7,18 +7,18 @@ import { Flexbox } from 'react-layout-kit'; import { FORM_STYLE } from '@/const/layoutTokens'; -import AiHeatmaps from './features/AiHeatmaps'; -import AssistantsRank from './features/AssistantsRank'; -import ModelsRank from './features/ModelsRank'; -import ShareButton from './features/ShareButton'; -import TopicsRank from './features/TopicsRank'; -import TotalAssistants from './features/TotalAssistants'; -import TotalMessages from './features/TotalMessages'; -import TotalTopics from './features/TotalTopics'; -import TotalWords from './features/TotalWords'; -import Welcome from './features/Welcome'; +import AiHeatmaps from '../stats/features/AiHeatmaps'; +import AssistantsRank from '../stats/features/AssistantsRank'; +import ModelsRank from '../stats/features/ModelsRank'; +import ShareButton from '../stats/features/ShareButton'; +import TopicsRank from '../stats/features/TopicsRank'; +import TotalAssistants from '../stats/features/TotalAssistants'; +import TotalMessages from '../stats/features/TotalMessages'; +import TotalTopics from '../stats/features/TotalTopics'; +import TotalWords from '../stats/features/TotalWords'; +import Welcome from '../stats/features/Welcome'; -const Client = memo<{ mobile?: boolean }>(({ mobile }) => { +const StatsPage = memo<{ mobile?: boolean }>(({ mobile }) => { const { t } = useTranslation('auth'); return ( @@ -49,4 +49,4 @@ const Client = memo<{ mobile?: boolean }>(({ mobile }) => { ); }); -export default Client; +export default StatsPage; diff --git a/src/app/[variants]/(main)/profile/security/page.tsx b/src/app/[variants]/(main)/profile/security/page.tsx deleted file mode 100644 index 5cc87e0ccd82b..0000000000000 --- a/src/app/[variants]/(main)/profile/security/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { notFound } from 'next/navigation'; - -import { enableClerk } from '@/const/auth'; -import { metadataModule } from '@/server/metadata'; -import { translation } from '@/server/translation'; -import { DynamicLayoutProps } from '@/types/next'; -import { RouteVariants } from '@/utils/server/routeVariants'; - -import ClerkProfile from '../features/ClerkProfile'; - -export const generateMetadata = async (props: DynamicLayoutProps) => { - const locale = await RouteVariants.getLocale(props); - const { t } = await translation('auth', locale); - return metadataModule.generate({ - description: t('header.desc'), - title: t('tab.security'), - url: '/profile/security', - }); -}; - -const Page = async (props: DynamicLayoutProps) => { - if (!enableClerk) return notFound(); - const mobile = await RouteVariants.getIsMobile(props); - - return ; -}; - -export default Page; diff --git a/src/app/[variants]/(main)/profile/stats/page.tsx b/src/app/[variants]/(main)/profile/stats/page.tsx deleted file mode 100644 index 91aafbb71696d..0000000000000 --- a/src/app/[variants]/(main)/profile/stats/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { metadataModule } from '@/server/metadata'; -import { translation } from '@/server/translation'; -import { DynamicLayoutProps } from '@/types/next'; -import { RouteVariants } from '@/utils/server/routeVariants'; - -import Client from './Client'; - -export const generateMetadata = async (props: DynamicLayoutProps) => { - const locale = await RouteVariants.getLocale(props); - const { t } = await translation('auth', locale); - return metadataModule.generate({ - description: t('header.desc'), - title: t('tab.stats'), - url: '/profile/stats', - }); -}; - -const Page = async (props: DynamicLayoutProps) => { - const mobile = await RouteVariants.getIsMobile(props); - return ; -}; - -export default Page;