@lobehub/chat 1.24.2 → 1.25.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 (50) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/locales/ar/chat.json +2 -1
  3. package/locales/ar/models.json +12 -9
  4. package/locales/bg-BG/chat.json +2 -1
  5. package/locales/bg-BG/models.json +12 -9
  6. package/locales/de-DE/chat.json +2 -1
  7. package/locales/de-DE/models.json +12 -9
  8. package/locales/en-US/chat.json +2 -1
  9. package/locales/en-US/models.json +12 -9
  10. package/locales/es-ES/chat.json +2 -1
  11. package/locales/es-ES/models.json +12 -9
  12. package/locales/fr-FR/chat.json +2 -1
  13. package/locales/fr-FR/models.json +12 -9
  14. package/locales/it-IT/chat.json +2 -1
  15. package/locales/it-IT/models.json +12 -9
  16. package/locales/ja-JP/chat.json +2 -1
  17. package/locales/ja-JP/models.json +12 -9
  18. package/locales/ko-KR/chat.json +2 -1
  19. package/locales/ko-KR/models.json +12 -9
  20. package/locales/nl-NL/chat.json +2 -1
  21. package/locales/nl-NL/models.json +12 -9
  22. package/locales/pl-PL/chat.json +2 -1
  23. package/locales/pl-PL/models.json +12 -9
  24. package/locales/pt-BR/chat.json +2 -1
  25. package/locales/pt-BR/models.json +12 -9
  26. package/locales/ru-RU/chat.json +2 -1
  27. package/locales/ru-RU/models.json +12 -9
  28. package/locales/tr-TR/chat.json +2 -1
  29. package/locales/tr-TR/models.json +12 -9
  30. package/locales/vi-VN/chat.json +2 -1
  31. package/locales/vi-VN/models.json +12 -9
  32. package/locales/zh-CN/chat.json +2 -1
  33. package/locales/zh-CN/models.json +12 -9
  34. package/locales/zh-TW/chat.json +2 -1
  35. package/locales/zh-TW/models.json +12 -9
  36. package/package.json +1 -1
  37. package/src/app/(main)/@nav/_layout/Desktop/index.tsx +12 -6
  38. package/src/app/(main)/chat/(workspace)/@conversation/default.tsx +2 -0
  39. package/src/app/(main)/chat/(workspace)/@conversation/features/ZenModeToast/Toast.tsx +87 -0
  40. package/src/app/(main)/chat/(workspace)/@conversation/features/ZenModeToast/index.tsx +16 -0
  41. package/src/app/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/index.tsx +18 -7
  42. package/src/app/(main)/chat/(workspace)/_layout/Desktop/HotKeys.tsx +10 -6
  43. package/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx +4 -4
  44. package/src/app/(main)/chat/_layout/Desktop/index.tsx +1 -1
  45. package/src/const/hotkeys.ts +7 -1
  46. package/src/locales/default/chat.ts +1 -0
  47. package/src/store/global/action.test.ts +162 -0
  48. package/src/store/global/action.ts +7 -0
  49. package/src/store/global/initialState.ts +2 -0
  50. package/src/store/global/selectors.ts +6 -2
@@ -42,6 +42,22 @@ describe('createPreferenceSlice', () => {
42
42
 
43
43
  expect(result.current.status.showChatSideBar).toBe(true);
44
44
  });
45
+ it('should set chat sidebar to specified value', () => {
46
+ const { result } = renderHook(() => useGlobalStore());
47
+
48
+ act(() => {
49
+ useGlobalStore.setState({ isStatusInit: true });
50
+ result.current.toggleChatSideBar(true);
51
+ });
52
+
53
+ expect(result.current.status.showChatSideBar).toBe(true);
54
+
55
+ act(() => {
56
+ result.current.toggleChatSideBar(false);
57
+ });
58
+
59
+ expect(result.current.status.showChatSideBar).toBe(false);
60
+ });
45
61
  });
46
62
 
47
63
  describe('toggleExpandSessionGroup', () => {
@@ -56,6 +72,120 @@ describe('createPreferenceSlice', () => {
56
72
 
57
73
  expect(result.current.status.expandSessionGroupKeys).toContain(groupId);
58
74
  });
75
+
76
+ const groupId = 'group-id';
77
+ const anotherGroupId = 'another-group-id';
78
+
79
+ beforeEach(() => {
80
+ // 确保每个测试前状态都是已初始化的
81
+ useGlobalStore.setState({ isStatusInit: true });
82
+ });
83
+
84
+ it('should add group id when expanding and id not exists', () => {
85
+ const { result } = renderHook(() => useGlobalStore());
86
+
87
+ act(() => {
88
+ result.current.toggleExpandSessionGroup(groupId, true);
89
+ });
90
+
91
+ expect(result.current.status.expandSessionGroupKeys).toEqual(['pinned', 'default', groupId]);
92
+ });
93
+
94
+ it('should not add duplicate group id when expanding', () => {
95
+ const { result } = renderHook(() => useGlobalStore());
96
+
97
+ act(() => {
98
+ // 先添加一个组
99
+ result.current.toggleExpandSessionGroup(groupId, true);
100
+ // 再次尝试添加同一个组
101
+ result.current.toggleExpandSessionGroup(groupId, true);
102
+ });
103
+
104
+ // 确保数组中只有一个实例
105
+ expect(result.current.status.expandSessionGroupKeys).toEqual(['pinned', 'default', groupId]);
106
+ });
107
+
108
+ it('should remove group id when collapsing', () => {
109
+ const { result } = renderHook(() => useGlobalStore());
110
+
111
+ act(() => {
112
+ // 先设置初始状态为展开
113
+ result.current.toggleExpandSessionGroup(groupId, true);
114
+ result.current.toggleExpandSessionGroup(anotherGroupId, true);
115
+
116
+ // 验证初始状态
117
+ // 收起第一个组
118
+ result.current.toggleExpandSessionGroup(groupId, false);
119
+ });
120
+
121
+ // 验证只移除了指定的组
122
+ expect(result.current.status.expandSessionGroupKeys).toEqual([
123
+ 'pinned',
124
+ 'default',
125
+ anotherGroupId,
126
+ ]);
127
+ });
128
+
129
+ it('should do nothing when collapsing non-existent group', () => {
130
+ const { result } = renderHook(() => useGlobalStore());
131
+
132
+ act(() => {
133
+ // 先添加一个组
134
+ result.current.toggleExpandSessionGroup(groupId, true);
135
+
136
+ // 尝试收起一个不存在的组
137
+ result.current.toggleExpandSessionGroup('non-existent-id', false);
138
+ });
139
+
140
+ // 验证原有的组没有受影响
141
+ expect(result.current.status.expandSessionGroupKeys).toEqual(['pinned', 'default', groupId]);
142
+ });
143
+
144
+ it('should handle multiple groups correctly', () => {
145
+ const { result } = renderHook(() => useGlobalStore());
146
+
147
+ act(() => {
148
+ // 添加多个组
149
+ result.current.toggleExpandSessionGroup(groupId, true);
150
+ result.current.toggleExpandSessionGroup(anotherGroupId, true);
151
+ result.current.toggleExpandSessionGroup('third-group', true);
152
+ });
153
+
154
+ expect(result.current.status.expandSessionGroupKeys).toEqual([
155
+ 'pinned',
156
+ 'default',
157
+ groupId,
158
+ anotherGroupId,
159
+ 'third-group',
160
+ ]);
161
+
162
+ act(() => {
163
+ // 收起中间的组
164
+ result.current.toggleExpandSessionGroup(anotherGroupId, false);
165
+ });
166
+
167
+ expect(result.current.status.expandSessionGroupKeys).toEqual([
168
+ 'pinned',
169
+ 'default',
170
+ groupId,
171
+ 'third-group',
172
+ ]);
173
+ });
174
+
175
+ it('should save to localStorage when groups are toggled', () => {
176
+ const { result } = renderHook(() => useGlobalStore());
177
+ const saveToLocalStorageSpy = vi.spyOn(result.current.statusStorage, 'saveToLocalStorage');
178
+
179
+ act(() => {
180
+ result.current.toggleExpandSessionGroup(groupId, true);
181
+ });
182
+
183
+ expect(saveToLocalStorageSpy).toHaveBeenCalledWith(
184
+ expect.objectContaining({
185
+ expandSessionGroupKeys: ['pinned', 'default', groupId],
186
+ }),
187
+ );
188
+ });
59
189
  });
60
190
 
61
191
  describe('toggleMobileTopic', () => {
@@ -84,6 +214,28 @@ describe('createPreferenceSlice', () => {
84
214
  });
85
215
  });
86
216
 
217
+ describe('toggleZenMode', () => {
218
+ it('should toggle zen mode', () => {
219
+ const { result } = renderHook(() => useGlobalStore());
220
+
221
+ act(() => {
222
+ useGlobalStore.setState({ isStatusInit: true });
223
+ // 初始值应该是 false
224
+ expect(result.current.status.zenMode).toBe(false);
225
+
226
+ result.current.toggleZenMode();
227
+ });
228
+
229
+ expect(result.current.status.zenMode).toBe(true);
230
+
231
+ act(() => {
232
+ result.current.toggleZenMode();
233
+ });
234
+
235
+ expect(result.current.status.zenMode).toBe(false);
236
+ });
237
+ });
238
+
87
239
  describe('toggleSystemRole', () => {
88
240
  it('should toggle system role', () => {
89
241
  const { result } = renderHook(() => useGlobalStore());
@@ -210,6 +362,16 @@ describe('createPreferenceSlice', () => {
210
362
  expect(useGlobalStore.getState().hasNewVersion).toBeUndefined();
211
363
  expect(useGlobalStore.getState().latestVersion).toBeUndefined();
212
364
  });
365
+
366
+ it('should not fetch version when check is disabled', () => {
367
+ const getLatestVersionSpy = vi.spyOn(globalService, 'getLatestVersion');
368
+
369
+ renderHook(() => useGlobalStore().useCheckLatestVersion(false), {
370
+ wrapper: withSWR,
371
+ });
372
+
373
+ expect(getLatestVersionSpy).not.toHaveBeenCalled();
374
+ });
213
375
  });
214
376
 
215
377
  describe('useInitGlobalPreference', () => {
@@ -27,6 +27,7 @@ export interface GlobalStoreAction {
27
27
  toggleMobilePortal: (visible?: boolean) => void;
28
28
  toggleMobileTopic: (visible?: boolean) => void;
29
29
  toggleSystemRole: (visible?: boolean) => void;
30
+ toggleZenMode: () => void;
30
31
  updateSystemStatus: (status: Partial<SystemStatus>, action?: any) => void;
31
32
  useCheckLatestVersion: (enabledCheck?: boolean) => SWRResponse<string>;
32
33
  useInitSystemStatus: () => SWRResponse;
@@ -77,6 +78,12 @@ export const globalActionSlice: StateCreator<
77
78
 
78
79
  get().updateSystemStatus({ showSystemRole }, n('toggleMobileTopic', newValue));
79
80
  },
81
+ toggleZenMode: () => {
82
+ const { status } = get();
83
+ const nextZenMode = !status.zenMode;
84
+
85
+ get().updateSystemStatus({ zenMode: nextZenMode }, n('toggleZenMode'));
86
+ },
80
87
  updateSystemStatus: (status, action) => {
81
88
  // Status cannot be modified when it is not initialized
82
89
  if (!get().isStatusInit) return;
@@ -43,6 +43,7 @@ export interface SystemStatus {
43
43
  showFilePanel?: boolean;
44
44
  showSessionPanel?: boolean;
45
45
  showSystemRole?: boolean;
46
+ zenMode?: boolean;
46
47
  }
47
48
 
48
49
  export interface GlobalState {
@@ -67,6 +68,7 @@ export const INITIAL_STATUS = {
67
68
  showFilePanel: true,
68
69
  showSessionPanel: true,
69
70
  showSystemRole: false,
71
+ zenMode: false,
70
72
  } satisfies SystemStatus;
71
73
 
72
74
  export const initialState: GlobalState = {
@@ -8,11 +8,13 @@ const sessionGroupKeys = (s: GlobalStore): string[] =>
8
8
  const showSystemRole = (s: GlobalStore) => s.status.showSystemRole;
9
9
  const mobileShowTopic = (s: GlobalStore) => s.status.mobileShowTopic;
10
10
  const mobileShowPortal = (s: GlobalStore) => s.status.mobileShowPortal;
11
- const showChatSideBar = (s: GlobalStore) => s.status.showChatSideBar;
12
- const showSessionPanel = (s: GlobalStore) => s.status.showSessionPanel;
11
+ const showChatSideBar = (s: GlobalStore) => !s.status.zenMode && s.status.showChatSideBar;
12
+ const showSessionPanel = (s: GlobalStore) => !s.status.zenMode && s.status.showSessionPanel;
13
13
  const showFilePanel = (s: GlobalStore) => s.status.showFilePanel;
14
14
  const hidePWAInstaller = (s: GlobalStore) => s.status.hidePWAInstaller;
15
15
 
16
+ const showChatHeader = (s: GlobalStore) => !s.status.zenMode;
17
+ const inZenMode = (s: GlobalStore) => s.status.zenMode;
16
18
  const sessionWidth = (s: GlobalStore) => s.status.sessionsWidth;
17
19
  const filePanelWidth = (s: GlobalStore) => s.status.filePanelWidth;
18
20
  const inputHeight = (s: GlobalStore) => s.status.inputHeight;
@@ -20,11 +22,13 @@ const inputHeight = (s: GlobalStore) => s.status.inputHeight;
20
22
  export const systemStatusSelectors = {
21
23
  filePanelWidth,
22
24
  hidePWAInstaller,
25
+ inZenMode,
23
26
  inputHeight,
24
27
  mobileShowPortal,
25
28
  mobileShowTopic,
26
29
  sessionGroupKeys,
27
30
  sessionWidth,
31
+ showChatHeader,
28
32
  showChatSideBar,
29
33
  showFilePanel,
30
34
  showSessionPanel,