@lobehub/chat 0.157.2 → 0.158.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 (92) hide show
  1. package/.eslintignore +1 -2
  2. package/CHANGELOG.md +25 -0
  3. package/locales/ar/auth.json +2 -0
  4. package/locales/ar/common.json +1 -0
  5. package/locales/bg-BG/auth.json +2 -0
  6. package/locales/bg-BG/common.json +1 -0
  7. package/locales/bg-BG/error.json +3 -3
  8. package/locales/de-DE/auth.json +2 -0
  9. package/locales/de-DE/common.json +1 -0
  10. package/locales/en-US/auth.json +2 -0
  11. package/locales/en-US/common.json +1 -0
  12. package/locales/es-ES/auth.json +2 -0
  13. package/locales/es-ES/common.json +1 -0
  14. package/locales/fr-FR/auth.json +2 -0
  15. package/locales/fr-FR/common.json +1 -0
  16. package/locales/it-IT/auth.json +2 -0
  17. package/locales/it-IT/common.json +1 -0
  18. package/locales/ja-JP/auth.json +2 -0
  19. package/locales/ja-JP/common.json +1 -0
  20. package/locales/ko-KR/auth.json +2 -0
  21. package/locales/ko-KR/common.json +1 -0
  22. package/locales/nl-NL/auth.json +2 -0
  23. package/locales/nl-NL/common.json +50 -49
  24. package/locales/pl-PL/auth.json +2 -0
  25. package/locales/pl-PL/common.json +1 -0
  26. package/locales/pl-PL/error.json +3 -3
  27. package/locales/pt-BR/auth.json +2 -0
  28. package/locales/pt-BR/common.json +1 -0
  29. package/locales/ru-RU/auth.json +2 -0
  30. package/locales/ru-RU/common.json +1 -0
  31. package/locales/ru-RU/error.json +3 -3
  32. package/locales/tr-TR/auth.json +2 -0
  33. package/locales/tr-TR/common.json +1 -0
  34. package/locales/vi-VN/auth.json +2 -0
  35. package/locales/vi-VN/common.json +1 -0
  36. package/locales/zh-CN/auth.json +2 -0
  37. package/locales/zh-CN/common.json +1 -0
  38. package/locales/zh-TW/auth.json +2 -0
  39. package/locales/zh-TW/common.json +1 -0
  40. package/package.json +1 -1
  41. package/src/app/(main)/(mobile)/me/(home)/__tests__/UserBanner.test.tsx +80 -0
  42. package/src/app/(main)/(mobile)/me/(home)/__tests__/useCategory.test.tsx +116 -0
  43. package/src/app/(main)/(mobile)/me/(home)/features/Category.tsx +15 -0
  44. package/src/app/(main)/(mobile)/me/(home)/features/UserBanner.tsx +37 -0
  45. package/src/app/(main)/(mobile)/me/(home)/features/useCategory.tsx +95 -0
  46. package/src/app/(main)/(mobile)/me/{page.tsx → (home)/page.tsx} +6 -10
  47. package/src/app/(main)/(mobile)/me/data/features/Category.tsx +48 -0
  48. package/src/app/(main)/(mobile)/me/data/features/Header.tsx +33 -0
  49. package/src/app/(main)/(mobile)/me/data/layout.tsx +13 -0
  50. package/src/app/(main)/(mobile)/me/data/loading.tsx +5 -0
  51. package/src/app/(main)/(mobile)/me/data/page.tsx +17 -0
  52. package/src/app/(main)/(mobile)/me/profile/features/Category.tsx +45 -0
  53. package/src/app/(main)/(mobile)/me/profile/features/Header.tsx +33 -0
  54. package/src/app/(main)/(mobile)/me/profile/layout.tsx +16 -0
  55. package/src/app/(main)/(mobile)/me/profile/loading.tsx +5 -0
  56. package/src/app/(main)/(mobile)/me/profile/page.tsx +17 -0
  57. package/src/app/(main)/(mobile)/me/settings/features/Category.tsx +15 -0
  58. package/src/app/(main)/(mobile)/me/settings/features/Header.tsx +33 -0
  59. package/src/app/(main)/(mobile)/me/settings/features/useCategory.tsx +57 -0
  60. package/src/app/(main)/(mobile)/me/settings/layout.tsx +13 -0
  61. package/src/app/(main)/(mobile)/me/settings/loading.tsx +5 -0
  62. package/src/app/(main)/(mobile)/me/settings/page.tsx +17 -0
  63. package/src/app/(main)/_layout/Mobile.tsx +5 -4
  64. package/src/app/(main)/profile/[[...slugs]]/Client.tsx +74 -0
  65. package/src/app/(main)/profile/[[...slugs]]/page.tsx +18 -0
  66. package/src/app/(main)/profile/_layout/Mobile/Header.tsx +26 -0
  67. package/src/app/(main)/profile/_layout/Mobile/index.tsx +16 -0
  68. package/src/app/(main)/profile/layout.tsx +20 -0
  69. package/src/app/(main)/profile/loading.tsx +23 -0
  70. package/src/app/(main)/settings/_layout/Mobile/Header.tsx +2 -1
  71. package/src/app/(main)/settings/hooks/useCategory.tsx +7 -13
  72. package/src/app/@modal/layout.tsx +3 -0
  73. package/src/components/Cell/Divider.tsx +3 -2
  74. package/src/components/Cell/index.tsx +28 -18
  75. package/src/features/User/DataStatistics.tsx +3 -1
  76. package/src/features/User/UserLoginOrSignup.tsx +2 -2
  77. package/src/features/User/UserPanel/PanelContent.tsx +9 -3
  78. package/src/features/User/UserPanel/useMenu.tsx +29 -29
  79. package/src/features/User/__tests__/PanelContent.test.tsx +7 -0
  80. package/src/features/User/__tests__/useMenu.test.tsx +142 -0
  81. package/src/layout/AuthProvider/Clerk/useAppearance.ts +5 -4
  82. package/src/locales/default/auth.ts +2 -0
  83. package/src/locales/default/common.ts +1 -0
  84. package/src/store/user/slices/auth/selectors.ts +2 -1
  85. package/src/app/(auth)/profile/[[...slugs]]/PageTitle.tsx +0 -13
  86. package/src/app/(auth)/profile/[[...slugs]]/page.tsx +0 -14
  87. package/src/app/(main)/(mobile)/me/features/Cate.tsx +0 -33
  88. package/src/app/(main)/(mobile)/me/features/ExtraCate.tsx +0 -24
  89. package/src/app/(main)/(mobile)/me/features/useExtraCate.tsx +0 -62
  90. /package/src/app/(main)/(mobile)/me/{features → (home)/features}/Header.tsx +0 -0
  91. /package/src/app/(main)/(mobile)/me/{layout.tsx → (home)/layout.tsx} +0 -0
  92. /package/src/app/(main)/(mobile)/me/{loading.tsx → (home)/loading.tsx} +0 -0
@@ -0,0 +1,116 @@
1
+ import { act, renderHook } from '@testing-library/react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+
4
+ import { useUserStore } from '@/store/user';
5
+
6
+ import { useCategory } from '../features/useCategory';
7
+
8
+ // Mock dependencies
9
+ vi.mock('next/navigation', () => ({
10
+ useRouter: vi.fn(() => ({
11
+ push: vi.fn(),
12
+ })),
13
+ }));
14
+
15
+ vi.mock('react-i18next', () => ({
16
+ useTranslation: vi.fn(() => ({
17
+ t: vi.fn((key) => key),
18
+ })),
19
+ }));
20
+
21
+ vi.mock('../../settings/features/useCategory', () => ({
22
+ useCategory: vi.fn(() => [{ key: 'extraSetting', label: 'Extra Setting' }]),
23
+ }));
24
+
25
+ // 定义一个变量来存储 enableAuth 的值
26
+ let enableAuth = true;
27
+ let enableClerk = true;
28
+ // 模拟 @/const/auth 模块
29
+ vi.mock('@/const/auth', () => ({
30
+ get enableAuth() {
31
+ return enableAuth;
32
+ },
33
+ get enableClerk() {
34
+ return enableClerk;
35
+ },
36
+ }));
37
+
38
+ afterEach(() => {
39
+ enableAuth = true;
40
+ enableClerk = true;
41
+ });
42
+
43
+ describe('useCategory', () => {
44
+ it('should return correct items when the user is logged in with authentication', () => {
45
+ act(() => {
46
+ useUserStore.setState({ isSignedIn: true });
47
+ });
48
+ enableAuth = true;
49
+ enableClerk = false;
50
+
51
+ const { result } = renderHook(() => useCategory());
52
+
53
+ act(() => {
54
+ const items = result.current;
55
+ expect(items.some((item) => item.key === 'profile')).toBe(false);
56
+ expect(items.some((item) => item.key === 'setting')).toBe(true);
57
+ expect(items.some((item) => item.key === 'data')).toBe(true);
58
+ expect(items.some((item) => item.key === 'docs')).toBe(true);
59
+ expect(items.some((item) => item.key === 'feedback')).toBe(true);
60
+ expect(items.some((item) => item.key === 'discord')).toBe(true);
61
+ });
62
+ });
63
+
64
+ it('should return correct items when the user is logged in with Clerk', () => {
65
+ act(() => {
66
+ useUserStore.setState({ isSignedIn: true });
67
+ });
68
+ enableAuth = true;
69
+ enableClerk = true;
70
+
71
+ const { result } = renderHook(() => useCategory());
72
+
73
+ act(() => {
74
+ const items = result.current;
75
+ expect(items.some((item) => item.key === 'profile')).toBe(true);
76
+ expect(items.some((item) => item.key === 'setting')).toBe(true);
77
+ expect(items.some((item) => item.key === 'data')).toBe(true);
78
+ expect(items.some((item) => item.key === 'docs')).toBe(true);
79
+ expect(items.some((item) => item.key === 'feedback')).toBe(true);
80
+ expect(items.some((item) => item.key === 'discord')).toBe(true);
81
+ });
82
+ });
83
+
84
+ it('should return correct items when the user is not logged in', () => {
85
+ act(() => {
86
+ useUserStore.setState({ isSignedIn: false });
87
+ });
88
+ enableAuth = true;
89
+
90
+ const { result } = renderHook(() => useCategory());
91
+
92
+ act(() => {
93
+ const items = result.current;
94
+ expect(items.some((item) => item.key === 'profile')).toBe(false);
95
+ expect(items.some((item) => item.key === 'setting')).toBe(false);
96
+ expect(items.some((item) => item.key === 'data')).toBe(false);
97
+ expect(items.some((item) => item.key === 'docs')).toBe(true);
98
+ expect(items.some((item) => item.key === 'feedback')).toBe(true);
99
+ expect(items.some((item) => item.key === 'discord')).toBe(true);
100
+ });
101
+ });
102
+
103
+ it('should handle settings for non-authenticated users', () => {
104
+ act(() => {
105
+ useUserStore.setState({ isSignedIn: false });
106
+ });
107
+ enableAuth = false;
108
+
109
+ const { result } = renderHook(() => useCategory());
110
+
111
+ act(() => {
112
+ const items = result.current;
113
+ expect(items.some((item) => item.key === 'extraSetting')).toBe(true);
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+
5
+ import Cell from '@/components/Cell';
6
+
7
+ import { useCategory } from './useCategory';
8
+
9
+ const Category = memo(() => {
10
+ const items = useCategory();
11
+
12
+ return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
13
+ });
14
+
15
+ export default Category;
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+ import { useRouter } from 'next/navigation';
4
+ import { memo } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { enableAuth } from '@/const/auth';
8
+ import DataStatistics from '@/features/User/DataStatistics';
9
+ import UserInfo from '@/features/User/UserInfo';
10
+ import UserLoginOrSignup from '@/features/User/UserLoginOrSignup';
11
+ import { useUserStore } from '@/store/user';
12
+ import { authSelectors } from '@/store/user/selectors';
13
+
14
+ const UserBanner = memo(() => {
15
+ const router = useRouter();
16
+ const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
17
+
18
+ return (
19
+ <Flexbox gap={12} paddingBlock={8}>
20
+ {!enableAuth ? (
21
+ <>
22
+ <UserInfo />
23
+ <DataStatistics paddingInline={12} />
24
+ </>
25
+ ) : isLoginWithAuth ? (
26
+ <>
27
+ <UserInfo onClick={() => router.push('/me/profile')} />
28
+ <DataStatistics paddingInline={12} />
29
+ </>
30
+ ) : (
31
+ <UserLoginOrSignup onClick={() => router.push('/login')} />
32
+ )}
33
+ </Flexbox>
34
+ );
35
+ });
36
+
37
+ export default UserBanner;
@@ -0,0 +1,95 @@
1
+ import { DiscordIcon } from '@lobehub/ui';
2
+ import { Book, CircleUserRound, Database, Feather, Settings2 } from 'lucide-react';
3
+ import { useRouter } from 'next/navigation';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import { CellProps } from '@/components/Cell';
7
+ import { enableAuth } from '@/const/auth';
8
+ import { DISCORD, DOCUMENTS, FEEDBACK } from '@/const/url';
9
+ import { useUserStore } from '@/store/user';
10
+ import { authSelectors } from '@/store/user/slices/auth/selectors';
11
+
12
+ import { useCategory as useSettingsCategory } from '../../settings/features/useCategory';
13
+
14
+ export const useCategory = () => {
15
+ const router = useRouter();
16
+ const { t } = useTranslation(['common', 'setting', 'auth']);
17
+ const [isLogin, isLoginWithAuth, isLoginWithClerk] = useUserStore((s) => [
18
+ authSelectors.isLogin(s),
19
+ authSelectors.isLoginWithAuth(s),
20
+ authSelectors.isLoginWithClerk(s),
21
+ ]);
22
+
23
+ const profile: CellProps[] = [
24
+ {
25
+ icon: CircleUserRound,
26
+ key: 'profile',
27
+ label: t('userPanel.profile'),
28
+ onClick: () => router.push('/me/profile'),
29
+ },
30
+ ];
31
+
32
+ const settings: CellProps[] = [
33
+ {
34
+ icon: Settings2,
35
+ key: 'setting',
36
+ label: t('userPanel.setting'),
37
+ onClick: () => router.push('/me/settings'),
38
+ },
39
+ {
40
+ type: 'divider',
41
+ },
42
+ ];
43
+
44
+ const settingsWithoutAuth = [
45
+ ...useSettingsCategory(),
46
+ {
47
+ type: 'divider',
48
+ },
49
+ ];
50
+
51
+ const data: CellProps[] = [
52
+ {
53
+ icon: Database,
54
+ key: 'data',
55
+ label: t('userPanel.data'),
56
+ onClick: () => router.push('/me/data'),
57
+ },
58
+ {
59
+ type: 'divider',
60
+ },
61
+ ];
62
+
63
+ const helps: CellProps[] = [
64
+ {
65
+ icon: Book,
66
+ key: 'docs',
67
+ label: t('document'),
68
+ onClick: () => window.open(DOCUMENTS, '__blank'),
69
+ },
70
+ {
71
+ icon: Feather,
72
+ key: 'feedback',
73
+ label: t('feedback'),
74
+ onClick: () => window.open(FEEDBACK, '__blank'),
75
+ },
76
+ {
77
+ icon: DiscordIcon,
78
+ key: 'discord',
79
+ label: 'Discord',
80
+ onClick: () => window.open(DISCORD, '__blank'),
81
+ },
82
+ ];
83
+
84
+ const mainItems = [
85
+ {
86
+ type: 'divider',
87
+ },
88
+ ...(isLoginWithClerk ? profile : []),
89
+ ...(enableAuth ? (isLoginWithAuth ? settings : []) : settingsWithoutAuth),
90
+ ...(isLogin ? data : []),
91
+ ...helps,
92
+ ].filter(Boolean) as CellProps[];
93
+
94
+ return mainItems;
95
+ };
@@ -2,13 +2,10 @@ import { redirect } from 'next/navigation';
2
2
  import { Center } from 'react-layout-kit';
3
3
 
4
4
  import BrandWatermark from '@/components/BrandWatermark';
5
- import Divider from '@/components/Cell/Divider';
6
- import DataStatistics from '@/features/User/DataStatistics';
7
- import UserInfo from '@/features/User/UserInfo';
8
5
  import { isMobileDevice } from '@/utils/responsive';
9
6
 
10
- import Cate from './features/Cate';
11
- import ExtraCate from './features/ExtraCate';
7
+ import Category from './features/Category';
8
+ import UserBanner from './features/UserBanner';
12
9
 
13
10
  const Page = () => {
14
11
  const mobile = isMobileDevice();
@@ -17,11 +14,8 @@ const Page = () => {
17
14
 
18
15
  return (
19
16
  <>
20
- <UserInfo />
21
- <DataStatistics paddingInline={12} style={{ paddingBottom: 6 }} />
22
- <Divider />
23
- <Cate />
24
- <ExtraCate />
17
+ <UserBanner />
18
+ <Category />
25
19
  <Center padding={16}>
26
20
  <BrandWatermark />
27
21
  </Center>
@@ -29,4 +23,6 @@ const Page = () => {
29
23
  );
30
24
  };
31
25
 
26
+ Page.displayName = 'Me';
27
+
32
28
  export default Page;
@@ -0,0 +1,48 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+
6
+ import Cell, { CellProps } from '@/components/Cell';
7
+ import DataImporter from '@/features/DataImporter';
8
+ import { configService } from '@/services/config';
9
+
10
+ const Category = memo(() => {
11
+ const { t } = useTranslation('common');
12
+ const items: CellProps[] = [
13
+ {
14
+ key: 'allAgent',
15
+ label: t('exportType.allAgent'),
16
+ onClick: configService.exportAgents,
17
+ },
18
+ {
19
+ key: 'allAgentWithMessage',
20
+ label: t('exportType.allAgentWithMessage'),
21
+ onClick: configService.exportSessions,
22
+ },
23
+ {
24
+ key: 'globalSetting',
25
+ label: t('exportType.globalSetting'),
26
+ onClick: configService.exportSettings,
27
+ },
28
+ {
29
+ type: 'divider',
30
+ },
31
+ {
32
+ key: 'all',
33
+ label: t('exportType.all'),
34
+ onClick: configService.exportAll,
35
+ },
36
+ {
37
+ type: 'divider',
38
+ },
39
+ {
40
+ key: 'import',
41
+ label: <DataImporter>{t('import')}</DataImporter>,
42
+ },
43
+ ];
44
+
45
+ return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
46
+ });
47
+
48
+ export default Category;
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
4
+ import { useRouter } from 'next/navigation';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { mobileHeaderSticky } from '@/styles/mobileHeader';
10
+
11
+ const Header = memo(() => {
12
+ const { t } = useTranslation('common');
13
+
14
+ const router = useRouter();
15
+ return (
16
+ <MobileNavBar
17
+ center={
18
+ <MobileNavBarTitle
19
+ title={
20
+ <Flexbox align={'center'} gap={4} horizontal>
21
+ {t('userPanel.data')}
22
+ </Flexbox>
23
+ }
24
+ />
25
+ }
26
+ onBackClick={() => router.push('/me')}
27
+ showBackButton
28
+ style={mobileHeaderSticky}
29
+ />
30
+ );
31
+ });
32
+
33
+ export default Header;
@@ -0,0 +1,13 @@
1
+ import { PropsWithChildren } from 'react';
2
+
3
+ import MobileContentLayout from '@/components/server/MobileNavLayout';
4
+
5
+ import Header from './features/Header';
6
+
7
+ const Layout = ({ children }: PropsWithChildren) => {
8
+ return <MobileContentLayout header={<Header />}>{children}</MobileContentLayout>;
9
+ };
10
+
11
+ Layout.displayName = 'MeDataLayout';
12
+
13
+ export default Layout;
@@ -0,0 +1,5 @@
1
+ import SkeletonLoading from '@/components/SkeletonLoading';
2
+
3
+ export default () => {
4
+ return <SkeletonLoading paragraph={{ rows: 8 }} />;
5
+ };
@@ -0,0 +1,17 @@
1
+ import { redirect } from 'next/navigation';
2
+
3
+ import { isMobileDevice } from '@/utils/responsive';
4
+
5
+ import Category from './features/Category';
6
+
7
+ const Page = () => {
8
+ const mobile = isMobileDevice();
9
+
10
+ if (!mobile) return redirect('/chat');
11
+
12
+ return <Category />;
13
+ };
14
+
15
+ Page.displayName = 'MeData';
16
+
17
+ export default Page;
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ import { LogOut, ShieldCheck, UserCircle } from 'lucide-react';
4
+ import { useRouter } from 'next/navigation';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import Cell, { CellProps } from '@/components/Cell';
9
+ import { useUserStore } from '@/store/user';
10
+
11
+ const Category = memo(() => {
12
+ const router = useRouter();
13
+ const { t } = useTranslation('auth');
14
+ const signOut = useUserStore((s) => s.logout);
15
+ const items: CellProps[] = [
16
+ {
17
+ icon: UserCircle,
18
+ key: 'profile',
19
+ label: t('profile'),
20
+ onClick: () => router.push('/profile'),
21
+ },
22
+ {
23
+ icon: ShieldCheck,
24
+ key: 'security',
25
+ label: t('security'),
26
+ onClick: () => router.push('/profile/security'),
27
+ },
28
+ {
29
+ type: 'divider',
30
+ },
31
+ {
32
+ icon: LogOut,
33
+ key: 'logout',
34
+ label: t('signout', { ns: 'auth' }),
35
+ onClick: () => {
36
+ signOut();
37
+ router.push('/login');
38
+ },
39
+ },
40
+ ];
41
+
42
+ return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
43
+ });
44
+
45
+ export default Category;
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
4
+ import { useRouter } from 'next/navigation';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { mobileHeaderSticky } from '@/styles/mobileHeader';
10
+
11
+ const Header = memo(() => {
12
+ const { t } = useTranslation('common');
13
+
14
+ const router = useRouter();
15
+ return (
16
+ <MobileNavBar
17
+ center={
18
+ <MobileNavBarTitle
19
+ title={
20
+ <Flexbox align={'center'} gap={4} horizontal>
21
+ {t('userPanel.profile')}
22
+ </Flexbox>
23
+ }
24
+ />
25
+ }
26
+ onBackClick={() => router.push('/me')}
27
+ showBackButton
28
+ style={mobileHeaderSticky}
29
+ />
30
+ );
31
+ });
32
+
33
+ export default Header;
@@ -0,0 +1,16 @@
1
+ import { notFound } from 'next/navigation';
2
+ import { PropsWithChildren } from 'react';
3
+
4
+ import MobileContentLayout from '@/components/server/MobileNavLayout';
5
+ import { enableClerk } from '@/const/auth';
6
+
7
+ import Header from './features/Header';
8
+
9
+ const Layout = ({ children }: PropsWithChildren) => {
10
+ if (!enableClerk) return notFound();
11
+ return <MobileContentLayout header={<Header />}>{children}</MobileContentLayout>;
12
+ };
13
+
14
+ Layout.displayName = 'MeProfileLayout';
15
+
16
+ export default Layout;
@@ -0,0 +1,5 @@
1
+ import SkeletonLoading from '@/components/SkeletonLoading';
2
+
3
+ export default () => {
4
+ return <SkeletonLoading paragraph={{ rows: 8 }} />;
5
+ };
@@ -0,0 +1,17 @@
1
+ import { redirect } from 'next/navigation';
2
+
3
+ import { isMobileDevice } from '@/utils/responsive';
4
+
5
+ import Category from './features/Category';
6
+
7
+ const Page = () => {
8
+ const mobile = isMobileDevice();
9
+
10
+ if (!mobile) return redirect('/profile');
11
+
12
+ return <Category />;
13
+ };
14
+
15
+ Page.displayName = 'MeProfile';
16
+
17
+ export default Page;
@@ -0,0 +1,15 @@
1
+ 'use client';
2
+
3
+ import { memo } from 'react';
4
+
5
+ import Cell from '@/components/Cell';
6
+
7
+ import { useCategory } from './useCategory';
8
+
9
+ const Category = memo(() => {
10
+ const items = useCategory();
11
+
12
+ return items?.map((item, index) => <Cell key={item.key || index} {...item} />);
13
+ });
14
+
15
+ export default Category;
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { MobileNavBar, MobileNavBarTitle } from '@lobehub/ui';
4
+ import { useRouter } from 'next/navigation';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+
9
+ import { mobileHeaderSticky } from '@/styles/mobileHeader';
10
+
11
+ const Header = memo(() => {
12
+ const { t } = useTranslation('common');
13
+
14
+ const router = useRouter();
15
+ return (
16
+ <MobileNavBar
17
+ center={
18
+ <MobileNavBarTitle
19
+ title={
20
+ <Flexbox align={'center'} gap={4} horizontal>
21
+ {t('userPanel.setting')}
22
+ </Flexbox>
23
+ }
24
+ />
25
+ }
26
+ onBackClick={() => router.push('/me')}
27
+ showBackButton
28
+ style={mobileHeaderSticky}
29
+ />
30
+ );
31
+ });
32
+
33
+ export default Header;
@@ -0,0 +1,57 @@
1
+ import { Tag } from 'antd';
2
+ import { Bot, Brain, Cloudy, Info, Mic2, Settings2 } from 'lucide-react';
3
+ import { useRouter } from 'next/navigation';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+ import urlJoin from 'url-join';
7
+
8
+ import { CellProps } from '@/components/Cell';
9
+ import { SettingsTabs } from '@/store/global/initialState';
10
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
11
+
12
+ export const useCategory = () => {
13
+ const router = useRouter();
14
+ const { t } = useTranslation('setting');
15
+ const { enableWebrtc, showLLM } = useServerConfigStore(featureFlagsSelectors);
16
+
17
+ const items: CellProps[] = [
18
+ {
19
+ icon: Settings2,
20
+ key: SettingsTabs.Common,
21
+ label: t('tab.common'),
22
+ },
23
+ enableWebrtc && {
24
+ icon: Cloudy,
25
+ key: SettingsTabs.Sync,
26
+ label: (
27
+ <Flexbox align={'center'} gap={8} horizontal>
28
+ {t('tab.sync')}
29
+ <Tag bordered={false} color={'warning'}>
30
+ {t('tab.experiment')}
31
+ </Tag>
32
+ </Flexbox>
33
+ ),
34
+ },
35
+ showLLM && {
36
+ icon: Brain,
37
+ key: SettingsTabs.LLM,
38
+ label: t('tab.llm'),
39
+ },
40
+ { icon: Mic2, key: SettingsTabs.TTS, label: t('tab.tts') },
41
+ {
42
+ icon: Bot,
43
+ key: SettingsTabs.Agent,
44
+ label: t('tab.agent'),
45
+ },
46
+ {
47
+ icon: Info,
48
+ key: SettingsTabs.About,
49
+ label: t('tab.about'),
50
+ },
51
+ ].filter(Boolean) as CellProps[];
52
+
53
+ return items.map((item) => ({
54
+ ...item,
55
+ onClick: () => router.push(urlJoin('/settings', item.key as SettingsTabs)),
56
+ }));
57
+ };
@@ -0,0 +1,13 @@
1
+ import { PropsWithChildren } from 'react';
2
+
3
+ import MobileContentLayout from '@/components/server/MobileNavLayout';
4
+
5
+ import Header from './features/Header';
6
+
7
+ const Layout = ({ children }: PropsWithChildren) => {
8
+ return <MobileContentLayout header={<Header />}>{children}</MobileContentLayout>;
9
+ };
10
+
11
+ Layout.displayName = 'MeSettingsLayout';
12
+
13
+ export default Layout;
@@ -0,0 +1,5 @@
1
+ import SkeletonLoading from '@/components/SkeletonLoading';
2
+
3
+ export default () => {
4
+ return <SkeletonLoading paragraph={{ rows: 8 }} />;
5
+ };