@lobehub/chat 0.81.0 → 0.82.2

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 (83) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +22 -6
  3. package/README.zh-CN.md +21 -5
  4. package/docs/Environment-Variable.md +45 -9
  5. package/docs/Environment-Variable.zh-CN.md +3 -6
  6. package/locales/en_US/common.json +13 -1
  7. package/locales/ru_RU/common.json +13 -1
  8. package/locales/zh_CN/common.json +13 -1
  9. package/locales/zh_TW/common.json +13 -1
  10. package/package.json +6 -3
  11. package/public/images/screenshot_background.webp +0 -0
  12. package/public/manifest.json +2 -16
  13. package/src/app/chat/features/Conversation/ChatList/Error/InvalidAccess.tsx +16 -6
  14. package/src/app/chat/features/Header/Mobile.tsx +12 -7
  15. package/src/app/chat/features/Header/ShareButton/Inner.tsx +114 -14
  16. package/src/app/chat/features/Header/ShareButton/Preview.tsx +140 -0
  17. package/src/app/chat/features/Header/ShareButton/index.tsx +12 -2
  18. package/src/app/chat/features/Header/ShareButton/style.ts +71 -0
  19. package/src/app/chat/features/SessionList/Header/Desktop.tsx +2 -4
  20. package/src/app/chat/features/SessionList/Header/Mobile.tsx +15 -14
  21. package/src/app/chat/features/Sidebar/Mobile.tsx +3 -17
  22. package/src/app/chat/layout.desktop.tsx +2 -1
  23. package/src/app/chat/layout.mobile.tsx +2 -1
  24. package/src/app/chat/settings/features/Header/index.tsx +3 -3
  25. package/src/app/chat/settings/layout.mobile.tsx +1 -8
  26. package/src/app/market/features/AgentCard/AgentCardBanner.tsx +2 -1
  27. package/src/app/market/features/Header/Mobile.tsx +2 -21
  28. package/src/app/market/features/SideBar/AgentInfo/Header.tsx +2 -1
  29. package/src/app/market/features/SideBar/AgentInfo/index.tsx +10 -2
  30. package/src/app/market/features/SideBar/Mobile.tsx +3 -17
  31. package/src/app/market/layout.desktop.tsx +2 -1
  32. package/src/app/market/layout.mobile.tsx +2 -1
  33. package/src/app/settings/agent/index.tsx +20 -0
  34. package/src/app/settings/agent/page.tsx +7 -0
  35. package/src/app/settings/{features/Settings → common}/Common.tsx +1 -1
  36. package/src/app/settings/common/index.tsx +20 -0
  37. package/src/app/settings/common/page.tsx +7 -0
  38. package/src/app/settings/features/Header/Desktop.tsx +9 -2
  39. package/src/app/settings/features/Header/Mobile.tsx +5 -2
  40. package/src/app/settings/features/SideBar/Item.tsx +39 -0
  41. package/src/app/settings/features/SideBar/List.tsx +30 -0
  42. package/src/app/settings/features/SideBar/index.tsx +38 -0
  43. package/src/app/settings/index.tsx +5 -7
  44. package/src/app/settings/layout.desktop.tsx +6 -2
  45. package/src/app/settings/layout.mobile.tsx +1 -3
  46. package/src/app/settings/{features/Settings → llm}/LLM/index.tsx +4 -2
  47. package/src/app/settings/llm/index.tsx +20 -0
  48. package/src/app/settings/llm/page.tsx +7 -0
  49. package/src/app/settings/mobile/ExtraList.tsx +61 -0
  50. package/src/app/settings/mobile/index.tsx +43 -0
  51. package/src/app/settings/mobile/page.tsx +7 -0
  52. package/src/app/settings/page.tsx +1 -7
  53. package/src/components/MobilePadding/index.tsx +32 -0
  54. package/src/const/layoutTokens.ts +1 -0
  55. package/src/features/AgentSetting/AgentMeta/index.tsx +0 -1
  56. package/src/features/AgentSetting/AgentPlugin/MarketSettingModal.tsx +29 -16
  57. package/src/features/AvatarWithUpload/index.tsx +23 -45
  58. package/src/features/MobileTabBar/index.tsx +15 -3
  59. package/src/features/PluginDevModal/PluginPreview.tsx +11 -4
  60. package/src/features/PluginDevModal/index.tsx +14 -10
  61. package/src/features/SideBar/BottomActions.tsx +3 -3
  62. package/src/features/SideBar/TopActions.tsx +5 -4
  63. package/src/features/SideBar/index.tsx +1 -1
  64. package/src/locales/create.ts +5 -3
  65. package/src/locales/default/common.ts +13 -1
  66. package/src/locales/default/plugin.ts +2 -1
  67. package/src/services/shareGPT.ts +11 -1
  68. package/src/store/global/hooks/useSwitchSettingsOnInit.ts +9 -0
  69. package/src/store/global/initialState.ts +13 -4
  70. package/src/store/global/store.ts +0 -4
  71. package/src/store/session/slices/chat/actions/share.ts +73 -39
  72. package/src/styles/antdOverride.ts +2 -26
  73. package/src/styles/global.ts +9 -0
  74. package/src/utils/imageToBase64.ts +37 -0
  75. package/src/utils/parseMarkdown.ts +9 -0
  76. package/public/icons/icon-maskable-192x192.png +0 -0
  77. package/public/icons/icon-maskable-512x512.png +0 -0
  78. package/src/app/settings/features/Banner/index.tsx +0 -94
  79. package/src/app/settings/features/Settings/index.tsx +0 -53
  80. /package/src/app/settings/{features/Settings → agent}/Agent.tsx +0 -0
  81. /package/src/app/settings/{features/Settings → llm}/LLM/Checker.tsx +0 -0
  82. /package/src/app/settings/{features/Settings → llm}/LLM/ModelList.tsx +0 -0
  83. /package/src/app/settings/{features/Settings → llm}/LLM/getModelList.ts +0 -0
@@ -4,6 +4,8 @@ import { useRouter } from 'next/navigation';
4
4
  import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
 
7
+ import ShareButton from '@/app/chat/features/Header/ShareButton';
8
+ import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
7
9
  import { useGlobalStore } from '@/store/global';
8
10
  import { useSessionStore } from '@/store/session';
9
11
  import { agentSelectors, sessionSelectors } from '@/store/session/selectors';
@@ -13,10 +15,9 @@ const MobileHeader = memo(() => {
13
15
  const { t } = useTranslation('common');
14
16
  const router = useRouter();
15
17
 
16
- const [isInbox, title, model] = useSessionStore((s) => [
18
+ const [isInbox, title] = useSessionStore((s) => [
17
19
  sessionSelectors.isInboxSession(s),
18
20
  agentSelectors.currentAgentTitle(s),
19
- agentSelectors.currentAgentModel(s),
20
21
  ]);
21
22
 
22
23
  const [toggleConfig] = useGlobalStore((s) => [s.toggleMobileTopic]);
@@ -25,17 +26,21 @@ const MobileHeader = memo(() => {
25
26
 
26
27
  return (
27
28
  <MobileNavBar
28
- center={<MobileNavBarTitle desc={model} title={displayTitle} />}
29
+ center={<MobileNavBarTitle title={displayTitle} />}
29
30
  onBackClick={() => router.push('/chat')}
30
31
  right={
31
32
  <>
32
- <ActionIcon icon={LayoutList} onClick={() => toggleConfig()} />
33
+ <ShareButton />
34
+ <ActionIcon
35
+ icon={LayoutList}
36
+ onClick={() => toggleConfig()}
37
+ size={MOBILE_HEADER_ICON_SIZE}
38
+ />
33
39
  {!isInbox && (
34
40
  <ActionIcon
35
41
  icon={Settings}
36
- onClick={() => {
37
- router.push(pathString('/chat/settings', { hash: location.hash }));
38
- }}
42
+ onClick={() => router.push(pathString('/chat/settings', { hash: location.hash }))}
43
+ size={MOBILE_HEADER_ICON_SIZE}
39
44
  />
40
45
  )}
41
46
  </>
@@ -1,28 +1,128 @@
1
- import { ActionIcon, Icon } from '@lobehub/ui';
2
- import { Loader2, Share2 } from 'lucide-react';
3
- import { memo } from 'react';
1
+ import { Form, ItemGroup } from '@lobehub/ui';
2
+ import { Button, Segmented, SegmentedProps, Switch } from 'antd';
3
+ import { Share2 } from 'lucide-react';
4
+ import { memo, useMemo, useState } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
5
7
 
8
+ import MobilePadding from '@/components/MobilePadding';
9
+ import { FORM_STYLE } from '@/const/layoutTokens';
10
+ import { useGlobalStore } from '@/store/global';
6
11
  import { useSessionStore } from '@/store/session';
7
12
 
8
- const ShareButton = memo(() => {
13
+ import Preview, { ImageType, imageTypeOptions } from './Preview';
14
+
15
+ enum Tab {
16
+ Screenshot = 'screenshot',
17
+ ShareGPT = 'sharegpt',
18
+ }
19
+
20
+ const Inner = memo(() => {
21
+ const [withSystemRole, setWithSystemRole] = useState(false);
22
+ const [withPluginInfo, setWithPluginInfo] = useState(false);
23
+ const [withBackground, setWithBackground] = useState(true);
24
+ const [withFooter, setWithFooter] = useState(true);
25
+ const [imageType, setImageType] = useState<ImageType>(ImageType.JPG);
26
+ const [tab, setTab] = useState<Tab>(Tab.Screenshot);
9
27
  const { t } = useTranslation('common');
10
28
 
29
+ const avatar = useGlobalStore((s) => s.settings.avatar);
11
30
  const [shareLoading, shareToShareGPT] = useSessionStore((s) => [
12
31
  s.shareLoading,
13
32
  s.shareToShareGPT,
14
33
  ]);
15
34
 
16
- return shareLoading ? (
17
- <Icon icon={Loader2} size={{ fontSize: 24 }} spin />
18
- ) : (
19
- <ActionIcon
20
- icon={Share2}
21
- onClick={shareToShareGPT}
22
- size={{ fontSize: 24 }}
23
- title={t('share')}
24
- />
35
+ const options: SegmentedProps['options'] = useMemo(
36
+ () => [
37
+ {
38
+ label: t('shareModal.screenshot'),
39
+ value: Tab.Screenshot,
40
+ },
41
+ {
42
+ label: 'ShareGPT',
43
+ value: Tab.ShareGPT,
44
+ },
45
+ ],
46
+ [],
47
+ );
48
+
49
+ const settings: ItemGroup = useMemo(
50
+ () =>
51
+ ({
52
+ children: [
53
+ {
54
+ children: <Switch checked={withSystemRole} onChange={setWithSystemRole} />,
55
+ label: t('shareModal.withSystemRole'),
56
+ minWidth: undefined,
57
+ },
58
+ tab === Tab.Screenshot && {
59
+ children: <Switch checked={withBackground} onChange={setWithBackground} />,
60
+ label: t('shareModal.withBackground'),
61
+ minWidth: undefined,
62
+ },
63
+ tab === Tab.Screenshot && {
64
+ children: <Switch checked={withFooter} onChange={setWithFooter} />,
65
+ label: t('shareModal.withFooter'),
66
+ minWidth: undefined,
67
+ },
68
+ tab === Tab.Screenshot && {
69
+ children: (
70
+ <Segmented
71
+ onChange={(value) => setImageType(value as ImageType)}
72
+ options={imageTypeOptions}
73
+ value={imageType}
74
+ />
75
+ ),
76
+ label: t('shareModal.imageType'),
77
+ minWidth: undefined,
78
+ },
79
+ tab === Tab.ShareGPT && {
80
+ children: <Switch checked={withPluginInfo} onChange={setWithPluginInfo} />,
81
+ label: t('shareModal.withPluginInfo'),
82
+ minWidth: undefined,
83
+ },
84
+ ].filter(Boolean),
85
+ icon: Share2,
86
+ title: t('shareModal.settings'),
87
+ }) as ItemGroup,
88
+ [tab, withSystemRole, withPluginInfo, withBackground, withFooter, imageType],
89
+ );
90
+
91
+ return (
92
+ <Flexbox gap={16}>
93
+ <MobilePadding bottom={0}>
94
+ <Segmented
95
+ block
96
+ onChange={(value) => setTab(value as Tab)}
97
+ options={options}
98
+ style={{ width: '100%' }}
99
+ value={tab}
100
+ />
101
+ </MobilePadding>
102
+ <Form items={[settings]} {...FORM_STYLE} />
103
+ <MobilePadding gap={16} top={0}>
104
+ {tab === Tab.Screenshot && (
105
+ <Preview
106
+ imageType={imageType}
107
+ withBackground={withBackground}
108
+ withFooter={withFooter}
109
+ withSystemRole={withSystemRole}
110
+ />
111
+ )}
112
+ {tab === Tab.ShareGPT && (
113
+ <Button
114
+ block
115
+ loading={shareLoading}
116
+ onClick={() => shareToShareGPT({ avatar, withPluginInfo, withSystemRole })}
117
+ size={'large'}
118
+ type={'primary'}
119
+ >
120
+ {t('shareModal.shareToShareGPT')}
121
+ </Button>
122
+ )}
123
+ </MobilePadding>
124
+ </Flexbox>
25
125
  );
26
126
  });
27
127
 
28
- export default ShareButton;
128
+ export default Inner;
@@ -0,0 +1,140 @@
1
+ import { SiOpenai } from '@icons-pack/react-simple-icons';
2
+ import { Avatar, ChatHeaderTitle, Logo, Markdown, Tag } from '@lobehub/ui';
3
+ import { Button, SegmentedProps } from 'antd';
4
+ import dayjs from 'dayjs';
5
+ import { toJpeg, toPng, toSvg } from 'html-to-image';
6
+ import { memo, useCallback, useState } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import pkg from '@/../package.json';
11
+ import ChatList from '@/app/chat/features/Conversation/ChatList';
12
+ import PluginTag from '@/app/chat/features/Header/PluginTag';
13
+ import { useSessionStore } from '@/store/session';
14
+ import { agentSelectors } from '@/store/session/slices/agentConfig';
15
+ import { sessionSelectors } from '@/store/session/slices/session/selectors';
16
+
17
+ import { useStyles } from './style';
18
+
19
+ export enum ImageType {
20
+ JPG = 'jpg',
21
+ PNG = 'png',
22
+ SVG = 'svg',
23
+ }
24
+
25
+ export const imageTypeOptions: SegmentedProps['options'] = [
26
+ {
27
+ label: 'JPG',
28
+ value: ImageType.JPG,
29
+ },
30
+ {
31
+ label: 'PNG',
32
+ value: ImageType.PNG,
33
+ },
34
+ {
35
+ label: 'SVG',
36
+ value: ImageType.SVG,
37
+ },
38
+ ];
39
+
40
+ interface PreviewProps {
41
+ imageType: ImageType;
42
+ withBackground: boolean;
43
+ withFooter: boolean;
44
+ withSystemRole: boolean;
45
+ }
46
+
47
+ const Preview = memo<PreviewProps>(({ withSystemRole, imageType, withBackground, withFooter }) => {
48
+ const [loading, setLoading] = useState(false);
49
+ const [isInbox, title, description, avatar, backgroundColor, model, plugins, systemRole] =
50
+ useSessionStore((s) => [
51
+ sessionSelectors.isInboxSession(s),
52
+ agentSelectors.currentAgentTitle(s),
53
+ agentSelectors.currentAgentDescription(s),
54
+ agentSelectors.currentAgentAvatar(s),
55
+ agentSelectors.currentAgentBackgroundColor(s),
56
+ agentSelectors.currentAgentModel(s),
57
+ agentSelectors.currentAgentPlugins(s),
58
+ agentSelectors.currentAgentSystemRole(s),
59
+ ]);
60
+ const { t } = useTranslation('common');
61
+ const { styles } = useStyles(withBackground);
62
+
63
+ const displayTitle = isInbox ? t('inbox.title') : title;
64
+ const displayDesc = isInbox ? t('inbox.desc') : description;
65
+
66
+ const handleDownload = useCallback(async () => {
67
+ setLoading(true);
68
+ try {
69
+ let screenshotFn: any;
70
+ switch (imageType) {
71
+ case ImageType.JPG: {
72
+ screenshotFn = toJpeg;
73
+ break;
74
+ }
75
+ case ImageType.PNG: {
76
+ screenshotFn = toPng;
77
+ break;
78
+ }
79
+ case ImageType.SVG: {
80
+ screenshotFn = toSvg;
81
+ break;
82
+ }
83
+ }
84
+ const dataUrl = await screenshotFn(document.querySelector('#preview') as HTMLDivElement);
85
+ const link = document.createElement('a');
86
+ link.download = `LobeChat_${title}_${dayjs().format('YYYY-MM-DD')}.${imageType}`;
87
+ link.href = dataUrl;
88
+ link.click();
89
+ setLoading(false);
90
+ } catch {
91
+ console.error('Failed to download image');
92
+ setLoading(false);
93
+ }
94
+ }, [imageType, title]);
95
+
96
+ return (
97
+ <>
98
+ <div className={styles.preview}>
99
+ <div className={withBackground ? styles.background : undefined} id={'preview'}>
100
+ <Flexbox className={styles.container} gap={16}>
101
+ <div className={styles.header}>
102
+ <Flexbox align={'flex-start'} gap={12} horizontal>
103
+ <Avatar avatar={avatar} background={backgroundColor} size={40} title={title} />
104
+ <ChatHeaderTitle
105
+ desc={displayDesc}
106
+ tag={
107
+ <>
108
+ <Tag icon={<SiOpenai size={'1em'} />}>{model}</Tag>
109
+ {plugins?.length > 0 && <PluginTag plugins={plugins} />}
110
+ </>
111
+ }
112
+ title={displayTitle}
113
+ />
114
+ </Flexbox>
115
+ {withSystemRole && systemRole && (
116
+ <div className={styles.role}>
117
+ <Markdown>{systemRole}</Markdown>
118
+ </div>
119
+ )}
120
+ </div>
121
+ <ChatList />
122
+ {withFooter ? (
123
+ <Flexbox align={'center'} className={styles.footer} gap={4}>
124
+ <Logo extra={'chat'} type={'combine'} />
125
+ <div className={styles.url}>{pkg.homepage}</div>
126
+ </Flexbox>
127
+ ) : (
128
+ <div />
129
+ )}
130
+ </Flexbox>
131
+ </div>
132
+ </div>
133
+ <Button block loading={loading} onClick={handleDownload} size={'large'} type={'primary'}>
134
+ {t('shareModal.download')}
135
+ </Button>
136
+ </>
137
+ );
138
+ });
139
+
140
+ export default Preview;
@@ -1,14 +1,21 @@
1
1
  import { ActionIcon, Modal } from '@lobehub/ui';
2
+ import { useResponsive } from 'antd-style';
2
3
  import { Share2 } from 'lucide-react';
3
4
  import { memo, useState } from 'react';
4
5
  import { useTranslation } from 'react-i18next';
5
6
 
7
+ import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
6
8
  import { useSessionStore } from '@/store/session';
7
9
 
10
+ import Inner from './Inner';
11
+
8
12
  const ShareButton = memo(() => {
9
13
  const [isModalOpen, setIsModalOpen] = useState(false);
10
14
  const { t } = useTranslation('common');
11
15
  const [shareLoading] = useSessionStore((s) => [s.shareLoading]);
16
+ const { mobile } = useResponsive();
17
+
18
+ const size = mobile ? MOBILE_HEADER_ICON_SIZE : { fontSize: 24 };
12
19
 
13
20
  return (
14
21
  <>
@@ -16,15 +23,18 @@ const ShareButton = memo(() => {
16
23
  icon={Share2}
17
24
  loading={shareLoading}
18
25
  onClick={() => setIsModalOpen(true)}
19
- size={{ fontSize: 24 }}
26
+ size={size}
20
27
  title={t('share')}
21
28
  />
22
29
  <Modal
30
+ centered={false}
23
31
  footer={null}
24
32
  onCancel={() => setIsModalOpen(false)}
25
33
  open={isModalOpen}
26
34
  title={t('share')}
27
- />
35
+ >
36
+ <Inner />
37
+ </Modal>
28
38
  </>
29
39
  );
30
40
  });
@@ -0,0 +1,71 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { rgba } from 'polished';
3
+
4
+ export const useStyles = createStyles(({ css, token, stylish, cx }, withBackground: boolean) => ({
5
+ background: css`
6
+ padding: 24px;
7
+ background: url('/images/screenshot_background.webp') no-repeat;
8
+ background-size: 100% 100%;
9
+ `,
10
+ container: cx(
11
+ withBackground &&
12
+ css`
13
+ overflow: hidden;
14
+ border: 1px solid ${rgba(token.colorText, 0.15)};
15
+ border-radius: ${token.borderRadiusLG}px;
16
+ box-shadow:
17
+ 0 8px 16px -4px rgba(0, 0, 0, 75%),
18
+ 0 8px 24px -4px rgba(0, 0, 0, 75%);
19
+ `,
20
+ css`
21
+ background: ${token.colorBgLayout};
22
+ `,
23
+ ),
24
+ footer: css`
25
+ padding: 16px;
26
+ border-top: 1px solid ${token.colorBorder};
27
+ `,
28
+ header: css`
29
+ margin-bottom: -24px;
30
+ padding: 16px;
31
+ background: ${token.colorBgContainer};
32
+ border-bottom: 1px solid ${token.colorBorder};
33
+ `,
34
+ preview: cx(
35
+ stylish.noScrollbar,
36
+ css`
37
+ overflow-x: hidden;
38
+ overflow-y: scroll;
39
+
40
+ width: 100%;
41
+ max-height: 40vh;
42
+
43
+ background: ${token.colorBgLayout};
44
+ border: 1px solid ${token.colorBorder};
45
+ border-radius: ${token.borderRadiusLG}px;
46
+
47
+ * {
48
+ pointer-events: none;
49
+ overflow: hidden;
50
+
51
+ ::-webkit-scrollbar {
52
+ width: 0 !important;
53
+ height: 0 !important;
54
+ }
55
+ }
56
+ `,
57
+ ),
58
+ role: css`
59
+ margin-top: 12px;
60
+ padding-top: 12px;
61
+ opacity: 0.75;
62
+ border-top: 1px dashed ${token.colorBorderSecondary};
63
+
64
+ * {
65
+ font-size: 12px !important;
66
+ }
67
+ `,
68
+ url: css`
69
+ color: ${token.colorTextDescription};
70
+ `,
71
+ }));
@@ -1,7 +1,6 @@
1
1
  import { ActionIcon, Logo } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
3
  import { MessageSquarePlus } from 'lucide-react';
4
- import Link from 'next/link';
5
4
  import { memo } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
  import { Flexbox } from 'react-layout-kit';
@@ -28,9 +27,8 @@ const Header = memo(() => {
28
27
  return (
29
28
  <Flexbox className={styles.top} gap={16} padding={16}>
30
29
  <Flexbox distribution={'space-between'} horizontal>
31
- <Link href={'/'}>
32
- <Logo className={styles.logo} size={36} type={'text'} />
33
- </Link>
30
+ <Logo className={styles.logo} size={36} type={'text'} />
31
+
34
32
  <ActionIcon
35
33
  icon={MessageSquarePlus}
36
34
  onClick={() => createSession()}
@@ -1,10 +1,11 @@
1
- import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
1
+ import { ActionIcon, Avatar, Logo, MobileNavBar } from '@lobehub/ui';
2
2
  import { createStyles } from 'antd-style';
3
- import { MessageSquarePlus, Settings2 } from 'lucide-react';
3
+ import { MessageSquarePlus } from 'lucide-react';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
 
7
- import AvatarWithUpload from '@/features/AvatarWithUpload';
7
+ import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
8
+ import { useGlobalStore } from '@/store/global';
8
9
  import { useSessionStore } from '@/store/session';
9
10
 
10
11
  export const useStyles = createStyles(({ css, token }) => ({
@@ -20,21 +21,21 @@ export const useStyles = createStyles(({ css, token }) => ({
20
21
  const Header = memo(() => {
21
22
  const [createSession] = useSessionStore((s) => [s.createSession]);
22
23
  const router = useRouter();
23
-
24
+ const avatar = useGlobalStore((st) => st.settings.avatar);
24
25
  return (
25
26
  <MobileNavBar
26
27
  center={<Logo type={'text'} />}
27
- left={<AvatarWithUpload size={28} style={{ marginLeft: 8 }} />}
28
+ left={
29
+ <div onClick={() => router.push('/settings/mobile')} style={{ marginLeft: 8 }}>
30
+ {avatar ? <Avatar avatar={avatar} size={28} /> : <Logo size={28} />}
31
+ </div>
32
+ }
28
33
  right={
29
- <>
30
- <ActionIcon icon={MessageSquarePlus} onClick={() => createSession()} />
31
- <ActionIcon
32
- icon={Settings2}
33
- onClick={() => {
34
- router.push('/settings');
35
- }}
36
- />
37
- </>
34
+ <ActionIcon
35
+ icon={MessageSquarePlus}
36
+ onClick={() => createSession()}
37
+ size={MOBILE_HEADER_ICON_SIZE}
38
+ />
38
39
  }
39
40
  />
40
41
  );
@@ -1,14 +1,10 @@
1
- import { ActionIcon } from '@lobehub/ui';
2
- import { Drawer } from 'antd';
3
- import { useTheme } from 'antd-style';
4
- import { X } from 'lucide-react';
1
+ import { Modal } from '@lobehub/ui';
5
2
  import { PropsWithChildren, memo } from 'react';
6
3
  import { useTranslation } from 'react-i18next';
7
4
 
8
5
  import { useGlobalStore } from '@/store/global';
9
6
 
10
7
  const Mobile = memo<PropsWithChildren>(({ children }) => {
11
- const theme = useTheme();
12
8
  const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
13
9
  s.preference.mobileShowTopic,
14
10
  s.toggleMobileTopic,
@@ -17,19 +13,9 @@ const Mobile = memo<PropsWithChildren>(({ children }) => {
17
13
  const { t } = useTranslation('common');
18
14
 
19
15
  return (
20
- <Drawer
21
- bodyStyle={{ padding: 0 }}
22
- closeIcon={<ActionIcon icon={X} size={{ blockSize: 32, fontSize: 20 }} />}
23
- drawerStyle={{ background: theme.colorBgContainer }}
24
- headerStyle={{ padding: '8px 4px' }}
25
- height={'75vh'}
26
- onClose={() => toggleConfig(false)}
27
- open={showAgentSettings}
28
- placement={'bottom'}
29
- title={t('topic.title')}
30
- >
16
+ <Modal onCancel={() => toggleConfig(false)} open={showAgentSettings} title={t('topic.title')}>
31
17
  {children}
32
- </Drawer>
18
+ </Modal>
33
19
  );
34
20
  });
35
21
 
@@ -3,11 +3,12 @@ import { Flexbox } from 'react-layout-kit';
3
3
 
4
4
  import AppLayout from '@/layout/AppLayout';
5
5
  import { useSwitchSideBarOnInit } from '@/store/global';
6
+ import { SidebarTabKey } from '@/store/global/initialState';
6
7
 
7
8
  import { Sessions } from './features/SessionList';
8
9
 
9
10
  const ChatLayout = memo<PropsWithChildren>(({ children }) => {
10
- useSwitchSideBarOnInit('chat');
11
+ useSwitchSideBarOnInit(SidebarTabKey.Chat);
11
12
 
12
13
  return (
13
14
  <AppLayout>
@@ -2,12 +2,13 @@ import { memo } from 'react';
2
2
 
3
3
  import AppMobileLayout from '@/layout/AppMobileLayout';
4
4
  import { useSwitchSideBarOnInit } from '@/store/global';
5
+ import { SidebarTabKey } from '@/store/global/initialState';
5
6
 
6
7
  import { Sessions } from './features/SessionList';
7
8
  import Header from './features/SessionList/Header';
8
9
 
9
10
  const ChatMobileLayout = memo(() => {
10
- useSwitchSideBarOnInit('chat');
11
+ useSwitchSideBarOnInit(SidebarTabKey.Chat);
11
12
 
12
13
  return (
13
14
  <AppMobileLayout navBar={<Header />} showTabBar>
@@ -1,10 +1,11 @@
1
1
  import { ActionIcon } from '@lobehub/ui';
2
2
  import { Dropdown, MenuProps } from 'antd';
3
3
  import { useResponsive } from 'antd-style';
4
- import { HardDriveDownload, Share2 } from 'lucide-react';
4
+ import { HardDriveDownload } from 'lucide-react';
5
5
  import { memo, useMemo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
+ import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
8
9
  import { exportSingleAgent, exportSingleSession } from '@/helpers/export';
9
10
  import { useSessionStore } from '@/store/session';
10
11
 
@@ -42,11 +43,10 @@ const Header = memo<{ mobile?: boolean }>(() => {
42
43
  [],
43
44
  );
44
45
 
45
- const size = mobile ? undefined : { fontSize: 24 };
46
+ const size = mobile ? MOBILE_HEADER_ICON_SIZE : { fontSize: 24 };
46
47
 
47
48
  return (
48
49
  <Render>
49
- <ActionIcon icon={Share2} size={size} title={t('share', { ns: 'common' })} />
50
50
  <Dropdown arrow={false} menu={{ items }} trigger={['click']}>
51
51
  <ActionIcon icon={HardDriveDownload} size={size} title={t('export', { ns: 'common' })} />
52
52
  </Dropdown>
@@ -1,18 +1,11 @@
1
1
  import { PropsWithChildren, memo } from 'react';
2
- import { Flexbox } from 'react-layout-kit';
3
2
 
4
3
  import AppMobileLayout from '@/layout/AppMobileLayout';
5
4
 
6
5
  import Header from './features/Header';
7
6
 
8
7
  const MobileLayout = memo<PropsWithChildren>(({ children }) => {
9
- return (
10
- <AppMobileLayout navBar={<Header />}>
11
- <Flexbox gap={16} padding={16}>
12
- {children}
13
- </Flexbox>
14
- </AppMobileLayout>
15
- );
8
+ return <AppMobileLayout navBar={<Header />}>{children}</AppMobileLayout>;
16
9
  });
17
10
 
18
11
  export default MobileLayout;
@@ -39,7 +39,7 @@ interface AgentCardBannerProps extends DivProps {
39
39
  }
40
40
 
41
41
  const AgentCardBanner = memo<AgentCardBannerProps>(
42
- ({ meta, className, mask, size = 8, maskColor, ...props }) => {
42
+ ({ meta, className, mask, size = 8, maskColor, children, ...props }) => {
43
43
  const { styles, theme, cx } = useStyles(maskColor);
44
44
 
45
45
  return (
@@ -56,6 +56,7 @@ const AgentCardBanner = memo<AgentCardBannerProps>(
56
56
  style={{ transform: `scale(${size})` }}
57
57
  />
58
58
  {mask && <div className={styles.bannerMask} />}
59
+ {children}
59
60
  </Flexbox>
60
61
  );
61
62
  },
@@ -1,27 +1,8 @@
1
- import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
2
- import { Settings2 } from 'lucide-react';
3
- import { useRouter } from 'next/navigation';
1
+ import { Logo, MobileNavBar } from '@lobehub/ui';
4
2
  import { memo } from 'react';
5
3
 
6
- import AvatarWithUpload from '@/features/AvatarWithUpload';
7
-
8
4
  const Header = memo(() => {
9
- const router = useRouter();
10
-
11
- return (
12
- <MobileNavBar
13
- center={<Logo type={'text'} />}
14
- left={<AvatarWithUpload size={28} style={{ marginLeft: 8 }} />}
15
- right={
16
- <ActionIcon
17
- icon={Settings2}
18
- onClick={() => {
19
- router.push('/settings');
20
- }}
21
- />
22
- }
23
- />
24
- );
5
+ return <MobileNavBar center={<Logo type={'text'} />} />;
25
6
  });
26
7
 
27
8
  export default Header;