@lobehub/chat 0.152.12 → 0.153.1

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 +50 -0
  2. package/README.md +8 -8
  3. package/README.zh-CN.md +8 -8
  4. package/locales/ar/common.json +2 -0
  5. package/locales/bg-BG/common.json +2 -0
  6. package/locales/de-DE/common.json +2 -0
  7. package/locales/en-US/common.json +2 -0
  8. package/locales/es-ES/common.json +2 -0
  9. package/locales/fr-FR/common.json +2 -0
  10. package/locales/it-IT/common.json +2 -0
  11. package/locales/ja-JP/common.json +2 -0
  12. package/locales/ko-KR/common.json +2 -0
  13. package/locales/nl-NL/common.json +2 -0
  14. package/locales/pl-PL/common.json +2 -0
  15. package/locales/pt-BR/common.json +2 -0
  16. package/locales/ru-RU/common.json +2 -0
  17. package/locales/tr-TR/common.json +2 -0
  18. package/locales/vi-VN/common.json +2 -0
  19. package/locales/zh-CN/common.json +2 -0
  20. package/locales/zh-TW/common.json +2 -0
  21. package/package.json +1 -1
  22. package/src/app/(main)/(mobile)/me/features/AvatarBanner.tsx +12 -40
  23. package/src/app/(main)/(mobile)/me/features/Header.tsx +37 -0
  24. package/src/app/(main)/(mobile)/me/features/style.ts +29 -0
  25. package/src/app/(main)/(mobile)/me/layout.tsx +7 -1
  26. package/src/app/(main)/(mobile)/me/loading.tsx +22 -7
  27. package/src/app/(main)/(mobile)/me/page.tsx +4 -5
  28. package/src/app/(main)/@nav/_layout/Mobile.tsx +2 -2
  29. package/src/app/(main)/chat/(desktop)/features/ChatHeader/Main.tsx +4 -9
  30. package/src/app/(main)/chat/(desktop)/features/SideBar/SystemRole/index.tsx +5 -6
  31. package/src/app/(main)/chat/features/SettingButton.tsx +3 -17
  32. package/src/app/(main)/chat/features/TopicListContent/Header.tsx +1 -1
  33. package/src/app/(main)/chat/settings/_layout/Desktop/Header.tsx +2 -1
  34. package/src/app/(main)/chat/settings/_layout/Mobile/Header.tsx +2 -1
  35. package/src/app/(main)/chat/settings/modal/page.tsx +23 -0
  36. package/src/app/(main)/market/@detail/features/{AgentDetailContent/index.tsx → AgentDetailContent.tsx} +6 -12
  37. package/src/app/(main)/market/@detail/features/Banner.tsx +46 -0
  38. package/src/app/(main)/market/@detail/features/{AgentDetailContent/Header.tsx → Header.tsx} +4 -18
  39. package/src/app/(main)/market/@detail/features/{AgentDetailContent/Loading.tsx → Loading.tsx} +5 -4
  40. package/src/app/(main)/market/@detail/features/{AgentDetailContent/style.ts → style.ts} +0 -3
  41. package/src/app/(main)/market/features/AgentCard/AgentCardBanner.tsx +13 -8
  42. package/src/app/(main)/market/loading.tsx +13 -1
  43. package/src/app/(main)/settings/@category/features/CategoryContent.tsx +5 -1
  44. package/src/app/(main)/settings/modal/page.tsx +27 -0
  45. package/src/app/@modal/(.)settings/modal/index.tsx +40 -0
  46. package/src/app/@modal/(.)settings/modal/layout.tsx +32 -0
  47. package/src/app/@modal/(.)settings/modal/loading.tsx +5 -0
  48. package/src/app/@modal/(.)settings/modal/page.tsx +19 -0
  49. package/src/app/@modal/_layout/SettingModalLayout.tsx +59 -0
  50. package/src/app/@modal/chat/(.)settings/modal/features/CategoryContent.tsx +37 -0
  51. package/src/app/@modal/chat/(.)settings/modal/features/useCategory.tsx +54 -0
  52. package/src/app/@modal/chat/(.)settings/modal/layout.tsx +55 -0
  53. package/src/app/@modal/chat/(.)settings/modal/loading.tsx +5 -0
  54. package/src/app/@modal/chat/(.)settings/modal/page.tsx +55 -0
  55. package/src/app/@modal/default.tsx +3 -0
  56. package/src/app/@modal/error.tsx +5 -0
  57. package/src/app/@modal/layout.tsx +30 -0
  58. package/src/app/@modal/loading.tsx +5 -0
  59. package/src/app/layout.tsx +6 -2
  60. package/src/components/Cell/Divider.tsx +2 -0
  61. package/src/components/Cell/index.tsx +5 -1
  62. package/src/components/server/MobileNavLayout.tsx +1 -0
  63. package/src/features/Conversation/Messages/index.ts +9 -12
  64. package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +15 -6
  65. package/src/features/Conversation/components/InboxWelcome/QuestionSuggest.tsx +9 -4
  66. package/src/features/Conversation/components/InboxWelcome/index.tsx +5 -3
  67. package/src/features/DataImporter/index.tsx +4 -1
  68. package/src/features/MobileTabBar/index.tsx +3 -3
  69. package/src/features/User/PlanTag.tsx +45 -0
  70. package/src/features/User/UserInfo.tsx +26 -9
  71. package/src/features/User/UserPanel/useMenu.tsx +31 -16
  72. package/src/hooks/useInterceptingRoutes.test.ts +70 -0
  73. package/src/hooks/useInterceptingRoutes.ts +46 -0
  74. package/src/hooks/useQuery.test.ts +0 -1
  75. package/src/hooks/useQuery.ts +2 -1
  76. package/src/layout/GlobalProvider/StoreInitialization.tsx +12 -5
  77. package/src/locales/default/common.ts +4 -2
  78. package/src/store/global/initialState.ts +9 -0
  79. package/src/styles/mobileHeader.ts +7 -0
  80. package/src/features/User/UserPanel/UserInfo.tsx +0 -35
  81. /package/src/app/(main)/market/@detail/features/{AgentDetailContent/Comment.tsx → Comment.tsx} +0 -0
  82. /package/src/app/(main)/market/@detail/features/{AgentDetailContent/TokenTag.tsx → TokenTag.tsx} +0 -0
  83. /package/src/{app/(main)/chat/components → components}/SidebarHeader/index.tsx +0 -0
@@ -1,21 +1,18 @@
1
1
  import { Avatar, ChatHeaderTitle } from '@lobehub/ui';
2
2
  import { Skeleton } from 'antd';
3
- import { useRouter } from 'next/navigation';
4
3
  import { memo } from 'react';
5
4
  import { useTranslation } from 'react-i18next';
6
5
  import { Flexbox } from 'react-layout-kit';
7
6
 
7
+ import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
8
8
  import { useSessionStore } from '@/store/session';
9
9
  import { sessionMetaSelectors, sessionSelectors } from '@/store/session/selectors';
10
- import { pathString } from '@/utils/url';
11
10
 
12
11
  import Tags from './Tags';
13
12
 
14
13
  const Main = memo(() => {
15
14
  const { t } = useTranslation('chat');
16
15
 
17
- const router = useRouter();
18
-
19
16
  const [init, isInbox, title, description, avatar, backgroundColor] = useSessionStore((s) => [
20
17
  sessionSelectors.isSomeSessionActive(s),
21
18
  sessionSelectors.isInboxSession(s),
@@ -25,6 +22,8 @@ const Main = memo(() => {
25
22
  sessionMetaSelectors.currentAgentBackgroundColor(s),
26
23
  ]);
27
24
 
25
+ const openChatSettings = useOpenChatSettings();
26
+
28
27
  const displayTitle = isInbox ? t('inbox.title') : title;
29
28
  const displayDesc = isInbox ? t('inbox.desc') : description;
30
29
 
@@ -42,11 +41,7 @@ const Main = memo(() => {
42
41
  <Avatar
43
42
  avatar={avatar}
44
43
  background={backgroundColor}
45
- onClick={() =>
46
- isInbox
47
- ? router.push('/settings/agent')
48
- : router.push(pathString('/chat/settings', { search: location.search }))
49
- }
44
+ onClick={openChatSettings}
50
45
  size={40}
51
46
  title={title}
52
47
  />
@@ -1,28 +1,27 @@
1
1
  import { ActionIcon, EditableMessage } from '@lobehub/ui';
2
2
  import { Skeleton } from 'antd';
3
3
  import { Edit } from 'lucide-react';
4
- import { useRouter } from 'next/navigation';
5
4
  import { memo, useState } from 'react';
6
5
  import { useTranslation } from 'react-i18next';
7
6
  import { Flexbox } from 'react-layout-kit';
8
7
  import useMergeState from 'use-merge-value';
9
8
 
9
+ import SidebarHeader from '@/components/SidebarHeader';
10
10
  import AgentInfo from '@/features/AgentInfo';
11
+ import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
11
12
  import { useAgentStore } from '@/store/agent';
12
13
  import { agentSelectors } from '@/store/agent/selectors';
13
14
  import { useGlobalStore } from '@/store/global';
15
+ import { ChatSettingsTabs } from '@/store/global/initialState';
14
16
  import { useSessionStore } from '@/store/session';
15
17
  import { sessionMetaSelectors, sessionSelectors } from '@/store/session/selectors';
16
- import { pathString } from '@/utils/url';
17
18
 
18
- import SidebarHeader from '../../../../components/SidebarHeader';
19
19
  import { useStyles } from './style';
20
20
 
21
21
  const SystemRole = memo(() => {
22
- const router = useRouter();
23
22
  const [editing, setEditing] = useState(false);
24
23
  const { styles } = useStyles();
25
-
24
+ const openChatSettings = useOpenChatSettings(ChatSettingsTabs.Prompt);
26
25
  const [init, meta] = useSessionStore((s) => [
27
26
  sessionSelectors.isSomeSessionActive(s),
28
27
  sessionMetaSelectors.currentAgentMeta(s),
@@ -93,7 +92,7 @@ const SystemRole = memo(() => {
93
92
  onAvatarClick={() => {
94
93
  setOpen(false);
95
94
  setEditing(false);
96
- router.push(pathString('/chat/settings', { search: location.search }));
95
+ openChatSettings();
97
96
  }}
98
97
  style={{ marginBottom: 16 }}
99
98
  />
@@ -4,30 +4,16 @@ import { memo } from 'react';
4
4
  import { useTranslation } from 'react-i18next';
5
5
 
6
6
  import { DESKTOP_HEADER_ICON_SIZE, MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
7
- import { useQueryRoute } from '@/hooks/useQueryRoute';
8
- import { useGlobalStore } from '@/store/global';
9
- import { SidebarTabKey } from '@/store/global/initialState';
10
- import { useSessionStore } from '@/store/session';
11
- import { sessionSelectors } from '@/store/session/selectors';
7
+ import { useOpenChatSettings } from '@/hooks/useInterceptingRoutes';
12
8
 
13
9
  const SettingButton = memo<{ mobile?: boolean }>(({ mobile }) => {
14
- const isInbox = useSessionStore(sessionSelectors.isInboxSession);
15
10
  const { t } = useTranslation('common');
16
- const router = useQueryRoute();
11
+ const openChatSettings = useOpenChatSettings();
17
12
 
18
13
  return (
19
14
  <ActionIcon
20
15
  icon={AlignJustify}
21
- onClick={() => {
22
- if (isInbox) {
23
- useGlobalStore.setState({
24
- sidebarKey: SidebarTabKey.Setting,
25
- });
26
- router.push('/settings/agent');
27
- } else {
28
- router.push('/chat/settings');
29
- }
30
- }}
16
+ onClick={openChatSettings}
31
17
  size={mobile ? MOBILE_HEADER_ICON_SIZE : DESKTOP_HEADER_ICON_SIZE}
32
18
  title={t('header.session', { ns: 'setting' })}
33
19
  />
@@ -5,10 +5,10 @@ import { memo, useMemo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
+ import SidebarHeader from '@/components/SidebarHeader';
8
9
  import { useChatStore } from '@/store/chat';
9
10
  import { topicSelectors } from '@/store/chat/selectors';
10
11
 
11
- import SidebarHeader from '../../components/SidebarHeader';
12
12
  import TopicSearchBar from './TopicSearchBar';
13
13
 
14
14
  const Header = memo(() => {
@@ -5,9 +5,10 @@ import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import HeaderContent from '@/app/(main)/chat/settings/features/HeaderContent';
9
8
  import { pathString } from '@/utils/url';
10
9
 
10
+ import HeaderContent from '../../features/HeaderContent';
11
+
11
12
  const Header = memo(() => {
12
13
  const { t } = useTranslation('setting');
13
14
  const router = useRouter();
@@ -5,10 +5,11 @@ import { useRouter } from 'next/navigation';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
- import HeaderContent from '@/app/(main)/chat/settings/features/HeaderContent';
9
8
  import { mobileHeaderSticky } from '@/styles/mobileHeader';
10
9
  import { pathString } from '@/utils/url';
11
10
 
11
+ import HeaderContent from '../../features/HeaderContent';
12
+
12
13
  const Header = memo(() => {
13
14
  const { t } = useTranslation('setting');
14
15
  const router = useRouter();
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { useLayoutEffect } from 'react';
4
+
5
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
6
+
7
+ /**
8
+ * @description: Chat Settings Modal (intercepting routes fallback when hard refresh)
9
+ * @example: /chat/settings/modal?tab=prompt => /chat/settings
10
+ * @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
11
+ */
12
+
13
+ const ChatSettingsModalFallback = () => {
14
+ const router = useQueryRoute();
15
+
16
+ useLayoutEffect(() => {
17
+ router.replace('/chat/settings', { query: { tab: '' } });
18
+ }, []);
19
+
20
+ return null;
21
+ };
22
+
23
+ export default ChatSettingsModalFallback;
@@ -5,21 +5,20 @@ import { memo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import { Flexbox } from 'react-layout-kit';
7
7
 
8
- import AgentCardBanner from '@/app/(main)/market/features/AgentCard/AgentCardBanner';
8
+ import Banner from '@/app/(main)/market/@detail/features/Banner';
9
9
  import { useMarketStore } from '@/store/market';
10
10
 
11
11
  import Comment from './Comment';
12
12
  import Header from './Header';
13
13
  import Loading from './Loading';
14
14
  import TokenTag from './TokenTag';
15
- import { useStyles } from './style';
16
15
 
17
16
  enum InfoTabs {
18
17
  comment = 'comment',
19
18
  prompt = 'prompt',
20
19
  }
21
20
 
22
- const AgentModalInner = memo<{ mobile?: boolean }>(({ mobile }) => {
21
+ const AgentDetailContent = memo<{ mobile?: boolean }>(({ mobile }) => {
23
22
  const [useFetchAgent, currentIdentifier] = useMarketStore((s) => [
24
23
  s.useFetchAgent,
25
24
  s.currentIdentifier,
@@ -27,7 +26,6 @@ const AgentModalInner = memo<{ mobile?: boolean }>(({ mobile }) => {
27
26
  const { t } = useTranslation('market');
28
27
  const [tab, setTab] = useState<string>(InfoTabs.prompt);
29
28
  const { data, isLoading } = useFetchAgent(currentIdentifier);
30
- const { styles } = useStyles();
31
29
 
32
30
  if (isLoading || !data?.meta) return <Loading />;
33
31
 
@@ -36,16 +34,11 @@ const AgentModalInner = memo<{ mobile?: boolean }>(({ mobile }) => {
36
34
 
37
35
  return (
38
36
  <>
39
- <AgentCardBanner
40
- avatar={meta?.avatar}
41
- size={800}
42
- style={{ height: 120, marginBottom: -60 }}
43
- />
44
- <Header mobile={mobile} />
37
+ <Banner avatar={meta.avatar} backgroundColor={meta.backgroundColor} mobile={mobile} />
38
+ <Header />
45
39
  <Flexbox align={'center'}>
46
40
  <TabsNav
47
41
  activeKey={tab}
48
- className={styles.nav}
49
42
  items={[
50
43
  {
51
44
  key: InfoTabs.prompt,
@@ -61,6 +54,7 @@ const AgentModalInner = memo<{ mobile?: boolean }>(({ mobile }) => {
61
54
  },
62
55
  ]}
63
56
  onChange={setTab}
57
+ style={{ paddingTop: 8 }}
64
58
  variant={'compact'}
65
59
  />
66
60
  </Flexbox>
@@ -76,4 +70,4 @@ const AgentModalInner = memo<{ mobile?: boolean }>(({ mobile }) => {
76
70
  );
77
71
  });
78
72
 
79
- export default AgentModalInner;
73
+ export default AgentDetailContent;
@@ -0,0 +1,46 @@
1
+ import { Avatar } from '@lobehub/ui';
2
+ import { Skeleton } from 'antd';
3
+ import { useTheme } from 'antd-style';
4
+ import { memo } from 'react';
5
+ import { Center, Flexbox } from 'react-layout-kit';
6
+
7
+ import AgentCardBanner from '../../features/AgentCard/AgentCardBanner';
8
+
9
+ const Banner = memo<{
10
+ avatar?: string;
11
+ backgroundColor?: string;
12
+ loading?: boolean;
13
+ mobile?: boolean;
14
+ }>(({ avatar, backgroundColor, mobile, loading }) => {
15
+ const theme = useTheme();
16
+
17
+ return (
18
+ <Flexbox align={'center'}>
19
+ <AgentCardBanner
20
+ avatar={loading ? undefined : avatar}
21
+ size={800}
22
+ style={{ height: 120, marginBottom: -60 }}
23
+ />
24
+ <Center
25
+ flex={'none'}
26
+ height={120}
27
+ style={{
28
+ backgroundColor:
29
+ backgroundColor || mobile ? theme.colorBgElevated : theme.colorBgContainer,
30
+ borderRadius: '50%',
31
+ overflow: 'hidden',
32
+ zIndex: 2,
33
+ }}
34
+ width={120}
35
+ >
36
+ {loading ? (
37
+ <Skeleton.Avatar active size={100} />
38
+ ) : (
39
+ <Avatar animation avatar={avatar} shape={'circle'} size={100} />
40
+ )}
41
+ </Center>
42
+ </Flexbox>
43
+ );
44
+ });
45
+
46
+ export default Banner;
@@ -1,4 +1,4 @@
1
- import { Avatar, Tag } from '@lobehub/ui';
1
+ import { Tag } from '@lobehub/ui';
2
2
  import { App, Button, Typography } from 'antd';
3
3
  import isEqual from 'fast-deep-equal';
4
4
  import { startCase } from 'lodash-es';
@@ -16,18 +16,18 @@ import { useStyles } from './style';
16
16
 
17
17
  const { Link } = Typography;
18
18
 
19
- const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
19
+ const Header = memo(() => {
20
20
  const setSearchKeywords = useMarketStore((s) => s.setSearchKeywords);
21
21
  const router = useRouter();
22
22
  const { t } = useTranslation('market');
23
- const { styles, theme } = useStyles();
23
+ const { styles } = useStyles();
24
24
  const createSession = useSessionStore((s) => s.createSession);
25
25
  const agentItem = useMarketStore(agentMarketSelectors.currentAgentItem, isEqual);
26
26
 
27
27
  const { message } = App.useApp();
28
28
 
29
29
  const { meta, createAt, author, homepage, config } = agentItem;
30
- const { avatar, title, description, tags, backgroundColor } = meta;
30
+ const { title, description, tags } = meta;
31
31
 
32
32
  const isMobile = useIsMobile();
33
33
 
@@ -47,20 +47,6 @@ const Header = memo<{ mobile?: boolean }>(({ mobile }) => {
47
47
 
48
48
  return (
49
49
  <Center className={styles.container} gap={16}>
50
- <Center
51
- flex={'none'}
52
- height={120}
53
- style={{
54
- backgroundColor:
55
- backgroundColor || mobile ? theme.colorBgElevated : theme.colorBgContainer,
56
- borderRadius: '50%',
57
- overflow: 'hidden',
58
- zIndex: 2,
59
- }}
60
- width={120}
61
- >
62
- <Avatar animation avatar={avatar} size={100} />
63
- </Center>
64
50
  <h2 className={styles.title}>{title}</h2>
65
51
  <Center gap={6} horizontal style={{ flexWrap: 'wrap' }}>
66
52
  {(tags as string[]).map((tag: string, index) => (
@@ -2,25 +2,26 @@ import { Skeleton } from 'antd';
2
2
  import { memo } from 'react';
3
3
  import { Center, Flexbox } from 'react-layout-kit';
4
4
 
5
+ import Banner from './Banner';
5
6
  import { useStyles } from './style';
6
7
 
7
8
  const Loading = memo(() => {
8
9
  const { styles } = useStyles();
9
10
  return (
10
11
  <>
11
- <Center className={styles.container} gap={16} style={{ paddingTop: 80 }}>
12
- <Skeleton.Avatar active shape={'circle'} size={100} />
12
+ <Banner loading />
13
+ <Center className={styles.container} gap={16}>
13
14
  <Skeleton
14
15
  active
15
16
  className={styles.loading}
16
17
  paragraph={{
17
- rows: 3,
18
+ rows: 2,
18
19
  style: {
19
20
  alignItems: 'center',
20
21
  display: 'flex',
21
22
  flexDirection: 'column',
22
23
  },
23
- width: ['60%', '80%', '20%'],
24
+ width: ['60%', '80%'],
24
25
  }}
25
26
  title={{
26
27
  style: {
@@ -19,9 +19,6 @@ export const useStyles = createStyles(({ css, token, prefixCls }) => ({
19
19
  flex-direction: column;
20
20
  }
21
21
  `,
22
- nav: css`
23
- padding-top: 8px;
24
- `,
25
22
  time: css`
26
23
  font-size: 12px;
27
24
  color: ${token.colorTextDescription};
@@ -22,6 +22,7 @@ export const useStyles = createStyles(({ css, token }) => ({
22
22
 
23
23
  interface AgentCardBannerProps extends DivProps {
24
24
  avatar?: string;
25
+ loading?: boolean;
25
26
  mask?: boolean;
26
27
  maskColor?: string;
27
28
  size?: number;
@@ -29,22 +30,26 @@ interface AgentCardBannerProps extends DivProps {
29
30
 
30
31
  const AgentCardBanner = memo<AgentCardBannerProps>(
31
32
  ({ avatar, className, size = 600, children, ...props }) => {
32
- const { styles, cx } = useStyles();
33
+ const { styles, theme, cx } = useStyles();
33
34
 
34
35
  return (
35
36
  <Flexbox
36
37
  align={'center'}
37
38
  className={cx(styles.banner, className)}
38
39
  justify={'center'}
40
+ style={avatar ? {} : { backgroundColor: theme.colorFillTertiary }}
41
+ width={'100%'}
39
42
  {...props}
40
43
  >
41
- <Avatar
42
- alt={'banner'}
43
- avatar={avatar}
44
- className={styles.bannerImg}
45
- shape={'square'}
46
- size={size}
47
- />
44
+ {avatar && (
45
+ <Avatar
46
+ alt={'banner'}
47
+ avatar={avatar}
48
+ className={styles.bannerImg}
49
+ shape={'square'}
50
+ size={size}
51
+ />
52
+ )}
48
53
  {children}
49
54
  </Flexbox>
50
55
  );
@@ -1,3 +1,15 @@
1
+ 'use client';
2
+
1
3
  import { Skeleton } from 'antd';
4
+ import { Flexbox } from 'react-layout-kit';
2
5
 
3
- export default () => <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />;
6
+ export default () => (
7
+ <Flexbox gap={16}>
8
+ <Skeleton.Input active block />
9
+ <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />
10
+ <Skeleton.Button active />
11
+ <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />
12
+ <Skeleton.Button active />
13
+ <Skeleton paragraph={{ rows: 8 }} style={{ marginBlock: 24 }} title={false} />
14
+ </Flexbox>
15
+ );
@@ -21,7 +21,11 @@ const CategoryContent = memo<{ modal?: boolean }>(({ modal }) => {
21
21
  <Menu
22
22
  items={cateItems}
23
23
  onClick={({ key }) => {
24
- router.push(urlJoin('/settings', key));
24
+ if (modal) {
25
+ router.replace('/settings/modal', { query: { tab: key } });
26
+ } else {
27
+ router.push(urlJoin('/settings', key));
28
+ }
25
29
  }}
26
30
  selectable
27
31
  selectedKeys={[modal ? tab : (activeTab as any)]}
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { useLayoutEffect } from 'react';
4
+ import urlJoin from 'url-join';
5
+
6
+ import { useQuery } from '@/hooks/useQuery';
7
+ import { useQueryRoute } from '@/hooks/useQueryRoute';
8
+ import { SettingsTabs } from '@/store/global/initialState';
9
+
10
+ /**
11
+ * @description: Settings Modal (intercepting routes fallback when hard refresh)
12
+ * @example: /settings/modal?tab=common => /settings/common
13
+ * @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
14
+ */
15
+
16
+ const SettingsModalFallback = () => {
17
+ const { tab = SettingsTabs.Common } = useQuery();
18
+ const router = useQueryRoute();
19
+
20
+ useLayoutEffect(() => {
21
+ router.replace(urlJoin('/settings', tab as SettingsTabs), { query: { tab: '' } });
22
+ }, []);
23
+
24
+ return null;
25
+ };
26
+
27
+ export default SettingsModalFallback;
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+
3
+ import dynamic from 'next/dynamic';
4
+ import { memo } from 'react';
5
+
6
+ import { useQuery } from '@/hooks/useQuery';
7
+ import { SettingsTabs } from '@/store/global/initialState';
8
+
9
+ import Skeleton from './loading';
10
+
11
+ const loading = () => <Skeleton />;
12
+
13
+ const Common = dynamic(() => import('@/app/(main)/settings/common'), { loading, ssr: false });
14
+ const About = dynamic(() => import('@/app/(main)/settings/about'), { loading, ssr: false });
15
+ const LLM = dynamic(() => import('@/app/(main)/settings/llm'), { loading, ssr: false });
16
+ const TTS = dynamic(() => import('@/app/(main)/settings/tts'), { loading, ssr: false });
17
+ const Agent = dynamic(() => import('@/app/(main)/settings/agent'), { loading, ssr: false });
18
+ const Sync = dynamic(() => import('@/app/(main)/settings/sync'), { loading, ssr: false });
19
+
20
+ interface SettingsModalProps {
21
+ browser?: string;
22
+ mobile?: boolean;
23
+ os?: string;
24
+ }
25
+
26
+ const SettingsModal = memo<SettingsModalProps>(({ browser, os, mobile }) => {
27
+ const { tab = SettingsTabs.Common } = useQuery();
28
+ return (
29
+ <>
30
+ {tab === SettingsTabs.Common && <Common />}
31
+ {tab === SettingsTabs.Sync && <Sync browser={browser} mobile={mobile} os={os} />}
32
+ {tab === SettingsTabs.LLM && <LLM />}
33
+ {tab === SettingsTabs.TTS && <TTS />}
34
+ {tab === SettingsTabs.Agent && <Agent />}
35
+ {tab === SettingsTabs.About && <About mobile={mobile} />}
36
+ </>
37
+ );
38
+ });
39
+
40
+ export default SettingsModal;
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { Skeleton } from 'antd';
4
+ import dynamic from 'next/dynamic';
5
+ import { PropsWithChildren, memo } from 'react';
6
+
7
+ import SettingModalLayout from '../../_layout/SettingModalLayout';
8
+
9
+ const CategoryContent = dynamic(
10
+ () => import('@/app/(main)/settings/@category/features/CategoryContent'),
11
+ { loading: () => <Skeleton paragraph={{ rows: 6 }} title={false} />, ssr: false },
12
+ );
13
+ const UpgradeAlert = dynamic(() => import('@/app/(main)/settings/features/UpgradeAlert'), {
14
+ ssr: false,
15
+ });
16
+
17
+ const Layout = memo<PropsWithChildren>(({ children }) => {
18
+ return (
19
+ <SettingModalLayout
20
+ category={
21
+ <>
22
+ <CategoryContent modal />
23
+ <UpgradeAlert />
24
+ </>
25
+ }
26
+ >
27
+ {children}
28
+ </SettingModalLayout>
29
+ );
30
+ });
31
+
32
+ export default Layout;
@@ -0,0 +1,5 @@
1
+ import { Skeleton } from 'antd';
2
+
3
+ export default () => {
4
+ return <Skeleton paragraph={{ rows: 6 }} style={{ paddingBlock: 16 }} />;
5
+ };
@@ -0,0 +1,19 @@
1
+ import { gerServerDeviceInfo, isMobileDevice } from '@/utils/responsive';
2
+
3
+ import SettingsModal from './index';
4
+
5
+ /**
6
+ * @description: Settings Modal (intercepting route: /settings/modal )
7
+ * @refs: https://github.com/lobehub/lobe-chat/discussions/2295#discussioncomment-9290942
8
+ */
9
+
10
+ const Page = () => {
11
+ const isMobile = isMobileDevice();
12
+ const { os, browser } = gerServerDeviceInfo();
13
+
14
+ return <SettingsModal browser={browser} mobile={isMobile} os={os} />;
15
+ };
16
+
17
+ Page.displayName = 'SettingModal';
18
+
19
+ export default Page;
@@ -0,0 +1,59 @@
1
+ 'use client';
2
+
3
+ import { useResponsive, useTheme, useThemeMode } from 'antd-style';
4
+ import { ReactNode, memo, useRef } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import Header from '@/app/(main)/settings/_layout/Desktop/Header';
8
+ import SideBar from '@/app/(main)/settings/_layout/Desktop/SideBar';
9
+
10
+ interface SettingLayoutProps {
11
+ category: ReactNode;
12
+ children: ReactNode;
13
+ desc?: string;
14
+ title?: string;
15
+ }
16
+
17
+ const SettingModalLayout = memo<SettingLayoutProps>(({ children, category, desc, title }) => {
18
+ const ref = useRef<any>(null);
19
+ const theme = useTheme();
20
+ const { isDarkMode } = useThemeMode();
21
+ const { md = true } = useResponsive();
22
+
23
+ return (
24
+ <>
25
+ {md ? (
26
+ <SideBar
27
+ desc={desc}
28
+ style={{
29
+ background: isDarkMode ? theme.colorBgContainer : theme.colorFillTertiary,
30
+ borderColor: theme.colorFillTertiary,
31
+ }}
32
+ title={title}
33
+ >
34
+ {category}
35
+ </SideBar>
36
+ ) : (
37
+ <Header getContainer={() => ref.current}>{category}</Header>
38
+ )}
39
+ <Flexbox
40
+ align={'center'}
41
+ gap={64}
42
+ style={{
43
+ background: isDarkMode ? theme.colorFillQuaternary : theme.colorBgElevated,
44
+ overflowX: 'hidden',
45
+ overflowY: 'auto',
46
+ paddingBlock: 40,
47
+ paddingInline: 56,
48
+ }}
49
+ width={'100%'}
50
+ >
51
+ {children}
52
+ </Flexbox>
53
+ </>
54
+ );
55
+ });
56
+
57
+ SettingModalLayout.displayName = 'SettingModalLayout';
58
+
59
+ export default SettingModalLayout;