@lobehub/chat 0.57.0 → 0.59.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 (45) hide show
  1. package/CHANGELOG.md +51 -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/AgentSetting/AgentPlugin/index.tsx +4 -4
  6. package/src/features/AvatarWithUpload/index.tsx +4 -3
  7. package/src/features/MobileTabBar/index.tsx +60 -0
  8. package/src/features/SideBar/TopActions.tsx +1 -1
  9. package/src/layout/AppMobileLayout.tsx +76 -0
  10. package/src/locales/default/common.ts +4 -1
  11. package/src/pages/chat/SessionList/Header.tsx +26 -2
  12. package/src/pages/chat/SessionList/index.tsx +10 -1
  13. package/src/pages/chat/[id]/index.page.tsx +19 -7
  14. package/src/pages/chat/[id]/mobile.tsx +31 -0
  15. package/src/pages/chat/[id]/setting/Header.tsx +25 -2
  16. package/src/pages/chat/[id]/setting/index.page.tsx +33 -16
  17. package/src/pages/chat/[id]/setting/mobile.tsx +11 -0
  18. package/src/pages/chat/features/Conversation/ChatList/Error/Plugin/PluginSettings.tsx +7 -5
  19. package/src/pages/chat/features/Conversation/ChatList/Plugins/CustomRender.tsx +6 -4
  20. package/src/pages/chat/features/Conversation/index.tsx +5 -5
  21. package/src/pages/chat/features/Header/Mobile.tsx +47 -0
  22. package/src/pages/chat/features/Header/PC.tsx +101 -0
  23. package/src/pages/chat/features/Header/PluginTag.tsx +40 -14
  24. package/src/pages/chat/features/Header/index.tsx +7 -93
  25. package/src/pages/chat/features/Sidebar/Header.tsx +6 -4
  26. package/src/pages/chat/features/Sidebar/Mobile.tsx +36 -0
  27. package/src/pages/chat/features/Sidebar/SystemRole.tsx +2 -2
  28. package/src/pages/chat/features/Sidebar/index.tsx +33 -15
  29. package/src/pages/chat/index.page.tsx +14 -8
  30. package/src/pages/chat/layout.tsx +12 -8
  31. package/src/pages/chat/mobile.tsx +25 -0
  32. package/src/pages/settings/index.page.tsx +40 -8
  33. package/src/pages/settings/layout.tsx +5 -27
  34. package/src/pages/settings/mobile.tsx +20 -0
  35. package/src/pages/welcome/Banner/Hero.tsx +3 -5
  36. package/src/pages/welcome/Banner/index.tsx +5 -5
  37. package/src/pages/welcome/index.page.tsx +13 -4
  38. package/src/pages/welcome/mobile.tsx +20 -0
  39. package/src/pages/welcome/style.ts +2 -2
  40. package/src/store/global/initialState.ts +2 -0
  41. package/src/store/global/slices/common.ts +7 -0
  42. package/src/store/plugin/helpers.ts +26 -0
  43. package/src/store/plugin/index.ts +1 -0
  44. package/src/store/plugin/selectors.ts +30 -4
  45. package/src/components/HeaderSpacing/index.tsx +0 -9
package/CHANGELOG.md CHANGED
@@ -2,6 +2,57 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 0.59.0](https://github.com/lobehub/lobe-chat/compare/v0.58.0...v0.59.0)
6
+
7
+ <sup>Released on **2023-08-26**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: 支持展示插件插件状态, 支持插件 i18n 模式展示.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's improved
19
+
20
+ - **misc**: 支持展示插件插件状态 ([7e916ac](https://github.com/lobehub/lobe-chat/commit/7e916ac))
21
+ - **misc**: 支持插件 i18n 模式展示 ([8614734](https://github.com/lobehub/lobe-chat/commit/8614734))
22
+
23
+ </details>
24
+
25
+ <div align="right">
26
+
27
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
28
+
29
+ </div>
30
+
31
+ ## [Version 0.58.0](https://github.com/lobehub/lobe-chat/compare/v0.57.0...v0.58.0)
32
+
33
+ <sup>Released on **2023-08-26**</sup>
34
+
35
+ #### ✨ Features
36
+
37
+ - **misc**: Implement responsive design for mobile devices.
38
+
39
+ <br/>
40
+
41
+ <details>
42
+ <summary><kbd>Improvements and Fixes</kbd></summary>
43
+
44
+ #### What's improved
45
+
46
+ - **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))
47
+
48
+ </details>
49
+
50
+ <div align="right">
51
+
52
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
53
+
54
+ </div>
55
+
5
56
  ## [Version 0.57.0](https://github.com/lobehub/lobe-chat/compare/v0.56.0...v0.57.0)
6
57
 
7
58
  <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.59.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;
@@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next';
9
9
  import { FORM_STYLE } from '@/const/layoutTokens';
10
10
  import { transformPluginSettings } from '@/features/PluginSettings';
11
11
  import PluginSettingRender from '@/features/PluginSettings/PluginSettingRender';
12
- import { usePluginStore } from '@/store/plugin';
12
+ import { pluginHelpers, usePluginStore } from '@/store/plugin';
13
13
  import { useSessionStore } from '@/store/session';
14
14
  import { AgentAction, agentSelectors } from '@/store/session/slices/agentConfig';
15
15
  import { LobeAgentConfig } from '@/types/session';
@@ -98,8 +98,8 @@ const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
98
98
  }}
99
99
  />
100
100
  ),
101
- desc: meta?.description,
102
- label: meta.title,
101
+ desc: pluginHelpers.getPluginDesc(meta),
102
+ label: pluginHelpers.getPluginTitle(meta),
103
103
  minWidth: undefined,
104
104
  tag: identifier,
105
105
  }));
@@ -131,7 +131,7 @@ const AgentPlugin = memo<AgentPluginProps>(({ config, updateConfig }) => {
131
131
  desc: item.desc && <Markdown className={styles.md}>{item.desc}</Markdown>,
132
132
  })),
133
133
  icon: ToyBrick,
134
- title: t('settingPlugin.config', { id: item.meta.title || item.identifier }),
134
+ title: t('settingPlugin.config', { id: pluginHelpers.getPluginTitle(item.meta) }),
135
135
  };
136
136
  })
137
137
  .filter(Boolean) as unknown as ItemGroup[]);
@@ -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;
@@ -53,6 +53,7 @@ export default {
53
53
  regenerate: '重新生成',
54
54
  rename: '重命名',
55
55
  reset: '重置',
56
+ retry: '重试',
56
57
  roleAndArchive: '角色与记录',
57
58
  searchAgentPlaceholder: '搜索助手和对话...',
58
59
  send: '发送',
@@ -63,7 +64,8 @@ export default {
63
64
  stop: '停止',
64
65
  tab: {
65
66
  chat: '会话',
66
- market: '助手市场',
67
+ market: '发现',
68
+ setting: '设置',
67
69
  },
68
70
  temp: '临时',
69
71
  tokenDetail: '角色设定: {{systemRoleToken}} · 历史消息: {{chatsToken}}',
@@ -77,6 +79,7 @@ export default {
77
79
  defaultTitle: '默认话题',
78
80
  saveCurrentMessages: '将当前会话保存为话题',
79
81
  searchPlaceholder: '搜索话题...',
82
+ title: '话题',
80
83
  },
81
84
  updateAgent: '更新助理信息',
82
85
  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;
@@ -1,12 +1,13 @@
1
1
  import { Avatar } from '@lobehub/ui';
2
2
  import { Button, Divider } from 'antd';
3
3
  import { useTheme } from 'antd-style';
4
+ import isEqual from 'fast-deep-equal';
4
5
  import { memo } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
7
  import { Center, Flexbox } from 'react-layout-kit';
7
8
 
8
9
  import PluginSettingsConfig from '@/features/PluginSettings';
9
- import { pluginSelectors, usePluginStore } from '@/store/plugin';
10
+ import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
10
11
  import { useSessionStore } from '@/store/session';
11
12
 
12
13
  import { ErrorActionContainer, useStyles } from '../style';
@@ -22,20 +23,21 @@ const PluginSettings = memo<PluginSettingsProps>(({ id, pluginIdentifier }) => {
22
23
  const { t } = useTranslation('error');
23
24
  const theme = useTheme();
24
25
  const [resend, deleteMessage] = useSessionStore((s) => [s.resendMessage, s.deleteMessage]);
25
- const plugin = usePluginStore(pluginSelectors.getPluginMetaById(pluginIdentifier));
26
- const manifest = usePluginStore(pluginSelectors.getPluginManifestSettingsById(pluginIdentifier));
26
+
27
+ const plugin = usePluginStore(pluginSelectors.getPluginMetaById(pluginIdentifier), isEqual);
28
+ const manifest = usePluginStore(pluginSelectors.getPluginManifestById(pluginIdentifier), isEqual);
27
29
 
28
30
  return (
29
31
  <ErrorActionContainer>
30
32
  <Center gap={16} style={{ maxWidth: 400 }}>
31
33
  <Avatar
32
- avatar={plugin?.meta.avatar || '⚙️'}
34
+ avatar={pluginHelpers.getPluginAvatar(plugin?.meta) || '⚙️'}
33
35
  background={theme.colorFillContent}
34
36
  gap={12}
35
37
  size={80}
36
38
  />
37
39
  <Flexbox style={{ fontSize: 20 }}>
38
- {t('pluginSettings.title', { name: plugin?.meta.title })}
40
+ {t('pluginSettings.title', { name: pluginHelpers.getPluginTitle(plugin?.meta) })}
39
41
  </Flexbox>
40
42
  <Flexbox className={styles.desc}>{t('pluginSettings.desc')}</Flexbox>
41
43
  <Divider style={{ margin: '0 16px' }} />