From bb8085e7071681685120d00473bd5816ef3bf68a Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Mon, 25 Sep 2023 14:33:53 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(migration):=20Nex?= =?UTF-8?q?t.js=20app=20router=20(#220)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/StyleRegistry.tsx | 23 +++++++ src/app/chat/mobile/page.page.tsx | 7 +++ src/app/chat/page.page.tsx | 7 +++ src/app/chat/settings/page.page.tsx | 7 +++ src/app/layout.page.tsx | 45 ++++++++++++++ src/app/market/page.page.tsx | 7 +++ src/app/page.page.tsx | 7 +++ src/app/settings/page.page.tsx | 7 +++ src/app/welcome/page.page.tsx | 7 +++ src/components/AppTheme/index.tsx | 61 +++++++++++++++++++ src/const/settings.ts | 2 - src/const/theme.ts | 5 ++ src/features/MobileTabBar/index.tsx | 7 ++- src/features/SideBar/BottomActions.tsx | 5 +- src/features/SideBar/TopActions.tsx | 10 +-- src/layout/{ => GlobalLayout}/index.tsx | 56 ++++++++--------- src/layout/{ => GlobalLayout}/style.ts | 0 src/pages/_app.page.tsx | 11 ---- src/pages/_document.page.tsx | 46 -------------- src/pages/chat/features/Header/Desktop.tsx | 5 +- src/pages/chat/features/Header/Mobile.tsx | 7 ++- .../features/SessionList/Header/Mobile.tsx | 5 +- src/pages/chat/{index.page.tsx => index.tsx} | 2 + .../chat/mobile/{index.page.tsx => index.tsx} | 2 + .../chat/setting/features/Header/Desktop.tsx | 5 +- .../chat/setting/features/Header/Mobile.tsx | 5 +- .../setting/{index.page.tsx => index.tsx} | 2 + src/pages/{index => home}/Loading.tsx | 0 src/pages/{index.page.tsx => home/index.tsx} | 16 +++-- src/pages/market/features/Header/Mobile.tsx | 6 +- .../market/{index.page.tsx => index.tsx} | 2 + src/pages/settings/features/Header/Mobile.tsx | 5 +- .../settings/{index.page.tsx => index.tsx} | 2 + .../welcome/{index.page.tsx => index.tsx} | 2 + .../hooks/useOnFinishHydrationSession.ts | 7 ++- src/store/session/slices/session/action.ts | 9 ++- .../session/slices/session/initialState.ts | 2 + src/types/settings.ts | 4 +- src/utils/cookie.ts | 4 ++ tsconfig.json | 10 ++- 40 files changed, 291 insertions(+), 131 deletions(-) create mode 100644 src/app/StyleRegistry.tsx create mode 100644 src/app/chat/mobile/page.page.tsx create mode 100644 src/app/chat/page.page.tsx create mode 100644 src/app/chat/settings/page.page.tsx create mode 100644 src/app/layout.page.tsx create mode 100644 src/app/market/page.page.tsx create mode 100644 src/app/page.page.tsx create mode 100644 src/app/settings/page.page.tsx create mode 100644 src/app/welcome/page.page.tsx create mode 100644 src/components/AppTheme/index.tsx create mode 100644 src/const/theme.ts rename src/layout/{ => GlobalLayout}/index.tsx (54%) rename src/layout/{ => GlobalLayout}/style.ts (100%) delete mode 100644 src/pages/_app.page.tsx delete mode 100644 src/pages/_document.page.tsx rename src/pages/chat/{index.page.tsx => index.tsx} (98%) rename src/pages/chat/mobile/{index.page.tsx => index.tsx} (98%) rename src/pages/chat/setting/{index.page.tsx => index.tsx} (98%) rename src/pages/{index => home}/Loading.tsx (100%) rename src/pages/{index.page.tsx => home/index.tsx} (61%) rename src/pages/market/{index.page.tsx => index.tsx} (98%) rename src/pages/settings/{index.page.tsx => index.tsx} (98%) rename src/pages/welcome/{index.page.tsx => index.tsx} (98%) create mode 100644 src/utils/cookie.ts diff --git a/src/app/StyleRegistry.tsx b/src/app/StyleRegistry.tsx new file mode 100644 index 0000000000..51a2c9e755 --- /dev/null +++ b/src/app/StyleRegistry.tsx @@ -0,0 +1,23 @@ +'use client'; + +import { StyleProvider, extractStaticStyle } from 'antd-style'; +import { useServerInsertedHTML } from 'next/navigation'; +import { PropsWithChildren, useRef } from 'react'; + +const StyleRegistry = ({ children }: PropsWithChildren) => { + const isInsert = useRef(false); + + useServerInsertedHTML(() => { + // avoid duplicate css insert + // refs: https://github.com/vercel/next.js/discussions/49354#discussioncomment-6279917 + if (isInsert.current) return; + + isInsert.current = true; + + return extractStaticStyle().map((item) => item.style); + }); + + return {children}; +}; + +export default StyleRegistry; diff --git a/src/app/chat/mobile/page.page.tsx b/src/app/chat/mobile/page.page.tsx new file mode 100644 index 0000000000..4e4a731752 --- /dev/null +++ b/src/app/chat/mobile/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/chat/mobile'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/app/chat/page.page.tsx b/src/app/chat/page.page.tsx new file mode 100644 index 0000000000..031e8e3b65 --- /dev/null +++ b/src/app/chat/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/chat'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/app/chat/settings/page.page.tsx b/src/app/chat/settings/page.page.tsx new file mode 100644 index 0000000000..12119a1db9 --- /dev/null +++ b/src/app/chat/settings/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/chat/setting'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/app/layout.page.tsx b/src/app/layout.page.tsx new file mode 100644 index 0000000000..90b4915121 --- /dev/null +++ b/src/app/layout.page.tsx @@ -0,0 +1,45 @@ +import { Analytics } from '@vercel/analytics/react'; +import { Metadata } from 'next'; +import { cookies } from 'next/headers'; +import { PropsWithChildren } from 'react'; + +import { + LOBE_THEME_APPEARANCE, + LOBE_THEME_NEUTRAL_COLOR, + LOBE_THEME_PRIMARY_COLOR, +} from '@/const/theme'; +import Layout from '@/layout/GlobalLayout'; + +import StyleRegistry from './StyleRegistry'; + +export const metadata: Metadata = { + manifest: '/manifest.json', + title: 'LobeChat', +}; + +const RootLayout = ({ children }: PropsWithChildren) => { + // get default theme config to use with ssr + const cookieStore = cookies(); + const appearance = cookieStore.get(LOBE_THEME_APPEARANCE); + const neutralColor = cookieStore.get(LOBE_THEME_NEUTRAL_COLOR); + const primaryColor = cookieStore.get(LOBE_THEME_PRIMARY_COLOR); + + return ( + + + + + {children} + + + + + + ); +}; + +export default RootLayout; diff --git a/src/app/market/page.page.tsx b/src/app/market/page.page.tsx new file mode 100644 index 0000000000..4922ba629c --- /dev/null +++ b/src/app/market/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/market'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/app/page.page.tsx b/src/app/page.page.tsx new file mode 100644 index 0000000000..10950f8100 --- /dev/null +++ b/src/app/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/home'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/app/settings/page.page.tsx b/src/app/settings/page.page.tsx new file mode 100644 index 0000000000..80a480fe65 --- /dev/null +++ b/src/app/settings/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/settings'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/app/welcome/page.page.tsx b/src/app/welcome/page.page.tsx new file mode 100644 index 0000000000..fbba61e0e3 --- /dev/null +++ b/src/app/welcome/page.page.tsx @@ -0,0 +1,7 @@ +import Page from '@/pages/welcome'; + +const Index = () => { + return ; +}; + +export default Index; diff --git a/src/components/AppTheme/index.tsx b/src/components/AppTheme/index.tsx new file mode 100644 index 0000000000..eb0286d07a --- /dev/null +++ b/src/components/AppTheme/index.tsx @@ -0,0 +1,61 @@ +import { NeutralColors, PrimaryColors, ThemeProvider } from '@lobehub/ui'; +import { ThemeAppearance } from 'antd-style'; +import { ReactNode, memo, useEffect } from 'react'; + +import { + LOBE_THEME_APPEARANCE, + LOBE_THEME_NEUTRAL_COLOR, + LOBE_THEME_PRIMARY_COLOR, +} from '@/const/theme'; +import { useGlobalStore } from '@/store/global'; +import { GlobalStyle } from '@/styles'; +import { setCookie } from '@/utils/cookie'; + +export interface AppThemeProps { + children?: ReactNode; + defaultAppearance?: ThemeAppearance; + defaultNeutralColor?: NeutralColors; + defaultPrimaryColor?: PrimaryColors; +} + +const AppTheme = memo( + ({ children, defaultAppearance, defaultPrimaryColor, defaultNeutralColor }) => { + console.log('server:appearance', defaultAppearance); + console.log('server:primaryColor', defaultPrimaryColor); + console.log('server:neutralColor', defaultNeutralColor); + const themeMode = useGlobalStore((s) => s.settings.themeMode); + + const [primaryColor, neutralColor] = useGlobalStore((s) => [ + s.settings.primaryColor, + s.settings.neutralColor, + ]); + + useEffect(() => { + console.log(primaryColor); + setCookie(LOBE_THEME_PRIMARY_COLOR, primaryColor); + }, [primaryColor]); + + useEffect(() => { + setCookie(LOBE_THEME_NEUTRAL_COLOR, neutralColor); + }, [neutralColor]); + + return ( + { + setCookie(LOBE_THEME_APPEARANCE, appearance); + }} + themeMode={themeMode} + > + + {children} + + ); + }, +); + +export default AppTheme; diff --git a/src/const/settings.ts b/src/const/settings.ts index ff70f0382b..ad48574e9a 100644 --- a/src/const/settings.ts +++ b/src/const/settings.ts @@ -13,9 +13,7 @@ export const DEFAULT_BASE_SETTINGS: GlobalBaseSettings = { avatar: '', fontSize: 14, language: 'zh-CN', - neutralColor: '', password: '', - primaryColor: '', themeMode: 'auto', }; diff --git a/src/const/theme.ts b/src/const/theme.ts new file mode 100644 index 0000000000..e0643be8f5 --- /dev/null +++ b/src/const/theme.ts @@ -0,0 +1,5 @@ +export const LOBE_THEME_APPEARANCE = 'LOBE_THEME_APPEARANCE'; + +export const LOBE_THEME_PRIMARY_COLOR = 'LOBE_THEME_PRIMARY_COLOR'; + +export const LOBE_THEME_NEUTRAL_COLOR = 'LOBE_THEME_NEUTRAL_COLOR'; diff --git a/src/features/MobileTabBar/index.tsx b/src/features/MobileTabBar/index.tsx index 796d7435c3..51b8c23bab 100644 --- a/src/features/MobileTabBar/index.tsx +++ b/src/features/MobileTabBar/index.tsx @@ -1,7 +1,7 @@ import { Icon, MobileTabBar, type MobileTabBarProps } from '@lobehub/ui'; import { createStyles } from 'antd-style'; import { Bot, MessageSquare } from 'lucide-react'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { rgba } from 'polished'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,6 +20,7 @@ export default memo<{ className?: string }>(({ className }) => { const [tab, setTab] = useGlobalStore((s) => [s.sidebarKey, s.switchSideBar]); const { t } = useTranslation('common'); const { styles } = useStyles(); + const router = useRouter(); const items: MobileTabBarProps['items'] = useMemo( () => [ { @@ -28,7 +29,7 @@ export default memo<{ className?: string }>(({ className }) => { ), key: 'chat', onClick: () => { - Router.push('/chat'); + router.push('/chat'); }, title: t('tab.chat'), }, @@ -36,7 +37,7 @@ export default memo<{ className?: string }>(({ className }) => { icon: (active) => , key: 'market', onClick: () => { - Router.push({ hash: '', pathname: `/market` }); + router.push('/market', { hash: '' }); }, title: t('tab.market'), }, diff --git a/src/features/SideBar/BottomActions.tsx b/src/features/SideBar/BottomActions.tsx index 86efaceb88..c7faa95d72 100644 --- a/src/features/SideBar/BottomActions.tsx +++ b/src/features/SideBar/BottomActions.tsx @@ -10,7 +10,7 @@ import { Settings, Settings2, } from 'lucide-react'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -25,6 +25,7 @@ export interface BottomActionProps { } const BottomActions = memo(({ tab, setTab }) => { + const router = useRouter(); const { t } = useTranslation('common'); const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig(); @@ -101,7 +102,7 @@ const BottomActions = memo(({ tab, setTab }) => { label: t('setting'), onClick: () => { setTab('settings'); - Router.push('/settings'); + router.push('/settings'); }, }, ], diff --git a/src/features/SideBar/TopActions.tsx b/src/features/SideBar/TopActions.tsx index 679c70fb4b..031c8313b3 100644 --- a/src/features/SideBar/TopActions.tsx +++ b/src/features/SideBar/TopActions.tsx @@ -1,6 +1,6 @@ import { ActionIcon } from '@lobehub/ui'; import { Bot, MessageSquare } from 'lucide-react'; -import Router from 'next/router'; +import { usePathname, useRouter } from 'next/navigation'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -13,6 +13,8 @@ export interface TopActionProps { } const TopActions = memo(({ tab, setTab }) => { + const pathname = usePathname(); + const router = useRouter(); const { t } = useTranslation('common'); const switchBackToChat = useSessionStore((s) => s.switchBackToChat); return ( @@ -22,7 +24,7 @@ const TopActions = memo(({ tab, setTab }) => { icon={MessageSquare} onClick={() => { // 如果已经在 chat 路径下了,那么就不用再跳转了 - if (Router.asPath.startsWith('/chat')) return; + if (pathname?.startsWith('/chat')) return; switchBackToChat(); setTab('chat'); }} @@ -34,8 +36,8 @@ const TopActions = memo(({ tab, setTab }) => { active={tab === 'market'} icon={Bot} onClick={() => { - if (Router.asPath.startsWith('/market')) return; - Router.push('/market'); + if (pathname?.startsWith('/market')) return; + router.push('/market'); setTab('market'); }} placement={'right'} diff --git a/src/layout/index.tsx b/src/layout/GlobalLayout/index.tsx similarity index 54% rename from src/layout/index.tsx rename to src/layout/GlobalLayout/index.tsx index 5fce95d2fd..3c5200899d 100644 --- a/src/layout/index.tsx +++ b/src/layout/GlobalLayout/index.tsx @@ -1,16 +1,17 @@ -import { ThemeProvider, lobeCustomTheme } from '@lobehub/ui'; +'use client'; + import { App, ConfigProvider } from 'antd'; -import { useThemeMode } from 'antd-style'; import 'antd/dist/reset.css'; import Zh_CN from 'antd/locale/zh_CN'; import { changeLanguage } from 'i18next'; -import { PropsWithChildren, memo, useCallback, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { PropsWithChildren, memo, useEffect } from 'react'; +import AppTheme, { AppThemeProps } from '@/components/AppTheme'; import { createI18nNext } from '@/locales/create'; import { useGlobalStore, useOnFinishHydrationGlobal } from '@/store/global'; import { usePluginStore } from '@/store/plugin'; import { useOnFinishHydrationSession, useSessionStore } from '@/store/session'; -import { GlobalStyle } from '@/styles'; import { useStyles } from './style'; @@ -19,14 +20,26 @@ const i18n = createI18nNext(); const Layout = memo(({ children }) => { const { styles } = useStyles(); + const router = useRouter(); + + useEffect(() => { + // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated + useSessionStore.persist.rehydrate(); + useGlobalStore.persist.rehydrate(); + usePluginStore.persist.rehydrate(); + }, []); + useOnFinishHydrationGlobal((state) => { i18n.then(() => { changeLanguage(state.settings.language); }); }); - useOnFinishHydrationSession((s) => { + useOnFinishHydrationSession((s, store) => { usePluginStore.getState().checkLocalEnabledPlugins(s.sessions); + + // add router instance to store + store.setState({ router }); }); return ( @@ -36,31 +49,10 @@ const Layout = memo(({ children }) => { ); }); -export default memo(({ children }: PropsWithChildren) => { - useEffect(() => { - // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated - useSessionStore.persist.rehydrate(); - useGlobalStore.persist.rehydrate(); - usePluginStore.persist.rehydrate(); - }, []); +const ThemeWrapper = ({ children, ...theme }: AppThemeProps) => ( + + {children} + +); - const themeMode = useGlobalStore((s) => s.settings.themeMode); - const [primaryColor, neutralColor] = useGlobalStore((s) => [ - s.settings.primaryColor, - s.settings.neutralColor, - ]); - const { browserPrefers } = useThemeMode(); - const isDarkMode = themeMode === 'auto' ? browserPrefers === 'dark' : themeMode === 'dark'; - - const genCustomToken: any = useCallback( - () => lobeCustomTheme({ isDarkMode, neutralColor, primaryColor }), - [primaryColor, neutralColor, isDarkMode], - ); - - return ( - - - {children} - - ); -}); +export default ThemeWrapper; diff --git a/src/layout/style.ts b/src/layout/GlobalLayout/style.ts similarity index 100% rename from src/layout/style.ts rename to src/layout/GlobalLayout/style.ts diff --git a/src/pages/_app.page.tsx b/src/pages/_app.page.tsx deleted file mode 100644 index 14bf5c53b1..0000000000 --- a/src/pages/_app.page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Analytics } from '@vercel/analytics/react'; -import type { AppProps } from 'next/app'; - -import Layout from '@/layout'; - -export default ({ Component, pageProps }: AppProps) => ( - - - - -); diff --git a/src/pages/_document.page.tsx b/src/pages/_document.page.tsx deleted file mode 100644 index dee2001179..0000000000 --- a/src/pages/_document.page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Meta } from '@lobehub/ui'; -import { StyleProvider, extractStaticStyle } from 'antd-style'; -import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document'; - -class MyDocument extends Document { - static async getInitialProps(ctx: DocumentContext) { - const page = await ctx.renderPage({ - enhanceApp: (App) => (props) => ( - - - - ), - }); - - const styles = extractStaticStyle(page.html).map((item) => item.style); - - const initialProps = await Document.getInitialProps(ctx); - - return { - ...initialProps, - styles: ( - <> - {initialProps.styles} - {styles} - - ), - }; - } - - render() { - return ( - - - - - - -
- - - - ); - } -} - -export default MyDocument; diff --git a/src/pages/chat/features/Header/Desktop.tsx b/src/pages/chat/features/Header/Desktop.tsx index 20917fc4ba..9b40aa0b8d 100644 --- a/src/pages/chat/features/Header/Desktop.tsx +++ b/src/pages/chat/features/Header/Desktop.tsx @@ -2,7 +2,7 @@ import { SiOpenai } from '@icons-pack/react-simple-icons'; import { ActionIcon, Avatar, ChatHeader, ChatHeaderTitle, Tag } from '@lobehub/ui'; import { Skeleton } from 'antd'; import { PanelRightClose, PanelRightOpen, Settings } from 'lucide-react'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; import { Flexbox } from 'react-layout-kit'; @@ -16,6 +16,7 @@ import ShareButton from './ShareButton'; const Header = memo(() => { const init = useSessionChatInit(); + const router = useRouter(); const { t } = useTranslation('common'); @@ -80,7 +81,7 @@ const Header = memo(() => { { - Router.push({ hash: location.hash, pathname: `/chat/setting` }); + router.push('/chat/settings', { hash: location.hash }); }} size={{ fontSize: 24 }} title={t('header.session', { ns: 'setting' })} diff --git a/src/pages/chat/features/Header/Mobile.tsx b/src/pages/chat/features/Header/Mobile.tsx index 9dfe172979..ba91304faf 100644 --- a/src/pages/chat/features/Header/Mobile.tsx +++ b/src/pages/chat/features/Header/Mobile.tsx @@ -1,6 +1,6 @@ import { ActionIcon, MobileNavBar, MobileNavBarTitle } from '@lobehub/ui'; import { LayoutList, Settings } from 'lucide-react'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -10,6 +10,7 @@ import { agentSelectors, sessionSelectors } from '@/store/session/selectors'; const MobileHeader = memo(() => { const { t } = useTranslation('common'); + const router = useRouter(); const [isInbox, title, model] = useSessionStore((s) => [ sessionSelectors.isInboxSession(s), @@ -24,7 +25,7 @@ const MobileHeader = memo(() => { return ( } - onBackClick={() => Router.push({ hash: null, pathname: `/chat` })} + onBackClick={() => router.push('/chat', { hash: null })} right={ <> toggleConfig()} /> @@ -32,7 +33,7 @@ const MobileHeader = memo(() => { { - Router.push({ hash: location.hash, pathname: `/chat/setting` }); + router.push('/chat/settings', { hash: location.hash }); }} /> )} diff --git a/src/pages/chat/features/SessionList/Header/Mobile.tsx b/src/pages/chat/features/SessionList/Header/Mobile.tsx index 4686d3bdf2..d0cb04d2e3 100644 --- a/src/pages/chat/features/SessionList/Header/Mobile.tsx +++ b/src/pages/chat/features/SessionList/Header/Mobile.tsx @@ -1,7 +1,7 @@ import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui'; import { createStyles } from 'antd-style'; import { MessageSquarePlus, Settings2 } from 'lucide-react'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { memo } from 'react'; import AvatarWithUpload from '@/features/AvatarWithUpload'; @@ -19,6 +19,7 @@ export const useStyles = createStyles(({ css, token }) => ({ const Header = memo(() => { const [createSession] = useSessionStore((s) => [s.createSession]); + const router = useRouter(); return ( { { - Router.push({ pathname: `/settings` }); + router.push('/settings'); }} /> diff --git a/src/pages/chat/index.page.tsx b/src/pages/chat/index.tsx similarity index 98% rename from src/pages/chat/index.page.tsx rename to src/pages/chat/index.tsx index fb05fbb81c..14be013f86 100644 --- a/src/pages/chat/index.page.tsx +++ b/src/pages/chat/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useResponsive } from 'antd-style'; import Head from 'next/head'; import { memo } from 'react'; diff --git a/src/pages/chat/mobile/index.page.tsx b/src/pages/chat/mobile/index.tsx similarity index 98% rename from src/pages/chat/mobile/index.page.tsx rename to src/pages/chat/mobile/index.tsx index c831341c0b..7cb0e7b1d1 100644 --- a/src/pages/chat/mobile/index.page.tsx +++ b/src/pages/chat/mobile/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import Head from 'next/head'; import { memo } from 'react'; import { Flexbox } from 'react-layout-kit'; diff --git a/src/pages/chat/setting/features/Header/Desktop.tsx b/src/pages/chat/setting/features/Header/Desktop.tsx index 1cf1a5745c..7eb4737fda 100644 --- a/src/pages/chat/setting/features/Header/Desktop.tsx +++ b/src/pages/chat/setting/features/Header/Desktop.tsx @@ -1,15 +1,16 @@ import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { ReactNode, memo } from 'react'; import { useTranslation } from 'react-i18next'; const Header = memo<{ children: ReactNode }>(({ children }) => { const { t } = useTranslation('setting'); + const router = useRouter(); return ( } - onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat` })} + onBackClick={() => router.push('/chat', { hash: location.hash })} right={children} showBackButton /> diff --git a/src/pages/chat/setting/features/Header/Mobile.tsx b/src/pages/chat/setting/features/Header/Mobile.tsx index bc52e747c4..cbe53085b2 100644 --- a/src/pages/chat/setting/features/Header/Mobile.tsx +++ b/src/pages/chat/setting/features/Header/Mobile.tsx @@ -1,15 +1,16 @@ import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { type ReactNode, memo } from 'react'; import { useTranslation } from 'react-i18next'; const Header = memo<{ children: ReactNode }>(({ children }) => { const { t } = useTranslation('setting'); + const router = useRouter(); return ( } - onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat/mobile` })} + onBackClick={() => router.push('/chat/mobile', { hash: location.hash })} right={children} showBackButton /> diff --git a/src/pages/chat/setting/index.page.tsx b/src/pages/chat/setting/index.tsx similarity index 98% rename from src/pages/chat/setting/index.page.tsx rename to src/pages/chat/setting/index.tsx index 4604805cfd..f38d11ee3c 100644 --- a/src/pages/chat/setting/index.page.tsx +++ b/src/pages/chat/setting/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useResponsive } from 'antd-style'; import isEqual from 'fast-deep-equal'; import Head from 'next/head'; diff --git a/src/pages/index/Loading.tsx b/src/pages/home/Loading.tsx similarity index 100% rename from src/pages/index/Loading.tsx rename to src/pages/home/Loading.tsx diff --git a/src/pages/index.page.tsx b/src/pages/home/index.tsx similarity index 61% rename from src/pages/index.page.tsx rename to src/pages/home/index.tsx index b8a9699caa..9405e07c95 100644 --- a/src/pages/index.page.tsx +++ b/src/pages/home/index.tsx @@ -1,10 +1,16 @@ -import { memo, useEffect } from 'react'; +'use client'; -import { useSessionHydrated, useSessionStore } from '@/store/session'; +import { memo } from 'react'; + +import Loading from '@/pages/home/Loading'; +import { + useEffectAfterSessionHydrated, + useSessionHydrated, + useSessionStore, +} from '@/store/session'; import { sessionSelectors } from '@/store/session/selectors'; -import Loading from './index/Loading'; -import Welcome from './welcome/index.page'; +import Welcome from '../welcome'; const Home = memo(() => { const hydrated = useSessionHydrated(); @@ -13,7 +19,7 @@ const Home = memo(() => { s.switchSession, ]); - useEffect(() => { + useEffectAfterSessionHydrated(() => { if (hasSession) switchSession(); }, [hasSession]); diff --git a/src/pages/market/features/Header/Mobile.tsx b/src/pages/market/features/Header/Mobile.tsx index 3848ce5107..ba140d1fea 100644 --- a/src/pages/market/features/Header/Mobile.tsx +++ b/src/pages/market/features/Header/Mobile.tsx @@ -1,11 +1,13 @@ import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui'; import { Settings2 } from 'lucide-react'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { memo } from 'react'; import AvatarWithUpload from '@/features/AvatarWithUpload'; const Header = memo(() => { + const router = useRouter(); + return ( } @@ -14,7 +16,7 @@ const Header = memo(() => { { - Router.push({ pathname: `/settings` }); + router.push('/settings'); }} /> } diff --git a/src/pages/market/index.page.tsx b/src/pages/market/index.tsx similarity index 98% rename from src/pages/market/index.page.tsx rename to src/pages/market/index.tsx index 7fb5de4b7a..2886194052 100644 --- a/src/pages/market/index.page.tsx +++ b/src/pages/market/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useResponsive } from 'antd-style'; import Head from 'next/head'; import { memo, useEffect } from 'react'; diff --git a/src/pages/settings/features/Header/Mobile.tsx b/src/pages/settings/features/Header/Mobile.tsx index 300dbc7b70..133e2bc152 100644 --- a/src/pages/settings/features/Header/Mobile.tsx +++ b/src/pages/settings/features/Header/Mobile.tsx @@ -1,15 +1,16 @@ import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui'; -import Router from 'next/router'; +import { useRouter } from 'next/navigation'; import { memo } from 'react'; import { useTranslation } from 'react-i18next'; const Header = memo(() => { const { t } = useTranslation('setting'); + const router = useRouter(); return ( } - onBackClick={() => Router.push('/chat')} + onBackClick={() => router.push('/chat')} showBackButton /> ); diff --git a/src/pages/settings/index.page.tsx b/src/pages/settings/index.tsx similarity index 98% rename from src/pages/settings/index.page.tsx rename to src/pages/settings/index.tsx index 1034a78fa2..c1189b496b 100644 --- a/src/pages/settings/index.page.tsx +++ b/src/pages/settings/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useResponsive } from 'antd-style'; import Head from 'next/head'; import { memo } from 'react'; diff --git a/src/pages/welcome/index.page.tsx b/src/pages/welcome/index.tsx similarity index 98% rename from src/pages/welcome/index.page.tsx rename to src/pages/welcome/index.tsx index f02318f94e..fb3fd3537a 100644 --- a/src/pages/welcome/index.page.tsx +++ b/src/pages/welcome/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import { useResponsive } from 'antd-style'; import Head from 'next/head'; import { memo } from 'react'; diff --git a/src/store/session/hooks/useOnFinishHydrationSession.ts b/src/store/session/hooks/useOnFinishHydrationSession.ts index 6596a4441d..707ce506ab 100644 --- a/src/store/session/hooks/useOnFinishHydrationSession.ts +++ b/src/store/session/hooks/useOnFinishHydrationSession.ts @@ -1,4 +1,5 @@ import { useEffect } from 'react'; +import { StoreApi, UseBoundStore } from 'zustand'; import { SessionStore, useSessionStore } from '../store'; @@ -6,11 +7,13 @@ import { SessionStore, useSessionStore } from '../store'; * 当 Session 水合完毕后才会执行的 useEffect * @param fn */ -export const useOnFinishHydrationSession = (fn: (state: SessionStore) => void) => { +export const useOnFinishHydrationSession = ( + fn: (state: SessionStore, store: UseBoundStore>) => void, +) => { useEffect(() => { // 只有当水合完毕后再开始做操作 useSessionStore.persist.onFinishHydration(() => { - fn(useSessionStore.getState()); + fn(useSessionStore.getState(), useSessionStore); }); }, []); }; diff --git a/src/store/session/slices/session/action.ts b/src/store/session/slices/session/action.ts index 613cb90c22..e1f2b41a88 100644 --- a/src/store/session/slices/session/action.ts +++ b/src/store/session/slices/session/action.ts @@ -1,6 +1,5 @@ import { produce } from 'immer'; import { merge } from 'lodash-es'; -import Router from 'next/router'; import { DeepPartial } from 'utility-types'; import { StateCreator } from 'zustand/vanilla'; @@ -154,22 +153,22 @@ export const createSessionSlice: StateCreator< }, switchBackToChat: () => { - const { activeId } = get(); + const { activeId, router } = get(); const id = activeId || INBOX_SESSION_ID; get().activeSession(id); - Router.push(SESSION_CHAT_URL(id, get().isMobile)); + router?.push(SESSION_CHAT_URL(id, get().isMobile)); }, switchSession: (sessionId = INBOX_SESSION_ID) => { - const { isMobile } = get(); + const { isMobile, router } = get(); // mobile also should switch session due to chat mobile route is different // fix https://github.com/lobehub/lobe-chat/issues/163 if (!isMobile && get().activeId === sessionId) return; get().activeSession(sessionId); - Router.push(SESSION_CHAT_URL(sessionId, isMobile)); + router?.push(SESSION_CHAT_URL(sessionId, isMobile)); }, }); diff --git a/src/store/session/slices/session/initialState.ts b/src/store/session/slices/session/initialState.ts index 8ea6043641..aa2791b2ab 100644 --- a/src/store/session/slices/session/initialState.ts +++ b/src/store/session/slices/session/initialState.ts @@ -1,4 +1,5 @@ import { merge } from 'lodash-es'; +import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context'; import { DEFAULT_AGENT_META, DEFAULT_INBOX_AVATAR } from '@/const/meta'; import { LobeAgentConfig, LobeAgentSession, LobeSessionType } from '@/types/session'; @@ -14,6 +15,7 @@ export interface SessionState { // 默认会话 inbox: LobeAgentSession; isMobile?: boolean; + router?: AppRouterInstance; searchKeywords: string; sessions: Record; topicSearchKeywords: string; diff --git a/src/types/settings.ts b/src/types/settings.ts index 0570d13ad6..50ee15fd7b 100644 --- a/src/types/settings.ts +++ b/src/types/settings.ts @@ -37,9 +37,9 @@ export interface GlobalBaseSettings { */ historyCount?: number; language: Locales; - neutralColor: NeutralColors | ''; + neutralColor?: NeutralColors; password: string; - primaryColor: PrimaryColors | ''; + primaryColor?: PrimaryColors; themeMode: ThemeMode; } diff --git a/src/utils/cookie.ts b/src/utils/cookie.ts new file mode 100644 index 0000000000..d7a3bfa988 --- /dev/null +++ b/src/utils/cookie.ts @@ -0,0 +1,4 @@ +export const setCookie = (key: string, value: string | undefined) => { + // eslint-disable-next-line unicorn/no-document-cookie + document.cookie = `${key}=${value};`; +}; diff --git a/tsconfig.json b/tsconfig.json index baf061b36e..84a8305f3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,7 +19,12 @@ "types": ["vitest/globals"], "paths": { "@/*": ["./src/*"] - } + }, + "plugins": [ + { + "name": "next" + } + ] }, "exclude": ["node_modules"], "include": [ @@ -29,7 +34,8 @@ "tests", "**/*.ts", "**/*.d.ts", - "**/*.tsx" + "**/*.tsx", + ".next/types/**/*.ts" ], "ts-node": { "compilerOptions": {