mirror of
https://github.com/lobehub/lobe-chat.git
synced 2026-06-13 19:20:04 +00:00
♻️ refactor(migration): Next.js app router (#220)
This commit is contained in:
@@ -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 <StyleProvider cache={extractStaticStyle.cache}>{children}</StyleProvider>;
|
||||
};
|
||||
|
||||
export default StyleRegistry;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/chat/mobile';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/chat';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/chat/setting';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -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 (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<StyleRegistry>
|
||||
<Layout
|
||||
defaultAppearance={appearance?.value}
|
||||
defaultNeutralColor={neutralColor?.value as any}
|
||||
defaultPrimaryColor={primaryColor?.value as any}
|
||||
>
|
||||
{children}
|
||||
</Layout>
|
||||
</StyleRegistry>
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootLayout;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/market';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/home';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/settings';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,7 @@
|
||||
import Page from '@/pages/welcome';
|
||||
|
||||
const Index = () => {
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -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<AppThemeProps>(
|
||||
({ 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 (
|
||||
<ThemeProvider
|
||||
customTheme={{
|
||||
neutralColor: neutralColor ?? defaultNeutralColor,
|
||||
primaryColor: primaryColor ?? defaultPrimaryColor,
|
||||
}}
|
||||
defaultAppearance={defaultAppearance}
|
||||
onAppearanceChange={(appearance) => {
|
||||
setCookie(LOBE_THEME_APPEARANCE, appearance);
|
||||
}}
|
||||
themeMode={themeMode}
|
||||
>
|
||||
<GlobalStyle />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default AppTheme;
|
||||
@@ -13,9 +13,7 @@ export const DEFAULT_BASE_SETTINGS: GlobalBaseSettings = {
|
||||
avatar: '',
|
||||
fontSize: 14,
|
||||
language: 'zh-CN',
|
||||
neutralColor: '',
|
||||
password: '',
|
||||
primaryColor: '',
|
||||
themeMode: 'auto',
|
||||
};
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -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) => <Icon className={active ? styles.active : undefined} icon={Bot} />,
|
||||
key: 'market',
|
||||
onClick: () => {
|
||||
Router.push({ hash: '', pathname: `/market` });
|
||||
router.push('/market', { hash: '' });
|
||||
},
|
||||
title: t('tab.market'),
|
||||
},
|
||||
|
||||
@@ -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<BottomActionProps>(({ tab, setTab }) => {
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { exportSessions, exportSettings, exportAll, exportAgents } = useExportConfig();
|
||||
@@ -101,7 +102,7 @@ const BottomActions = memo<BottomActionProps>(({ tab, setTab }) => {
|
||||
label: t('setting'),
|
||||
onClick: () => {
|
||||
setTab('settings');
|
||||
Router.push('/settings');
|
||||
router.push('/settings');
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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<TopActionProps>(({ 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<TopActionProps>(({ 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<TopActionProps>(({ 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'}
|
||||
|
||||
@@ -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<PropsWithChildren>(({ 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<PropsWithChildren>(({ 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) => (
|
||||
<AppTheme {...theme}>
|
||||
<Layout>{children}</Layout>
|
||||
</AppTheme>
|
||||
);
|
||||
|
||||
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 (
|
||||
<ThemeProvider customToken={genCustomToken || {}} themeMode={themeMode}>
|
||||
<GlobalStyle />
|
||||
<Layout>{children}</Layout>
|
||||
</ThemeProvider>
|
||||
);
|
||||
});
|
||||
export default ThemeWrapper;
|
||||
@@ -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) => (
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
<Analytics />
|
||||
</Layout>
|
||||
);
|
||||
@@ -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) => (
|
||||
<StyleProvider cache={extractStaticStyle.cache}>
|
||||
<App {...props} />
|
||||
</StyleProvider>
|
||||
),
|
||||
});
|
||||
|
||||
const styles = extractStaticStyle(page.html).map((item) => item.style);
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
styles: (
|
||||
<>
|
||||
{initialProps.styles}
|
||||
{styles}
|
||||
</>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<Meta title={'LobeChat'} />
|
||||
<link href="/manifest.json" rel="manifest" />
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyDocument;
|
||||
@@ -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(() => {
|
||||
<ActionIcon
|
||||
icon={Settings}
|
||||
onClick={() => {
|
||||
Router.push({ hash: location.hash, pathname: `/chat/setting` });
|
||||
router.push('/chat/settings', { hash: location.hash });
|
||||
}}
|
||||
size={{ fontSize: 24 }}
|
||||
title={t('header.session', { ns: 'setting' })}
|
||||
|
||||
@@ -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 (
|
||||
<MobileNavBar
|
||||
center={<MobileNavBarTitle desc={model} title={displayTitle} />}
|
||||
onBackClick={() => Router.push({ hash: null, pathname: `/chat` })}
|
||||
onBackClick={() => router.push('/chat', { hash: null })}
|
||||
right={
|
||||
<>
|
||||
<ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
|
||||
@@ -32,7 +33,7 @@ const MobileHeader = memo(() => {
|
||||
<ActionIcon
|
||||
icon={Settings}
|
||||
onClick={() => {
|
||||
Router.push({ hash: location.hash, pathname: `/chat/setting` });
|
||||
router.push('/chat/settings', { hash: location.hash });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
<MobileNavBar
|
||||
@@ -30,7 +31,7 @@ const Header = memo(() => {
|
||||
<ActionIcon
|
||||
icon={Settings2}
|
||||
onClick={() => {
|
||||
Router.push({ pathname: `/settings` });
|
||||
router.push('/settings');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useResponsive } from 'antd-style';
|
||||
import Head from 'next/head';
|
||||
import { memo } from 'react';
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import Head from 'next/head';
|
||||
import { memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
@@ -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 (
|
||||
<ChatHeader
|
||||
left={<ChatHeaderTitle title={t('header.session')} />}
|
||||
onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat` })}
|
||||
onBackClick={() => router.push('/chat', { hash: location.hash })}
|
||||
right={children}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
@@ -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 (
|
||||
<MobileNavBar
|
||||
center={<MobileNavBarTitle title={t('header.session')} />}
|
||||
onBackClick={() => Router.push({ hash: location.hash, pathname: `/chat/mobile` })}
|
||||
onBackClick={() => router.push('/chat/mobile', { hash: location.hash })}
|
||||
right={children}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useResponsive } from 'antd-style';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
import Head from 'next/head';
|
||||
@@ -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]);
|
||||
|
||||
@@ -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 (
|
||||
<MobileNavBar
|
||||
center={<Logo type={'text'} />}
|
||||
@@ -14,7 +16,7 @@ const Header = memo(() => {
|
||||
<ActionIcon
|
||||
icon={Settings2}
|
||||
onClick={() => {
|
||||
Router.push({ pathname: `/settings` });
|
||||
router.push('/settings');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useResponsive } from 'antd-style';
|
||||
import Head from 'next/head';
|
||||
import { memo, useEffect } from 'react';
|
||||
@@ -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 (
|
||||
<MobileNavBar
|
||||
center={<MobileNavBarTitle title={t('header.global')} />}
|
||||
onBackClick={() => Router.push('/chat')}
|
||||
onBackClick={() => router.push('/chat')}
|
||||
showBackButton
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useResponsive } from 'antd-style';
|
||||
import Head from 'next/head';
|
||||
import { memo } from 'react';
|
||||
@@ -1,3 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { useResponsive } from 'antd-style';
|
||||
import Head from 'next/head';
|
||||
import { memo } from 'react';
|
||||
@@ -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<StoreApi<SessionStore>>) => void,
|
||||
) => {
|
||||
useEffect(() => {
|
||||
// 只有当水合完毕后再开始做操作
|
||||
useSessionStore.persist.onFinishHydration(() => {
|
||||
fn(useSessionStore.getState());
|
||||
fn(useSessionStore.getState(), useSessionStore);
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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<string, LobeAgentSession>;
|
||||
topicSearchKeywords: string;
|
||||
|
||||
@@ -37,9 +37,9 @@ export interface GlobalBaseSettings {
|
||||
*/
|
||||
historyCount?: number;
|
||||
language: Locales;
|
||||
neutralColor: NeutralColors | '';
|
||||
neutralColor?: NeutralColors;
|
||||
password: string;
|
||||
primaryColor: PrimaryColors | '';
|
||||
primaryColor?: PrimaryColors;
|
||||
themeMode: ThemeMode;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export const setCookie = (key: string, value: string | undefined) => {
|
||||
// eslint-disable-next-line unicorn/no-document-cookie
|
||||
document.cookie = `${key}=${value};`;
|
||||
};
|
||||
+8
-2
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user