@lobehub/chat 0.153.1 → 0.154.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 (143) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/docs/self-hosting/advanced/authentication.mdx +25 -18
  3. package/docs/self-hosting/advanced/authentication.zh-CN.mdx +26 -17
  4. package/docs/self-hosting/advanced/model-list.mdx +2 -2
  5. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +2 -2
  6. package/docs/self-hosting/environment-variables/auth.mdx +48 -39
  7. package/docs/self-hosting/environment-variables/auth.zh-CN.mdx +49 -43
  8. package/locales/ar/auth.json +6 -0
  9. package/locales/ar/clerk.json +769 -0
  10. package/locales/ar/common.json +1 -0
  11. package/locales/ar/error.json +8 -0
  12. package/locales/bg-BG/auth.json +6 -0
  13. package/locales/bg-BG/clerk.json +769 -0
  14. package/locales/bg-BG/common.json +1 -0
  15. package/locales/bg-BG/error.json +8 -0
  16. package/locales/de-DE/auth.json +6 -0
  17. package/locales/de-DE/clerk.json +769 -0
  18. package/locales/de-DE/common.json +1 -0
  19. package/locales/de-DE/error.json +8 -0
  20. package/locales/en-US/auth.json +6 -0
  21. package/locales/en-US/clerk.json +769 -0
  22. package/locales/en-US/common.json +1 -0
  23. package/locales/en-US/error.json +8 -0
  24. package/locales/es-ES/auth.json +6 -0
  25. package/locales/es-ES/clerk.json +769 -0
  26. package/locales/es-ES/common.json +1 -0
  27. package/locales/es-ES/error.json +8 -0
  28. package/locales/fr-FR/auth.json +6 -0
  29. package/locales/fr-FR/clerk.json +769 -0
  30. package/locales/fr-FR/common.json +1 -0
  31. package/locales/fr-FR/error.json +8 -0
  32. package/locales/it-IT/auth.json +6 -0
  33. package/locales/it-IT/clerk.json +769 -0
  34. package/locales/it-IT/common.json +1 -0
  35. package/locales/it-IT/error.json +8 -0
  36. package/locales/ja-JP/auth.json +6 -0
  37. package/locales/ja-JP/clerk.json +769 -0
  38. package/locales/ja-JP/common.json +1 -0
  39. package/locales/ja-JP/error.json +8 -0
  40. package/locales/ko-KR/auth.json +6 -0
  41. package/locales/ko-KR/clerk.json +769 -0
  42. package/locales/ko-KR/common.json +1 -0
  43. package/locales/ko-KR/error.json +8 -0
  44. package/locales/nl-NL/auth.json +6 -0
  45. package/locales/nl-NL/clerk.json +769 -0
  46. package/locales/nl-NL/common.json +1 -0
  47. package/locales/nl-NL/error.json +8 -0
  48. package/locales/pl-PL/auth.json +6 -0
  49. package/locales/pl-PL/clerk.json +769 -0
  50. package/locales/pl-PL/common.json +1 -0
  51. package/locales/pl-PL/error.json +8 -0
  52. package/locales/pt-BR/auth.json +6 -0
  53. package/locales/pt-BR/clerk.json +769 -0
  54. package/locales/pt-BR/common.json +1 -0
  55. package/locales/pt-BR/error.json +8 -0
  56. package/locales/ru-RU/auth.json +6 -0
  57. package/locales/ru-RU/clerk.json +769 -0
  58. package/locales/ru-RU/common.json +1 -0
  59. package/locales/ru-RU/error.json +8 -0
  60. package/locales/tr-TR/auth.json +6 -0
  61. package/locales/tr-TR/clerk.json +769 -0
  62. package/locales/tr-TR/common.json +1 -0
  63. package/locales/tr-TR/error.json +8 -0
  64. package/locales/vi-VN/auth.json +6 -0
  65. package/locales/vi-VN/clerk.json +769 -0
  66. package/locales/vi-VN/common.json +1 -0
  67. package/locales/vi-VN/error.json +8 -0
  68. package/locales/zh-CN/auth.json +6 -0
  69. package/locales/zh-CN/clerk.json +769 -0
  70. package/locales/zh-CN/common.json +1 -0
  71. package/locales/zh-CN/error.json +8 -0
  72. package/locales/zh-TW/auth.json +6 -0
  73. package/locales/zh-TW/clerk.json +769 -0
  74. package/locales/zh-TW/common.json +1 -0
  75. package/locales/zh-TW/error.json +8 -0
  76. package/package.json +10 -5
  77. package/src/app/(auth)/layout.tsx +19 -0
  78. package/src/app/(auth)/login/[[...login]]/PageTitle.tsx +13 -0
  79. package/src/app/(auth)/login/[[...login]]/page.tsx +14 -0
  80. package/src/app/(auth)/profile/[[...slugs]]/PageTitle.tsx +13 -0
  81. package/src/app/(auth)/profile/[[...slugs]]/page.tsx +14 -0
  82. package/src/app/(auth)/signup/[[...signup]]/PageTitle.tsx +13 -0
  83. package/src/app/(auth)/signup/[[...signup]]/page.tsx +14 -0
  84. package/src/app/(main)/settings/common/features/Common.tsx +1 -1
  85. package/src/app/(main)/settings/common/features/Theme/index.tsx +2 -0
  86. package/src/app/(main)/settings/common/index.tsx +7 -3
  87. package/src/app/api/chat/[provider]/route.test.ts +76 -3
  88. package/src/app/api/chat/auth/index.test.ts +77 -0
  89. package/src/app/api/chat/auth/index.ts +19 -3
  90. package/src/app/api/chat/auth/utils.ts +31 -9
  91. package/src/app/api/plugin/gateway/route.ts +3 -3
  92. package/src/config/auth.ts +146 -0
  93. package/src/config/server/index.ts +1 -3
  94. package/src/const/auth.ts +7 -0
  95. package/src/features/AgentSetting/AgentMeta/AutoGenerateInput.tsx +1 -2
  96. package/src/features/AgentSetting/AgentMeta/AutoGenerateSelect.tsx +1 -1
  97. package/src/features/AgentSetting/AgentMeta/index.tsx +7 -2
  98. package/src/features/Conversation/Error/ClerkLogin/index.tsx +47 -0
  99. package/src/features/Conversation/Error/index.tsx +5 -0
  100. package/src/features/Conversation/components/InboxWelcome/index.tsx +4 -17
  101. package/src/features/DataImporter/index.tsx +2 -0
  102. package/src/features/User/UserAvatar.tsx +11 -5
  103. package/src/features/User/UserInfo.tsx +8 -7
  104. package/src/features/User/UserLoginOrSignup.tsx +23 -0
  105. package/src/features/User/UserPanel/PanelContent.tsx +74 -0
  106. package/src/features/User/UserPanel/UpgradeBadge.tsx +19 -0
  107. package/src/features/User/UserPanel/index.tsx +8 -27
  108. package/src/features/User/UserPanel/useMenu.tsx +49 -21
  109. package/src/features/User/__tests__/PanelContent.test.tsx +151 -0
  110. package/src/features/User/__tests__/UserAvatar.test.tsx +78 -0
  111. package/src/hooks/useGreeting/greetingTime.ts +14 -0
  112. package/src/hooks/useGreeting/index.ts +16 -0
  113. package/src/hooks/useTokenCount.ts +1 -0
  114. package/src/layout/AuthProvider/Clerk/UserUpdater.tsx +41 -0
  115. package/src/layout/AuthProvider/Clerk/index.tsx +26 -0
  116. package/src/layout/AuthProvider/Clerk/useAppearance.ts +118 -0
  117. package/src/layout/AuthProvider/index.tsx +8 -4
  118. package/src/layout/GlobalProvider/AppTheme.tsx +1 -1
  119. package/src/libs/next-auth/index.ts +4 -6
  120. package/src/libs/next-auth/sso-providers/auth0.ts +4 -6
  121. package/src/libs/next-auth/sso-providers/authentik.ts +4 -6
  122. package/src/libs/next-auth/sso-providers/azure-ad.ts +4 -6
  123. package/src/libs/next-auth/sso-providers/github.ts +3 -5
  124. package/src/libs/next-auth/sso-providers/zitadel.ts +4 -6
  125. package/src/locales/default/auth.ts +6 -0
  126. package/src/locales/default/clerk.ts +782 -0
  127. package/src/locales/default/common.ts +3 -0
  128. package/src/locales/default/error.ts +8 -0
  129. package/src/locales/default/index.ts +4 -0
  130. package/src/middleware.ts +19 -6
  131. package/src/server/globalConfig/index.ts +2 -2
  132. package/src/store/user/selectors.ts +1 -1
  133. package/src/store/user/slices/auth/action.test.ts +105 -0
  134. package/src/store/user/slices/auth/action.ts +40 -5
  135. package/src/store/user/slices/auth/initialState.ts +15 -0
  136. package/src/store/user/slices/auth/selectors.test.ts +127 -0
  137. package/src/store/user/slices/auth/selectors.ts +38 -1
  138. package/src/store/user/slices/settings/selectors/selectors.test.ts +25 -0
  139. package/src/store/user/slices/settings/selectors/settings.ts +6 -0
  140. package/src/styles/antdOverride.ts +5 -0
  141. package/src/types/fetch.ts +1 -0
  142. package/src/config/server/auth.ts +0 -71
  143. package/src/features/User/UserPanel/Popover.tsx +0 -35
@@ -0,0 +1,146 @@
1
+ /* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
2
+ import { createEnv } from '@t3-oss/env-nextjs';
3
+ import { z } from 'zod';
4
+
5
+ declare global {
6
+ // eslint-disable-next-line @typescript-eslint/no-namespace
7
+ namespace NodeJS {
8
+ interface ProcessEnv {
9
+ // ===== Clerk ===== //
10
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY?: string;
11
+ CLERK_SECRET_KEY?: string;
12
+ CLERK_WEBHOOK_SECRET?: string;
13
+
14
+ // ===== Next Auth ===== //
15
+ /**
16
+ * @deprecated
17
+ */
18
+ ENABLE_OAUTH_SSO?: string;
19
+ NEXT_AUTH_SECRET?: string;
20
+ /**
21
+ * @deprecated
22
+ */
23
+ SSO_PROVIDERS?: string;
24
+ NEXT_AUTH_SSO_PROVIDERS?: string;
25
+
26
+ AUTH0_CLIENT_ID?: string;
27
+ AUTH0_CLIENT_SECRET?: string;
28
+ AUTH0_ISSUER?: string;
29
+
30
+ // Github
31
+ GITHUB_CLIENT_ID?: string;
32
+ GITHUB_CLIENT_SECRET?: string;
33
+
34
+ // Azure AD
35
+ AZURE_AD_CLIENT_ID?: string;
36
+ AZURE_AD_CLIENT_SECRET?: string;
37
+ AZURE_AD_TENANT_ID?: string;
38
+
39
+ // AUTHENTIK
40
+ AUTHENTIK_CLIENT_ID?: string;
41
+ AUTHENTIK_CLIENT_SECRET?: string;
42
+ AUTHENTIK_ISSUER?: string;
43
+
44
+ // ZITADEL
45
+ ZITADEL_CLIENT_ID?: string;
46
+ ZITADEL_CLIENT_SECRET?: string;
47
+ ZITADEL_ISSUER?: string;
48
+ }
49
+ }
50
+ }
51
+
52
+ export const getAuthConfig = () => {
53
+ if (process.env.ENABLE_OAUTH_SSO) {
54
+ console.warn(
55
+ '`ENABLE_OAUTH_SSO` is deprecated and will be removed in LobeChat 1.0. just set `NEXT_AUTH_SECRET` enough',
56
+ );
57
+ }
58
+
59
+ if (process.env.SSO_PROVIDERS) {
60
+ console.warn(
61
+ '`SSO_PROVIDERS` is deprecated and will be removed in LobeChat 1.0. Please replace with `NEXT_AUTH_SSO_PROVIDERS`',
62
+ );
63
+ }
64
+
65
+ return createEnv({
66
+ client: {
67
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().optional(),
68
+ /**
69
+ * whether to enabled clerk
70
+ */
71
+ NEXT_PUBLIC_ENABLE_CLERK_AUTH: z.boolean().optional(),
72
+
73
+ NEXT_PUBLIC_ENABLE_NEXT_AUTH: z.boolean().optional(),
74
+ },
75
+ server: {
76
+ // Clerk
77
+ CLERK_SECRET_KEY: z.string().optional(),
78
+
79
+ // NEXT-AUTH
80
+ NEXT_AUTH_SECRET: z.string().optional(),
81
+ NEXT_AUTH_SSO_PROVIDERS: z.string().optional().default('auth0'),
82
+
83
+ // Auth0
84
+ AUTH0_CLIENT_ID: z.string().optional(),
85
+ AUTH0_CLIENT_SECRET: z.string().optional(),
86
+ AUTH0_ISSUER: z.string().optional(),
87
+
88
+ // Github
89
+ GITHUB_CLIENT_ID: z.string().optional(),
90
+ GITHUB_CLIENT_SECRET: z.string().optional(),
91
+
92
+ // Azure AD
93
+ AZURE_AD_CLIENT_ID: z.string().optional(),
94
+ AZURE_AD_CLIENT_SECRET: z.string().optional(),
95
+ AZURE_AD_TENANT_ID: z.string().optional(),
96
+
97
+ // AUTHENTIK
98
+ AUTHENTIK_CLIENT_ID: z.string().optional(),
99
+ AUTHENTIK_CLIENT_SECRET: z.string().optional(),
100
+ AUTHENTIK_ISSUER: z.string().optional(),
101
+
102
+ // ZITADEL
103
+ ZITADEL_CLIENT_ID: z.string().optional(),
104
+ ZITADEL_CLIENT_SECRET: z.string().optional(),
105
+ ZITADEL_ISSUER: z.string().optional(),
106
+ },
107
+
108
+ runtimeEnv: {
109
+ // Clerk
110
+ NEXT_PUBLIC_ENABLE_CLERK_AUTH: !!process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
111
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
112
+ CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
113
+
114
+ // Next Auth
115
+ NEXT_PUBLIC_ENABLE_NEXT_AUTH: !!process.env.NEXT_AUTH_SECRET || process.env.ENABLE_OAUTH_SSO,
116
+ NEXT_AUTH_SSO_PROVIDERS: process.env.NEXT_AUTH_SSO_PROVIDERS || process.env.SSO_PROVIDERS,
117
+ NEXT_AUTH_SECRET: process.env.NEXT_AUTH_SECRET,
118
+
119
+ // Auth0
120
+ AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
121
+ AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
122
+ AUTH0_ISSUER: process.env.AUTH0_ISSUER,
123
+
124
+ // Github
125
+ GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
126
+ GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
127
+
128
+ // Azure AD
129
+ AZURE_AD_CLIENT_ID: process.env.AZURE_AD_CLIENT_ID,
130
+ AZURE_AD_CLIENT_SECRET: process.env.AZURE_AD_CLIENT_SECRET,
131
+ AZURE_AD_TENANT_ID: process.env.AZURE_AD_TENANT_ID,
132
+
133
+ // AUTHENTIK
134
+ AUTHENTIK_CLIENT_ID: process.env.AUTHENTIK_CLIENT_ID,
135
+ AUTHENTIK_CLIENT_SECRET: process.env.AUTHENTIK_CLIENT_SECRET,
136
+ AUTHENTIK_ISSUER: process.env.AUTHENTIK_ISSUER,
137
+
138
+ // ZITADEL
139
+ ZITADEL_CLIENT_ID: process.env.ZITADEL_CLIENT_ID,
140
+ ZITADEL_CLIENT_SECRET: process.env.ZITADEL_CLIENT_SECRET,
141
+ ZITADEL_ISSUER: process.env.ZITADEL_ISSUER,
142
+ },
143
+ });
144
+ };
145
+
146
+ export const authEnv = getAuthConfig();
@@ -1,6 +1,5 @@
1
1
  import { getAnalyticsConfig } from './analytics';
2
2
  import { getAppConfig } from './app';
3
- import { getAuthConfig } from './auth';
4
3
  import { getProviderConfig } from './provider';
5
4
 
6
5
  export const getServerConfig = () => {
@@ -10,8 +9,7 @@ export const getServerConfig = () => {
10
9
 
11
10
  const provider = getProviderConfig();
12
11
  const app = getAppConfig();
13
- const auth = getAuthConfig();
14
12
  const analytics = getAnalyticsConfig();
15
13
 
16
- return { ...provider, ...app, ...analytics, ...auth };
14
+ return { ...provider, ...app, ...analytics };
17
15
  };
package/src/const/auth.ts CHANGED
@@ -1,3 +1,10 @@
1
+ import { authEnv } from '@/config/auth';
2
+
3
+ export const enableClerk = authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH;
4
+ export const enableNextAuth = authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH;
5
+ export const enableAuth =
6
+ authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH || authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH;
7
+
1
8
  export const LOBE_CHAT_AUTH_HEADER = 'X-lobe-chat-auth';
2
9
 
3
10
  export const OAUTH_AUTHORIZED = 'X-oauth-authorized';
@@ -5,7 +5,6 @@ import { Wand2 } from 'lucide-react';
5
5
  import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
 
8
-
9
8
  export interface AutoGenerateInputProps extends InputProps {
10
9
  canAutoGenerate?: boolean;
11
10
  loading?: boolean;
@@ -32,7 +31,7 @@ const AutoGenerateInput = memo<AutoGenerateInputProps>(
32
31
  color: theme.colorInfo,
33
32
  marginRight: -4,
34
33
  }}
35
- title={t('autoGenerate')}
34
+ title={!canAutoGenerate ? t('autoGenerateTooltipDisabled') : t('autoGenerate')}
36
35
  />
37
36
  )
38
37
  }
@@ -35,7 +35,7 @@ const AutoGenerateSelect = memo<AutoGenerateInputProps>(
35
35
  color: theme.colorInfo,
36
36
  marginRight: -4,
37
37
  }}
38
- title={t('autoGenerate')}
38
+ title={!canAutoGenerate ? t('autoGenerateTooltipDisabled') : t('autoGenerate')}
39
39
  />
40
40
  )
41
41
  }
@@ -64,7 +64,6 @@ const AgentMeta = memo(() => {
64
64
  children: (
65
65
  <AutoGenerate
66
66
  canAutoGenerate={hasSystemRole}
67
- disabled={!hasSystemRole}
68
67
  loading={loading[item.key as keyof SessionLoadingState]}
69
68
  onChange={item.onChange}
70
69
  onGenerate={() => {
@@ -106,7 +105,13 @@ const AgentMeta = memo(() => {
106
105
  ...autocompleteItems,
107
106
  ],
108
107
  extra: (
109
- <Tooltip title={t('autoGenerateTooltip', { ns: 'common' })}>
108
+ <Tooltip
109
+ title={
110
+ !hasSystemRole
111
+ ? t('autoGenerateTooltipDisabled', { ns: 'common' })
112
+ : t('autoGenerateTooltip', { ns: 'common' })
113
+ }
114
+ >
110
115
  <Button
111
116
  disabled={!hasSystemRole}
112
117
  icon={<Icon icon={Wand2} />}
@@ -0,0 +1,47 @@
1
+ import { Button } from 'antd';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+
5
+ import UserLoginOrSignup from '@/features/User/UserLoginOrSignup';
6
+ import { useGreeting } from '@/hooks/useGreeting';
7
+ import { useChatStore } from '@/store/chat';
8
+ import { useUserStore } from '@/store/user';
9
+ import { userProfileSelectors } from '@/store/user/selectors';
10
+
11
+ import { ErrorActionContainer, FormAction } from '../style';
12
+
13
+ const ClerkLogin = memo<{ id: string }>(({ id }) => {
14
+ const { t } = useTranslation('error');
15
+ const [openSignIn, isSignedIn] = useUserStore((s) => [s.openLogin, s.isSignedIn]);
16
+ const greeting = useGreeting();
17
+ const nickName = useUserStore(userProfileSelectors.nickName);
18
+ const [resend, deleteMessage] = useChatStore((s) => [s.internalResendMessage, s.deleteMessage]);
19
+
20
+ return (
21
+ <ErrorActionContainer>
22
+ {isSignedIn ? (
23
+ <FormAction
24
+ avatar={'🌟'}
25
+ description={t('clerkAuth.loginSuccess.desc', { greeting })}
26
+ title={t('clerkAuth.loginSuccess.title', { nickName })}
27
+ >
28
+ <Button
29
+ block
30
+ onClick={() => {
31
+ resend(id);
32
+ deleteMessage(id);
33
+ }}
34
+ size={'large'}
35
+ type={'primary'}
36
+ >
37
+ {t('clerkAuth.loginSuccess.action')}
38
+ </Button>
39
+ </FormAction>
40
+ ) : (
41
+ <UserLoginOrSignup onClick={openSignIn} />
42
+ )}
43
+ </ErrorActionContainer>
44
+ );
45
+ });
46
+
47
+ export default ClerkLogin;
@@ -6,6 +6,7 @@ import { AgentRuntimeErrorType, ILobeAgentRuntimeErrorType } from '@/libs/agent-
6
6
  import { ChatErrorType, ErrorType } from '@/types/fetch';
7
7
  import { ChatMessage, ChatMessageError } from '@/types/message';
8
8
 
9
+ import ClerkLogin from './ClerkLogin';
9
10
  import ErrorJsonViewer from './ErrorJsonViewer';
10
11
  import InvalidAPIKey from './InvalidAPIKey';
11
12
  import InvalidAccessCode from './InvalidAccessCode';
@@ -63,6 +64,10 @@ const ErrorMessageExtra = memo<{ data: ChatMessage }>(({ data }) => {
63
64
  return <OllamaBizError {...data} />;
64
65
  }
65
66
 
67
+ case ChatErrorType.InvalidClerkUser: {
68
+ return <ClerkLogin id={data.id} />;
69
+ }
70
+
66
71
  case ChatErrorType.InvalidAccessCode: {
67
72
  return <InvalidAccessCode id={data.id} provider={data.error?.body?.provider} />;
68
73
  }
@@ -2,10 +2,11 @@
2
2
 
3
3
  import { FluentEmoji, Markdown } from '@lobehub/ui';
4
4
  import { createStyles } from 'antd-style';
5
- import { memo, useEffect, useState } from 'react';
5
+ import { memo } from 'react';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import { Center, Flexbox } from 'react-layout-kit';
8
8
 
9
+ import { useGreeting } from '@/hooks/useGreeting';
9
10
  import { useIsMobile } from '@/hooks/useIsMobile';
10
11
 
11
12
  import AgentsSuggest from './AgentsSuggest';
@@ -40,30 +41,16 @@ const useStyles = createStyles(({ css, responsive }) => ({
40
41
 
41
42
  const InboxWelcome = memo(() => {
42
43
  const { t } = useTranslation('welcome');
43
- const [greeting, setGreeting] = useState<'morning' | 'noon' | 'afternoon' | 'night'>();
44
44
  const { styles } = useStyles();
45
45
  const mobile = useIsMobile();
46
- useEffect(() => {
47
- const now = new Date();
48
- const hours = now.getHours();
49
-
50
- if (hours >= 4 && hours < 11) {
51
- setGreeting('morning');
52
- } else if (hours >= 11 && hours < 14) {
53
- setGreeting('noon');
54
- } else if (hours >= 14 && hours < 18) {
55
- setGreeting('afternoon');
56
- } else {
57
- setGreeting('night');
58
- }
59
- }, []);
46
+ const greeting = useGreeting();
60
47
 
61
48
  return (
62
49
  <Center padding={16} width={'100%'}>
63
50
  <Flexbox className={styles.container} gap={16} style={{ maxWidth: 800 }} width={'100%'}>
64
51
  <Flexbox align={'center'} gap={8} horizontal>
65
52
  <FluentEmoji emoji={'👋'} size={40} type={'anim'} />
66
- <h1 className={styles.title}>{greeting && t(`guide.welcome.${greeting}`)}</h1>
53
+ <h1 className={styles.title}>{greeting}</h1>
67
54
  </Flexbox>
68
55
  <Markdown className={styles.desc} variant={'chat'}>
69
56
  {t('guide.defaultMessage')}
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  import { Icon } from '@lobehub/ui';
2
4
  import { Button, Result, Table, Upload } from 'antd';
3
5
  import { createStyles } from 'antd-style';
@@ -6,7 +6,7 @@ import { memo } from 'react';
6
6
 
7
7
  import { DEFAULT_USER_AVATAR_URL } from '@/const/meta';
8
8
  import { useUserStore } from '@/store/user';
9
- import { userProfileSelectors } from '@/store/user/selectors';
9
+ import { authSelectors, userProfileSelectors } from '@/store/user/selectors';
10
10
 
11
11
  const useStyles = createStyles(({ css, token }) => ({
12
12
  clickable: css`
@@ -47,12 +47,18 @@ export interface UserAvatarProps extends AvatarProps {
47
47
  const UserAvatar = memo<UserAvatarProps>(
48
48
  ({ size = 40, background, clickable, className, ...rest }) => {
49
49
  const { styles, cx } = useStyles();
50
- const avatar = useUserStore(userProfileSelectors.userAvatar);
50
+ const [avatar, username] = useUserStore((s) => [
51
+ userProfileSelectors.userAvatar(s),
52
+ userProfileSelectors.username(s),
53
+ ]);
54
+
55
+ const isSignedIn = useUserStore(authSelectors.isLogin);
56
+
51
57
  return (
52
58
  <Avatar
53
- alt={avatar ? 'UserAvatar' : 'LobeChat'}
54
- avatar={avatar || DEFAULT_USER_AVATAR_URL}
55
- background={avatar ? background : undefined}
59
+ alt={isSignedIn ? (username as string) : 'LobeChat'}
60
+ avatar={isSignedIn ? avatar || DEFAULT_USER_AVATAR_URL : DEFAULT_USER_AVATAR_URL}
61
+ background={isSignedIn && avatar ? background : undefined}
56
62
  className={cx(clickable && styles.clickable, className)}
57
63
  size={size}
58
64
  unoptimized
@@ -2,15 +2,14 @@
2
2
 
3
3
  import { createStyles } from 'antd-style';
4
4
  import { memo } from 'react';
5
- import { useTranslation } from 'react-i18next';
6
5
  import { Flexbox, FlexboxProps } from 'react-layout-kit';
7
6
 
8
7
  import PlanTag from '@/features/User/PlanTag';
8
+ import { useUserStore } from '@/store/user';
9
+ import { userProfileSelectors } from '@/store/user/selectors';
9
10
 
10
11
  import UserAvatar, { type UserAvatarProps } from './UserAvatar';
11
12
 
12
- const DEFAULT_USERNAME = 'LobeChat';
13
-
14
13
  const useStyles = createStyles(({ css, token }) => ({
15
14
  nickname: css`
16
15
  font-size: 16px;
@@ -28,10 +27,12 @@ export interface UserInfoProps extends FlexboxProps {
28
27
  }
29
28
 
30
29
  const UserInfo = memo<UserInfoProps>(({ avatarProps, ...rest }) => {
31
- const { t } = useTranslation('common');
32
30
  const { styles, theme } = useStyles();
33
31
 
34
- const DEFAULT_NICKNAME = t('userPanel.defaultNickname');
32
+ const [nickname, username] = useUserStore((s) => [
33
+ userProfileSelectors.nickName(s),
34
+ userProfileSelectors.username(s),
35
+ ]);
35
36
 
36
37
  return (
37
38
  <Flexbox
@@ -46,8 +47,8 @@ const UserInfo = memo<UserInfoProps>(({ avatarProps, ...rest }) => {
46
47
  <Flexbox align={'center'} gap={12} horizontal>
47
48
  <UserAvatar background={theme.colorFill} size={48} {...avatarProps} />
48
49
  <Flexbox flex={1} gap={6}>
49
- <div className={styles.nickname}>{DEFAULT_NICKNAME}</div>
50
- <div className={styles.username}>{DEFAULT_USERNAME}</div>
50
+ <div className={styles.nickname}>{nickname}</div>
51
+ <div className={styles.username}>{username}</div>
51
52
  </Flexbox>
52
53
  </Flexbox>
53
54
  <PlanTag />
@@ -0,0 +1,23 @@
1
+ import { Button } from 'antd';
2
+ import { memo } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import UserInfo from '@/features/User/UserInfo';
7
+
8
+ const UserLoginOrSignup = memo<{ onClick: () => void }>(({ onClick }) => {
9
+ const { t } = useTranslation('auth');
10
+
11
+ return (
12
+ <>
13
+ <UserInfo />
14
+ <Flexbox paddingBlock={'0 12px'} paddingInline={16} width={'100%'}>
15
+ <Button block onClick={onClick} type={'primary'}>
16
+ {t('loginOrSignup')}
17
+ </Button>
18
+ </Flexbox>
19
+ </>
20
+ );
21
+ });
22
+
23
+ export default UserLoginOrSignup;
@@ -0,0 +1,74 @@
1
+ import { useRouter } from 'next/navigation';
2
+ import { memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import BrandWatermark from '@/components/BrandWatermark';
6
+ import Menu from '@/components/Menu';
7
+ import { enableAuth } from '@/const/auth';
8
+ import { useUserStore } from '@/store/user';
9
+ import { authSelectors } from '@/store/user/selectors';
10
+
11
+ import UserInfo from '../UserInfo';
12
+ import UserLoginOrSignup from '../UserLoginOrSignup';
13
+ import LangButton from './LangButton';
14
+ import ThemeButton from './ThemeButton';
15
+ import { useMenu } from './useMenu';
16
+
17
+ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
18
+ const router = useRouter();
19
+ const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
20
+ const [openSignIn, signOut, openUserProfile] = useUserStore((s) => [
21
+ s.openLogin,
22
+ s.logout,
23
+ s.openUserProfile,
24
+ ]);
25
+ const { mainItems, logoutItems } = useMenu();
26
+
27
+ const handleOpenProfile = () => {
28
+ if (!enableAuth) return;
29
+ openUserProfile();
30
+ closePopover();
31
+ };
32
+
33
+ const handleSignIn = () => {
34
+ openSignIn();
35
+ closePopover();
36
+ };
37
+
38
+ const handleSignOut = () => {
39
+ signOut();
40
+ closePopover();
41
+ router.push('/login');
42
+ };
43
+
44
+ return (
45
+ <Flexbox gap={2} style={{ minWidth: 300 }}>
46
+ {!enableAuth ? (
47
+ <UserInfo />
48
+ ) : isLoginWithAuth ? (
49
+ <UserInfo onClick={handleOpenProfile} />
50
+ ) : (
51
+ <UserLoginOrSignup onClick={handleSignIn} />
52
+ )}
53
+ <Menu items={mainItems} onClick={closePopover} />
54
+ <Flexbox
55
+ align={'center'}
56
+ horizontal
57
+ justify={'space-between'}
58
+ style={isLoginWithAuth ? { paddingRight: 6 } : { padding: '6px 6px 6px 16px' }}
59
+ >
60
+ {isLoginWithAuth ? (
61
+ <Menu items={logoutItems} onClick={handleSignOut} />
62
+ ) : (
63
+ <BrandWatermark />
64
+ )}
65
+ <Flexbox align={'center'} flex={'none'} gap={6} horizontal>
66
+ <LangButton />
67
+ <ThemeButton />
68
+ </Flexbox>
69
+ </Flexbox>
70
+ </Flexbox>
71
+ );
72
+ });
73
+
74
+ export default PanelContent;
@@ -0,0 +1,19 @@
1
+ import { Badge, ConfigProvider } from 'antd';
2
+ import { PropsWithChildren, memo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ const UpgradeBadge = memo(({ children, showBadge }: PropsWithChildren<{ showBadge?: boolean }>) => {
6
+ if (!showBadge) return children;
7
+
8
+ return (
9
+ <Flexbox>
10
+ <ConfigProvider theme={{ components: { Badge: { dotSize: 8 } } }}>
11
+ <Badge dot offset={[-4, 4]}>
12
+ {children}
13
+ </Badge>
14
+ </ConfigProvider>
15
+ </Flexbox>
16
+ );
17
+ });
18
+
19
+ export default UpgradeBadge;
@@ -1,11 +1,11 @@
1
1
  'use client';
2
2
 
3
- import { Badge, ConfigProvider, Popover } from 'antd';
3
+ import { Popover } from 'antd';
4
4
  import { createStyles } from 'antd-style';
5
- import { PropsWithChildren, memo, useCallback, useState } from 'react';
6
- import { Flexbox } from 'react-layout-kit';
5
+ import { PropsWithChildren, memo, useState } from 'react';
7
6
 
8
- import PopoverContent from './Popover';
7
+ import PanelContent from './PanelContent';
8
+ import UpgradeBadge from './UpgradeBadge';
9
9
  import { useNewVersion } from './useNewVersion';
10
10
 
11
11
  const useStyles = createStyles(({ css }) => ({
@@ -20,40 +20,21 @@ const UserPanel = memo<PropsWithChildren>(({ children }) => {
20
20
  const [open, setOpen] = useState(false);
21
21
  const { styles } = useStyles();
22
22
 
23
- const AvatarBadge = useCallback(
24
- ({ children: badgeChildren, showBadge }: PropsWithChildren<{ showBadge?: boolean }>) => {
25
- if (!showBadge) return badgeChildren;
26
-
27
- return (
28
- <Flexbox>
29
- <ConfigProvider theme={{ components: { Badge: { dotSize: 8 } } }}>
30
- <Badge dot offset={[-4, 4]}>
31
- {badgeChildren}
32
- </Badge>
33
- </ConfigProvider>
34
- </Flexbox>
35
- );
36
- },
37
- [],
38
- );
39
-
40
23
  return (
41
- <AvatarBadge showBadge={hasNewVersion}>
24
+ <UpgradeBadge showBadge={hasNewVersion}>
42
25
  <Popover
43
26
  arrow={false}
44
- content={<PopoverContent closePopover={() => setOpen(false)} />}
27
+ content={<PanelContent closePopover={() => setOpen(false)} />}
45
28
  onOpenChange={setOpen}
46
29
  open={open}
47
- overlayInnerStyle={{
48
- padding: 0,
49
- }}
30
+ overlayInnerStyle={{ padding: 0 }}
50
31
  placement={'topRight'}
51
32
  rootClassName={styles.popover}
52
33
  trigger={['click']}
53
34
  >
54
35
  {children}
55
36
  </Popover>
56
- </AvatarBadge>
37
+ </UpgradeBadge>
57
38
  );
58
39
  });
59
40