@lobehub/chat 1.70.3 → 1.70.5

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 (37) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/changelog/v1.json +18 -0
  5. package/docs/self-hosting/advanced/model-list.mdx +3 -3
  6. package/docs/self-hosting/advanced/model-list.zh-CN.mdx +3 -3
  7. package/docs/self-hosting/environment-variables/model-provider.mdx +17 -3
  8. package/docs/self-hosting/environment-variables/model-provider.zh-CN.mdx +16 -2
  9. package/package.json +1 -1
  10. package/packages/web-crawler/src/crawImpl/naive.ts +11 -3
  11. package/packages/web-crawler/src/urlRules.ts +6 -0
  12. package/src/app/[variants]/(main)/(mobile)/me/(home)/features/Header.tsx +2 -2
  13. package/src/app/[variants]/(main)/settings/common/features/Theme/index.tsx +5 -3
  14. package/src/components/Branding/ProductLogo/Custom.tsx +4 -0
  15. package/src/config/modelProviders/openrouter.ts +7 -0
  16. package/src/const/settings/common.ts +0 -1
  17. package/src/database/schemas/user.ts +3 -3
  18. package/src/database/server/models/__tests__/plugin.test.ts +8 -8
  19. package/src/database/server/models/plugin.ts +20 -20
  20. package/src/features/User/UserPanel/ThemeButton.tsx +4 -4
  21. package/src/layout/GlobalProvider/AppTheme.tsx +5 -9
  22. package/src/libs/agent-runtime/openrouter/index.test.ts +10 -1
  23. package/src/services/plugin/client.test.ts +21 -21
  24. package/src/services/user/_deprecated.test.ts +1 -1
  25. package/src/services/user/client.test.ts +1 -1
  26. package/src/store/aiInfra/slices/aiProvider/__tests__/selectors.test.ts +249 -0
  27. package/src/store/global/action.test.ts +15 -0
  28. package/src/store/global/actions/__tests__/general.test.ts +221 -0
  29. package/src/store/global/actions/general.ts +10 -1
  30. package/src/store/global/initialState.ts +6 -0
  31. package/src/store/global/selectors/systemStatus.test.ts +209 -0
  32. package/src/store/global/selectors/systemStatus.ts +22 -21
  33. package/src/store/user/slices/settings/action.test.ts +1 -19
  34. package/src/store/user/slices/settings/action.ts +0 -5
  35. package/src/store/user/slices/settings/selectors/general.test.ts +5 -15
  36. package/src/store/user/slices/settings/selectors/general.ts +0 -6
  37. package/src/types/user/settings/general.ts +0 -2
@@ -1,3 +1,4 @@
1
+ import type { ThemeMode } from 'antd-style';
1
2
  import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';
2
3
 
3
4
  import { DatabaseLoadingState } from '@/types/clientDB';
@@ -61,6 +62,10 @@ export interface SystemStatus {
61
62
  showFilePanel?: boolean;
62
63
  showSessionPanel?: boolean;
63
64
  showSystemRole?: boolean;
65
+ /**
66
+ * theme mode
67
+ */
68
+ themeMode?: ThemeMode;
64
69
  threadInputHeight: number;
65
70
  zenMode?: boolean;
66
71
  }
@@ -96,6 +101,7 @@ export const INITIAL_STATUS = {
96
101
  showFilePanel: true,
97
102
  showSessionPanel: true,
98
103
  showSystemRole: false,
104
+ themeMode: 'auto',
99
105
  threadInputHeight: 200,
100
106
  zenMode: false,
101
107
  } satisfies SystemStatus;
@@ -0,0 +1,209 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { DatabaseLoadingState } from '@/types/clientDB';
4
+ import { merge } from '@/utils/merge';
5
+
6
+ import { GlobalState, INITIAL_STATUS, initialState } from '../initialState';
7
+ import { systemStatusSelectors } from './systemStatus';
8
+
9
+ // Mock version constants
10
+ vi.mock('@/const/version', () => ({
11
+ isServerMode: false,
12
+ isUsePgliteDB: true,
13
+ }));
14
+
15
+ describe('systemStatusSelectors', () => {
16
+ describe('sessionGroupKeys', () => {
17
+ it('should return expandSessionGroupKeys from status', () => {
18
+ const s: GlobalState = merge(initialState, {
19
+ status: {
20
+ expandSessionGroupKeys: ['group1', 'group2'],
21
+ },
22
+ });
23
+ expect(systemStatusSelectors.sessionGroupKeys(s)).toEqual(['group1', 'group2']);
24
+ });
25
+
26
+ it('should return initial value if not set', () => {
27
+ const s: GlobalState = merge(initialState, {
28
+ status: {
29
+ expandSessionGroupKeys: undefined,
30
+ },
31
+ });
32
+ expect(systemStatusSelectors.sessionGroupKeys(s)).toEqual(
33
+ INITIAL_STATUS.expandSessionGroupKeys,
34
+ );
35
+ });
36
+ });
37
+
38
+ describe('basic selectors', () => {
39
+ const s: GlobalState = merge(initialState, {
40
+ status: {
41
+ showSystemRole: true,
42
+ mobileShowTopic: true,
43
+ mobileShowPortal: true,
44
+ showChatSideBar: true,
45
+ showSessionPanel: true,
46
+ showFilePanel: true,
47
+ hidePWAInstaller: true,
48
+ isShowCredit: true,
49
+ zenMode: false,
50
+ sessionsWidth: 300,
51
+ portalWidth: 500,
52
+ filePanelWidth: 400,
53
+ inputHeight: 150,
54
+ threadInputHeight: 100,
55
+ },
56
+ });
57
+
58
+ it('should return correct values for basic selectors', () => {
59
+ expect(systemStatusSelectors.showSystemRole(s)).toBe(true);
60
+ expect(systemStatusSelectors.mobileShowTopic(s)).toBe(true);
61
+ expect(systemStatusSelectors.mobileShowPortal(s)).toBe(true);
62
+ expect(systemStatusSelectors.showChatSideBar(s)).toBe(true);
63
+ expect(systemStatusSelectors.showSessionPanel(s)).toBe(true);
64
+ expect(systemStatusSelectors.showFilePanel(s)).toBe(true);
65
+ expect(systemStatusSelectors.hidePWAInstaller(s)).toBe(true);
66
+ expect(systemStatusSelectors.isShowCredit(s)).toBe(true);
67
+ expect(systemStatusSelectors.showChatHeader(s)).toBe(true);
68
+ expect(systemStatusSelectors.inZenMode(s)).toBe(false);
69
+ expect(systemStatusSelectors.sessionWidth(s)).toBe(300);
70
+ expect(systemStatusSelectors.portalWidth(s)).toBe(500);
71
+ expect(systemStatusSelectors.filePanelWidth(s)).toBe(400);
72
+ expect(systemStatusSelectors.inputHeight(s)).toBe(150);
73
+ expect(systemStatusSelectors.threadInputHeight(s)).toBe(100);
74
+ });
75
+
76
+ it('should handle zen mode effects', () => {
77
+ const zenState = merge(s, {
78
+ status: { zenMode: true },
79
+ });
80
+ expect(systemStatusSelectors.showChatSideBar(zenState)).toBe(false);
81
+ expect(systemStatusSelectors.showSessionPanel(zenState)).toBe(false);
82
+ expect(systemStatusSelectors.showChatHeader(zenState)).toBe(false);
83
+ });
84
+
85
+ it('should return default portal width if not set', () => {
86
+ const noPortalWidth = merge(initialState, {
87
+ status: { portalWidth: undefined },
88
+ });
89
+ expect(systemStatusSelectors.portalWidth(noPortalWidth)).toBe(400);
90
+ });
91
+ });
92
+
93
+ describe('theme mode', () => {
94
+ it('should return the correct theme', () => {
95
+ const s: GlobalState = merge(initialState, {
96
+ status: {
97
+ themeMode: 'light',
98
+ },
99
+ });
100
+ expect(systemStatusSelectors.themeMode(s)).toBe('light');
101
+ });
102
+
103
+ it('should return auto if not set', () => {
104
+ const s: GlobalState = merge(initialState, {
105
+ status: {
106
+ themeMode: undefined,
107
+ },
108
+ });
109
+ expect(systemStatusSelectors.themeMode(s)).toBe('auto');
110
+ });
111
+ });
112
+
113
+ describe('pglite status selectors', () => {
114
+ describe('isPgliteNotEnabled', () => {
115
+ it('should return true when conditions are met', () => {
116
+ const s: GlobalState = {
117
+ ...initialState,
118
+ isStatusInit: true,
119
+ status: {
120
+ ...initialState.status,
121
+ isEnablePglite: false,
122
+ },
123
+ };
124
+ expect(systemStatusSelectors.isPgliteNotEnabled(s)).toBe(true);
125
+ });
126
+
127
+ it('should return false when isStatusInit is false', () => {
128
+ const s: GlobalState = {
129
+ ...initialState,
130
+ isStatusInit: false,
131
+ status: {
132
+ ...initialState.status,
133
+ isEnablePglite: false,
134
+ },
135
+ };
136
+ expect(systemStatusSelectors.isPgliteNotEnabled(s)).toBe(false);
137
+ });
138
+ });
139
+
140
+ describe('isPgliteNotInited', () => {
141
+ it('should return true when pglite is enabled but not ready', () => {
142
+ const s: GlobalState = {
143
+ ...initialState,
144
+ isStatusInit: true,
145
+ status: {
146
+ ...initialState.status,
147
+ isEnablePglite: true,
148
+ },
149
+ initClientDBStage: DatabaseLoadingState.Initializing,
150
+ };
151
+ expect(systemStatusSelectors.isPgliteNotInited(s)).toBe(true);
152
+ });
153
+
154
+ it('should return false when pglite is ready', () => {
155
+ const s: GlobalState = {
156
+ ...initialState,
157
+ isStatusInit: true,
158
+ status: {
159
+ ...initialState.status,
160
+ isEnablePglite: true,
161
+ },
162
+ initClientDBStage: DatabaseLoadingState.Ready,
163
+ };
164
+ expect(systemStatusSelectors.isPgliteNotInited(s)).toBe(false);
165
+ });
166
+
167
+ it('should return false when pglite is not enabled', () => {
168
+ const s: GlobalState = {
169
+ ...initialState,
170
+ isStatusInit: true,
171
+ status: {
172
+ ...initialState.status,
173
+ isEnablePglite: false,
174
+ },
175
+ initClientDBStage: DatabaseLoadingState.Initializing,
176
+ };
177
+ expect(systemStatusSelectors.isPgliteNotInited(s)).toBe(false);
178
+ });
179
+ });
180
+
181
+ describe('isPgliteInited', () => {
182
+ it('should return true when pglite is enabled and ready', () => {
183
+ const s: GlobalState = {
184
+ ...initialState,
185
+ isStatusInit: true,
186
+ status: {
187
+ ...initialState.status,
188
+ isEnablePglite: true,
189
+ },
190
+ initClientDBStage: DatabaseLoadingState.Ready,
191
+ };
192
+ expect(systemStatusSelectors.isPgliteInited(s)).toBe(true);
193
+ });
194
+
195
+ it('should return false when not ready', () => {
196
+ const s: GlobalState = {
197
+ ...initialState,
198
+ isStatusInit: true,
199
+ status: {
200
+ ...initialState.status,
201
+ isEnablePglite: true,
202
+ },
203
+ initClientDBStage: DatabaseLoadingState.Initializing,
204
+ };
205
+ expect(systemStatusSelectors.isPgliteInited(s)).toBe(false);
206
+ });
207
+ });
208
+ });
209
+ });
@@ -1,38 +1,38 @@
1
1
  import { isServerMode, isUsePgliteDB } from '@/const/version';
2
- import { GlobalStore } from '@/store/global';
3
2
  import { DatabaseLoadingState } from '@/types/clientDB';
4
3
 
5
4
  import { GlobalState, INITIAL_STATUS } from '../initialState';
6
5
 
7
6
  export const systemStatus = (s: GlobalState) => s.status;
8
7
 
9
- const sessionGroupKeys = (s: GlobalStore): string[] =>
8
+ const sessionGroupKeys = (s: GlobalState): string[] =>
10
9
  s.status.expandSessionGroupKeys || INITIAL_STATUS.expandSessionGroupKeys;
11
10
 
12
- const showSystemRole = (s: GlobalStore) => s.status.showSystemRole;
13
- const mobileShowTopic = (s: GlobalStore) => s.status.mobileShowTopic;
14
- const mobileShowPortal = (s: GlobalStore) => s.status.mobileShowPortal;
15
- const showChatSideBar = (s: GlobalStore) => !s.status.zenMode && s.status.showChatSideBar;
16
- const showSessionPanel = (s: GlobalStore) => !s.status.zenMode && s.status.showSessionPanel;
17
- const showFilePanel = (s: GlobalStore) => s.status.showFilePanel;
18
- const hidePWAInstaller = (s: GlobalStore) => s.status.hidePWAInstaller;
19
- const isShowCredit = (s: GlobalStore) => s.status.isShowCredit;
11
+ const showSystemRole = (s: GlobalState) => s.status.showSystemRole;
12
+ const mobileShowTopic = (s: GlobalState) => s.status.mobileShowTopic;
13
+ const mobileShowPortal = (s: GlobalState) => s.status.mobileShowPortal;
14
+ const showChatSideBar = (s: GlobalState) => !s.status.zenMode && s.status.showChatSideBar;
15
+ const showSessionPanel = (s: GlobalState) => !s.status.zenMode && s.status.showSessionPanel;
16
+ const showFilePanel = (s: GlobalState) => s.status.showFilePanel;
17
+ const hidePWAInstaller = (s: GlobalState) => s.status.hidePWAInstaller;
18
+ const isShowCredit = (s: GlobalState) => s.status.isShowCredit;
19
+ const themeMode = (s: GlobalState) => s.status.themeMode || 'auto';
20
20
 
21
- const showChatHeader = (s: GlobalStore) => !s.status.zenMode;
22
- const inZenMode = (s: GlobalStore) => s.status.zenMode;
23
- const sessionWidth = (s: GlobalStore) => s.status.sessionsWidth;
24
- const portalWidth = (s: GlobalStore) => s.status.portalWidth || 400;
25
- const filePanelWidth = (s: GlobalStore) => s.status.filePanelWidth;
26
- const inputHeight = (s: GlobalStore) => s.status.inputHeight;
27
- const threadInputHeight = (s: GlobalStore) => s.status.threadInputHeight;
21
+ const showChatHeader = (s: GlobalState) => !s.status.zenMode;
22
+ const inZenMode = (s: GlobalState) => s.status.zenMode;
23
+ const sessionWidth = (s: GlobalState) => s.status.sessionsWidth;
24
+ const portalWidth = (s: GlobalState) => s.status.portalWidth || 400;
25
+ const filePanelWidth = (s: GlobalState) => s.status.filePanelWidth;
26
+ const inputHeight = (s: GlobalState) => s.status.inputHeight;
27
+ const threadInputHeight = (s: GlobalState) => s.status.threadInputHeight;
28
28
 
29
- const isPgliteNotEnabled = (s: GlobalStore) =>
29
+ const isPgliteNotEnabled = (s: GlobalState) =>
30
30
  isUsePgliteDB && !isServerMode && s.isStatusInit && !s.status.isEnablePglite;
31
31
 
32
32
  /**
33
33
  * 当且仅当 client db 模式,且 pglite 未初始化完成时返回 true
34
34
  */
35
- const isPgliteNotInited = (s: GlobalStore) =>
35
+ const isPgliteNotInited = (s: GlobalState) =>
36
36
  isUsePgliteDB &&
37
37
  s.isStatusInit &&
38
38
  s.status.isEnablePglite &&
@@ -41,14 +41,14 @@ const isPgliteNotInited = (s: GlobalStore) =>
41
41
  /**
42
42
  * 当且仅当 client db 模式,且 pglite 初始化完成时返回 true
43
43
  */
44
- const isPgliteInited = (s: GlobalStore): boolean =>
44
+ const isPgliteInited = (s: GlobalState): boolean =>
45
45
  (s.isStatusInit &&
46
46
  s.status.isEnablePglite &&
47
47
  s.initClientDBStage === DatabaseLoadingState.Ready) ||
48
48
  false;
49
49
 
50
50
  // 这个变量控制 clientdb 是否完成初始化,正常来说,只有 pgliteDB 模式下,才会存在变化,其他时候都是 true
51
- const isDBInited = (s: GlobalStore): boolean => (isUsePgliteDB ? isPgliteInited(s) : true);
51
+ const isDBInited = (s: GlobalState): boolean => (isUsePgliteDB ? isPgliteInited(s) : true);
52
52
 
53
53
  export const systemStatusSelectors = {
54
54
  filePanelWidth,
@@ -71,5 +71,6 @@ export const systemStatusSelectors = {
71
71
  showSessionPanel,
72
72
  showSystemRole,
73
73
  systemStatus,
74
+ themeMode,
74
75
  threadInputHeight,
75
76
  };
@@ -70,7 +70,7 @@ describe('SettingsAction', () => {
70
70
  describe('setSettings', () => {
71
71
  it('should set partial settings', async () => {
72
72
  const { result } = renderHook(() => useUserStore());
73
- const partialSettings: DeepPartial<UserSettings> = { general: { themeMode: 'dark' } };
73
+ const partialSettings: DeepPartial<UserSettings> = { general: { fontSize: 12 } };
74
74
 
75
75
  // Perform the action
76
76
  await act(async () => {
@@ -85,24 +85,6 @@ describe('SettingsAction', () => {
85
85
  });
86
86
  });
87
87
 
88
- describe('switchThemeMode', () => {
89
- it('should switch theme mode', async () => {
90
- const { result } = renderHook(() => useUserStore());
91
- const themeMode = 'light';
92
-
93
- // Perform the action
94
- await act(async () => {
95
- await result.current.switchThemeMode(themeMode);
96
- });
97
-
98
- // Assert that updateUserSettings was called with the correct theme mode
99
- expect(userService.updateUserSettings).toHaveBeenCalledWith(
100
- { general: { themeMode } },
101
- expect.any(AbortSignal),
102
- );
103
- });
104
- });
105
-
106
88
  describe('updateDefaultAgent', () => {
107
89
  it('should update default agent settings', async () => {
108
90
  const { result } = renderHook(() => useUserStore());
@@ -1,4 +1,3 @@
1
- import { ThemeMode } from 'antd-style';
2
1
  import isEqual from 'fast-deep-equal';
3
2
  import { DeepPartial } from 'utility-types';
4
3
  import type { StateCreator } from 'zustand/vanilla';
@@ -24,7 +23,6 @@ export interface UserSettingsAction {
24
23
  internal_createSignal: () => AbortController;
25
24
  resetSettings: () => Promise<void>;
26
25
  setSettings: (settings: DeepPartial<UserSettings>) => Promise<void>;
27
- switchThemeMode: (themeMode: ThemeMode) => Promise<void>;
28
26
  updateDefaultAgent: (agent: DeepPartial<LobeAgentSettings>) => Promise<void>;
29
27
  updateGeneralConfig: (settings: Partial<UserGeneralConfig>) => Promise<void>;
30
28
  updateKeyVaults: (settings: Partial<UserKeyVaults>) => Promise<void>;
@@ -92,9 +90,6 @@ export const createSettingsSlice: StateCreator<
92
90
  await userService.updateUserSettings(diffs, abortController.signal);
93
91
  await get().refreshUserState();
94
92
  },
95
- switchThemeMode: async (themeMode) => {
96
- await get().updateGeneralConfig({ themeMode });
97
- },
98
93
  updateDefaultAgent: async (defaultAgent) => {
99
94
  await get().setSettings({ defaultAgent });
100
95
  },
@@ -5,27 +5,17 @@ import { merge } from '@/utils/merge';
5
5
  import { userGeneralSettingsSelectors } from './general';
6
6
 
7
7
  describe('settingsSelectors', () => {
8
- describe('currentThemeMode', () => {
9
- it('should return the correct theme', () => {
8
+ describe('fontSize', () => {
9
+ it('should return the fontSize', () => {
10
10
  const s: UserState = merge(initialState, {
11
11
  settings: {
12
- general: { themeMode: 'light' },
12
+ general: { fontSize: 12 },
13
13
  },
14
14
  });
15
15
 
16
- const result = userGeneralSettingsSelectors.currentThemeMode(s as UserStore);
16
+ const result = userGeneralSettingsSelectors.fontSize(s as UserStore);
17
17
 
18
- expect(result).toBe('light');
19
- });
20
- it('should return the auto if not set the themeMode', () => {
21
- const s: UserState = merge(initialState, {
22
- settings: {
23
- general: { themeMode: undefined },
24
- },
25
- });
26
- const result = userGeneralSettingsSelectors.currentThemeMode(s as UserStore);
27
-
28
- expect(result).toBe('auto');
18
+ expect(result).toBe(12);
29
19
  });
30
20
  });
31
21
  });
@@ -3,18 +3,12 @@ import { currentSettings } from './settings';
3
3
 
4
4
  const generalConfig = (s: UserStore) => currentSettings(s).general || {};
5
5
 
6
- const currentThemeMode = (s: UserStore) => {
7
- const themeMode = generalConfig(s).themeMode;
8
- return themeMode || 'auto';
9
- };
10
-
11
6
  const neutralColor = (s: UserStore) => generalConfig(s).neutralColor;
12
7
  const primaryColor = (s: UserStore) => generalConfig(s).primaryColor;
13
8
  const fontSize = (s: UserStore) => generalConfig(s).fontSize;
14
9
 
15
10
  export const userGeneralSettingsSelectors = {
16
11
  config: generalConfig,
17
- currentThemeMode,
18
12
  fontSize,
19
13
  neutralColor,
20
14
  primaryColor,
@@ -1,9 +1,7 @@
1
1
  import type { NeutralColors, PrimaryColors } from '@lobehub/ui';
2
- import type { ThemeMode } from 'antd-style';
3
2
 
4
3
  export interface UserGeneralConfig {
5
4
  fontSize: number;
6
5
  neutralColor?: NeutralColors;
7
6
  primaryColor?: PrimaryColors;
8
- themeMode: ThemeMode;
9
7
  }