@lobehub/chat 0.79.0 → 0.79.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -1
  3. package/src/app/StyleRegistry.tsx +23 -0
  4. package/src/app/chat/mobile/page.page.tsx +7 -0
  5. package/src/app/chat/page.page.tsx +7 -0
  6. package/src/app/chat/settings/page.page.tsx +7 -0
  7. package/src/app/layout.page.tsx +45 -0
  8. package/src/app/market/page.page.tsx +7 -0
  9. package/src/app/page.page.tsx +7 -0
  10. package/src/app/settings/page.page.tsx +7 -0
  11. package/src/app/welcome/page.page.tsx +7 -0
  12. package/src/components/AppTheme/index.tsx +61 -0
  13. package/src/const/settings.ts +0 -2
  14. package/src/const/theme.ts +5 -0
  15. package/src/features/MobileTabBar/index.tsx +4 -3
  16. package/src/features/SideBar/BottomActions.tsx +3 -2
  17. package/src/features/SideBar/TopActions.tsx +6 -4
  18. package/src/layout/{index.tsx → GlobalLayout/index.tsx} +24 -32
  19. package/src/pages/chat/features/Header/Desktop.tsx +3 -2
  20. package/src/pages/chat/features/Header/Mobile.tsx +4 -3
  21. package/src/pages/chat/features/SessionList/Header/Mobile.tsx +3 -2
  22. package/src/pages/chat/{index.page.tsx → index.tsx} +2 -0
  23. package/src/pages/chat/mobile/{index.page.tsx → index.tsx} +2 -0
  24. package/src/pages/chat/setting/features/Header/Desktop.tsx +3 -2
  25. package/src/pages/chat/setting/features/Header/Mobile.tsx +3 -2
  26. package/src/pages/chat/setting/{index.page.tsx → index.tsx} +2 -0
  27. package/src/pages/{index.page.tsx → home/index.tsx} +11 -5
  28. package/src/pages/market/features/Header/Mobile.tsx +4 -2
  29. package/src/pages/market/{index.page.tsx → index.tsx} +2 -0
  30. package/src/pages/settings/features/Header/Mobile.tsx +3 -2
  31. package/src/pages/settings/{index.page.tsx → index.tsx} +2 -0
  32. package/src/pages/welcome/{index.page.tsx → index.tsx} +2 -0
  33. package/src/store/session/hooks/useOnFinishHydrationSession.ts +5 -2
  34. package/src/store/session/slices/session/action.ts +4 -5
  35. package/src/store/session/slices/session/initialState.ts +2 -0
  36. package/src/types/settings.ts +2 -2
  37. package/src/utils/cookie.ts +4 -0
  38. package/tsconfig.json +8 -2
  39. package/src/pages/_app.page.tsx +0 -11
  40. package/src/pages/_document.page.tsx +0 -46
  41. /package/src/layout/{style.ts → GlobalLayout/style.ts} +0 -0
  42. /package/src/pages/{index → home}/Loading.tsx +0 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.79.1](https://github.com/lobehub/lobe-chat/compare/v0.79.0...v0.79.1)
6
+
7
+ <sup>Released on **2023-09-25**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **migration**: Next.js app router.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **migration**: Next.js app router, closes [#220](https://github.com/lobehub/lobe-chat/issues/220) ([bb8085e](https://github.com/lobehub/lobe-chat/commit/bb8085e))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 0.79.0](https://github.com/lobehub/lobe-chat/compare/v0.78.1...v0.79.0)
6
31
 
7
32
  <sup>Released on **2023-09-25**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.79.0",
3
+ "version": "0.79.1",
4
4
  "description": "Lobe Chat is an open-source chatbot client using Vercel AI SDK, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { StyleProvider, extractStaticStyle } from 'antd-style';
4
+ import { useServerInsertedHTML } from 'next/navigation';
5
+ import { PropsWithChildren, useRef } from 'react';
6
+
7
+ const StyleRegistry = ({ children }: PropsWithChildren) => {
8
+ const isInsert = useRef(false);
9
+
10
+ useServerInsertedHTML(() => {
11
+ // avoid duplicate css insert
12
+ // refs: https://github.com/vercel/next.js/discussions/49354#discussioncomment-6279917
13
+ if (isInsert.current) return;
14
+
15
+ isInsert.current = true;
16
+
17
+ return extractStaticStyle().map((item) => item.style);
18
+ });
19
+
20
+ return <StyleProvider cache={extractStaticStyle.cache}>{children}</StyleProvider>;
21
+ };
22
+
23
+ export default StyleRegistry;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/chat/mobile';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/chat';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/chat/setting';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,45 @@
1
+ import { Analytics } from '@vercel/analytics/react';
2
+ import { Metadata } from 'next';
3
+ import { cookies } from 'next/headers';
4
+ import { PropsWithChildren } from 'react';
5
+
6
+ import {
7
+ LOBE_THEME_APPEARANCE,
8
+ LOBE_THEME_NEUTRAL_COLOR,
9
+ LOBE_THEME_PRIMARY_COLOR,
10
+ } from '@/const/theme';
11
+ import Layout from '@/layout/GlobalLayout';
12
+
13
+ import StyleRegistry from './StyleRegistry';
14
+
15
+ export const metadata: Metadata = {
16
+ manifest: '/manifest.json',
17
+ title: 'LobeChat',
18
+ };
19
+
20
+ const RootLayout = ({ children }: PropsWithChildren) => {
21
+ // get default theme config to use with ssr
22
+ const cookieStore = cookies();
23
+ const appearance = cookieStore.get(LOBE_THEME_APPEARANCE);
24
+ const neutralColor = cookieStore.get(LOBE_THEME_NEUTRAL_COLOR);
25
+ const primaryColor = cookieStore.get(LOBE_THEME_PRIMARY_COLOR);
26
+
27
+ return (
28
+ <html lang="en">
29
+ <body>
30
+ <StyleRegistry>
31
+ <Layout
32
+ defaultAppearance={appearance?.value}
33
+ defaultNeutralColor={neutralColor?.value as any}
34
+ defaultPrimaryColor={primaryColor?.value as any}
35
+ >
36
+ {children}
37
+ </Layout>
38
+ </StyleRegistry>
39
+ <Analytics />
40
+ </body>
41
+ </html>
42
+ );
43
+ };
44
+
45
+ export default RootLayout;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/market';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/home';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/settings';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,7 @@
1
+ import Page from '@/pages/welcome';
2
+
3
+ const Index = () => {
4
+ return <Page />;
5
+ };
6
+
7
+ export default Index;
@@ -0,0 +1,61 @@
1
+ import { NeutralColors, PrimaryColors, ThemeProvider } from '@lobehub/ui';
2
+ import { ThemeAppearance } from 'antd-style';
3
+ import { ReactNode, memo, useEffect } from 'react';
4
+
5
+ import {
6
+ LOBE_THEME_APPEARANCE,
7
+ LOBE_THEME_NEUTRAL_COLOR,
8
+ LOBE_THEME_PRIMARY_COLOR,
9
+ } from '@/const/theme';
10
+ import { useGlobalStore } from '@/store/global';
11
+ import { GlobalStyle } from '@/styles';
12
+ import { setCookie } from '@/utils/cookie';
13
+
14
+ export interface AppThemeProps {
15
+ children?: ReactNode;
16
+ defaultAppearance?: ThemeAppearance;
17
+ defaultNeutralColor?: NeutralColors;
18
+ defaultPrimaryColor?: PrimaryColors;
19
+ }
20
+
21
+ const AppTheme = memo<AppThemeProps>(
22
+ ({ children, defaultAppearance, defaultPrimaryColor, defaultNeutralColor }) => {
23
+ console.log('server:appearance', defaultAppearance);
24
+ console.log('server:primaryColor', defaultPrimaryColor);
25
+ console.log('server:neutralColor', defaultNeutralColor);
26
+ const themeMode = useGlobalStore((s) => s.settings.themeMode);
27
+
28
+ const [primaryColor, neutralColor] = useGlobalStore((s) => [
29
+ s.settings.primaryColor,
30
+ s.settings.neutralColor,
31
+ ]);
32
+
33
+ useEffect(() => {
34
+ console.log(primaryColor);
35
+ setCookie(LOBE_THEME_PRIMARY_COLOR, primaryColor);
36
+ }, [primaryColor]);
37
+
38
+ useEffect(() => {
39
+ setCookie(LOBE_THEME_NEUTRAL_COLOR, neutralColor);
40
+ }, [neutralColor]);
41
+
42
+ return (
43
+ <ThemeProvider
44
+ customTheme={{
45
+ neutralColor: neutralColor ?? defaultNeutralColor,
46
+ primaryColor: primaryColor ?? defaultPrimaryColor,
47
+ }}
48
+ defaultAppearance={defaultAppearance}
49
+ onAppearanceChange={(appearance) => {
50
+ setCookie(LOBE_THEME_APPEARANCE, appearance);
51
+ }}
52
+ themeMode={themeMode}
53
+ >
54
+ <GlobalStyle />
55
+ {children}
56
+ </ThemeProvider>
57
+ );
58
+ },
59
+ );
60
+
61
+ export default AppTheme;
@@ -13,9 +13,7 @@ export const DEFAULT_BASE_SETTINGS: GlobalBaseSettings = {
13
13
  avatar: '',
14
14
  fontSize: 14,
15
15
  language: 'zh-CN',
16
- neutralColor: '',
17
16
  password: '',
18
- primaryColor: '',
19
17
  themeMode: 'auto',
20
18
  };
21
19
 
@@ -0,0 +1,5 @@
1
+ export const LOBE_THEME_APPEARANCE = 'LOBE_THEME_APPEARANCE';
2
+
3
+ export const LOBE_THEME_PRIMARY_COLOR = 'LOBE_THEME_PRIMARY_COLOR';
4
+
5
+ export const LOBE_THEME_NEUTRAL_COLOR = 'LOBE_THEME_NEUTRAL_COLOR';
@@ -1,7 +1,7 @@
1
1
  import { Icon, MobileTabBar, type MobileTabBarProps } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { Bot, MessageSquare } from 'lucide-react';
4
- import Router from 'next/router';
4
+ import { useRouter } from 'next/navigation';
5
5
  import { rgba } from 'polished';
6
6
  import { memo, useMemo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
@@ -20,6 +20,7 @@ export default memo<{ className?: string }>(({ className }) => {
20
20
  const [tab, setTab] = useGlobalStore((s) => [s.sidebarKey, s.switchSideBar]);
21
21
  const { t } = useTranslation('common');
22
22
  const { styles } = useStyles();
23
+ const router = useRouter();
23
24
  const items: MobileTabBarProps['items'] = useMemo(
24
25
  () => [
25
26
  {
@@ -28,7 +29,7 @@ export default memo<{ className?: string }>(({ className }) => {
28
29
  ),
29
30
  key: 'chat',
30
31
  onClick: () => {
31
- Router.push('/chat');
32
+ router.push('/chat');
32
33
  },
33
34
  title: t('tab.chat'),
34
35
  },
@@ -36,7 +37,7 @@ export default memo<{ className?: string }>(({ className }) => {
36
37
  icon: (active) => <Icon className={active ? styles.active : undefined} icon={Bot} />,
37
38
  key: 'market',
38
39
  onClick: () => {
39
- Router.push({ hash: '', pathname: `/market` });
40
+ router.push('/market', { hash: '' });
40
41
  },
41
42
  title: t('tab.market'),
42
43
  },
@@ -10,7 +10,7 @@ import {
10
10
  Settings,
11
11
  Settings2,
12
12
  } from 'lucide-react';
13
- import Router from 'next/router';
13
+ import { useRouter } from 'next/navigation';
14
14
  import { memo, useMemo } from 'react';
15
15
  import { useTranslation } from 'react-i18next';
16
16
 
@@ -25,6 +25,7 @@ export interface BottomActionProps {
25
25
  }
26
26
 
27
27
  const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
28
+ const router = useRouter();
28
29
  const { t } = useTranslation('common');
29
30
 
30
31
  const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
@@ -101,7 +102,7 @@ const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
101
102
  label: t('setting'),
102
103
  onClick: () => {
103
104
  setTab('settings');
104
- Router.push('/settings');
105
+ router.push('/settings');
105
106
  },
106
107
  },
107
108
  ],
@@ -1,6 +1,6 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
2
  import { Bot, MessageSquare } from 'lucide-react';
3
- import Router from 'next/router';
3
+ import { usePathname, useRouter } from 'next/navigation';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
@@ -13,6 +13,8 @@ export interface TopActionProps {
13
13
  }
14
14
 
15
15
  const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
16
+ const pathname = usePathname();
17
+ const router = useRouter();
16
18
  const { t } = useTranslation('common');
17
19
  const switchBackToChat = useSessionStore((s) => s.switchBackToChat);
18
20
  return (
@@ -22,7 +24,7 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
22
24
  icon={MessageSquare}
23
25
  onClick={() => {
24
26
  // 如果已经在 chat 路径下了,那么就不用再跳转了
25
- if (Router.asPath.startsWith('/chat')) return;
27
+ if (pathname?.startsWith('/chat')) return;
26
28
  switchBackToChat();
27
29
  setTab('chat');
28
30
  }}
@@ -34,8 +36,8 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
34
36
  active={tab === 'market'}
35
37
  icon={Bot}
36
38
  onClick={() => {
37
- if (Router.asPath.startsWith('/market')) return;
38
- Router.push('/market');
39
+ if (pathname?.startsWith('/market')) return;
40
+ router.push('/market');
39
41
  setTab('market');
40
42
  }}
41
43
  placement={'right'}
@@ -1,16 +1,17 @@
1
- import { ThemeProvider, lobeCustomTheme } from '@lobehub/ui';
1
+ 'use client';
2
+
2
3
  import { App, ConfigProvider } from 'antd';
3
- import { useThemeMode } from 'antd-style';
4
4
  import 'antd/dist/reset.css';
5
5
  import Zh_CN from 'antd/locale/zh_CN';
6
6
  import { changeLanguage } from 'i18next';
7
- import { PropsWithChildren, memo, useCallback, useEffect } from 'react';
7
+ import { useRouter } from 'next/navigation';
8
+ import { PropsWithChildren, memo, useEffect } from 'react';
8
9
 
10
+ import AppTheme, { AppThemeProps } from '@/components/AppTheme';
9
11
  import { createI18nNext } from '@/locales/create';
10
12
  import { useGlobalStore, useOnFinishHydrationGlobal } from '@/store/global';
11
13
  import { usePluginStore } from '@/store/plugin';
12
14
  import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
13
- import { GlobalStyle } from '@/styles';
14
15
 
15
16
  import { useStyles } from './style';
16
17
 
@@ -19,14 +20,26 @@ const i18n = createI18nNext();
19
20
  const Layout = memo<PropsWithChildren>(({ children }) => {
20
21
  const { styles } = useStyles();
21
22
 
23
+ const router = useRouter();
24
+
25
+ useEffect(() => {
26
+ // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
27
+ useSessionStore.persist.rehydrate();
28
+ useGlobalStore.persist.rehydrate();
29
+ usePluginStore.persist.rehydrate();
30
+ }, []);
31
+
22
32
  useOnFinishHydrationGlobal((state) => {
23
33
  i18n.then(() => {
24
34
  changeLanguage(state.settings.language);
25
35
  });
26
36
  });
27
37
 
28
- useOnFinishHydrationSession((s) => {
38
+ useOnFinishHydrationSession((s, store) => {
29
39
  usePluginStore.getState().checkLocalEnabledPlugins(s.sessions);
40
+
41
+ // add router instance to store
42
+ store.setState({ router });
30
43
  });
31
44
 
32
45
  return (
@@ -36,31 +49,10 @@ const Layout = memo<PropsWithChildren>(({ children }) => {
36
49
  );
37
50
  });
38
51
 
39
- export default memo(({ children }: PropsWithChildren) => {
40
- useEffect(() => {
41
- // refs: https://github.com/pmndrs/zustand/blob/main/docs/integrations/persisting-store-data.md#hashydrated
42
- useSessionStore.persist.rehydrate();
43
- useGlobalStore.persist.rehydrate();
44
- usePluginStore.persist.rehydrate();
45
- }, []);
46
-
47
- const themeMode = useGlobalStore((s) => s.settings.themeMode);
48
- const [primaryColor, neutralColor] = useGlobalStore((s) => [
49
- s.settings.primaryColor,
50
- s.settings.neutralColor,
51
- ]);
52
- const { browserPrefers } = useThemeMode();
53
- const isDarkMode = themeMode === 'auto' ? browserPrefers === 'dark' : themeMode === 'dark';
52
+ const ThemeWrapper = ({ children, ...theme }: AppThemeProps) => (
53
+ <AppTheme {...theme}>
54
+ <Layout>{children}</Layout>
55
+ </AppTheme>
56
+ );
54
57
 
55
- const genCustomToken: any = useCallback(
56
- () => lobeCustomTheme({ isDarkMode, neutralColor, primaryColor }),
57
- [primaryColor, neutralColor, isDarkMode],
58
- );
59
-
60
- return (
61
- <ThemeProvider customToken={genCustomToken || {}} themeMode={themeMode}>
62
- <GlobalStyle />
63
- <Layout>{children}</Layout>
64
- </ThemeProvider>
65
- );
66
- });
58
+ export default ThemeWrapper;
@@ -2,7 +2,7 @@ import { SiOpenai } from '@icons-pack/react-simple-icons';
2
2
  import { ActionIcon, Avatar, ChatHeader, ChatHeaderTitle, Tag } from '@lobehub/ui';
3
3
  import { Skeleton } from 'antd';
4
4
  import { PanelRightClose, PanelRightOpen, Settings } from 'lucide-react';
5
- import Router from 'next/router';
5
+ import { useRouter } from 'next/navigation';
6
6
  import { memo } from 'react';
7
7
  import { useTranslation } from 'react-i18next';
8
8
  import { Flexbox } from 'react-layout-kit';
@@ -16,6 +16,7 @@ import ShareButton from './ShareButton';
16
16
 
17
17
  const Header = memo(() => {
18
18
  const init = useSessionChatInit();
19
+ const router = useRouter();
19
20
 
20
21
  const { t } = useTranslation('common');
21
22
 
@@ -80,7 +81,7 @@ const Header = memo(() => {
80
81
  <ActionIcon
81
82
  icon={Settings}
82
83
  onClick={() => {
83
- Router.push({ hash: location.hash, pathname: `/chat/setting` });
84
+ router.push('/chat/settings', { hash: location.hash });
84
85
  }}
85
86
  size={{ fontSize: 24 }}
86
87
  title={t('header.session', { ns: 'setting' })}
@@ -1,6 +1,6 @@
1
1
  import { ActionIcon, MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
2
2
  import { LayoutList, Settings } from 'lucide-react';
3
- import Router from 'next/router';
3
+ import { useRouter } from 'next/navigation';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
@@ -10,6 +10,7 @@ import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
10
10
 
11
11
  const MobileHeader = memo(() => {
12
12
  const { t } = useTranslation('common');
13
+ const router = useRouter();
13
14
 
14
15
  const [isInbox, title, model] = useSessionStore((s) => [
15
16
  sessionSelectors.isInboxSession(s),
@@ -24,7 +25,7 @@ const MobileHeader = memo(() => {
24
25
  return (
25
26
  <MobileNavBar
26
27
  center={<MobileNavBarTitle desc={model} title={displayTitle} />}
27
- onBackClick={() => Router.push({ hash: null, pathname: `/chat` })}
28
+ onBackClick={() => router.push('/chat', { hash: null })}
28
29
  right={
29
30
  <>
30
31
  <ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
@@ -32,7 +33,7 @@ const MobileHeader = memo(() => {
32
33
  <ActionIcon
33
34
  icon={Settings}
34
35
  onClick={() => {
35
- Router.push({ hash: location.hash, pathname: `/chat/setting` });
36
+ router.push('/chat/settings', { hash: location.hash });
36
37
  }}
37
38
  />
38
39
  )}
@@ -1,7 +1,7 @@
1
1
  import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { MessageSquarePlus, Settings2 } from 'lucide-react';
4
- import Router from 'next/router';
4
+ import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
 
7
7
  import AvatarWithUpload from '@/features/AvatarWithUpload';
@@ -19,6 +19,7 @@ export const useStyles = createStyles(({ css, token }) => ({
19
19
 
20
20
  const Header = memo(() => {
21
21
  const [createSession] = useSessionStore((s) => [s.createSession]);
22
+ const router = useRouter();
22
23
 
23
24
  return (
24
25
  <MobileNavBar
@@ -30,7 +31,7 @@ const Header = memo(() => {
30
31
  <ActionIcon
31
32
  icon={Settings2}
32
33
  onClick={() => {
33
- Router.push({ pathname: `/settings` });
34
+ router.push('/settings');
34
35
  }}
35
36
  />
36
37
  </>
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useResponsive } from 'antd-style';
2
4
  import Head from 'next/head';
3
5
  import { memo } from 'react';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import Head from 'next/head';
2
4
  import { memo } from 'react';
3
5
  import { Flexbox } from 'react-layout-kit';
@@ -1,15 +1,16 @@
1
1
  import { ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
2
- import Router from 'next/router';
2
+ import { useRouter } from 'next/navigation';
3
3
  import { ReactNode, memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  const Header = memo<{ children: ReactNode }>(({ children }) => {
7
7
  const { t } = useTranslation('setting');
8
+ const router = useRouter();
8
9
 
9
10
  return (
10
11
  <ChatHeader
11
12
  left={<ChatHeaderTitle title={t('header.session')} />}
12
- onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat` })}
13
+ onBackClick={() => router.push('/chat', { hash: location.hash })}
13
14
  right={children}
14
15
  showBackButton
15
16
  />
@@ -1,15 +1,16 @@
1
1
  import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
2
- import Router from 'next/router';
2
+ import { useRouter } from 'next/navigation';
3
3
  import { type ReactNode, memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  const Header = memo<{ children: ReactNode }>(({ children }) => {
7
7
  const { t } = useTranslation('setting');
8
+ const router = useRouter();
8
9
 
9
10
  return (
10
11
  <MobileNavBar
11
12
  center={<MobileNavBarTitle title={t('header.session')} />}
12
- onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat/mobile` })}
13
+ onBackClick={() => router.push('/chat/mobile', { hash: location.hash })}
13
14
  right={children}
14
15
  showBackButton
15
16
  />
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useResponsive } from 'antd-style';
2
4
  import isEqual from 'fast-deep-equal';
3
5
  import Head from 'next/head';
@@ -1,10 +1,16 @@
1
- import { memo, useEffect } from 'react';
1
+ 'use client';
2
2
 
3
- import { useSessionHydrated, useSessionStore } from '@/store/session';
3
+ import { memo } from 'react';
4
+
5
+ import Loading from '@/pages/home/Loading';
6
+ import {
7
+ useEffectAfterSessionHydrated,
8
+ useSessionHydrated,
9
+ useSessionStore,
10
+ } from '@/store/session';
4
11
  import { sessionSelectors } from '@/store/session/selectors';
5
12
 
6
- import Loading from './index/Loading';
7
- import Welcome from './welcome/index.page';
13
+ import Welcome from '../welcome';
8
14
 
9
15
  const Home = memo(() => {
10
16
  const hydrated = useSessionHydrated();
@@ -13,7 +19,7 @@ const Home = memo(() => {
13
19
  s.switchSession,
14
20
  ]);
15
21
 
16
- useEffect(() => {
22
+ useEffectAfterSessionHydrated(() => {
17
23
  if (hasSession) switchSession();
18
24
  }, [hasSession]);
19
25
 
@@ -1,11 +1,13 @@
1
1
  import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
2
2
  import { Settings2 } from 'lucide-react';
3
- import Router from 'next/router';
3
+ import { useRouter } from 'next/navigation';
4
4
  import { memo } from 'react';
5
5
 
6
6
  import AvatarWithUpload from '@/features/AvatarWithUpload';
7
7
 
8
8
  const Header = memo(() => {
9
+ const router = useRouter();
10
+
9
11
  return (
10
12
  <MobileNavBar
11
13
  center={<Logo type={'text'} />}
@@ -14,7 +16,7 @@ const Header = memo(() => {
14
16
  <ActionIcon
15
17
  icon={Settings2}
16
18
  onClick={() => {
17
- Router.push({ pathname: `/settings` });
19
+ router.push('/settings');
18
20
  }}
19
21
  />
20
22
  }
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useResponsive } from 'antd-style';
2
4
  import Head from 'next/head';
3
5
  import { memo, useEffect } from 'react';
@@ -1,15 +1,16 @@
1
1
  import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
2
- import Router from 'next/router';
2
+ import { useRouter } from 'next/navigation';
3
3
  import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  const Header = memo(() => {
7
7
  const { t } = useTranslation('setting');
8
+ const router = useRouter();
8
9
 
9
10
  return (
10
11
  <MobileNavBar
11
12
  center={<MobileNavBarTitle title={t('header.global')} />}
12
- onBackClick={() => Router.push('/chat')}
13
+ onBackClick={() => router.push('/chat')}
13
14
  showBackButton
14
15
  />
15
16
  );
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useResponsive } from 'antd-style';
2
4
  import Head from 'next/head';
3
5
  import { memo } from 'react';
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { useResponsive } from 'antd-style';
2
4
  import Head from 'next/head';
3
5
  import { memo } from 'react';
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from 'react';
2
+ import { StoreApi, UseBoundStore } from 'zustand';
2
3
 
3
4
  import { SessionStore, useSessionStore } from '../store';
4
5
 
@@ -6,11 +7,13 @@ import { SessionStore, useSessionStore } from '../store';
6
7
  * 当 Session 水合完毕后才会执行的 useEffect
7
8
  * @param fn
8
9
  */
9
- export const useOnFinishHydrationSession = (fn: (state: SessionStore) => void) => {
10
+ export const useOnFinishHydrationSession = (
11
+ fn: (state: SessionStore, store: UseBoundStore<StoreApi<SessionStore>>) => void,
12
+ ) => {
10
13
  useEffect(() => {
11
14
  // 只有当水合完毕后再开始做操作
12
15
  useSessionStore.persist.onFinishHydration(() => {
13
- fn(useSessionStore.getState());
16
+ fn(useSessionStore.getState(), useSessionStore);
14
17
  });
15
18
  }, []);
16
19
  };
@@ -1,6 +1,5 @@
1
1
  import { produce } from 'immer';
2
2
  import { merge } from 'lodash-es';
3
- import Router from 'next/router';
4
3
  import { DeepPartial } from 'utility-types';
5
4
  import { StateCreator } from 'zustand/vanilla';
6
5
 
@@ -154,22 +153,22 @@ export const createSessionSlice: StateCreator<
154
153
  },
155
154
 
156
155
  switchBackToChat: () => {
157
- const { activeId } = get();
156
+ const { activeId, router } = get();
158
157
 
159
158
  const id = activeId || INBOX_SESSION_ID;
160
159
 
161
160
  get().activeSession(id);
162
161
 
163
- Router.push(SESSION_CHAT_URL(id, get().isMobile));
162
+ router?.push(SESSION_CHAT_URL(id, get().isMobile));
164
163
  },
165
164
  switchSession: (sessionId = INBOX_SESSION_ID) => {
166
- const { isMobile } = get();
165
+ const { isMobile, router } = get();
167
166
  // mobile also should switch session due to chat mobile route is different
168
167
  // fix https://github.com/lobehub/lobe-chat/issues/163
169
168
  if (!isMobile && get().activeId === sessionId) return;
170
169
 
171
170
  get().activeSession(sessionId);
172
171
 
173
- Router.push(SESSION_CHAT_URL(sessionId, isMobile));
172
+ router?.push(SESSION_CHAT_URL(sessionId, isMobile));
174
173
  },
175
174
  });
@@ -1,4 +1,5 @@
1
1
  import { merge } from 'lodash-es';
2
+ import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context';
2
3
 
3
4
  import { DEFAULT_AGENT_META, DEFAULT_INBOX_AVATAR } from '@/const/meta';
4
5
  import { LobeAgentConfig, LobeAgentSession, LobeSessionType } from '@/types/session';
@@ -14,6 +15,7 @@ export interface SessionState {
14
15
  // 默认会话
15
16
  inbox: LobeAgentSession;
16
17
  isMobile?: boolean;
18
+ router?: AppRouterInstance;
17
19
  searchKeywords: string;
18
20
  sessions: Record<string, LobeAgentSession>;
19
21
  topicSearchKeywords: string;
@@ -37,9 +37,9 @@ export interface GlobalBaseSettings {
37
37
  */
38
38
  historyCount?: number;
39
39
  language: Locales;
40
- neutralColor: NeutralColors | '';
40
+ neutralColor?: NeutralColors;
41
41
  password: string;
42
- primaryColor: PrimaryColors | '';
42
+ primaryColor?: PrimaryColors;
43
43
  themeMode: ThemeMode;
44
44
  }
45
45
 
@@ -0,0 +1,4 @@
1
+ export const setCookie = (key: string, value: string | undefined) => {
2
+ // eslint-disable-next-line unicorn/no-document-cookie
3
+ document.cookie = `${key}=${value};`;
4
+ };
package/tsconfig.json CHANGED
@@ -19,7 +19,12 @@
19
19
  "types": ["vitest/globals"],
20
20
  "paths": {
21
21
  "@/*": ["./src/*"]
22
- }
22
+ },
23
+ "plugins": [
24
+ {
25
+ "name": "next"
26
+ }
27
+ ]
23
28
  },
24
29
  "exclude": ["node_modules"],
25
30
  "include": [
@@ -29,7 +34,8 @@
29
34
  "tests",
30
35
  "**/*.ts",
31
36
  "**/*.d.ts",
32
- "**/*.tsx"
37
+ "**/*.tsx",
38
+ ".next/types/**/*.ts"
33
39
  ],
34
40
  "ts-node": {
35
41
  "compilerOptions": {
@@ -1,11 +0,0 @@
1
- import { Analytics } from '@vercel/analytics/react';
2
- import type { AppProps } from 'next/app';
3
-
4
- import Layout from '@/layout';
5
-
6
- export default ({ Component, pageProps }: AppProps) => (
7
- <Layout>
8
- <Component {...pageProps} />
9
- <Analytics />
10
- </Layout>
11
- );
@@ -1,46 +0,0 @@
1
- import { Meta } from '@lobehub/ui';
2
- import { StyleProvider, extractStaticStyle } from 'antd-style';
3
- import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document';
4
-
5
- class MyDocument extends Document {
6
- static async getInitialProps(ctx: DocumentContext) {
7
- const page = await ctx.renderPage({
8
- enhanceApp: (App) => (props) => (
9
- <StyleProvider cache={extractStaticStyle.cache}>
10
- <App {...props} />
11
- </StyleProvider>
12
- ),
13
- });
14
-
15
- const styles = extractStaticStyle(page.html).map((item) => item.style);
16
-
17
- const initialProps = await Document.getInitialProps(ctx);
18
-
19
- return {
20
- ...initialProps,
21
- styles: (
22
- <>
23
- {initialProps.styles}
24
- {styles}
25
- </>
26
- ),
27
- };
28
- }
29
-
30
- render() {
31
- return (
32
- <Html>
33
- <Head>
34
- <Meta title={'LobeChat'} />
35
- <link href="/manifest.json" rel="manifest" />
36
- </Head>
37
- <body>
38
- <Main />
39
- <NextScript />
40
- </body>
41
- </Html>
42
- );
43
- }
44
- }
45
-
46
- export default MyDocument;
File without changes
File without changes