@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,142 @@
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 { useMenu } from '../UserPanel/useMenu';
7
+
8
+ // Mock dependencies
9
+ vi.mock('next/link', () => ({
10
+ default: vi.fn(({ children }) => <div>{children}</div>),
11
+ }));
12
+
13
+ vi.mock('@/hooks/useQueryRoute', () => ({
14
+ useQueryRoute: vi.fn(() => ({
15
+ push: vi.fn(),
16
+ })),
17
+ }));
18
+
19
+ vi.mock('@/hooks/useInterceptingRoutes', () => ({
20
+ useOpenSettings: vi.fn(() => vi.fn()),
21
+ }));
22
+
23
+ vi.mock('@/features/DataImporter', () => ({
24
+ default: vi.fn(({ children }) => <div>{children}</div>),
25
+ }));
26
+
27
+ vi.mock('react-i18next', () => ({
28
+ useTranslation: vi.fn(() => ({
29
+ t: vi.fn((key) => key),
30
+ })),
31
+ }));
32
+
33
+ vi.mock('@/services/config', () => ({
34
+ configService: {
35
+ exportAgents: vi.fn(),
36
+ exportAll: vi.fn(),
37
+ exportSessions: vi.fn(),
38
+ exportSettings: vi.fn(),
39
+ },
40
+ }));
41
+
42
+ vi.mock('./useNewVersion', () => ({
43
+ useNewVersion: vi.fn(() => false),
44
+ }));
45
+
46
+ // 定义一个变量来存储 enableAuth 的值
47
+ let enableAuth = true;
48
+ let enableClerk = true;
49
+ // 模拟 @/const/auth 模块
50
+ vi.mock('@/const/auth', () => ({
51
+ get enableAuth() {
52
+ return enableAuth;
53
+ },
54
+ get enableClerk() {
55
+ return enableClerk;
56
+ },
57
+ }));
58
+
59
+ afterEach(() => {
60
+ enableAuth = true;
61
+ enableClerk = true;
62
+ });
63
+
64
+ describe('useMenu', () => {
65
+ it('should provide correct menu items when user is logged in with auth', () => {
66
+ act(() => {
67
+ useUserStore.setState({ isSignedIn: true });
68
+ });
69
+ enableAuth = true;
70
+ enableClerk = false;
71
+
72
+ const { result } = renderHook(() => useMenu());
73
+
74
+ act(() => {
75
+ const { mainItems, logoutItems } = result.current;
76
+ expect(mainItems?.some((item) => item?.key === 'profile')).toBe(false);
77
+ expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
78
+ expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
79
+ expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
80
+ expect(mainItems?.some((item) => item?.key === 'discord')).toBe(true);
81
+ expect(logoutItems.some((item) => item?.key === 'logout')).toBe(true);
82
+ });
83
+ });
84
+
85
+ it('should provide correct menu items when user is logged in with Clerk', () => {
86
+ act(() => {
87
+ useUserStore.setState({ isSignedIn: true });
88
+ });
89
+ enableAuth = true;
90
+ enableClerk = true;
91
+
92
+ const { result } = renderHook(() => useMenu());
93
+
94
+ act(() => {
95
+ const { mainItems, logoutItems } = result.current;
96
+ expect(mainItems?.some((item) => item?.key === 'profile')).toBe(true);
97
+ expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
98
+ expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
99
+ expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
100
+ expect(mainItems?.some((item) => item?.key === 'discord')).toBe(true);
101
+ expect(logoutItems.some((item) => item?.key === 'logout')).toBe(true);
102
+ });
103
+ });
104
+
105
+ it('should provide correct menu items when user is logged in without auth', () => {
106
+ act(() => {
107
+ useUserStore.setState({ isSignedIn: false });
108
+ });
109
+ enableAuth = false;
110
+
111
+ const { result } = renderHook(() => useMenu());
112
+
113
+ act(() => {
114
+ const { mainItems, logoutItems } = result.current;
115
+ expect(mainItems?.some((item) => item?.key === 'profile')).toBe(false);
116
+ expect(mainItems?.some((item) => item?.key === 'setting')).toBe(true);
117
+ expect(mainItems?.some((item) => item?.key === 'import')).toBe(true);
118
+ expect(mainItems?.some((item) => item?.key === 'export')).toBe(true);
119
+ expect(mainItems?.some((item) => item?.key === 'discord')).toBe(true);
120
+ expect(logoutItems.some((item) => item?.key === 'logout')).toBe(false);
121
+ });
122
+ });
123
+
124
+ it('should provide correct menu items when user is not logged in', () => {
125
+ act(() => {
126
+ useUserStore.setState({ isSignedIn: false });
127
+ });
128
+ enableAuth = true;
129
+
130
+ const { result } = renderHook(() => useMenu());
131
+
132
+ act(() => {
133
+ const { mainItems, logoutItems } = result.current;
134
+ expect(mainItems?.some((item) => item?.key === 'profile')).toBe(false);
135
+ expect(mainItems?.some((item) => item?.key === 'setting')).toBe(false);
136
+ expect(mainItems?.some((item) => item?.key === 'import')).toBe(false);
137
+ expect(mainItems?.some((item) => item?.key === 'export')).toBe(false);
138
+ expect(mainItems?.some((item) => item?.key === 'discord')).toBe(true);
139
+ expect(logoutItems.some((item) => item?.key === 'logout')).toBe(false);
140
+ });
141
+ });
142
+ });
@@ -7,13 +7,14 @@ import { createStyles, useThemeMode } from 'antd-style';
7
7
  const prefixCls = 'cl';
8
8
 
9
9
  export const useStyles = createStyles(
10
- ({ css, token }) =>
10
+ ({ css, token, isDarkMode }) =>
11
11
  ({
12
12
  avatarBox: css`
13
13
  width: 40px;
14
14
  height: 40px;
15
15
  `,
16
16
  cardBox: css`
17
+ background: ${token.colorBgContainer};
17
18
  border-radius: ${token.borderRadiusLG}px;
18
19
  box-shadow: 0 0 0 1px ${token.colorBorderSecondary};
19
20
  `,
@@ -42,7 +43,7 @@ export const useStyles = createStyles(
42
43
  }
43
44
  `,
44
45
  navbar: css`
45
- background: ${token.colorBgContainer};
46
+ background: ${isDarkMode ? token.colorBgContainer : token.colorFillTertiary};
46
47
  `,
47
48
  navbarButton: css`
48
49
  line-height: 2;
@@ -68,10 +69,10 @@ export const useStyles = createStyles(
68
69
  }
69
70
  `,
70
71
  scrollBox: css`
71
- background: ${token.colorBgLayout};
72
+ background: ${isDarkMode ? token.colorFillQuaternary : token.colorBgElevated};
72
73
  border: unset;
73
74
  border-radius: unset;
74
- box-shadow: 0 1px 0 1px ${token.colorSplit};
75
+ box-shadow: 0 1px 0 1px ${token.colorFillTertiary};
75
76
  `,
76
77
  socialButtons: css`
77
78
  display: flex;
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  login: '登录',
3
3
  loginOrSignup: '登录 / 注册',
4
+ profile: '个人资料',
5
+ security: '安全',
4
6
  signout: '退出登录',
5
7
  signup: '注册',
6
8
  };
@@ -163,6 +163,7 @@ export default {
163
163
  userPanel: {
164
164
  anonymousNickName: '匿名用户',
165
165
  billing: '账单管理',
166
+ data: '数据存储',
166
167
  defaultNickname: '社区版用户',
167
168
  discord: '社区支持',
168
169
  docs: '使用文档',
@@ -1,6 +1,6 @@
1
1
  import { t } from 'i18next';
2
2
 
3
- import { enableAuth } from '@/const/auth';
3
+ import { enableAuth, enableClerk } from '@/const/auth';
4
4
  import { UserStore } from '@/store/user';
5
5
  import { LobeUser } from '@/types/user';
6
6
 
@@ -43,4 +43,5 @@ const isLogin = (s: UserStore) => {
43
43
  export const authSelectors = {
44
44
  isLogin,
45
45
  isLoginWithAuth: (s: UserStore) => s.isSignedIn,
46
+ isLoginWithClerk: (s: UserStore) => s.isSignedIn && enableClerk,
46
47
  };
@@ -1,13 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
- import { useTranslation } from 'react-i18next';
5
-
6
- import PageTitle from '@/components/PageTitle';
7
-
8
- const Title = memo(() => {
9
- const { t } = useTranslation('auth');
10
-
11
- return <PageTitle title={t('signup')} />;
12
- });
13
- export default Title;
@@ -1,14 +0,0 @@
1
- import { UserProfile } from '@clerk/nextjs';
2
-
3
- import PageTitle from './PageTitle';
4
-
5
- const Page = () => {
6
- return (
7
- <>
8
- <PageTitle />
9
- <UserProfile />
10
- </>
11
- );
12
- };
13
-
14
- export default Page;
@@ -1,33 +0,0 @@
1
- 'use client';
2
-
3
- import { useRouter } from 'next/navigation';
4
- import { memo } from 'react';
5
- import { Flexbox } from 'react-layout-kit';
6
- import urlJoin from 'url-join';
7
-
8
- import { useCategory } from '@/app/(main)/settings/hooks/useCategory';
9
- import Cell from '@/components/Cell';
10
- import Divider from '@/components/Cell/Divider';
11
-
12
- const SettingCate = memo(() => {
13
- const settingItems = useCategory({ mobile: true });
14
- const router = useRouter();
15
-
16
- return (
17
- <Flexbox width={'100%'}>
18
- {settingItems?.map(({ key, icon, label, type }: any, index) => {
19
- if (type === 'divider') return <Divider key={index} />;
20
- return (
21
- <Cell
22
- icon={icon}
23
- key={key}
24
- label={label}
25
- onClick={() => router.push(urlJoin('/settings', key))}
26
- />
27
- );
28
- })}
29
- </Flexbox>
30
- );
31
- });
32
-
33
- export default SettingCate;
@@ -1,24 +0,0 @@
1
- 'use client';
2
-
3
- import { memo } from 'react';
4
- import { Flexbox } from 'react-layout-kit';
5
-
6
- import Cell from '@/components/Cell';
7
- import Divider from '@/components/Cell/Divider';
8
-
9
- import { useExtraCate } from './useExtraCate';
10
-
11
- const ExtraCate = memo(() => {
12
- const mainItems = useExtraCate();
13
-
14
- return (
15
- <Flexbox width={'100%'}>
16
- {mainItems?.map(({ key, icon, label, type, onClick }: any, index) => {
17
- if (type === 'divider') return <Divider key={index} />;
18
- return <Cell icon={icon} key={key} label={label} onClick={onClick} />;
19
- })}
20
- </Flexbox>
21
- );
22
- });
23
-
24
- export default ExtraCate;
@@ -1,62 +0,0 @@
1
- import { DiscordIcon, Icon } from '@lobehub/ui';
2
- import { Book, Feather, HardDriveDownload, HardDriveUpload } from 'lucide-react';
3
- import { useTranslation } from 'react-i18next';
4
-
5
- import { type MenuProps } from '@/components/Menu';
6
- import { DISCORD, DOCUMENTS, FEEDBACK } from '@/const/url';
7
- import DataImporter from '@/features/DataImporter';
8
- import { configService } from '@/services/config';
9
-
10
- export const useExtraCate = () => {
11
- const { t } = useTranslation(['common', 'setting']);
12
-
13
- const iconSize = { fontSize: 20 };
14
-
15
- const exports: MenuProps['items'] = [
16
- {
17
- icon: <Icon icon={HardDriveUpload} size={iconSize} />,
18
- key: 'import',
19
- label: <DataImporter>{t('import')}</DataImporter>,
20
- },
21
- {
22
- icon: <Icon icon={HardDriveDownload} size={iconSize} />,
23
- key: 'export',
24
- label: t('export'),
25
- onClick: configService.exportAll,
26
- },
27
- {
28
- type: 'divider',
29
- },
30
- ];
31
-
32
- const helps: MenuProps['items'] = [
33
- {
34
- icon: <Icon icon={Book} size={iconSize} />,
35
- key: 'docs',
36
- label: t('document'),
37
- onClick: () => window.open(DOCUMENTS, '__blank'),
38
- },
39
- {
40
- icon: <Icon icon={Feather} size={iconSize} />,
41
- key: 'feedback',
42
- label: t('feedback'),
43
- onClick: () => window.open(FEEDBACK, '__blank'),
44
- },
45
- {
46
- icon: <Icon icon={DiscordIcon} size={iconSize} />,
47
- key: 'discord',
48
- label: 'Discord',
49
- onClick: () => window.open(DISCORD, '__blank'),
50
- },
51
- ];
52
-
53
- const mainItems = [
54
- {
55
- type: 'divider',
56
- },
57
- ...exports,
58
- ...helps,
59
- ].filter(Boolean) as MenuProps['items'];
60
-
61
- return mainItems;
62
- };