@lobehub/chat 1.24.1 → 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.
- package/CHANGELOG.md +50 -0
- package/locales/ar/chat.json +2 -1
- package/locales/ar/models.json +12 -9
- package/locales/bg-BG/chat.json +2 -1
- package/locales/bg-BG/models.json +12 -9
- package/locales/de-DE/chat.json +2 -1
- package/locales/de-DE/models.json +12 -9
- package/locales/en-US/chat.json +2 -1
- package/locales/en-US/models.json +12 -9
- package/locales/es-ES/chat.json +2 -1
- package/locales/es-ES/models.json +12 -9
- package/locales/fr-FR/chat.json +2 -1
- package/locales/fr-FR/models.json +12 -9
- package/locales/it-IT/chat.json +2 -1
- package/locales/it-IT/models.json +12 -9
- package/locales/ja-JP/chat.json +2 -1
- package/locales/ja-JP/models.json +12 -9
- package/locales/ko-KR/chat.json +2 -1
- package/locales/ko-KR/models.json +12 -9
- package/locales/nl-NL/chat.json +2 -1
- package/locales/nl-NL/models.json +12 -9
- package/locales/pl-PL/chat.json +2 -1
- package/locales/pl-PL/models.json +12 -9
- package/locales/pt-BR/chat.json +2 -1
- package/locales/pt-BR/models.json +12 -9
- package/locales/ru-RU/chat.json +2 -1
- package/locales/ru-RU/models.json +12 -9
- package/locales/tr-TR/chat.json +2 -1
- package/locales/tr-TR/models.json +12 -9
- package/locales/vi-VN/chat.json +2 -1
- package/locales/vi-VN/models.json +12 -9
- package/locales/zh-CN/chat.json +2 -1
- package/locales/zh-CN/models.json +12 -9
- package/locales/zh-TW/chat.json +2 -1
- package/locales/zh-TW/models.json +12 -9
- package/package.json +1 -1
- package/src/app/(main)/@nav/_layout/Desktop/index.tsx +12 -6
- package/src/app/(main)/chat/(workspace)/@conversation/default.tsx +2 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ZenModeToast/Toast.tsx +87 -0
- package/src/app/(main)/chat/(workspace)/@conversation/features/ZenModeToast/index.tsx +16 -0
- package/src/app/(main)/chat/(workspace)/_layout/Desktop/ChatHeader/index.tsx +18 -7
- package/src/app/(main)/chat/(workspace)/_layout/Desktop/HotKeys.tsx +10 -6
- package/src/app/(main)/chat/(workspace)/_layout/Desktop/TopicPanel.tsx +4 -4
- package/src/app/(main)/chat/_layout/Desktop/index.tsx +1 -1
- package/src/config/modelProviders/google.ts +16 -1
- package/src/const/hotkeys.ts +7 -1
- package/src/locales/default/chat.ts +1 -0
- package/src/store/global/action.test.ts +162 -0
- package/src/store/global/action.ts +7 -0
- package/src/store/global/initialState.ts +2 -0
- 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,
|