@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
@@ -1,6 +1,6 @@
1
1
  import { PluginRender, PluginRenderProps } from '@lobehub/chat-plugin-sdk';
2
2
  import { Skeleton } from 'antd';
3
- import { memo, useState } from 'react';
3
+ import { memo, useEffect, useState } from 'react';
4
4
 
5
5
  import { system } from './dynamticLoader';
6
6
 
@@ -10,7 +10,7 @@ interface CustomRenderProps extends PluginRenderProps {
10
10
  const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {
11
11
  const [component, setComp] = useState<PluginRender | null>(null);
12
12
 
13
- if (!component) {
13
+ useEffect(() => {
14
14
  system
15
15
  .import(url)
16
16
  .then((module1) => {
@@ -21,12 +21,14 @@ const CustomRender = memo<CustomRenderProps>(({ url, ...props }) => {
21
21
  setComp(null);
22
22
  console.error(error);
23
23
  });
24
+ }, [url]);
24
25
 
26
+ if (!component) {
25
27
  return <Skeleton active style={{ width: 300 }} />;
26
28
  }
27
29
 
28
- const Comp = component;
30
+ const Render = component;
29
31
 
30
- return <Comp {...props} />;
32
+ return <Render {...props} />;
31
33
  });
32
34
  export default CustomRender;
@@ -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;
@@ -0,0 +1,101 @@
1
+ import { SiOpenai } from '@icons-pack/react-simple-icons';
2
+ import { ActionIcon, Avatar, ChatHeader, ChatHeaderTitle, Tag } from '@lobehub/ui';
3
+ import { Skeleton } from 'antd';
4
+ import { PanelRightClose, PanelRightOpen, Settings } from 'lucide-react';
5
+ import Router from 'next/router';
6
+ import { memo } from 'react';
7
+ import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
9
+
10
+ import { INBOX_SESSION_ID } from '@/const/session';
11
+ import { useGlobalStore } from '@/store/global';
12
+ import { agentSelectors, useSessionChatInit, useSessionStore } from '@/store/session';
13
+
14
+ import PluginTag from './PluginTag';
15
+
16
+ const Header = memo<{ settings?: boolean }>(({ settings = true }) => {
17
+ const init = useSessionChatInit();
18
+
19
+ const { t } = useTranslation('common');
20
+
21
+ const [id, title, description, avatar, backgroundColor, model, plugins] = useSessionStore((s) => [
22
+ s.activeId,
23
+
24
+ agentSelectors.currentAgentTitle(s),
25
+ agentSelectors.currentAgentDescription(s),
26
+ agentSelectors.currentAgentAvatar(s),
27
+ agentSelectors.currentAgentBackgroundColor(s),
28
+ agentSelectors.currentAgentModel(s),
29
+ agentSelectors.currentAgentPlugins(s),
30
+ ]);
31
+
32
+ const isInbox = id === INBOX_SESSION_ID;
33
+ const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
34
+ s.preference.showChatSideBar,
35
+ s.toggleChatSideBar,
36
+ ]);
37
+
38
+ const displayTitle = isInbox ? t('inbox.title') : title;
39
+ const displayDesc = isInbox ? t('inbox.desc') : description;
40
+
41
+ return (
42
+ <ChatHeader
43
+ left={
44
+ !init ? (
45
+ <Flexbox horizontal>
46
+ <Skeleton
47
+ active
48
+ avatar={{ shape: 'circle', size: 'default' }}
49
+ paragraph={false}
50
+ title={{ style: { margin: 0, marginTop: 8 }, width: 200 }}
51
+ />
52
+ </Flexbox>
53
+ ) : (
54
+ <Flexbox align={'flex-start'} gap={12} horizontal>
55
+ <Avatar avatar={avatar} background={backgroundColor} size={40} title={title} />
56
+ <ChatHeaderTitle
57
+ desc={displayDesc}
58
+ tag={
59
+ <>
60
+ <Tag icon={<SiOpenai size={'1em'} />}>{model}</Tag>
61
+ {plugins?.length > 0 && <PluginTag plugins={plugins} />}
62
+ </>
63
+ }
64
+ title={displayTitle}
65
+ />
66
+ </Flexbox>
67
+ )
68
+ }
69
+ right={
70
+ <>
71
+ {/*<ActionIcon*/}
72
+ {/* icon={Share2}*/}
73
+ {/* onClick={() => {*/}
74
+ {/* // genShareUrl();*/}
75
+ {/* }}*/}
76
+ {/* size={{ fontSize: 24 }}*/}
77
+ {/* title={t('share')}*/}
78
+ {/*/>*/}
79
+ <ActionIcon
80
+ icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
81
+ onClick={() => toggleConfig()}
82
+ size={{ fontSize: 24 }}
83
+ title={t('roleAndArchive')}
84
+ />
85
+ {settings && (
86
+ <ActionIcon
87
+ icon={Settings}
88
+ onClick={() => {
89
+ Router.push(`/chat/${id}/setting`);
90
+ }}
91
+ size={{ fontSize: 24 }}
92
+ title={t('header.session', { ns: 'setting' })}
93
+ />
94
+ )}
95
+ </>
96
+ }
97
+ />
98
+ );
99
+ });
100
+
101
+ export default Header;
@@ -1,33 +1,59 @@
1
- import { Avatar, Icon, Tag } from '@lobehub/ui';
1
+ import { ActionIcon, Avatar, Icon, Tag } from '@lobehub/ui';
2
2
  import type { MenuProps } from 'antd';
3
- import { Dropdown } from 'antd';
4
- import { LucideToyBrick } from 'lucide-react';
5
- import { memo } from 'react';
3
+ import { Badge, Dropdown } from 'antd';
4
+ import isEqual from 'fast-deep-equal';
5
+ import { LucideRotateCw, LucideToyBrick } from 'lucide-react';
6
+ import { memo, useMemo } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
8
+ import { Flexbox } from 'react-layout-kit';
7
9
 
8
- import { usePluginStore } from '@/store/plugin';
10
+ import { pluginHelpers, pluginSelectors, usePluginStore } from '@/store/plugin';
11
+
12
+ const PluginStatus = memo<{ id: string; title?: string }>(({ title, id }) => {
13
+ const { t } = useTranslation('common');
14
+ const status = usePluginStore(pluginSelectors.getPluginManifestLoadingStatus(id));
15
+
16
+ const renderStatus = useMemo(() => {
17
+ switch (status) {
18
+ case 'loading': {
19
+ return <Badge color={'blue'} status={'processing'} />;
20
+ }
21
+ case 'error': {
22
+ return <ActionIcon icon={LucideRotateCw} size={'small'} title={t('retry')} />;
23
+ }
24
+ case 'success': {
25
+ return <Badge status={status} />;
26
+ }
27
+ }
28
+ }, [status]);
29
+
30
+ return (
31
+ <Flexbox gap={12} horizontal justify={'space-between'}>
32
+ {title} {renderStatus}
33
+ </Flexbox>
34
+ );
35
+ });
9
36
 
10
37
  export interface PluginTagProps {
11
38
  plugins: string[];
12
39
  }
13
40
 
14
41
  const PluginTag = memo<PluginTagProps>(({ plugins }) => {
15
- const { t } = useTranslation('plugin');
16
-
17
- const list = usePluginStore((s) => s.pluginList);
42
+ const list = usePluginStore(pluginSelectors.displayPluginList);
43
+ const displayPlugin = usePluginStore(pluginSelectors.getPluginMetaById(plugins[0]), isEqual);
18
44
 
19
45
  if (plugins.length === 0) return null;
20
46
 
21
47
  const items: MenuProps['items'] = plugins.map((id) => {
22
- const item = list?.find((p) => p.identifier === id);
48
+ const item = list.find((i) => i.identifier === id);
49
+
23
50
  return {
24
- icon: (
25
- <Avatar avatar={item?.meta.avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />
26
- ),
51
+ icon: <Avatar avatar={item?.avatar} size={24} style={{ marginLeft: -6, marginRight: 2 }} />,
27
52
  key: id,
28
- label: t(`plugins.${id}` as any),
53
+ label: <PluginStatus id={id} title={item?.title} />,
29
54
  };
30
55
  });
56
+
31
57
  const count = plugins.length;
32
58
 
33
59
  return (
@@ -35,7 +61,7 @@ const PluginTag = memo<PluginTagProps>(({ plugins }) => {
35
61
  <div>
36
62
  <Tag>
37
63
  {<Icon icon={LucideToyBrick} />}
38
- {t(`plugins.${plugins[0]}` as any)}
64
+ {pluginHelpers.getPluginTitle(displayPlugin?.meta)}
39
65
  {count > 1 && <div>({plugins.length - 1}+)</div>}
40
66
  </Tag>
41
67
  </div>
@@ -1,101 +1,15 @@
1
- import { SiOpenai } from '@icons-pack/react-simple-icons';
2
- import { ActionIcon, Avatar, ChatHeader, ChatHeaderTitle, Tag } from '@lobehub/ui';
3
- import { Skeleton } from 'antd';
4
- import { PanelRightClose, PanelRightOpen, Settings } from 'lucide-react';
5
- import Router from 'next/router';
1
+ import { useResponsive } from 'antd-style';
6
2
  import { memo } from 'react';
7
- import { useTranslation } from 'react-i18next';
8
- import { Flexbox } from 'react-layout-kit';
9
3
 
10
- import { INBOX_SESSION_ID } from '@/const/session';
11
- import { useGlobalStore } from '@/store/global';
12
- import { agentSelectors, useSessionChatInit, useSessionStore } from '@/store/session';
4
+ import Mobile from './Mobile';
5
+ import PC from './PC';
13
6
 
14
- import PluginTag from './PluginTag';
7
+ const Header = memo<{ settings?: boolean }>((props) => {
8
+ const { mobile } = useResponsive();
15
9
 
16
- const Header = memo<{ settings?: boolean }>(({ settings = true }) => {
17
- const init = useSessionChatInit();
10
+ if (mobile) return <Mobile {...props} />;
18
11
 
19
- const { t } = useTranslation('common');
20
-
21
- const [id, title, description, avatar, backgroundColor, model, plugins] = useSessionStore((s) => [
22
- s.activeId,
23
-
24
- agentSelectors.currentAgentTitle(s),
25
- agentSelectors.currentAgentDescription(s),
26
- agentSelectors.currentAgentAvatar(s),
27
- agentSelectors.currentAgentBackgroundColor(s),
28
- agentSelectors.currentAgentModel(s),
29
- agentSelectors.currentAgentPlugins(s),
30
- ]);
31
-
32
- const isInbox = id === INBOX_SESSION_ID;
33
- const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
34
- s.preference.showChatSideBar,
35
- s.toggleChatSideBar,
36
- ]);
37
-
38
- const displayTitle = isInbox ? t('inbox.title') : title;
39
- const displayDesc = isInbox ? t('inbox.desc') : description;
40
-
41
- return (
42
- <ChatHeader
43
- left={
44
- !init ? (
45
- <Flexbox horizontal>
46
- <Skeleton
47
- active
48
- avatar={{ shape: 'circle', size: 'default' }}
49
- paragraph={false}
50
- title={{ style: { margin: 0, marginTop: 8 }, width: 200 }}
51
- />
52
- </Flexbox>
53
- ) : (
54
- <Flexbox align={'flex-start'} gap={12} horizontal>
55
- <Avatar avatar={avatar} background={backgroundColor} size={40} title={title} />
56
- <ChatHeaderTitle
57
- desc={displayDesc}
58
- tag={
59
- <>
60
- <Tag icon={<SiOpenai size={'1em'} />}>{model}</Tag>
61
- {plugins?.length > 0 && <PluginTag plugins={plugins} />}
62
- </>
63
- }
64
- title={displayTitle}
65
- />
66
- </Flexbox>
67
- )
68
- }
69
- right={
70
- <>
71
- {/*<ActionIcon*/}
72
- {/* icon={Share2}*/}
73
- {/* onClick={() => {*/}
74
- {/* // genShareUrl();*/}
75
- {/* }}*/}
76
- {/* size={{ fontSize: 24 }}*/}
77
- {/* title={t('share')}*/}
78
- {/*/>*/}
79
- <ActionIcon
80
- icon={showAgentSettings ? PanelRightClose : PanelRightOpen}
81
- onClick={() => toggleConfig()}
82
- size={{ fontSize: 24 }}
83
- title={t('roleAndArchive')}
84
- />
85
- {settings && (
86
- <ActionIcon
87
- icon={Settings}
88
- onClick={() => {
89
- Router.push(`/chat/${id}/setting`);
90
- }}
91
- size={{ fontSize: 24 }}
92
- title={t('header.session', { ns: 'setting' })}
93
- />
94
- )}
95
- </>
96
- }
97
- />
98
- );
12
+ return <PC {...props} />;
99
13
  });
100
14
 
101
15
  export default Header;
@@ -2,24 +2,26 @@ import { createStyles } from 'antd-style';
2
2
  import { type ReactNode, memo } from 'react';
3
3
  import { Flexbox } from 'react-layout-kit';
4
4
 
5
- const useStyles = createStyles(
6
- ({ css, token }) => css`
5
+ const useStyles = createStyles(({ css, token }) => ({
6
+ header: css`
7
7
  z-index: 10;
8
8
  box-shadow: 0 2px 6px ${token.colorBgLayout};
9
9
  `,
10
- );
10
+ }));
11
11
 
12
12
  interface HeaderProps {
13
13
  actions?: ReactNode;
14
+ mobile?: boolean;
14
15
  title: string;
15
16
  }
16
17
 
17
18
  const Header = memo<HeaderProps>(({ title, actions }) => {
18
19
  const { styles } = useStyles();
20
+
19
21
  return (
20
22
  <Flexbox
21
23
  align={'center'}
22
- className={styles}
24
+ className={styles.header}
23
25
  distribution={'space-between'}
24
26
  horizontal
25
27
  padding={12}
@@ -0,0 +1,36 @@
1
+ import { ActionIcon } from '@lobehub/ui';
2
+ import { Drawer } from 'antd';
3
+ import { useTheme } from 'antd-style';
4
+ import { X } from 'lucide-react';
5
+ import { PropsWithChildren, memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { useGlobalStore } from '@/store/global';
9
+
10
+ const Mobile = memo<PropsWithChildren>(({ children }) => {
11
+ const theme = useTheme();
12
+ const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
13
+ s.preference.mobileShowTopic,
14
+ s.toggleMobileTopic,
15
+ ]);
16
+
17
+ const { t } = useTranslation('common');
18
+
19
+ 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
+ >
31
+ {children}
32
+ </Drawer>
33
+ );
34
+ });
35
+
36
+ export default Mobile;
@@ -44,7 +44,7 @@ const useStyles = createStyles(({ css, token }) => ({
44
44
  `,
45
45
  }));
46
46
 
47
- const SystemRole = memo(() => {
47
+ const SystemRole = memo<{ mobile?: boolean }>(() => {
48
48
  const [openModal, setOpenModal] = useState(false);
49
49
 
50
50
  const { styles } = useStyles();
@@ -62,7 +62,7 @@ const SystemRole = memo(() => {
62
62
  <ActionIcon
63
63
  icon={Edit}
64
64
  onClick={() => setOpenModal(true)}
65
- size="small"
65
+ size={'small'}
66
66
  title={t('edit')}
67
67
  />
68
68
  }
@@ -5,20 +5,25 @@ import { memo } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
- import HeaderSpacing from '@/components/HeaderSpacing';
8
+ import SafeSpacing from '@/components/SafeSpacing';
9
9
  import { CHAT_SIDEBAR_WIDTH } from '@/const/layoutTokens';
10
10
  import { useGlobalStore } from '@/store/global';
11
11
 
12
+ import Mobile from './Mobile';
12
13
  import SystemRole from './SystemRole';
13
14
  import { Topic } from './Topic';
14
15
 
15
- const useStyles = createStyles(({ cx, css, token, stylish }) => ({
16
+ const useStyles = createStyles(({ cx, css, token, stylish }, mobile: boolean) => ({
16
17
  content: css`
17
18
  display: flex;
18
19
  flex-direction: column;
19
20
  `,
20
21
  drawer: cx(
21
- stylish.blurStrong,
22
+ mobile
23
+ ? css`
24
+ background: ${token.colorBgLayout} !important;
25
+ `
26
+ : stylish.blurStrong,
22
27
  css`
23
28
  background: ${rgba(token.colorBgLayout, 0.4)};
24
29
  `,
@@ -29,10 +34,11 @@ const useStyles = createStyles(({ cx, css, token, stylish }) => ({
29
34
  }));
30
35
 
31
36
  interface SideBarProps {
37
+ mobile?: boolean;
32
38
  systemRole?: boolean;
33
39
  }
34
- const SideBar = memo<SideBarProps>(({ systemRole = true }) => {
35
- const { styles } = useStyles();
40
+ const SideBar = memo<SideBarProps>(({ systemRole = true, mobile }) => {
41
+ const { styles } = useStyles(mobile);
36
42
  const [showAgentSettings, toggleConfig] = useGlobalStore((s) => [
37
43
  s.preference.showChatSideBar,
38
44
  s.toggleChatSideBar,
@@ -40,6 +46,23 @@ const SideBar = memo<SideBarProps>(({ systemRole = true }) => {
40
46
 
41
47
  const { t } = useTranslation('common');
42
48
 
49
+ const topic = (
50
+ <Flexbox height={'100%'} style={{ overflow: 'hidden' }}>
51
+ <Flexbox padding={16}>
52
+ <SearchBar
53
+ placeholder={t('topic.searchPlaceholder')}
54
+ spotlight={!mobile}
55
+ type={mobile ? 'block' : 'ghost'}
56
+ />
57
+ </Flexbox>
58
+ <Flexbox gap={16} paddingInline={16} style={{ overflowY: 'auto', position: 'relative' }}>
59
+ <Topic />
60
+ </Flexbox>
61
+ </Flexbox>
62
+ );
63
+
64
+ if (mobile) return <Mobile>{topic}</Mobile>;
65
+
43
66
  return (
44
67
  <DraggablePanel
45
68
  className={styles.drawer}
@@ -48,22 +71,17 @@ const SideBar = memo<SideBarProps>(({ systemRole = true }) => {
48
71
  }}
49
72
  expand={showAgentSettings}
50
73
  minWidth={CHAT_SIDEBAR_WIDTH}
74
+ mode={'fixed'}
51
75
  onExpandChange={toggleConfig}
52
76
  placement={'right'}
53
77
  >
54
78
  <DraggablePanelContainer
55
79
  style={{ flex: 'none', height: '100%', minWidth: CHAT_SIDEBAR_WIDTH }}
56
80
  >
57
- <HeaderSpacing />
58
- {systemRole && <SystemRole />}
59
- <Flexbox height={'100%'} style={{ overflow: 'hidden' }}>
60
- <Flexbox padding={16}>
61
- <SearchBar placeholder={t('topic.searchPlaceholder')} spotlight type={'ghost'} />
62
- </Flexbox>
63
- <Flexbox gap={16} paddingInline={16} style={{ overflowY: 'auto', position: 'relative' }}>
64
- <Topic />
65
- </Flexbox>
66
- </Flexbox>
81
+ <SafeSpacing />
82
+ {systemRole && <SystemRole mobile={mobile} />}
83
+
84
+ {topic}
67
85
  </DraggablePanelContainer>
68
86
  </DraggablePanel>
69
87
  );
@@ -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 { useTranslation } from 'react-i18next';
@@ -11,10 +12,11 @@ import Conversation from './features/Conversation';
11
12
  import Header from './features/Header';
12
13
  import SideBar from './features/Sidebar';
13
14
  import Layout from './layout';
15
+ import Mobile from './mobile';
14
16
 
15
17
  const Chat = memo(() => {
16
18
  const { t } = useTranslation('common');
17
-
19
+ const { mobile } = useResponsive();
18
20
  const pageTitle = genSiteHeadTitle(t('inbox.title'));
19
21
 
20
22
  useOnFinishHydrationSession(() => {
@@ -26,13 +28,17 @@ const Chat = memo(() => {
26
28
  <Head>
27
29
  <title>{pageTitle}</title>
28
30
  </Head>
29
- <Layout>
30
- <Header settings={false} />
31
- <Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
32
- <Conversation />
33
- <SideBar systemRole={false} />
34
- </Flexbox>
35
- </Layout>
31
+ {mobile ? (
32
+ <Mobile />
33
+ ) : (
34
+ <Layout>
35
+ <Header settings={false} />
36
+ <Flexbox flex={1} height={'calc(100vh - 64px)'} horizontal>
37
+ <Conversation />
38
+ <SideBar systemRole={false} />
39
+ </Flexbox>
40
+ </Layout>
41
+ )}
36
42
  </>
37
43
  );
38
44
  });
@@ -1,3 +1,4 @@
1
+ import { useResponsive } from 'antd-style';
1
2
  import { PropsWithChildren, memo } from 'react';
2
3
  import { Flexbox } from 'react-layout-kit';
3
4
 
@@ -7,19 +8,22 @@ import { useSwitchSideBarOnInit } from '@/store/global';
7
8
  import { Sessions } from './SessionList';
8
9
 
9
10
  const ChatLayout = memo<PropsWithChildren>(({ children }) => {
11
+ const { mobile } = useResponsive();
10
12
  useSwitchSideBarOnInit('chat');
11
13
 
12
14
  return (
13
15
  <AppLayout>
14
16
  <Sessions />
15
- <Flexbox
16
- flex={1}
17
- height={'100vh'}
18
- id={'lobe-conversion-container'}
19
- style={{ position: 'relative' }}
20
- >
21
- {children}
22
- </Flexbox>
17
+ {!mobile && (
18
+ <Flexbox
19
+ flex={1}
20
+ height={'100vh'}
21
+ id={'lobe-conversion-container'}
22
+ style={{ position: 'relative' }}
23
+ >
24
+ {children}
25
+ </Flexbox>
26
+ )}
23
27
  </AppLayout>
24
28
  );
25
29
  });