@lobehub/chat 0.57.0 → 0.58.0

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 (38) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +1 -1
  3. package/src/components/SafeSpacing/index.tsx +27 -0
  4. package/src/const/layoutTokens.ts +2 -0
  5. package/src/features/AvatarWithUpload/index.tsx +4 -3
  6. package/src/features/MobileTabBar/index.tsx +60 -0
  7. package/src/features/SideBar/TopActions.tsx +1 -1
  8. package/src/layout/AppMobileLayout.tsx +76 -0
  9. package/src/locales/default/common.ts +3 -1
  10. package/src/pages/chat/SessionList/Header.tsx +26 -2
  11. package/src/pages/chat/SessionList/index.tsx +10 -1
  12. package/src/pages/chat/[id]/index.page.tsx +19 -7
  13. package/src/pages/chat/[id]/mobile.tsx +31 -0
  14. package/src/pages/chat/[id]/setting/Header.tsx +25 -2
  15. package/src/pages/chat/[id]/setting/index.page.tsx +33 -16
  16. package/src/pages/chat/[id]/setting/mobile.tsx +11 -0
  17. package/src/pages/chat/features/Conversation/index.tsx +5 -5
  18. package/src/pages/chat/features/Header/Mobile.tsx +47 -0
  19. package/src/pages/chat/features/Header/PC.tsx +101 -0
  20. package/src/pages/chat/features/Header/index.tsx +7 -93
  21. package/src/pages/chat/features/Sidebar/Header.tsx +6 -4
  22. package/src/pages/chat/features/Sidebar/Mobile.tsx +36 -0
  23. package/src/pages/chat/features/Sidebar/SystemRole.tsx +2 -2
  24. package/src/pages/chat/features/Sidebar/index.tsx +33 -15
  25. package/src/pages/chat/index.page.tsx +14 -8
  26. package/src/pages/chat/layout.tsx +12 -8
  27. package/src/pages/chat/mobile.tsx +25 -0
  28. package/src/pages/settings/index.page.tsx +40 -8
  29. package/src/pages/settings/layout.tsx +5 -27
  30. package/src/pages/settings/mobile.tsx +20 -0
  31. package/src/pages/welcome/Banner/Hero.tsx +3 -5
  32. package/src/pages/welcome/Banner/index.tsx +5 -5
  33. package/src/pages/welcome/index.page.tsx +13 -4
  34. package/src/pages/welcome/mobile.tsx +20 -0
  35. package/src/pages/welcome/style.ts +2 -2
  36. package/src/store/global/initialState.ts +2 -0
  37. package/src/store/global/slices/common.ts +7 -0
  38. package/src/components/HeaderSpacing/index.tsx +0 -9
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.58.0](https://github.com/lobehub/lobe-chat/compare/v0.57.0...v0.58.0)
6
+
7
+ <sup>Released on **2023-08-26**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Implement responsive design for mobile devices.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: Implement responsive design for mobile devices, closes [#95](https://github.com/lobehub/lobe-chat/issues/95) ([fdb3c93](https://github.com/lobehub/lobe-chat/commit/fdb3c93))
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.57.0](https://github.com/lobehub/lobe-chat/compare/v0.56.0...v0.57.0)
6
31
 
7
32
  <sup>Released on **2023-08-26**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.57.0",
3
+ "version": "0.58.0",
4
4
  "description": "Lobe Chat is an open-source chatbot client using LangChain, Typescript and Next.js",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -0,0 +1,27 @@
1
+ import { MobileSafeArea, type MobileSafeAreaProps } from '@lobehub/ui';
2
+ import { memo } from 'react';
3
+
4
+ import { HEADER_HEIGHT, MOBILE_NABBAR_HEIGHT, MOBILE_TABBAR_HEIGHT } from '@/const/layoutTokens';
5
+
6
+ interface SafeSpacingProps {
7
+ height?: number;
8
+ mobile?: boolean;
9
+ position?: MobileSafeAreaProps['position'];
10
+ }
11
+
12
+ const SafeSpacing = memo<SafeSpacingProps>(({ height, position = 'top', mobile }) => {
13
+ let h;
14
+ if (mobile) {
15
+ h = position === 'top' ? MOBILE_NABBAR_HEIGHT : MOBILE_TABBAR_HEIGHT;
16
+ } else {
17
+ h = HEADER_HEIGHT;
18
+ }
19
+ return (
20
+ <>
21
+ <div style={{ flex: 'none', height: height || h }} />
22
+ {mobile && <MobileSafeArea position={position} />}
23
+ </>
24
+ );
25
+ });
26
+
27
+ export default SafeSpacing;
@@ -1,6 +1,8 @@
1
1
  import type { FormProps } from '@lobehub/ui';
2
2
 
3
3
  export const HEADER_HEIGHT = 64;
4
+ export const MOBILE_NABBAR_HEIGHT = 44;
5
+ export const MOBILE_TABBAR_HEIGHT = 48;
4
6
  export const CHAT_TEXTAREA_HEIGHT = 200;
5
7
  export const CHAT_SIDEBAR_WIDTH = 280;
6
8
  export const FOLDER_WIDTH = 256;
@@ -1,7 +1,7 @@
1
1
  import { Avatar, Logo } from '@lobehub/ui';
2
2
  import { Upload } from 'antd';
3
3
  import { createStyles } from 'antd-style';
4
- import { memo } from 'react';
4
+ import { CSSProperties, memo } from 'react';
5
5
 
6
6
  import { useGlobalStore } from '@/store/global';
7
7
  import { createUploadImageHandler } from '@/utils/uploadFIle';
@@ -25,9 +25,10 @@ const useStyle = createStyles(
25
25
  interface AvatarWithUploadProps {
26
26
  compressSize?: number;
27
27
  size?: number;
28
+ style?: CSSProperties;
28
29
  }
29
30
 
30
- const AvatarWithUpload = memo<AvatarWithUploadProps>(({ size = 40, compressSize = 128 }) => {
31
+ const AvatarWithUpload = memo<AvatarWithUploadProps>(({ size = 40, compressSize = 128, style }) => {
31
32
  const [avatar, setSettings] = useGlobalStore((st) => [st.settings.avatar, st.setSettings]);
32
33
  const { styles } = useStyle();
33
34
 
@@ -67,7 +68,7 @@ const AvatarWithUpload = memo<AvatarWithUploadProps>(({ size = 40, compressSize
67
68
  });
68
69
 
69
70
  return (
70
- <div className={styles} style={{ maxHeight: size, maxWidth: size }}>
71
+ <div className={styles} style={{ maxHeight: size, maxWidth: size, ...style }}>
71
72
  <Upload beforeUpload={handleUploadAvatar} itemRender={() => void 0} maxCount={1}>
72
73
  {avatar ? <Avatar avatar={avatar} size={size} /> : <Logo size={size} />}
73
74
  </Upload>
@@ -0,0 +1,60 @@
1
+ import { Icon, MobileTabBar, type MobileTabBarProps } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import { MessageSquare, Settings2, Sticker } from 'lucide-react';
4
+ import Router from 'next/router';
5
+ import { rgba } from 'polished';
6
+ import { memo, useMemo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+
9
+ import { useGlobalStore } from '@/store/global';
10
+
11
+ const useStyles = createStyles(({ css, token }) => ({
12
+ active: css`
13
+ svg {
14
+ fill: ${rgba(token.colorPrimary, 0.25)};
15
+ }
16
+ `,
17
+ }));
18
+
19
+ export default memo<{ className?: string }>(({ className }) => {
20
+ const [tab, setTab] = useGlobalStore((s) => [s.sidebarKey, s.switchSideBar]);
21
+ const { t } = useTranslation('common');
22
+ const { styles } = useStyles();
23
+ const items: MobileTabBarProps['items'] = useMemo(
24
+ () => [
25
+ {
26
+ icon: (active) => (
27
+ <Icon className={active ? styles.active : undefined} icon={MessageSquare} />
28
+ ),
29
+ key: 'chat',
30
+ onClick: () => {
31
+ Router.push('/chat');
32
+ },
33
+ title: t('tab.chat'),
34
+ },
35
+ {
36
+ icon: (active) => <Icon className={active ? styles.active : undefined} icon={Sticker} />,
37
+ key: 'market',
38
+
39
+ title: t('tab.market'),
40
+ },
41
+ {
42
+ icon: (active) => <Icon className={active ? styles.active : undefined} icon={Settings2} />,
43
+ key: 'settings',
44
+ onClick: () => {
45
+ Router.push('/settings');
46
+ },
47
+ title: t('tab.setting'),
48
+ },
49
+ ],
50
+ [t],
51
+ );
52
+ return (
53
+ <MobileTabBar
54
+ activeKey={tab}
55
+ className={className}
56
+ items={items}
57
+ onChange={(key) => setTab(key as any)}
58
+ />
59
+ );
60
+ });
@@ -22,8 +22,8 @@ const TopActions = memo<TopActionProps>(({ tab, setTab }) => {
22
22
  onClick={() => {
23
23
  // 如果已经在 chat 路径下了,那么就不用再跳转了
24
24
  if (Router.asPath.startsWith('/chat')) return;
25
-
26
25
  Router.push('/chat');
26
+ setTab('chat');
27
27
  }}
28
28
  size="large"
29
29
  title={t('tab.chat')}
@@ -0,0 +1,76 @@
1
+ import { type MobileNavBarTitleProps } from '@lobehub/ui';
2
+ import { createStyles } from 'antd-style';
3
+ import Head from 'next/head';
4
+ import { CSSProperties, PropsWithChildren, ReactNode, memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import SafeSpacing from '@/components/SafeSpacing';
8
+ import MobileTabBar from '@/features/MobileTabBar';
9
+
10
+ const useStyles = createStyles(({ css }) => ({
11
+ container: css`
12
+ position: relative;
13
+
14
+ overflow-x: hidden;
15
+ overflow-y: auto;
16
+
17
+ width: 100vw;
18
+ height: 100vh;
19
+ `,
20
+ mobileNavBar: css`
21
+ position: fixed;
22
+ z-index: 100;
23
+ top: 0;
24
+ right: 0;
25
+ left: 0;
26
+ `,
27
+ mobileTabBar: css`
28
+ position: fixed;
29
+ z-index: 100;
30
+ right: 0;
31
+ bottom: 0;
32
+ left: 0;
33
+ `,
34
+ }));
35
+
36
+ interface AppMobileLayoutProps extends PropsWithChildren {
37
+ className?: string;
38
+ navBar?: ReactNode;
39
+ showTabBar?: boolean;
40
+ style?: CSSProperties;
41
+ title?: MobileNavBarTitleProps;
42
+ }
43
+
44
+ const AppMobileLayout = memo<AppMobileLayoutProps>(
45
+ ({ children, showTabBar, navBar, style, className }) => {
46
+ const { styles, cx } = useStyles();
47
+
48
+ return (
49
+ <>
50
+ <Head>
51
+ <meta
52
+ content="width=device-width, initial-scale=1.0, viewport-fit=cover"
53
+ name="viewport"
54
+ />
55
+ </Head>
56
+ <Flexbox className={cx(styles.container, className)} style={style}>
57
+ {navBar && (
58
+ <>
59
+ <div className={styles.mobileNavBar}>{navBar}</div>
60
+ <SafeSpacing mobile position={'top'} />
61
+ </>
62
+ )}
63
+ {children}
64
+ {showTabBar && (
65
+ <>
66
+ <SafeSpacing mobile position={'bottom'} />
67
+ <MobileTabBar className={styles.mobileTabBar} />
68
+ </>
69
+ )}
70
+ </Flexbox>
71
+ </>
72
+ );
73
+ },
74
+ );
75
+
76
+ export default AppMobileLayout;
@@ -63,7 +63,8 @@ export default {
63
63
  stop: '停止',
64
64
  tab: {
65
65
  chat: '会话',
66
- market: '助手市场',
66
+ market: '发现',
67
+ setting: '设置',
67
68
  },
68
69
  temp: '临时',
69
70
  tokenDetail: '角色设定: {{systemRoleToken}} · 历史消息: {{chatsToken}}',
@@ -77,6 +78,7 @@ export default {
77
78
  defaultTitle: '默认话题',
78
79
  saveCurrentMessages: '将当前会话保存为话题',
79
80
  searchPlaceholder: '搜索话题...',
81
+ title: '话题',
80
82
  },
81
83
  updateAgent: '更新助理信息',
82
84
  updatePrompt: '更新提示词',
@@ -1,4 +1,4 @@
1
- import { ActionIcon, Logo, SearchBar } from '@lobehub/ui';
1
+ import { ActionIcon, Logo, MobileNavBar, SearchBar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { MessageSquarePlus } from 'lucide-react';
4
4
  import Link from 'next/link';
@@ -6,6 +6,7 @@ import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Flexbox } from 'react-layout-kit';
8
8
 
9
+ import AvatarWithUpload from '@/features/AvatarWithUpload';
9
10
  import { useSessionStore } from '@/store/session';
10
11
 
11
12
  export const useStyles = createStyles(({ css, token }) => ({
@@ -18,11 +19,34 @@ export const useStyles = createStyles(({ css, token }) => ({
18
19
  `,
19
20
  }));
20
21
 
21
- const Header = memo(() => {
22
+ const Header = memo<{ mobile?: 'navbar' | 'search' }>(({ mobile }) => {
22
23
  const { styles } = useStyles();
23
24
  const { t } = useTranslation('common');
24
25
  const [keywords, createSession] = useSessionStore((s) => [s.searchKeywords, s.createSession]);
25
26
 
27
+ if (mobile === 'navbar') {
28
+ return (
29
+ <MobileNavBar
30
+ center={<Logo type={'text'} />}
31
+ left={<AvatarWithUpload size={28} style={{ marginLeft: 8 }} />}
32
+ right={<ActionIcon icon={MessageSquarePlus} onClick={createSession} />}
33
+ />
34
+ );
35
+ }
36
+
37
+ if (mobile === 'search')
38
+ return (
39
+ <Flexbox className={styles.top} padding={16}>
40
+ <SearchBar
41
+ allowClear
42
+ onChange={(e) => useSessionStore.setState({ searchKeywords: e.target.value })}
43
+ placeholder={t('searchAgentPlaceholder')}
44
+ type={'block'}
45
+ value={keywords}
46
+ />
47
+ </Flexbox>
48
+ );
49
+
26
50
  return (
27
51
  <Flexbox className={styles.top} gap={16} padding={16}>
28
52
  <Flexbox distribution={'space-between'} horizontal>
@@ -13,7 +13,7 @@ import SessionList from './List';
13
13
 
14
14
  const useStyles = createStyles(({ stylish }) => stylish.noScrollbar);
15
15
 
16
- export const Sessions = memo(() => {
16
+ export const Sessions = memo<{ mobile?: boolean }>(({ mobile }) => {
17
17
  const { t } = useTranslation('common');
18
18
  const { styles } = useStyles();
19
19
 
@@ -28,6 +28,15 @@ export const Sessions = memo(() => {
28
28
  [],
29
29
  );
30
30
 
31
+ if (mobile)
32
+ return (
33
+ <>
34
+ <Header mobile={'search'} />
35
+ <Inbox />
36
+ <CollapseGroup defaultActiveKey={['sessionList']} items={items} />
37
+ </>
38
+ );
39
+
31
40
  return (
32
41
  <FolderPanel>
33
42
  <Header />
@@ -1,3 +1,4 @@
1
+ import { useResponsive } from 'antd-style';
1
2
  import Head from 'next/head';
2
3
  import { memo } from 'react';
3
4
  import { Flexbox } from 'react-layout-kit';
@@ -9,8 +10,10 @@ import Conversation from '../features/Conversation';
9
10
  import Header from '../features/Header';
10
11
  import SideBar from '../features/Sidebar';
11
12
  import Layout from './layout';
13
+ import Mobile from './mobile';
12
14
 
13
15
  const Chat = memo(() => {
16
+ const { mobile } = useResponsive();
14
17
  const [avatar, title] = useSessionStore((s) => [
15
18
  agentSelectors.currentAgentAvatar(s),
16
19
  agentSelectors.currentAgentTitle(s),
@@ -23,13 +26,22 @@ const Chat = memo(() => {
23
26
  <Head>
24
27
  <title>{pageTitle}</title>
25
28
  </Head>
26
- <Layout>
27
- <Header />
28
- <Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
29
- <Conversation />
30
- <SideBar />
31
- </Flexbox>
32
- </Layout>
29
+ {mobile ? (
30
+ <Mobile>
31
+ <Flexbox height={'calc(100vh - 44px)'} horizontal>
32
+ <Conversation mobile />
33
+ <SideBar mobile />
34
+ </Flexbox>
35
+ </Mobile>
36
+ ) : (
37
+ <Layout>
38
+ <Header />
39
+ <Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
40
+ <Conversation />
41
+ <SideBar />
42
+ </Flexbox>
43
+ </Layout>
44
+ )}
33
45
  </>
34
46
  );
35
47
  });
@@ -0,0 +1,31 @@
1
+ import { useRouter } from 'next/router';
2
+ import { PropsWithChildren, memo } from 'react';
3
+ import { shallow } from 'zustand/shallow';
4
+
5
+ import AppMobileLayout from '@/layout/AppMobileLayout';
6
+ import { useOnFinishHydrationSession, useSessionStore } from '@/store/session';
7
+
8
+ import Header from '../features/Header';
9
+
10
+ const MobileLayout = memo<PropsWithChildren>(({ children }) => {
11
+ const [activeSession, toggleTopic] = useSessionStore((s) => {
12
+ return [s.activeSession, s.toggleTopic];
13
+ }, shallow);
14
+
15
+ const router = useRouter();
16
+ const { id } = router.query;
17
+
18
+ useOnFinishHydrationSession(() => {
19
+ // 1. 正常激活会话
20
+ if (typeof id === 'string') {
21
+ activeSession(id);
22
+ }
23
+
24
+ // 将话题重置为默认值
25
+ toggleTopic();
26
+ }, [id]);
27
+
28
+ return <AppMobileLayout navBar={<Header />}>{children}</AppMobileLayout>;
29
+ });
30
+
31
+ export default MobileLayout;
@@ -1,4 +1,10 @@
1
- import { ActionIcon, ChatHeader, ChatHeaderTitle } from '@lobehub/ui';
1
+ import {
2
+ ActionIcon,
3
+ ChatHeader,
4
+ ChatHeaderTitle,
5
+ MobileNavBar,
6
+ MobileNavBarTitle,
7
+ } from '@lobehub/ui';
2
8
  import { Dropdown, MenuProps } from 'antd';
3
9
  import { HardDriveDownload, Share2 } from 'lucide-react';
4
10
  import Router from 'next/router';
@@ -8,7 +14,7 @@ import { useTranslation } from 'react-i18next';
8
14
  import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
9
15
  import { useSessionStore } from '@/store/session';
10
16
 
11
- const Header = memo(() => {
17
+ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
12
18
  const { t } = useTranslation('setting');
13
19
  const id = useSessionStore((s) => s.activeId);
14
20
 
@@ -36,6 +42,23 @@ const Header = memo(() => {
36
42
  [],
37
43
  );
38
44
 
45
+ if (mobile)
46
+ return (
47
+ <MobileNavBar
48
+ center={<MobileNavBarTitle title={t('header.session')} />}
49
+ onBackClick={() => Router.back()}
50
+ right={
51
+ <>
52
+ <ActionIcon icon={Share2} title={t('share', { ns: 'common' })} />
53
+ <Dropdown arrow={false} menu={{ items }} trigger={['click']}>
54
+ <ActionIcon icon={HardDriveDownload} title={t('export', { ns: 'common' })} />
55
+ </Dropdown>
56
+ </>
57
+ }
58
+ showBackButton
59
+ />
60
+ );
61
+
39
62
  return (
40
63
  <ChatHeader
41
64
  left={<ChatHeaderTitle title={t('header.session')} />}
@@ -1,10 +1,11 @@
1
+ import { useResponsive } from 'antd-style';
1
2
  import isEqual from 'fast-deep-equal';
2
3
  import Head from 'next/head';
3
4
  import { memo } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
  import { Flexbox } from 'react-layout-kit';
6
7
 
7
- import HeaderSpacing from '@/components/HeaderSpacing';
8
+ import SafeSpacing from '@/components/SafeSpacing';
8
9
  import { HEADER_HEIGHT } from '@/const/layoutTokens';
9
10
  import { AgentConfig, AgentMeta, AgentPlugin, AgentPrompt } from '@/features/AgentSetting';
10
11
  import { agentSelectors, useSessionStore } from '@/store/session';
@@ -12,8 +13,10 @@ import { genSiteHeadTitle } from '@/utils/genSiteHeadTitle';
12
13
 
13
14
  import ChatLayout from '../layout';
14
15
  import Header from './Header';
16
+ import Mobile from './mobile';
15
17
 
16
18
  const EditPage = memo(() => {
19
+ const { mobile } = useResponsive();
17
20
  const { t } = useTranslation('setting');
18
21
  const config = useSessionStore(agentSelectors.currentAgentConfig, isEqual);
19
22
  const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
@@ -27,26 +30,40 @@ const EditPage = memo(() => {
27
30
 
28
31
  const pageTitle = genSiteHeadTitle(t('header.sessionWithName', { name: title }));
29
32
 
33
+ const settings = (
34
+ <>
35
+ <AgentPrompt config={config} updateConfig={updateAgentConfig} />
36
+ <AgentMeta
37
+ autocomplete={autocomplete}
38
+ config={config}
39
+ meta={meta}
40
+ updateMeta={updateAgentMeta}
41
+ />
42
+ <AgentConfig config={config} updateConfig={updateAgentConfig} />
43
+ <AgentPlugin config={config} updateConfig={toggleAgentPlugin} />
44
+ </>
45
+ );
46
+
30
47
  return (
31
48
  <>
32
49
  <Head>
33
50
  <title>{pageTitle}</title>
34
51
  </Head>
35
- <ChatLayout>
36
- <Header />
37
- <Flexbox align={'center'} flex={1} gap={16} padding={24} style={{ overflow: 'auto' }}>
38
- <HeaderSpacing height={HEADER_HEIGHT - 16} />
39
- <AgentPrompt config={config} updateConfig={updateAgentConfig} />
40
- <AgentMeta
41
- autocomplete={autocomplete}
42
- config={config}
43
- meta={meta}
44
- updateMeta={updateAgentMeta}
45
- />
46
- <AgentConfig config={config} updateConfig={updateAgentConfig} />
47
- <AgentPlugin config={config} updateConfig={toggleAgentPlugin} />
48
- </Flexbox>
49
- </ChatLayout>
52
+ {mobile ? (
53
+ <Mobile>
54
+ <Flexbox gap={16} padding={16}>
55
+ {settings}
56
+ </Flexbox>
57
+ </Mobile>
58
+ ) : (
59
+ <ChatLayout>
60
+ <Header />
61
+ <Flexbox align={'center'} flex={1} gap={16} padding={24} style={{ overflow: 'auto' }}>
62
+ <SafeSpacing height={HEADER_HEIGHT - 16} />
63
+ {settings}
64
+ </Flexbox>
65
+ </ChatLayout>
66
+ )}
50
67
  </>
51
68
  );
52
69
  });
@@ -0,0 +1,11 @@
1
+ import { PropsWithChildren, memo } from 'react';
2
+
3
+ import AppMobileLayout from '@/layout/AppMobileLayout';
4
+
5
+ import Header from './Header';
6
+
7
+ const MobileLayout = memo<PropsWithChildren>(({ children }) => {
8
+ return <AppMobileLayout navBar={<Header mobile />}>{children}</AppMobileLayout>;
9
+ });
10
+
11
+ export default MobileLayout;
@@ -3,19 +3,19 @@ import { memo, useRef } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { Flexbox } from 'react-layout-kit';
5
5
 
6
- import HeaderSpacing from '@/components/HeaderSpacing';
6
+ import SafeSpacing from '@/components/SafeSpacing';
7
7
 
8
8
  import ChatList from './ChatList';
9
9
  import ChatInput from './Input';
10
10
 
11
- const Conversation = () => {
11
+ const Conversation = memo<{ mobile?: boolean }>(({ mobile }) => {
12
12
  const ref = useRef(null);
13
13
  const { t } = useTranslation('common');
14
14
  return (
15
15
  <Flexbox flex={1} style={{ position: 'relative' }}>
16
16
  <div style={{ flex: 1, overflow: 'hidden', position: 'relative' }}>
17
17
  <div ref={ref} style={{ height: '100%', overflowY: 'scroll' }}>
18
- <HeaderSpacing />
18
+ {!mobile && <SafeSpacing />}
19
19
  <ChatList />
20
20
  </div>
21
21
  <BackBottom target={ref} text={t('backToBottom')} />
@@ -23,6 +23,6 @@ const Conversation = () => {
23
23
  <ChatInput />
24
24
  </Flexbox>
25
25
  );
26
- };
26
+ });
27
27
 
28
- export default memo(Conversation);
28
+ export default Conversation;
@@ -0,0 +1,47 @@
1
+ import { ActionIcon, MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
2
+ import { LayoutList, Settings } from 'lucide-react';
3
+ import Router from 'next/router';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+
7
+ import { INBOX_SESSION_ID } from '@/const/session';
8
+ import { useGlobalStore } from '@/store/global';
9
+ import { agentSelectors, useSessionStore } from '@/store/session';
10
+
11
+ const MobileHeader = memo<{ settings?: boolean }>(({ settings = true }) => {
12
+ const { t } = useTranslation('common');
13
+
14
+ const [id, title, model] = useSessionStore((s) => [
15
+ s.activeId,
16
+ agentSelectors.currentAgentTitle(s),
17
+ agentSelectors.currentAgentModel(s),
18
+ ]);
19
+
20
+ const isInbox = id === INBOX_SESSION_ID;
21
+ const [toggleConfig] = useGlobalStore((s) => [s.toggleMobileTopic]);
22
+
23
+ const displayTitle = isInbox ? t('inbox.title') : title;
24
+
25
+ return (
26
+ <MobileNavBar
27
+ center={<MobileNavBarTitle desc={model} title={displayTitle} />}
28
+ onBackClick={() => Router.back()}
29
+ right={
30
+ <>
31
+ <ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
32
+ {settings && (
33
+ <ActionIcon
34
+ icon={Settings}
35
+ onClick={() => {
36
+ Router.push(`/chat/${id}/setting`);
37
+ }}
38
+ />
39
+ )}
40
+ </>
41
+ }
42
+ showBackButton
43
+ />
44
+ );
45
+ });
46
+
47
+ export default MobileHeader;