@lobehub/chat 0.107.15 → 0.107.16

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.107.16](https://github.com/lobehub/lobe-chat/compare/v0.107.15...v0.107.16)
6
+
7
+ <sup>Released on **2023-12-03**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix custom agent meta issue.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix custom agent meta issue, closes [#567](https://github.com/lobehub/lobe-chat/issues/567) ([75560e1](https://github.com/lobehub/lobe-chat/commit/75560e1))
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.107.15](https://github.com/lobehub/lobe-chat/compare/v0.107.14...v0.107.15)
6
31
 
7
32
  <sup>Released on **2023-12-03**</sup>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "0.107.15",
3
+ "version": "0.107.16",
4
4
  "description": "An open-source, extensible (Function Calling), high-performance chatbot framework. It supports one-click free deployment of your private ChatGPT/LLM web application",
5
5
  "keywords": [
6
6
  "chatbot",
@@ -18,8 +18,8 @@ import SkeletonList from './SkeletonList';
18
18
 
19
19
  const List = memo(() => {
20
20
  const { t } = useTranslation('common');
21
-
22
- const data = useChatStore(chatSelectors.currentChatsWithGuideMessage, isEqual);
21
+ const meta = useSessionStore(agentSelectors.currentAgentMeta, isEqual);
22
+ const data = useChatStore(chatSelectors.currentChatsWithGuideMessage(meta), isEqual);
23
23
 
24
24
  const [init, chatLoadingId, resendMessage, updateMessageContent] = useChatStore((s) => [
25
25
  s.messagesInit,
@@ -10,7 +10,7 @@ import { chatSelectors } from '@/store/chat/selectors';
10
10
  import { ChatStore } from '@/store/chat/store';
11
11
  import { setNamespace } from '@/utils/storeDebug';
12
12
 
13
- const t = setNamespace('enhance');
13
+ const n = setNamespace('enhance');
14
14
 
15
15
  /**
16
16
  * enhance chat action like translate,tts
@@ -49,7 +49,7 @@ export const chatEnhance: StateCreator<
49
49
  await messageService.updateMessageTranslate(id, { content: '', from: '', to: targetLang });
50
50
  await refreshMessages();
51
51
 
52
- toggleChatLoading(true, id, t('translateMessage(start)', { id }) as string);
52
+ toggleChatLoading(true, id, n('translateMessage(start)', { id }) as string);
53
53
 
54
54
  let content = '';
55
55
  let from = '';
@@ -25,7 +25,7 @@ import { setNamespace } from '@/utils/storeDebug';
25
25
  import { MessageDispatch, messagesReducer } from '../reducers/message';
26
26
  import { chatSelectors } from '../selectors';
27
27
 
28
- const t = setNamespace('message');
28
+ const n = setNamespace('message');
29
29
 
30
30
  export interface ChatMessageAction {
31
31
  // create
@@ -191,10 +191,10 @@ export const chatMessage: StateCreator<
191
191
 
192
192
  abortController.abort();
193
193
 
194
- toggleChatLoading(false, undefined, t('stopGenerateMessage') as string);
194
+ toggleChatLoading(false, undefined, n('stopGenerateMessage') as string);
195
195
  },
196
196
  updateInputMessage: (message) => {
197
- set({ inputMessage: message }, false, t('updateInputMessage', message));
197
+ set({ inputMessage: message }, false, n('updateInputMessage', message));
198
198
  },
199
199
  updateMessageContent: async (id, content) => {
200
200
  const { dispatchMessage, refreshMessages } = get();
@@ -217,7 +217,7 @@ export const chatMessage: StateCreator<
217
217
  set(
218
218
  { activeId: sessionId, messages, messagesInit: true },
219
219
  false,
220
- t('useFetchMessages', {
220
+ n('useFetchMessages', {
221
221
  messages,
222
222
  queryKey: key,
223
223
  }),
@@ -291,7 +291,7 @@ export const chatMessage: StateCreator<
291
291
 
292
292
  const messages = messagesReducer(get().messages, payload);
293
293
 
294
- set({ messages }, false, t(`dispatchMessage/${payload.type}`, payload));
294
+ set({ messages }, false, n(`dispatchMessage/${payload.type}`, payload));
295
295
  },
296
296
  fetchAIChatMessage: async (messages, assistantId) => {
297
297
  const { toggleChatLoading, refreshMessages } = get();
@@ -299,7 +299,7 @@ export const chatMessage: StateCreator<
299
299
  const abortController = toggleChatLoading(
300
300
  true,
301
301
  assistantId,
302
- t('generateMessage(start)', { assistantId, messages }) as string,
302
+ n('generateMessage(start)', { assistantId, messages }) as string,
303
303
  );
304
304
 
305
305
  const config = getAgentConfig();
@@ -380,7 +380,7 @@ export const chatMessage: StateCreator<
380
380
  },
381
381
  });
382
382
 
383
- toggleChatLoading(false, undefined, t('generateMessage(end)') as string);
383
+ toggleChatLoading(false, undefined, n('generateMessage(end)') as string);
384
384
 
385
385
  // also exist message like this:
386
386
  // 请稍等,我帮您查询一下。{"function_call": {"name": "plugin-identifier____recommendClothes____standalone", "arguments": "{\n "mood": "",\n "gender": "man"\n}"}}
@@ -10,7 +10,7 @@ import { setNamespace } from '@/utils/storeDebug';
10
10
 
11
11
  import { chatSelectors } from '../selectors';
12
12
 
13
- const t = setNamespace('plugin');
13
+ const n = setNamespace('plugin');
14
14
 
15
15
  export interface ChatPluginAction {
16
16
  fillPluginMessageContent: (id: string, content: string) => Promise<void>;
@@ -38,7 +38,7 @@ export const chatPlugin: StateCreator<
38
38
  const { refreshMessages, coreProcessMessage, toggleChatLoading } = get();
39
39
  let data: string;
40
40
  try {
41
- const abortController = toggleChatLoading(true, id, t('fetchPlugin') as string);
41
+ const abortController = toggleChatLoading(true, id, n('fetchPlugin') as string);
42
42
  data = await chatService.runPluginApi(payload, { signal: abortController?.signal });
43
43
  } catch (error) {
44
44
  await messageService.updateMessageError(id, error as any);
@@ -0,0 +1,391 @@
1
+ import { act, renderHook, waitFor } from '@testing-library/react';
2
+ import { mutate } from 'swr';
3
+ import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { LOADING_FLAT } from '@/const/message';
6
+ import { chatService } from '@/services/chat';
7
+ import { messageService } from '@/services/message';
8
+ import { topicService } from '@/services/topic';
9
+ import { ChatMessage } from '@/types/chatMessage';
10
+ import { ChatTopic } from '@/types/topic';
11
+
12
+ import { useChatStore } from '../store';
13
+
14
+ // Mock topicService 和 messageService
15
+ vi.mock('@/services/topic', () => ({
16
+ topicService: {
17
+ removeTopics: vi.fn(),
18
+ removeAllTopic: vi.fn(),
19
+ removeTopic: vi.fn(),
20
+ createTopic: vi.fn(),
21
+ updateTitle: vi.fn(),
22
+ updateFavorite: vi.fn(),
23
+ batchRemoveTopics: vi.fn(),
24
+ getTopics: vi.fn(),
25
+ searchTopics: vi.fn(),
26
+ },
27
+ }));
28
+
29
+ vi.mock('@/services/message', () => ({
30
+ messageService: {
31
+ removeMessages: vi.fn(),
32
+ },
33
+ }));
34
+
35
+ beforeEach(() => {
36
+ // Setup initial state and mocks before each test
37
+ vi.clearAllMocks();
38
+ useChatStore.setState(
39
+ {
40
+ activeId: undefined,
41
+ activeTopicId: undefined,
42
+ // ... initial state
43
+ },
44
+ false,
45
+ );
46
+ });
47
+
48
+ afterEach(() => {
49
+ // Cleanup mocks after each test
50
+ vi.restoreAllMocks();
51
+ });
52
+
53
+ describe('topic action', () => {
54
+ describe('openNewTopicOrSaveTopic', () => {
55
+ it('should call switchTopic if activeTopicId exists', async () => {
56
+ const { result } = renderHook(() => useChatStore());
57
+ await act(async () => {
58
+ useChatStore.setState({ activeTopicId: 'existing-topic-id' });
59
+ });
60
+
61
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
62
+
63
+ await act(async () => {
64
+ result.current.openNewTopicOrSaveTopic();
65
+ });
66
+
67
+ expect(switchTopicSpy).toHaveBeenCalled();
68
+ });
69
+
70
+ it('should call saveToTopic if activeTopicId does not exist', async () => {
71
+ const { result } = renderHook(() => useChatStore());
72
+ await act(async () => {
73
+ useChatStore.setState({ activeTopicId: '' });
74
+ });
75
+
76
+ const saveToTopicSpy = vi.spyOn(result.current, 'saveToTopic');
77
+
78
+ await act(async () => {
79
+ await result.current.openNewTopicOrSaveTopic();
80
+ });
81
+
82
+ expect(saveToTopicSpy).toHaveBeenCalled();
83
+ });
84
+ });
85
+ describe('saveToTopic', () => {
86
+ it('should not create a topic if there are no messages', async () => {
87
+ const { result } = renderHook(() => useChatStore());
88
+ useChatStore.setState({ messages: [] });
89
+
90
+ const createTopicSpy = vi.spyOn(topicService, 'createTopic');
91
+
92
+ const topicId = await result.current.saveToTopic();
93
+
94
+ expect(createTopicSpy).not.toHaveBeenCalled();
95
+ expect(topicId).toBeUndefined();
96
+ });
97
+
98
+ it('should create a topic and bind messages to it', async () => {
99
+ const { result } = renderHook(() => useChatStore());
100
+ const messages = [{ id: 'message1' }, { id: 'message2' }] as ChatMessage[];
101
+ useChatStore.setState({ messages, activeId: 'session-id' });
102
+
103
+ const createTopicSpy = vi
104
+ .spyOn(topicService, 'createTopic')
105
+ .mockResolvedValue('new-topic-id');
106
+
107
+ const topicId = await result.current.saveToTopic();
108
+
109
+ expect(createTopicSpy).toHaveBeenCalledWith(
110
+ expect.objectContaining({
111
+ sessionId: 'session-id',
112
+ messages: messages.map((m) => m.id),
113
+ }),
114
+ );
115
+ expect(topicId).toEqual('new-topic-id');
116
+ });
117
+ });
118
+ describe('refreshTopic', () => {
119
+ beforeEach(() => {
120
+ vi.mock('swr', async () => {
121
+ const actual = await vi.importActual('swr');
122
+ return {
123
+ ...(actual as any),
124
+ mutate: vi.fn(),
125
+ };
126
+ });
127
+ });
128
+ afterEach(() => {
129
+ // 在每个测试用例开始前恢复到实际的 SWR 实现
130
+ vi.resetAllMocks();
131
+ });
132
+
133
+ it('should call mutate to refresh topics', async () => {
134
+ const { result } = renderHook(() => useChatStore());
135
+ const activeId = 'test-session-id';
136
+ useChatStore.setState({ activeId });
137
+
138
+ // Mock the mutate function to resolve immediately
139
+
140
+ await act(async () => {
141
+ await result.current.refreshTopic();
142
+ });
143
+
144
+ // Check if mutate has been called with the active session ID
145
+ expect(mutate).toHaveBeenCalledWith(activeId);
146
+ });
147
+
148
+ it('should handle errors during refreshing topics', async () => {
149
+ const { result } = renderHook(() => useChatStore());
150
+ const activeId = 'test-session-id';
151
+ useChatStore.setState({ activeId });
152
+
153
+ // Mock the mutate function to throw an error
154
+ // 设置模拟错误
155
+ (mutate as Mock).mockImplementation(() => {
156
+ throw new Error('Mutate error');
157
+ });
158
+
159
+ await act(async () => {
160
+ await expect(result.current.refreshTopic()).rejects.toThrow('Mutate error');
161
+ });
162
+
163
+ // 确保恢复 mutate 的模拟,以免影响其他测试
164
+ (mutate as Mock).mockReset();
165
+ });
166
+
167
+ // Additional tests for refreshTopic can be added here...
168
+ });
169
+ describe('favoriteTopic', () => {
170
+ it('should update the favorite state of a topic and refresh topics', async () => {
171
+ const { result } = renderHook(() => useChatStore());
172
+ const topicId = 'topic-id';
173
+ const favState = true;
174
+
175
+ const updateFavoriteSpy = vi.spyOn(topicService, 'updateFavorite').mockResolvedValue(true);
176
+
177
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
178
+
179
+ await act(async () => {
180
+ await result.current.favoriteTopic(topicId, favState);
181
+ });
182
+
183
+ expect(updateFavoriteSpy).toHaveBeenCalledWith(topicId, favState);
184
+ expect(refreshTopicSpy).toHaveBeenCalled();
185
+ });
186
+ });
187
+ describe('useFetchTopics', () => {
188
+ it('should fetch topics for a given session id', async () => {
189
+ const sessionId = 'test-session-id';
190
+ const topics = [{ id: 'topic-id', title: 'Test Topic' }];
191
+
192
+ // Mock the topicService.getTopics to resolve with topics array
193
+ (topicService.getTopics as Mock).mockResolvedValue(topics);
194
+
195
+ // Use the hook with the session id
196
+ const { result } = renderHook(() => useChatStore().useFetchTopics(sessionId));
197
+
198
+ // Wait for the hook to resolve and update the state
199
+ await waitFor(() => {
200
+ expect(result.current.data).toEqual(topics);
201
+ });
202
+ expect(useChatStore.getState().topicsInit).toBeTruthy();
203
+ expect(useChatStore.getState().topics).toEqual(topics);
204
+ });
205
+ });
206
+ describe('useSearchTopics', () => {
207
+ it('should search topics with the given keywords', async () => {
208
+ const keywords = 'search-term';
209
+ const searchResults = [{ id: 'searched-topic-id', title: 'Searched Topic' }];
210
+
211
+ // Mock the topicService.searchTopics to resolve with search results
212
+ (topicService.searchTopics as Mock).mockResolvedValue(searchResults);
213
+
214
+ // Use the hook with the keywords
215
+ const { result } = renderHook(() => useChatStore().useSearchTopics(keywords));
216
+
217
+ // Wait for the hook to resolve and update the state
218
+ await waitFor(() => {
219
+ expect(result.current.data).toEqual(searchResults);
220
+ });
221
+ });
222
+ });
223
+ describe('updateTopicTitle', () => {
224
+ it('should call topicService.updateTitle with correct parameters and refresh the topic', async () => {
225
+ const topicId = 'topic-id';
226
+ const newTitle = 'Updated Topic Title';
227
+ // Mock the topicService.updateTitle to resolve immediately
228
+ (topicService.updateTitle as Mock).mockResolvedValue(undefined);
229
+
230
+ const { result } = renderHook(() => useChatStore());
231
+
232
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
233
+
234
+ // Call the action with the topicId and newTitle
235
+ await act(async () => {
236
+ await result.current.updateTopicTitle(topicId, newTitle);
237
+ });
238
+
239
+ // Verify that the topicService.updateTitle was called with correct parameters
240
+ expect(topicService.updateTitle).toHaveBeenCalledWith(topicId, newTitle);
241
+
242
+ // Verify that the refreshTopic was called to update the state
243
+ expect(refreshTopicSpy).toHaveBeenCalled();
244
+ });
245
+ });
246
+ describe('switchTopic', () => {
247
+ it('should update activeTopicId and call refreshMessages', async () => {
248
+ const topicId = 'topic-id';
249
+ const { result } = renderHook(() => useChatStore());
250
+
251
+ const refreshMessagesSpy = vi.spyOn(result.current, 'refreshMessages');
252
+ // Call the switchTopic action with the topicId
253
+ await act(async () => {
254
+ await result.current.switchTopic(topicId);
255
+ });
256
+
257
+ // Verify that the activeTopicId has been updated
258
+ expect(useChatStore.getState().activeTopicId).toBe(topicId);
259
+
260
+ // Verify that the refreshMessages was called to update the messages
261
+ expect(refreshMessagesSpy).toHaveBeenCalled();
262
+ });
263
+ });
264
+ describe('removeSessionTopics', () => {
265
+ it('should remove all topics from the current session and refresh the topic list', async () => {
266
+ const { result } = renderHook(() => useChatStore());
267
+ const activeId = 'test-session-id';
268
+ await act(async () => {
269
+ useChatStore.setState({ activeId });
270
+ });
271
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
272
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
273
+
274
+ await act(async () => {
275
+ await result.current.removeSessionTopics();
276
+ });
277
+
278
+ expect(topicService.removeTopics).toHaveBeenCalledWith(activeId);
279
+ expect(refreshTopicSpy).toHaveBeenCalled();
280
+ expect(switchTopicSpy).toHaveBeenCalled();
281
+ });
282
+ });
283
+ describe('removeAllTopics', () => {
284
+ it('should remove all topics and refresh the topic list', async () => {
285
+ const { result } = renderHook(() => useChatStore());
286
+
287
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
288
+
289
+ await act(async () => {
290
+ await result.current.removeAllTopics();
291
+ });
292
+
293
+ expect(topicService.removeAllTopic).toHaveBeenCalled();
294
+ expect(refreshTopicSpy).toHaveBeenCalled();
295
+ });
296
+ });
297
+ describe('removeTopic', () => {
298
+ it('should remove a specific topic and its messages, then refresh the topic list', async () => {
299
+ const topicId = 'topic-1';
300
+ const { result } = renderHook(() => useChatStore());
301
+ const activeId = 'test-session-id';
302
+
303
+ await act(async () => {
304
+ useChatStore.setState({ activeId });
305
+ });
306
+
307
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
308
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
309
+
310
+ await act(async () => {
311
+ await result.current.removeTopic(topicId);
312
+ });
313
+
314
+ expect(messageService.removeMessages).toHaveBeenCalledWith(activeId, topicId);
315
+ expect(topicService.removeTopic).toHaveBeenCalledWith(topicId);
316
+ expect(refreshTopicSpy).toHaveBeenCalled();
317
+ expect(switchTopicSpy).toHaveBeenCalled();
318
+ });
319
+ });
320
+ describe('removeUnstarredTopic', () => {
321
+ it('should remove unstarred topics and refresh the topic list', async () => {
322
+ const { result } = renderHook(() => useChatStore());
323
+ // Set up mock state with unstarred topics
324
+ await act(async () => {
325
+ useChatStore.setState({
326
+ topics: [
327
+ { id: 'topic-1', favorite: false },
328
+ { id: 'topic-2', favorite: true },
329
+ { id: 'topic-3', favorite: false },
330
+ ] as ChatTopic[],
331
+ });
332
+ });
333
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
334
+ const switchTopicSpy = vi.spyOn(result.current, 'switchTopic');
335
+
336
+ await act(async () => {
337
+ await result.current.removeUnstarredTopic();
338
+ });
339
+
340
+ expect(topicService.batchRemoveTopics).toHaveBeenCalledWith(['topic-1', 'topic-3']);
341
+ expect(refreshTopicSpy).toHaveBeenCalled();
342
+ expect(switchTopicSpy).toHaveBeenCalled();
343
+ });
344
+ });
345
+ describe('updateTopicLoading', () => {
346
+ it('should call update topicLoadingId', async () => {
347
+ const { result } = renderHook(() => useChatStore());
348
+ expect(result.current.topicLoadingId).toBeUndefined();
349
+
350
+ // Call the action with the topicId and newTitle
351
+ await act(async () => {
352
+ await result.current.updateTopicLoading('loading-id');
353
+ });
354
+
355
+ expect(result.current.topicLoadingId).toEqual('loading-id');
356
+ });
357
+ });
358
+ describe('summaryTopicTitle', () => {
359
+ it('should auto-summarize the topic title and update it', async () => {
360
+ const topicId = 'topic-1';
361
+ const messages = [{ id: 'message-1', content: 'Hello' }] as ChatMessage[];
362
+ const topics = [{ id: 'topic-1', title: 'Test Topic' }] as ChatTopic[];
363
+ const { result } = renderHook(() => useChatStore());
364
+ await act(async () => {
365
+ useChatStore.setState({ topics });
366
+ });
367
+
368
+ // Mock the `updateTopicTitleInSummary` and `refreshTopic` for spying
369
+ const updateTopicTitleInSummarySpy = vi.spyOn(result.current, 'updateTopicTitleInSummary');
370
+ const refreshTopicSpy = vi.spyOn(result.current, 'refreshTopic');
371
+
372
+ // Mock the `chatService.fetchPresetTaskResult` to simulate the AI response
373
+ vi.spyOn(chatService, 'fetchPresetTaskResult').mockImplementation((params) => {
374
+ if (params) {
375
+ params.onFinish?.('Summarized Title');
376
+ }
377
+ return Promise.resolve(undefined);
378
+ });
379
+
380
+ await act(async () => {
381
+ await result.current.summaryTopicTitle(topicId, messages);
382
+ });
383
+
384
+ // Verify that the title was updated and the topic was refreshed
385
+ expect(updateTopicTitleInSummarySpy).toHaveBeenCalledWith(topicId, LOADING_FLAT);
386
+ expect(refreshTopicSpy).toHaveBeenCalled();
387
+
388
+ // TODO: need to test with fetchPresetTaskResult
389
+ });
390
+ });
391
+ });
@@ -22,7 +22,7 @@ const n = setNamespace('topic');
22
22
 
23
23
  export interface ChatTopicAction {
24
24
  favoriteTopic: (id: string, favState: boolean) => Promise<void>;
25
- openNewTopicOrSaveTopic: () => void;
25
+ openNewTopicOrSaveTopic: () => Promise<void>;
26
26
  refreshTopic: () => Promise<void>;
27
27
  removeAllTopics: () => Promise<void>;
28
28
  removeSessionTopics: () => Promise<void>;
@@ -45,7 +45,7 @@ export const chatTopic: StateCreator<
45
45
  ChatTopicAction
46
46
  > = (set, get) => ({
47
47
  // create
48
- openNewTopicOrSaveTopic: () => {
48
+ openNewTopicOrSaveTopic: async () => {
49
49
  const { switchTopic, saveToTopic, activeTopicId } = get();
50
50
  const hasTopic = !!activeTopicId;
51
51
 
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+
3
+ import { LobeAgentConfig } from '@/types/agent';
4
+ import { ChatMessage } from '@/types/chatMessage';
5
+ import { OpenAIChatMessage } from '@/types/openai/chat';
6
+ import { encodeAsync } from '@/utils/tokenizer';
7
+ import * as tokenizerObj from '@/utils/tokenizer';
8
+
9
+ import { chatHelpers } from './helpers';
10
+
11
+ // Mock encodeAsync function
12
+ vi.mock('@/utils/tokenizer', () => ({
13
+ encodeAsync: vi.fn((text) => Promise.resolve(text.length)),
14
+ }));
15
+
16
+ describe('chatHelpers', () => {
17
+ describe('getMessagesTokenCount', () => {
18
+ it('returns token count for a list of messages', async () => {
19
+ const messages = [{ content: 'Hello' }, { content: 'World' }] as OpenAIChatMessage[];
20
+ const tokenCount = await chatHelpers.getMessagesTokenCount(messages);
21
+ expect(tokenCount).toBe('HelloWorld'.length);
22
+ });
23
+
24
+ it('returns 0 for an empty array', async () => {
25
+ const tokenCount = await chatHelpers.getMessagesTokenCount([]);
26
+ expect(tokenCount).toBe(0);
27
+ });
28
+
29
+ it('handles messages with empty content', async () => {
30
+ const messages = [
31
+ { content: 'Hello' },
32
+ { content: '' },
33
+ { content: 'World' },
34
+ ] as OpenAIChatMessage[];
35
+ const tokenCount = await chatHelpers.getMessagesTokenCount(messages);
36
+ expect(tokenCount).toBe('HelloWorld'.length);
37
+ });
38
+
39
+ it('throws an error when encodeAsync fails', async () => {
40
+ vi.spyOn(tokenizerObj, 'encodeAsync').mockRejectedValue(new Error('Test error'));
41
+ await expect(
42
+ chatHelpers.getMessagesTokenCount([{ content: 'Hello' }] as OpenAIChatMessage[]),
43
+ ).rejects.toThrow('Test error');
44
+ });
45
+ });
46
+
47
+ describe('getMessageById', () => {
48
+ const messages = [
49
+ { id: '1', content: 'Hello' },
50
+ { id: '2', content: 'World' },
51
+ ] as ChatMessage[];
52
+
53
+ it('finds a message by id', () => {
54
+ const message = chatHelpers.getMessageById(messages, '1');
55
+ expect(message).toEqual({ id: '1', content: 'Hello' });
56
+ });
57
+
58
+ it('returns undefined for an invalid id', () => {
59
+ const message = chatHelpers.getMessageById(messages, '3');
60
+ expect(message).toBeUndefined();
61
+ });
62
+
63
+ it('returns undefined for an empty array', () => {
64
+ const message = chatHelpers.getMessageById([], '1');
65
+ expect(message).toBeUndefined();
66
+ });
67
+ });
68
+
69
+ describe('getSlicedMessagesWithConfig', () => {
70
+ const messages = [
71
+ { id: '1', content: 'First' },
72
+ { id: '2', content: 'Second' },
73
+ { id: '3', content: 'Third' },
74
+ ] as ChatMessage[];
75
+
76
+ it('returns all messages if history is disabled', () => {
77
+ const config = { enableHistoryCount: false, historyCount: 0 } as LobeAgentConfig;
78
+ const slicedMessages = chatHelpers.getSlicedMessagesWithConfig(messages, config);
79
+ expect(slicedMessages).toEqual(messages);
80
+ });
81
+
82
+ it('returns last N messages based on historyCount', () => {
83
+ const config = { enableHistoryCount: true, historyCount: 2 } as LobeAgentConfig;
84
+ const slicedMessages = chatHelpers.getSlicedMessagesWithConfig(messages, config);
85
+ expect(slicedMessages).toEqual([
86
+ { id: '2', content: 'Second' },
87
+ { id: '3', content: 'Third' },
88
+ ]);
89
+ });
90
+
91
+ it('returns empty array when historyCount is negative', () => {
92
+ const config = { enableHistoryCount: true, historyCount: -1 } as LobeAgentConfig;
93
+ const slicedMessages = chatHelpers.getSlicedMessagesWithConfig(messages, config);
94
+ expect(slicedMessages).toEqual([]);
95
+ });
96
+
97
+ it('returns all messages if historyCount exceeds the array length', () => {
98
+ const config = { enableHistoryCount: true, historyCount: 5 } as LobeAgentConfig;
99
+ const slicedMessages = chatHelpers.getSlicedMessagesWithConfig(messages, config);
100
+ expect(slicedMessages).toEqual(messages);
101
+ });
102
+
103
+ it('returns an empty array for an empty message array', () => {
104
+ const config = { enableHistoryCount: true, historyCount: 2 } as LobeAgentConfig;
105
+ const slicedMessages = chatHelpers.getSlicedMessagesWithConfig([], config);
106
+ expect(slicedMessages).toEqual([]);
107
+ });
108
+ });
109
+ });
@@ -6,18 +6,25 @@ import { encodeAsync } from '@/utils/tokenizer';
6
6
  export const getMessagesTokenCount = async (messages: OpenAIChatMessage[]) =>
7
7
  encodeAsync(messages.map((m) => m.content).join(''));
8
8
 
9
+ export const getMessageById = (messages: ChatMessage[], id: string) =>
10
+ messages.find((m) => m.id === id);
11
+
9
12
  const getSlicedMessagesWithConfig = (
10
13
  messages: ChatMessage[],
11
14
  config: LobeAgentConfig,
12
15
  ): ChatMessage[] => {
13
- // 如果没有开启历史消息数限制,或者限制为 0,则直接返回
16
+ // if historyCount is not enabled or set to 0, return all messages
14
17
  if (!config.enableHistoryCount || !config.historyCount) return messages;
15
18
 
16
- // 如果开启了,则返回尾部的N条消息
17
- return messages.reverse().slice(0, config.historyCount).reverse();
19
+ // if historyCount is negative, return empty array
20
+ if (config.historyCount <= 0) return [];
21
+
22
+ // if historyCount is positive, return last N messages
23
+ return messages.slice(-config.historyCount);
18
24
  };
19
25
 
20
26
  export const chatHelpers = {
27
+ getMessageById,
21
28
  getMessagesTokenCount,
22
29
  getSlicedMessagesWithConfig,
23
30
  };
@@ -0,0 +1,251 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { DEFAULT_INBOX_AVATAR } from '@/const/meta';
4
+ import { INBOX_SESSION_ID } from '@/const/session';
5
+ import { ChatStore } from '@/store/chat';
6
+ import { initialState } from '@/store/chat/initialState';
7
+ import { useSessionStore } from '@/store/session';
8
+ import { ChatMessage } from '@/types/chatMessage';
9
+ import { MetaData } from '@/types/meta';
10
+ import { merge } from '@/utils/merge';
11
+
12
+ import { chatSelectors } from '../chat';
13
+
14
+ vi.mock('i18next', () => ({
15
+ t: vi.fn((key) => key), // Simplified mock return value
16
+ }));
17
+
18
+ const initialStore = initialState as ChatStore;
19
+
20
+ const mockMessages = [
21
+ {
22
+ id: 'msg1',
23
+ content: 'Hello World',
24
+ role: 'user',
25
+ },
26
+ {
27
+ id: 'msg2',
28
+ content: 'Goodbye World',
29
+ role: 'user',
30
+ },
31
+ {
32
+ id: 'msg3',
33
+ content: 'Function Message',
34
+ role: 'function',
35
+ plugin: {
36
+ arguments: ['arg1', 'arg2'],
37
+ identifier: 'func1',
38
+ type: 'pluginType',
39
+ },
40
+ },
41
+ ] as ChatMessage[];
42
+
43
+ const mockedChats = [
44
+ {
45
+ id: 'msg1',
46
+ content: 'Hello World',
47
+ role: 'user',
48
+ meta: {
49
+ avatar: '😀',
50
+ },
51
+ },
52
+ {
53
+ id: 'msg2',
54
+ content: 'Goodbye World',
55
+ role: 'user',
56
+ meta: {
57
+ avatar: '😀',
58
+ },
59
+ },
60
+ {
61
+ id: 'msg3',
62
+ content: 'Function Message',
63
+ role: 'function',
64
+ meta: {
65
+ avatar: '🧩',
66
+ title: 'plugin-unknown',
67
+ },
68
+ plugin: {
69
+ arguments: ['arg1', 'arg2'],
70
+ identifier: 'func1',
71
+ type: 'pluginType',
72
+ },
73
+ },
74
+ ] as ChatMessage[];
75
+
76
+ const mockChatStore = { messages: mockMessages } as ChatStore;
77
+
78
+ describe('chatSelectors', () => {
79
+ describe('getMessageById', () => {
80
+ it('should return undefined if the message with the given id does not exist', () => {
81
+ const message = chatSelectors.getMessageById('non-existent-id')(initialStore);
82
+ expect(message).toBeUndefined();
83
+ });
84
+
85
+ it('should return the message object with the matching id', () => {
86
+ const state = merge(initialStore, { messages: mockMessages });
87
+ const message = chatSelectors.getMessageById('msg1')(state);
88
+ expect(message).toEqual(mockMessages[0]);
89
+ });
90
+
91
+ it('should return the message with the matching id', () => {
92
+ const message = chatSelectors.getMessageById('msg1')(mockChatStore);
93
+ expect(message).toEqual(mockMessages[0]);
94
+ });
95
+
96
+ it('should return undefined if no message matches the id', () => {
97
+ const message = chatSelectors.getMessageById('nonexistent')(mockChatStore);
98
+ expect(message).toBeUndefined();
99
+ });
100
+ });
101
+
102
+ describe('getFunctionMessageProps', () => {
103
+ it('should return the properties of a function message', () => {
104
+ const state = merge(initialStore, {
105
+ messages: mockMessages,
106
+ chatLoadingId: 'msg3', // Assuming this id represents a loading state
107
+ });
108
+ const props = chatSelectors.getFunctionMessageProps(mockMessages[2])(state);
109
+ expect(props).toEqual({
110
+ arguments: ['arg1', 'arg2'],
111
+ command: mockMessages[2].plugin,
112
+ content: 'Function Message',
113
+ id: 'func1',
114
+ loading: true,
115
+ type: 'pluginType',
116
+ });
117
+ });
118
+
119
+ it('should return loading as false if the message id is not the current loading id', () => {
120
+ const state = merge(initialStore, { messages: mockMessages, chatLoadingId: 'msg1' });
121
+ const props = chatSelectors.getFunctionMessageProps(mockMessages[2])(state);
122
+ expect(props.loading).toBe(false);
123
+ });
124
+
125
+ it('should return correct properties when no plugin is present', () => {
126
+ const messageWithoutPlugin = {
127
+ id: 'msg4',
128
+ content: 'No Plugin Message',
129
+ role: 'function',
130
+ // No plugin property
131
+ };
132
+ const state = merge(initialStore, {
133
+ messages: [...mockMessages, messageWithoutPlugin],
134
+ chatLoadingId: 'msg1',
135
+ });
136
+ const props = chatSelectors.getFunctionMessageProps(messageWithoutPlugin)(state);
137
+ expect(props).toEqual({
138
+ arguments: undefined,
139
+ command: undefined,
140
+ content: 'No Plugin Message',
141
+ id: undefined,
142
+ loading: false,
143
+ type: undefined,
144
+ });
145
+ });
146
+ });
147
+
148
+ describe('currentChatsWithHistoryConfig', () => {
149
+ it('should slice the messages according to the current agent config', () => {
150
+ const state = merge(initialStore, { messages: mockMessages });
151
+
152
+ const chats = chatSelectors.currentChatsWithHistoryConfig(state);
153
+ expect(chats).toHaveLength(3);
154
+ expect(chats).toEqual(mockedChats);
155
+ });
156
+ it('should slice the messages according to config, assuming historyCount is mocked to 2', async () => {
157
+ const state = merge(initialStore, { messages: mockMessages });
158
+
159
+ useSessionStore.getState().updateAgentConfig({ historyCount: 2, enableHistoryCount: true });
160
+
161
+ const chats = chatSelectors.currentChatsWithHistoryConfig(state);
162
+
163
+ expect(chats).toHaveLength(2);
164
+ expect(chats).toEqual([
165
+ {
166
+ id: 'msg2',
167
+ content: 'Goodbye World',
168
+ role: 'user',
169
+ meta: {
170
+ avatar: '😀',
171
+ },
172
+ },
173
+ {
174
+ id: 'msg3',
175
+ content: 'Function Message',
176
+ role: 'function',
177
+ meta: {
178
+ avatar: '🧩',
179
+ title: 'plugin-unknown',
180
+ },
181
+ plugin: {
182
+ arguments: ['arg1', 'arg2'],
183
+ identifier: 'func1',
184
+ type: 'pluginType',
185
+ },
186
+ },
187
+ ]);
188
+ });
189
+ });
190
+
191
+ describe('currentChatsWithGuideMessage', () => {
192
+ it('should return existing messages if there are any', () => {
193
+ const state = merge(initialStore, { messages: mockMessages, activeId: 'someActiveId' });
194
+ const chats = chatSelectors.currentChatsWithGuideMessage({} as MetaData)(state);
195
+ expect(chats).toEqual(mockedChats);
196
+ });
197
+
198
+ it('should add a guide message if the chat is brand new', () => {
199
+ const state = merge(initialStore, { messages: [], activeId: 'someActiveId' });
200
+ const metaData = { title: 'Mock Agent', description: 'Mock Description' };
201
+
202
+ const chats = chatSelectors.currentChatsWithGuideMessage(metaData)(state);
203
+
204
+ expect(chats).toHaveLength(1);
205
+ expect(chats[0].content).toBeDefined();
206
+ expect(chats[0].meta.avatar).toEqual(DEFAULT_INBOX_AVATAR);
207
+ expect(chats[0].meta).toEqual(expect.objectContaining(metaData));
208
+ });
209
+
210
+ it('should use inbox message for INBOX_SESSION_ID', () => {
211
+ const state = merge(initialStore, { messages: [], activeId: INBOX_SESSION_ID });
212
+ const metaData = { title: 'Mock Agent', description: 'Mock Description' };
213
+
214
+ const chats = chatSelectors.currentChatsWithGuideMessage(metaData)(state);
215
+
216
+ expect(chats[0].content).toEqual('inbox.defaultMessage'); // Assuming translation returns a string containing this
217
+ });
218
+
219
+ it('should use agent default message for non-inbox sessions', () => {
220
+ const state = merge(initialStore, { messages: [], activeId: 'someActiveId' });
221
+ const metaData = { title: 'Mock Agent' };
222
+
223
+ const chats = chatSelectors.currentChatsWithGuideMessage(metaData)(state);
224
+
225
+ expect(chats[0].content).toMatch('agentDefaultMessage'); // Assuming translation returns a string containing this
226
+ });
227
+ });
228
+
229
+ describe('chatsMessageString', () => {
230
+ it('should concatenate the contents of all messages returned by currentChatsWithHistoryConfig', () => {
231
+ // Prepare a state with a few messages
232
+ const state = merge(initialStore, {
233
+ messages: mockMessages,
234
+ activeId: 'active-session',
235
+ });
236
+
237
+ // Assume that the currentChatsWithHistoryConfig will return the last two messages
238
+ const expectedString = mockMessages
239
+ .slice(-2)
240
+ .map((m) => m.content)
241
+ .join('');
242
+
243
+ // Call the selector and verify the result
244
+ const concatenatedString = chatSelectors.chatsMessageString(state);
245
+ expect(concatenatedString).toBe(expectedString);
246
+
247
+ // Restore the mocks after the test
248
+ vi.restoreAllMocks();
249
+ });
250
+ });
251
+ });
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
- import { ChatStore, ChatStoreState } from '@/store/chat';
3
+ import { ChatStore } from '@/store/chat';
4
4
  import { initialState } from '@/store/chat/initialState';
5
5
  import { merge } from '@/utils/merge';
6
6
 
@@ -3,13 +3,14 @@ import { t } from 'i18next';
3
3
 
4
4
  import { DEFAULT_INBOX_AVATAR, DEFAULT_USER_AVATAR } from '@/const/meta';
5
5
  import { INBOX_SESSION_ID } from '@/const/session';
6
- import { chatHelpers } from '@/store/chat/helpers';
7
6
  import { useGlobalStore } from '@/store/global';
8
7
  import { useSessionStore } from '@/store/session';
9
8
  import { agentSelectors } from '@/store/session/selectors';
10
9
  import { ChatMessage } from '@/types/chatMessage';
10
+ import { MetaData } from '@/types/meta';
11
11
  import { merge } from '@/utils/merge';
12
12
 
13
+ import { chatHelpers } from '../helpers';
13
14
  import type { ChatStore } from '../store';
14
15
 
15
16
  const getMeta = (message: ChatMessage) => {
@@ -47,40 +48,41 @@ const currentChats = (s: ChatStore): ChatMessage[] => {
47
48
 
48
49
  const initTime = Date.now();
49
50
  // 针对新助手添加初始化时的自定义消息
50
- const currentChatsWithGuideMessage = (s: ChatStore): ChatMessage[] => {
51
- const data = currentChats(s);
52
-
53
- const isBrandNewChat = data.length === 0;
54
-
55
- if (!isBrandNewChat) return data;
56
-
57
- const [activeId, isInbox] = [s.activeId, s.activeId === INBOX_SESSION_ID];
58
- const meta = agentSelectors.currentAgentMeta(useSessionStore.getState());
59
-
60
- const inboxMsg = t('inbox.defaultMessage', { ns: 'chat' });
61
- const agentSystemRoleMsg = t('agentDefaultMessageWithSystemRole', {
62
- name: meta.title || t('defaultAgent'),
63
- ns: 'chat',
64
- systemRole: meta.description,
65
- });
66
- const agentMsg = t('agentDefaultMessage', {
67
- id: activeId,
68
- name: meta.title || t('defaultAgent'),
69
- ns: 'chat',
70
- });
71
-
72
- const emptyInboxGuideMessage = {
73
- content: isInbox ? inboxMsg : !!meta.description ? agentSystemRoleMsg : agentMsg,
74
- createdAt: initTime,
75
- extra: {},
76
- id: 'default',
77
- meta: merge({ avatar: DEFAULT_INBOX_AVATAR }, meta),
78
- role: 'assistant',
79
- updatedAt: initTime,
80
- } as ChatMessage;
81
-
82
- return [emptyInboxGuideMessage];
83
- };
51
+ const currentChatsWithGuideMessage =
52
+ (meta: MetaData) =>
53
+ (s: ChatStore): ChatMessage[] => {
54
+ const data = currentChats(s);
55
+
56
+ const isBrandNewChat = data.length === 0;
57
+
58
+ if (!isBrandNewChat) return data;
59
+
60
+ const [activeId, isInbox] = [s.activeId, s.activeId === INBOX_SESSION_ID];
61
+
62
+ const inboxMsg = t('inbox.defaultMessage', { ns: 'chat' });
63
+ const agentSystemRoleMsg = t('agentDefaultMessageWithSystemRole', {
64
+ name: meta.title || t('defaultAgent'),
65
+ ns: 'chat',
66
+ systemRole: meta.description,
67
+ });
68
+ const agentMsg = t('agentDefaultMessage', {
69
+ id: activeId,
70
+ name: meta.title || t('defaultAgent'),
71
+ ns: 'chat',
72
+ });
73
+
74
+ const emptyInboxGuideMessage = {
75
+ content: isInbox ? inboxMsg : !!meta.description ? agentSystemRoleMsg : agentMsg,
76
+ createdAt: initTime,
77
+ extra: {},
78
+ id: 'default',
79
+ meta: merge({ avatar: DEFAULT_INBOX_AVATAR }, meta),
80
+ role: 'assistant',
81
+ updatedAt: initTime,
82
+ } as ChatMessage;
83
+
84
+ return [emptyInboxGuideMessage];
85
+ };
84
86
 
85
87
  const currentChatsWithHistoryConfig = (s: ChatStore): ChatMessage[] => {
86
88
  const chats = currentChats(s);
@@ -105,9 +107,7 @@ const getFunctionMessageProps =
105
107
  type: plugin?.type as LobePluginType,
106
108
  });
107
109
 
108
- const getMessageById = (id: string) => (s: ChatStore) => {
109
- return s.messages.find((m) => m.id === id);
110
- };
110
+ const getMessageById = (id: string) => (s: ChatStore) => chatHelpers.getMessageById(s.messages, id);
111
111
 
112
112
  export const chatSelectors = {
113
113
  chatsMessageString,
@@ -8,7 +8,7 @@ import { setNamespace } from '@/utils/storeDebug';
8
8
 
9
9
  import { FileStore } from '../../store';
10
10
 
11
- const t = setNamespace('image');
11
+ const n = setNamespace('image');
12
12
 
13
13
  export interface FileAction {
14
14
  clearImageList: () => void;
@@ -28,7 +28,7 @@ export const createFileSlice: StateCreator<
28
28
  FileAction
29
29
  > = (set, get) => ({
30
30
  clearImageList: () => {
31
- set({ inputFilesList: [] }, false, t('clearImageList'));
31
+ set({ inputFilesList: [] }, false, n('clearImageList'));
32
32
  },
33
33
  removeAllFiles: async () => {
34
34
  await fileService.removeAllFiles();
@@ -39,7 +39,7 @@ export const createFileSlice: StateCreator<
39
39
  set(
40
40
  ({ inputFilesList }) => ({ inputFilesList: inputFilesList.filter((i) => i !== id) }),
41
41
  false,
42
- t('removeFile'),
42
+ n('removeFile'),
43
43
  );
44
44
  },
45
45
  setImageMapItem: (id, item) => {
@@ -50,7 +50,7 @@ export const createFileSlice: StateCreator<
50
50
  draft.imagesMap[id] = item;
51
51
  }),
52
52
  false,
53
- t('setImageMapItem'),
53
+ n('setImageMapItem'),
54
54
  );
55
55
  },
56
56
  uploadFile: async (file) => {
@@ -67,7 +67,7 @@ export const createFileSlice: StateCreator<
67
67
  set(
68
68
  ({ inputFilesList }) => ({ inputFilesList: [...inputFilesList, data.id] }),
69
69
  false,
70
- t('uploadFile'),
70
+ n('uploadFile'),
71
71
  );
72
72
  } catch (error) {
73
73
  // 提示用户上传失败
@@ -9,7 +9,7 @@ import { setNamespace } from '@/utils/storeDebug';
9
9
 
10
10
  import type { GlobalStore } from '../store';
11
11
 
12
- const t = setNamespace('settings');
12
+ const n = setNamespace('settings');
13
13
 
14
14
  /**
15
15
  * 设置操作
@@ -29,6 +29,6 @@ export const createAgentSlice: StateCreator<
29
29
  draft.defaultAgent = merge(draft.defaultAgent, agent);
30
30
  });
31
31
 
32
- set({ settings }, false, t('updateDefaultAgent', agent));
32
+ set({ settings }, false, n('updateDefaultAgent', agent));
33
33
  },
34
34
  });
@@ -11,7 +11,7 @@ import { setNamespace } from '@/utils/storeDebug';
11
11
  import type { GlobalPreference, GlobalState, Guide, SidebarTabKey } from '../initialState';
12
12
  import type { GlobalStore } from '../store';
13
13
 
14
- const t = setNamespace('settings');
14
+ const n = setNamespace('settings');
15
15
 
16
16
  /**
17
17
  * 设置操作
@@ -37,25 +37,25 @@ export const createCommonSlice: StateCreator<
37
37
  CommonAction
38
38
  > = (set, get) => ({
39
39
  switchSideBar: (key) => {
40
- set({ sidebarKey: key }, false, t('switchSideBar', key));
40
+ set({ sidebarKey: key }, false, n('switchSideBar', key));
41
41
  },
42
42
  toggleChatSideBar: (newValue) => {
43
43
  const showChatSideBar =
44
44
  typeof newValue === 'boolean' ? newValue : !get().preference.showChatSideBar;
45
45
 
46
- get().updatePreference({ showChatSideBar }, t('toggleAgentPanel', newValue) as string);
46
+ get().updatePreference({ showChatSideBar }, n('toggleAgentPanel', newValue) as string);
47
47
  },
48
48
  toggleMobileTopic: (newValue) => {
49
49
  const mobileShowTopic =
50
50
  typeof newValue === 'boolean' ? newValue : !get().preference.mobileShowTopic;
51
51
 
52
- get().updatePreference({ mobileShowTopic }, t('toggleMobileTopic', newValue) as string);
52
+ get().updatePreference({ mobileShowTopic }, n('toggleMobileTopic', newValue) as string);
53
53
  },
54
54
  toggleSystemRole: (newValue) => {
55
55
  const showSystemRole =
56
56
  typeof newValue === 'boolean' ? newValue : !get().preference.mobileShowTopic;
57
57
 
58
- get().updatePreference({ showSystemRole }, t('toggleMobileTopic', newValue) as string);
58
+ get().updatePreference({ showSystemRole }, n('toggleMobileTopic', newValue) as string);
59
59
  },
60
60
  updateGuideState: (guide) => {
61
61
  const { updatePreference } = get();
@@ -75,7 +75,7 @@ export const createCommonSlice: StateCreator<
75
75
  useSWR('checkLatestVersion', featLatestVersion, {
76
76
  onSuccess: (data: string) => {
77
77
  if (gt(data, CURRENT_VERSION))
78
- set({ hasNewVersion: true, latestVersion: data }, false, t('checkLatestVersion'));
78
+ set({ hasNewVersion: true, latestVersion: data }, false, n('checkLatestVersion'));
79
79
  },
80
80
  }),
81
81
  });
@@ -12,7 +12,7 @@ import { setNamespace } from '@/utils/storeDebug';
12
12
 
13
13
  import type { GlobalStore } from '../store';
14
14
 
15
- const t = setNamespace('settings');
15
+ const n = setNamespace('settings');
16
16
 
17
17
  /**
18
18
  * 设置操作
@@ -58,10 +58,10 @@ export const createSettingsSlice: StateCreator<
58
58
  const settings = produce(get().settings, (draft: GlobalSettings) => {
59
59
  draft.defaultAgent = DEFAULT_AGENT;
60
60
  });
61
- set({ settings }, false, t('resetDefaultAgent'));
61
+ set({ settings }, false, n('resetDefaultAgent'));
62
62
  },
63
63
  resetSettings: () => {
64
- set({ settings: DEFAULT_SETTINGS }, false, t('resetSettings'));
64
+ set({ settings: DEFAULT_SETTINGS }, false, n('resetSettings'));
65
65
  },
66
66
  setOpenAIConfig: (config) => {
67
67
  get().setSettings({ languageModel: { openAI: config } });
@@ -73,7 +73,7 @@ export const createSettingsSlice: StateCreator<
73
73
 
74
74
  if (isEqual(prevSetting, nextSettings)) return;
75
75
 
76
- set({ settings: merge(prevSetting, settings) }, false, t('setSettings', settings));
76
+ set({ settings: merge(prevSetting, settings) }, false, n('setSettings', settings));
77
77
  },
78
78
  switchSettingTabs: (tab) => {
79
79
  set({ settingsTab: tab });
@@ -8,7 +8,7 @@ import { PluginStore } from '../../store';
8
8
  import { defaultCustomPlugin } from './initialState';
9
9
  import { CustomPluginListDispatch, devPluginListReducer } from './reducers/customPluginList';
10
10
 
11
- const t = setNamespace('customPlugin');
11
+ const n = setNamespace('customPlugin');
12
12
 
13
13
  /**
14
14
  * 代理行为接口
@@ -40,11 +40,11 @@ export const createCustomPluginSlice: StateCreator<
40
40
  const { customPluginList } = get();
41
41
 
42
42
  const nextList = devPluginListReducer(customPluginList, payload);
43
- set({ customPluginList: nextList }, false, t('dispatchCustomPluginList', payload));
43
+ set({ customPluginList: nextList }, false, n('dispatchCustomPluginList', payload));
44
44
  },
45
45
  saveToCustomPluginList: (value) => {
46
46
  get().dispatchCustomPluginList({ plugin: value, type: 'addItem' });
47
- set({ newCustomPlugin: defaultCustomPlugin }, false, t('saveToCustomPluginList'));
47
+ set({ newCustomPlugin: defaultCustomPlugin }, false, n('saveToCustomPluginList'));
48
48
  },
49
49
  updateCustomPlugin: (id, value) => {
50
50
  const { dispatchCustomPluginList, installPlugin } = get();
@@ -58,7 +58,7 @@ export const createCustomPluginSlice: StateCreator<
58
58
  set(
59
59
  { newCustomPlugin: merge({}, get().newCustomPlugin, newCustomPlugin) },
60
60
  false,
61
- t('updateNewDevPlugin'),
61
+ n('updateNewDevPlugin'),
62
62
  );
63
63
  },
64
64
  });
@@ -17,7 +17,7 @@ import { setNamespace } from '@/utils/storeDebug';
17
17
  import { PluginStore } from '../../store';
18
18
  import { PluginDispatch, pluginManifestReducer } from './reducers/manifest';
19
19
 
20
- const t = setNamespace('plugin');
20
+ const n = setNamespace('plugin');
21
21
 
22
22
  /**
23
23
  * 插件接口
@@ -73,7 +73,7 @@ export const createPluginSlice: StateCreator<
73
73
 
74
74
  await installPlugins(plugins);
75
75
 
76
- set({ manifestPrepared: true }, false, t('checkPluginsIsInstalled'));
76
+ set({ manifestPrepared: true }, false, n('checkPluginsIsInstalled'));
77
77
  },
78
78
  deletePluginSettings: (id) => {
79
79
  set(
@@ -81,14 +81,14 @@ export const createPluginSlice: StateCreator<
81
81
  draft.pluginsSettings[id] = undefined;
82
82
  }),
83
83
  false,
84
- t('deletePluginSettings'),
84
+ n('deletePluginSettings'),
85
85
  );
86
86
  },
87
87
  dispatchPluginManifest: (payload) => {
88
88
  const { pluginManifestMap } = get();
89
89
  const nextManifest = pluginManifestReducer(pluginManifestMap, payload);
90
90
 
91
- set({ pluginManifestMap: nextManifest }, false, t('dispatchPluginManifest', payload));
91
+ set({ pluginManifestMap: nextManifest }, false, n('dispatchPluginManifest', payload));
92
92
  },
93
93
  installPlugin: async (name) => {
94
94
  const plugin = pluginSelectors.getPluginMetaById(name)(get());
@@ -140,12 +140,12 @@ export const createPluginSlice: StateCreator<
140
140
  loadPluginStore: async () => {
141
141
  const pluginMarketIndex = await pluginService.getPluginList();
142
142
 
143
- set({ pluginList: pluginMarketIndex.plugins }, false, t('loadPluginList'));
143
+ set({ pluginList: pluginMarketIndex.plugins }, false, n('loadPluginList'));
144
144
 
145
145
  return pluginMarketIndex;
146
146
  },
147
147
  resetPluginSettings: () => {
148
- set({ pluginsSettings: {} }, false, t('resetPluginSettings'));
148
+ set({ pluginsSettings: {} }, false, n('resetPluginSettings'));
149
149
  },
150
150
  updateManifestLoadingState: (key, value) => {
151
151
  set(
@@ -153,7 +153,7 @@ export const createPluginSlice: StateCreator<
153
153
  draft.pluginManifestLoading[key] = value;
154
154
  }),
155
155
  false,
156
- t('updateManifestLoadingState'),
156
+ n('updateManifestLoadingState'),
157
157
  );
158
158
  },
159
159
  updatePluginSettings: (id, settings) => {
@@ -162,7 +162,7 @@ export const createPluginSlice: StateCreator<
162
162
  draft.pluginsSettings[id] = merge({}, draft.pluginsSettings[id], settings);
163
163
  }),
164
164
  false,
165
- t('updatePluginSettings'),
165
+ n('updatePluginSettings'),
166
166
  );
167
167
  },
168
168
 
@@ -18,7 +18,7 @@ import { setNamespace } from '@/utils/storeDebug';
18
18
 
19
19
  import { initLobeSession } from './initialState';
20
20
 
21
- const t = setNamespace('session');
21
+ const n = setNamespace('session');
22
22
 
23
23
  const FETCH_SESSIONS_KEY = 'fetchSessions';
24
24
 
@@ -80,7 +80,7 @@ export const createSessionSlice: StateCreator<
80
80
  SessionAction
81
81
  > = (set, get) => ({
82
82
  activeSession: (sessionId) => {
83
- set({ activeId: sessionId }, false, t('activeSession'));
83
+ set({ activeId: sessionId }, false, n('activeSession'));
84
84
  },
85
85
 
86
86
  clearSessions: async () => {
@@ -153,7 +153,7 @@ export const createSessionSlice: StateCreator<
153
153
  sessions: data,
154
154
  },
155
155
  false,
156
- t('useFetchSessions/onSuccess', data),
156
+ n('useFetchSessions/onSuccess', data),
157
157
  );
158
158
  },
159
159
  }),
@@ -161,7 +161,7 @@ export const createSessionSlice: StateCreator<
161
161
  useSearchSessions: (keyword) =>
162
162
  useSWR<LobeSessions>(keyword, sessionService.searchSessions, {
163
163
  onSuccess: (data) => {
164
- set({ searchSessions: data }, false, t('useSearchSessions(success)', data));
164
+ set({ searchSessions: data }, false, n('useSearchSessions(success)', data));
165
165
  },
166
166
  revalidateOnFocus: false,
167
167
  }),