@lobehub/chat 0.161.11 → 0.161.12

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ### [Version 0.161.12](https://github.com/lobehub/lobe-chat/compare/v0.161.11...v0.161.12)
6
+
7
+ <sup>Released on **2024-05-23**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor the home redirect implement.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor the home redirect implement, closes [#2626](https://github.com/lobehub/lobe-chat/issues/2626) ([ab4216e](https://github.com/lobehub/lobe-chat/commit/ab4216e))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ### [Version 0.161.11](https://github.com/lobehub/lobe-chat/compare/v0.161.10...v0.161.11)
6
31
 
7
32
  <sup>Released on **2024-05-23**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.161.11",
3
+ "version": "0.161.12",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -3,35 +3,47 @@
3
3
  import { useRouter } from 'next/navigation';
4
4
  import { memo, useEffect } from 'react';
5
5
 
6
- import { messageService } from '@/services/message';
7
- import { sessionService } from '@/services/session';
8
6
  import { useUserStore } from '@/store/user';
9
7
  import { authSelectors } from '@/store/user/selectors';
10
8
 
11
- const checkHasConversation = async () => {
12
- const hasMessages = await messageService.hasMessages();
13
- const hasAgents = await sessionService.hasSessions();
14
- return hasMessages || hasAgents;
15
- };
16
-
17
9
  const Redirect = memo(() => {
18
10
  const router = useRouter();
19
- const isLogin = useUserStore(authSelectors.isLogin);
11
+ const [isLogin, isLoaded, isUserStateInit, isUserHasConversation, isOnboard] = useUserStore(
12
+ (s) => [
13
+ authSelectors.isLogin(s),
14
+ authSelectors.isLoaded(s),
15
+ s.isUserStateInit,
16
+ s.isUserHasConversation,
17
+ s.isOnboard,
18
+ ],
19
+ );
20
20
 
21
21
  useEffect(() => {
22
+ // if user auth state is not ready, wait for loading
23
+ if (!isLoaded) return;
24
+
25
+ // this mean user is definitely not login
22
26
  if (!isLogin) {
23
27
  router.replace('/welcome');
24
28
  return;
25
29
  }
26
30
 
27
- checkHasConversation().then((hasData) => {
28
- if (hasData) {
29
- router.replace('/chat');
30
- } else {
31
- router.replace('/welcome');
32
- }
33
- });
34
- }, []);
31
+ // if user state not init, wait for loading
32
+ if (!isUserStateInit) return;
33
+
34
+ // user need to onboard
35
+ if (!isOnboard) {
36
+ router.replace('/onboard');
37
+ return;
38
+ }
39
+
40
+ // finally check the conversation status
41
+ if (isUserHasConversation) {
42
+ router.replace('/chat');
43
+ } else {
44
+ router.replace('/welcome');
45
+ }
46
+ }, [isUserStateInit, isLoaded, isUserHasConversation, isOnboard, isLogin]);
35
47
 
36
48
  return null;
37
49
  });
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+
3
+ import { PropsWithChildren, memo } from 'react';
4
+ import { createStoreUpdater } from 'zustand-utils';
5
+
6
+ import { useUserStore } from '@/store/user';
7
+
8
+ const NoAuthProvider = memo<PropsWithChildren>(({ children }) => {
9
+ const useStoreUpdater = createStoreUpdater(useUserStore);
10
+
11
+ useStoreUpdater('isLoaded', true);
12
+
13
+ return children;
14
+ });
15
+
16
+ export default NoAuthProvider;
@@ -4,13 +4,14 @@ import { authEnv } from '@/config/auth';
4
4
 
5
5
  import Clerk from './Clerk';
6
6
  import NextAuth from './NextAuth';
7
+ import NoAuth from './NoAuth';
7
8
 
8
9
  const AuthProvider = ({ children }: PropsWithChildren) => {
9
10
  if (authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH) return <Clerk>{children}</Clerk>;
10
11
 
11
12
  if (authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH) return <NextAuth>{children}</NextAuth>;
12
13
 
13
- return children;
14
+ return <NoAuth>{children}</NoAuth>;
14
15
  };
15
16
 
16
17
  export default AuthProvider;
package/src/middleware.ts CHANGED
@@ -41,16 +41,7 @@ const nextAuthMiddleware = auth((req) => {
41
41
  });
42
42
 
43
43
  export default authEnv.NEXT_PUBLIC_ENABLE_CLERK_AUTH
44
- ? // can't lift to a function because if there is no clerk public key, it will throw error
45
- clerkMiddleware((auth, request) => {
46
- // if user is logged in and on the home page, redirect to chat
47
- if (auth().userId && request.nextUrl.pathname === '/') {
48
- request.nextUrl.pathname = '/chat';
49
- return NextResponse.redirect(request.nextUrl);
50
- }
51
-
52
- return NextResponse.next();
53
- })
44
+ ? clerkMiddleware()
54
45
  : authEnv.NEXT_PUBLIC_ENABLE_NEXT_AUTH
55
46
  ? nextAuthMiddleware
56
47
  : defaultMiddleware;
@@ -46,6 +46,8 @@ describe('ClientService', () => {
46
46
  expect(userState).toEqual({
47
47
  avatar: mockUser.avatar,
48
48
  isOnboard: true,
49
+ canEnablePWAGuide: false,
50
+ hasConversation: false,
49
51
  canEnableTrace: false,
50
52
  preference: mockPreference,
51
53
  settings: mockUser.settings,
@@ -1,6 +1,7 @@
1
1
  import { DeepPartial } from 'utility-types';
2
2
 
3
3
  import { MessageModel } from '@/database/client/models/message';
4
+ import { SessionModel } from '@/database/client/models/session';
4
5
  import { UserModel } from '@/database/client/models/user';
5
6
  import { GlobalSettings } from '@/types/settings';
6
7
  import { UserInitializationState, UserPreference } from '@/types/user';
@@ -18,10 +19,13 @@ export class ClientService implements IUserService {
18
19
  async getUserState(): Promise<UserInitializationState> {
19
20
  const user = await UserModel.getUser();
20
21
  const messageCount = await MessageModel.count();
22
+ const sessionCount = await SessionModel.count();
21
23
 
22
24
  return {
23
25
  avatar: user.avatar,
26
+ canEnablePWAGuide: messageCount >= 2,
24
27
  canEnableTrace: messageCount >= 4,
28
+ hasConversation: messageCount > 0 || sessionCount > 0,
25
29
  isOnboard: true,
26
30
  preference: await this.preferenceStorage.getFromLocalStorage(),
27
31
  settings: user.settings as GlobalSettings,
@@ -6,7 +6,6 @@ import { UserInitializationState, UserPreference } from '@/types/user';
6
6
  export interface IUserService {
7
7
  getUserState: () => Promise<UserInitializationState>;
8
8
  resetUserSettings: () => Promise<any>;
9
- updateAvatar: (avatar: string) => Promise<any>;
10
9
  updatePreference: (preference: UserPreference) => Promise<any>;
11
10
  updateUserSettings: (patch: DeepPartial<GlobalSettings>) => Promise<any>;
12
11
  }
@@ -41,6 +41,7 @@ const isLogin = (s: UserStore) => {
41
41
  };
42
42
 
43
43
  export const authSelectors = {
44
+ isLoaded: (s: UserStore) => s.isLoaded,
44
45
  isLogin,
45
46
  isLoginWithAuth: (s: UserStore) => s.isSignedIn,
46
47
  isLoginWithClerk: (s: UserStore): boolean => (s.isSignedIn && enableClerk) || false,
@@ -5,6 +5,7 @@ import { withSWR } from '~test-utils';
5
5
 
6
6
  import { DEFAULT_PREFERENCE } from '@/const/user';
7
7
  import { userService } from '@/services/user';
8
+ import { ClientService } from '@/services/user/client';
8
9
  import { useUserStore } from '@/store/user';
9
10
  import { preferenceSelectors } from '@/store/user/selectors';
10
11
  import { GlobalServerConfig } from '@/types/serverConfig';
@@ -36,7 +37,7 @@ describe('createCommonSlice', () => {
36
37
  const avatar = 'new-avatar';
37
38
 
38
39
  const spyOn = vi.spyOn(result.current, 'refreshUserState');
39
- const updateAvatarSpy = vi.spyOn(userService, 'updateAvatar');
40
+ const updateAvatarSpy = vi.spyOn(ClientService.prototype, 'updateAvatar');
40
41
 
41
42
  await act(async () => {
42
43
  await result.current.updateAvatar(avatar);
@@ -4,6 +4,7 @@ import type { StateCreator } from 'zustand/vanilla';
4
4
 
5
5
  import { DEFAULT_PREFERENCE } from '@/const/user';
6
6
  import { userService } from '@/services/user';
7
+ import { ClientService } from '@/services/user/client';
7
8
  import type { UserStore } from '@/store/user';
8
9
  import type { GlobalServerConfig } from '@/types/serverConfig';
9
10
  import type { GlobalSettings } from '@/types/settings';
@@ -45,7 +46,9 @@ export const createCommonSlice: StateCreator<
45
46
  await mutate(GET_USER_STATE_KEY);
46
47
  },
47
48
  updateAvatar: async (avatar) => {
48
- await userService.updateAvatar(avatar);
49
+ const clientService = new ClientService();
50
+
51
+ await clientService.updateAvatar(avatar);
49
52
  await get().refreshUserState();
50
53
  },
51
54
 
@@ -89,8 +92,12 @@ export const createCommonSlice: StateCreator<
89
92
  {
90
93
  defaultSettings,
91
94
  enabledNextAuth: serverConfig.enabledOAuthSSO,
95
+ isOnboard: data.isOnboard,
96
+ isShowPWAGuide: data.canEnablePWAGuide,
92
97
  isUserCanEnableTrace: data.canEnableTrace,
98
+ isUserHasConversation: data.hasConversation,
93
99
  isUserStateInit: true,
100
+
94
101
  preference,
95
102
  serverLanguageModel: serverConfig.languageModel,
96
103
  settings: data.settings || {},
@@ -1,9 +1,15 @@
1
1
  export interface CommonState {
2
+ isOnboard: boolean;
3
+ isShowPWAGuide: boolean;
2
4
  isUserCanEnableTrace: boolean;
5
+ isUserHasConversation: boolean;
3
6
  isUserStateInit: boolean;
4
7
  }
5
8
 
6
9
  export const initialCommonState: CommonState = {
10
+ isOnboard: false,
11
+ isShowPWAGuide: false,
7
12
  isUserCanEnableTrace: false,
13
+ isUserHasConversation: false,
8
14
  isUserStateInit: false,
9
15
  };
@@ -34,7 +34,9 @@ export interface UserPreference {
34
34
 
35
35
  export interface UserInitializationState {
36
36
  avatar?: string;
37
+ canEnablePWAGuide?: boolean;
37
38
  canEnableTrace?: boolean;
39
+ hasConversation?: boolean;
38
40
  isOnboard?: boolean;
39
41
  preference: UserPreference;
40
42
  settings: DeepPartial<GlobalSettings>;