@lobehub/chat 1.37.0 → 1.37.2

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 +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/common.json +2 -2
  4. package/package.json +1 -1
  5. package/src/services/file/_deprecated.test.ts +119 -0
  6. package/src/services/file/{pglite.ts → _deprecated.ts} +28 -32
  7. package/src/services/file/client.test.ts +153 -74
  8. package/src/services/file/client.ts +32 -28
  9. package/src/services/file/index.ts +2 -2
  10. package/src/services/import/_deprecated.ts +74 -0
  11. package/src/services/import/{pglite.test.ts → client.test.ts} +1 -1
  12. package/src/services/import/client.ts +21 -61
  13. package/src/services/import/index.ts +2 -2
  14. package/src/services/message/_deprecated.test.ts +398 -0
  15. package/src/services/message/_deprecated.ts +121 -0
  16. package/src/services/message/client.test.ts +191 -159
  17. package/src/services/message/client.ts +47 -50
  18. package/src/services/message/index.ts +2 -2
  19. package/src/services/plugin/_deprecated.test.ts +162 -0
  20. package/src/services/plugin/_deprecated.ts +42 -0
  21. package/src/services/plugin/client.test.ts +68 -55
  22. package/src/services/plugin/client.ts +20 -11
  23. package/src/services/plugin/index.ts +2 -2
  24. package/src/services/session/_deprecated.test.ts +440 -0
  25. package/src/services/session/_deprecated.ts +183 -0
  26. package/src/services/session/client.test.ts +212 -241
  27. package/src/services/session/client.ts +61 -60
  28. package/src/services/session/index.ts +2 -2
  29. package/src/services/topic/{client.test.ts → _deprecated.test.ts} +1 -1
  30. package/src/services/topic/_deprecated.ts +70 -0
  31. package/src/services/topic/client.ts +40 -25
  32. package/src/services/topic/index.ts +2 -2
  33. package/src/services/topic/pglite.test.ts +1 -1
  34. package/src/services/user/{pglite.test.ts → _deprecated.test.ts} +32 -29
  35. package/src/services/user/_deprecated.ts +57 -0
  36. package/src/services/user/client.test.ts +28 -31
  37. package/src/services/user/client.ts +51 -16
  38. package/src/services/user/index.ts +2 -2
  39. package/src/store/chat/slices/builtinTool/action.test.ts +1 -1
  40. package/src/store/user/slices/common/action.test.ts +1 -1
  41. package/src/services/file/pglite.test.ts +0 -198
  42. package/src/services/import/pglite.ts +0 -34
  43. package/src/services/message/pglite.test.ts +0 -430
  44. package/src/services/message/pglite.ts +0 -118
  45. package/src/services/plugin/pglite.test.ts +0 -175
  46. package/src/services/plugin/pglite.ts +0 -51
  47. package/src/services/session/pglite.test.ts +0 -411
  48. package/src/services/session/pglite.ts +0 -184
  49. package/src/services/topic/pglite.ts +0 -85
  50. package/src/services/user/pglite.ts +0 -92
@@ -1,24 +1,31 @@
1
- import { FileModel } from '@/database/_deprecated/models/file';
1
+ import { clientDB } from '@/database/client/db';
2
+ import { FileModel } from '@/database/server/models/file';
3
+ import { BaseClientService } from '@/services/baseClientService';
2
4
  import { clientS3Storage } from '@/services/file/ClientS3';
3
5
  import { FileItem, UploadFileParams } from '@/types/files';
4
6
 
5
7
  import { IFileService } from './type';
6
8
 
7
- export class ClientService implements IFileService {
9
+ export class ClientService extends BaseClientService implements IFileService {
10
+ private get fileModel(): FileModel {
11
+ return new FileModel(clientDB as any, this.userId);
12
+ }
13
+
8
14
  async createFile(file: UploadFileParams) {
9
15
  // save to local storage
10
16
  // we may want to save to a remote server later
11
- const res = await FileModel.create({
12
- createdAt: Date.now(),
13
- data: undefined,
14
- fileHash: file.hash,
15
- fileType: file.fileType,
16
- metadata: file.metadata,
17
- name: file.name,
18
- saveMode: 'url',
19
- size: file.size,
20
- url: file.url,
21
- } as any);
17
+ const res = await this.fileModel.create(
18
+ {
19
+ fileHash: file.hash,
20
+ fileType: file.fileType,
21
+ knowledgeBaseId: file.knowledgeBaseId,
22
+ metadata: file.metadata,
23
+ name: file.name,
24
+ size: file.size,
25
+ url: file.url!,
26
+ },
27
+ true,
28
+ );
22
29
 
23
30
  // get file to base64 url
24
31
  const base64 = await this.getBase64ByFileHash(file.hash!);
@@ -29,24 +36,17 @@ export class ClientService implements IFileService {
29
36
  };
30
37
  }
31
38
 
32
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
33
- async checkFileHash(_hash: string) {
34
- return { isExist: false, metadata: {} };
35
- }
36
-
37
39
  async getFile(id: string): Promise<FileItem> {
38
- const item = await FileModel.findById(id);
40
+ const item = await this.fileModel.findById(id);
39
41
  if (!item) {
40
42
  throw new Error('file not found');
41
43
  }
42
44
 
43
- // arrayBuffer to blob or base64 to blob
44
- const blob = !!item.data
45
- ? new Blob([item.data!], { type: item.fileType })
46
- : // @ts-ignore
47
- new Blob([Buffer.from(item.base64!, 'base64')], { type: item.fileType });
45
+ // arrayBuffer to url
46
+ const fileItem = await clientS3Storage.getObject(item.fileHash!);
47
+ if (!fileItem) throw new Error('file not found');
48
48
 
49
- const url = URL.createObjectURL(blob);
49
+ const url = URL.createObjectURL(fileItem);
50
50
 
51
51
  return {
52
52
  createdAt: new Date(item.createdAt),
@@ -60,15 +60,19 @@ export class ClientService implements IFileService {
60
60
  }
61
61
 
62
62
  async removeFile(id: string) {
63
- return FileModel.delete(id);
63
+ await this.fileModel.delete(id, false);
64
64
  }
65
65
 
66
66
  async removeFiles(ids: string[]) {
67
- await Promise.all(ids.map((id) => FileModel.delete(id)));
67
+ await this.fileModel.deleteMany(ids, false);
68
68
  }
69
69
 
70
70
  async removeAllFiles() {
71
- return FileModel.clear();
71
+ return this.fileModel.clear();
72
+ }
73
+
74
+ async checkFileHash(hash: string) {
75
+ return this.fileModel.checkHash(hash);
72
76
  }
73
77
 
74
78
  private async getBase64ByFileHash(hash: string) {
@@ -1,5 +1,5 @@
1
- import { ClientService as DeprecatedService } from './client';
2
- import { ClientService } from './pglite';
1
+ import { ClientService as DeprecatedService } from './_deprecated';
2
+ import { ClientService } from './client';
3
3
  import { ServerService } from './server';
4
4
 
5
5
  const clientService =
@@ -0,0 +1,74 @@
1
+ import { MessageModel } from '@/database/_deprecated/models/message';
2
+ import { SessionModel } from '@/database/_deprecated/models/session';
3
+ import { SessionGroupModel } from '@/database/_deprecated/models/sessionGroup';
4
+ import { TopicModel } from '@/database/_deprecated/models/topic';
5
+ import { ImportResult, ImportResults } from '@/services/config';
6
+ import { useUserStore } from '@/store/user';
7
+ import { ImportStage, ImporterEntryData, OnImportCallbacks } from '@/types/importer';
8
+ import { UserSettings } from '@/types/user/settings';
9
+
10
+ export class ClientService {
11
+ importSettings = async (settings: UserSettings) => {
12
+ await useUserStore.getState().importAppSettings(settings);
13
+ };
14
+
15
+ importData = async (
16
+ config: ImporterEntryData,
17
+ callbacks?: OnImportCallbacks,
18
+ ): Promise<ImportResults> => {
19
+ callbacks?.onStageChange?.(ImportStage.Importing);
20
+ const time = Date.now();
21
+
22
+ const { messages = [], sessionGroups = [], sessions = [], topics = [] } = config;
23
+
24
+ let messageResult: ImportResult | undefined;
25
+ let sessionResult: ImportResult | undefined;
26
+ let sessionGroupResult: ImportResult | undefined;
27
+ let topicResult: ImportResult | undefined;
28
+
29
+ if (messages.length > 0) {
30
+ const res = await MessageModel.batchCreate(messages as any);
31
+ messageResult = this.mapImportResult(res);
32
+ }
33
+
34
+ if (sessionGroups.length > 0) {
35
+ const res = await SessionGroupModel.batchCreate(sessionGroups as any);
36
+ sessionGroupResult = this.mapImportResult(res);
37
+ }
38
+
39
+ if (topics.length > 0) {
40
+ const res = await TopicModel.batchCreate(topics as any);
41
+ topicResult = this.mapImportResult(res);
42
+ }
43
+
44
+ if (sessions.length > 0) {
45
+ const data = await SessionModel.batchCreate(sessions as any);
46
+ sessionResult = this.mapImportResult(data);
47
+ }
48
+
49
+ const result = {
50
+ messages: messageResult,
51
+ sessionGroups: sessionGroupResult,
52
+ sessions: sessionResult,
53
+ topics: topicResult,
54
+ };
55
+
56
+ const duration = Date.now() - time;
57
+ callbacks?.onStageChange?.(ImportStage.Success);
58
+ callbacks?.onSuccess?.(result, duration);
59
+
60
+ return result;
61
+ };
62
+
63
+ private mapImportResult = (input: {
64
+ added: number;
65
+ errors?: Error[];
66
+ skips: string[];
67
+ }): ImportResult => {
68
+ return {
69
+ added: input.added,
70
+ errors: input.errors?.length || 0,
71
+ skips: input.skips.length,
72
+ };
73
+ };
74
+ }
@@ -15,7 +15,7 @@ import {
15
15
  import { CURRENT_CONFIG_VERSION } from '@/migrations';
16
16
  import { ImportResults, ImporterEntryData } from '@/types/importer';
17
17
 
18
- import { ClientService } from './pglite';
18
+ import { ClientService } from './client';
19
19
 
20
20
  const userId = 'test-user-id';
21
21
  const service = new ClientService(userId);
@@ -1,74 +1,34 @@
1
- import { MessageModel } from '@/database/_deprecated/models/message';
2
- import { SessionModel } from '@/database/_deprecated/models/session';
3
- import { SessionGroupModel } from '@/database/_deprecated/models/sessionGroup';
4
- import { TopicModel } from '@/database/_deprecated/models/topic';
5
- import { ImportResult, ImportResults } from '@/services/config';
1
+ import { clientDB } from '@/database/client/db';
2
+ import { DataImporterRepos } from '@/database/repositories/dataImporter';
3
+ import { BaseClientService } from '@/services/baseClientService';
6
4
  import { useUserStore } from '@/store/user';
7
5
  import { ImportStage, ImporterEntryData, OnImportCallbacks } from '@/types/importer';
8
6
  import { UserSettings } from '@/types/user/settings';
9
7
 
10
- export class ClientService {
8
+ export class ClientService extends BaseClientService {
9
+ private get dataImporter(): DataImporterRepos {
10
+ return new DataImporterRepos(clientDB as any, this.userId);
11
+ }
12
+
11
13
  importSettings = async (settings: UserSettings) => {
12
14
  await useUserStore.getState().importAppSettings(settings);
13
15
  };
14
16
 
15
- importData = async (
16
- config: ImporterEntryData,
17
- callbacks?: OnImportCallbacks,
18
- ): Promise<ImportResults> => {
17
+ importData = async (data: ImporterEntryData, callbacks?: OnImportCallbacks) => {
19
18
  callbacks?.onStageChange?.(ImportStage.Importing);
20
19
  const time = Date.now();
21
-
22
- const { messages = [], sessionGroups = [], sessions = [], topics = [] } = config;
23
-
24
- let messageResult: ImportResult | undefined;
25
- let sessionResult: ImportResult | undefined;
26
- let sessionGroupResult: ImportResult | undefined;
27
- let topicResult: ImportResult | undefined;
28
-
29
- if (messages.length > 0) {
30
- const res = await MessageModel.batchCreate(messages as any);
31
- messageResult = this.mapImportResult(res);
32
- }
33
-
34
- if (sessionGroups.length > 0) {
35
- const res = await SessionGroupModel.batchCreate(sessionGroups as any);
36
- sessionGroupResult = this.mapImportResult(res);
37
- }
38
-
39
- if (topics.length > 0) {
40
- const res = await TopicModel.batchCreate(topics as any);
41
- topicResult = this.mapImportResult(res);
20
+ try {
21
+ const result = await this.dataImporter.importData(data);
22
+ const duration = Date.now() - time;
23
+
24
+ callbacks?.onStageChange?.(ImportStage.Success);
25
+ callbacks?.onSuccess?.(result, duration);
26
+ } catch (e) {
27
+ console.error(e);
28
+ callbacks?.onStageChange?.(ImportStage.Error);
29
+ const error = e as Error;
30
+
31
+ callbacks?.onError?.({ code: 'ImportError', httpStatus: 0, message: error.message });
42
32
  }
43
-
44
- if (sessions.length > 0) {
45
- const data = await SessionModel.batchCreate(sessions as any);
46
- sessionResult = this.mapImportResult(data);
47
- }
48
-
49
- const result = {
50
- messages: messageResult,
51
- sessionGroups: sessionGroupResult,
52
- sessions: sessionResult,
53
- topics: topicResult,
54
- };
55
-
56
- const duration = Date.now() - time;
57
- callbacks?.onStageChange?.(ImportStage.Success);
58
- callbacks?.onSuccess?.(result, duration);
59
-
60
- return result;
61
- };
62
-
63
- private mapImportResult = (input: {
64
- added: number;
65
- errors?: Error[];
66
- skips: string[];
67
- }): ImportResult => {
68
- return {
69
- added: input.added,
70
- errors: input.errors?.length || 0,
71
- skips: input.skips.length,
72
- };
73
33
  };
74
34
  }
@@ -1,5 +1,5 @@
1
- import { ClientService as DeprecatedService } from './client';
2
- import { ClientService } from './pglite';
1
+ import { ClientService as DeprecatedService } from './_deprecated';
2
+ import { ClientService } from './client';
3
3
  import { ServerService } from './server';
4
4
 
5
5
  const clientService =
@@ -0,0 +1,398 @@
1
+ import dayjs from 'dayjs';
2
+ import { Mock, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { CreateMessageParams, MessageModel } from '@/database/_deprecated/models/message';
5
+ import {
6
+ ChatMessage,
7
+ ChatMessageError,
8
+ ChatPluginPayload,
9
+ ChatTTS,
10
+ ChatTranslate,
11
+ } from '@/types/message';
12
+
13
+ import { ClientService } from './_deprecated';
14
+
15
+ const messageService = new ClientService();
16
+
17
+ // Mock the MessageModel
18
+ vi.mock('@/database/_deprecated/models/message', () => {
19
+ return {
20
+ MessageModel: {
21
+ create: vi.fn(),
22
+ batchCreate: vi.fn(),
23
+ count: vi.fn(),
24
+ query: vi.fn(),
25
+ delete: vi.fn(),
26
+ bulkDelete: vi.fn(),
27
+ queryBySessionId: vi.fn(),
28
+ update: vi.fn(),
29
+ updatePlugin: vi.fn(),
30
+ batchDelete: vi.fn(),
31
+ clearTable: vi.fn(),
32
+ batchUpdate: vi.fn(),
33
+ queryAll: vi.fn(),
34
+ updatePluginState: vi.fn(),
35
+ },
36
+ };
37
+ });
38
+
39
+ describe('MessageClientService', () => {
40
+ // Mock data
41
+ const mockMessageId = 'mock-message-id';
42
+ const mockMessage = {
43
+ id: mockMessageId,
44
+ content: 'Mock message content',
45
+ sessionId: 'mock-session-id',
46
+ createdAt: 100,
47
+ updatedAt: 100,
48
+ role: 'user',
49
+ // ... other properties
50
+ } as ChatMessage;
51
+ const mockMessages = [mockMessage];
52
+
53
+ beforeEach(() => {
54
+ // Reset all mocks before running each test case
55
+ vi.resetAllMocks();
56
+ });
57
+
58
+ describe('create', () => {
59
+ it('should create a message and return its id', async () => {
60
+ // Setup
61
+ const createParams = {
62
+ content: 'New message content',
63
+ sessionId: '1',
64
+ // ... other properties
65
+ } as CreateMessageParams;
66
+ (MessageModel.create as Mock).mockResolvedValue({ id: mockMessageId });
67
+
68
+ // Execute
69
+ const messageId = await messageService.createMessage(createParams);
70
+
71
+ // Assert
72
+ expect(MessageModel.create).toHaveBeenCalledWith(createParams);
73
+ expect(messageId).toBe(mockMessageId);
74
+ });
75
+ });
76
+
77
+ describe('batchCreate', () => {
78
+ it('should batch create messages', async () => {
79
+ // Setup
80
+ (MessageModel.batchCreate as Mock).mockResolvedValue(mockMessages);
81
+
82
+ // Execute
83
+ const result = await messageService.batchCreateMessages(mockMessages);
84
+
85
+ // Assert
86
+ expect(MessageModel.batchCreate).toHaveBeenCalledWith(mockMessages);
87
+ expect(result).toBe(mockMessages);
88
+ });
89
+ });
90
+
91
+ describe('removeMessage', () => {
92
+ it('should remove a message by id', async () => {
93
+ // Setup
94
+ (MessageModel.delete as Mock).mockResolvedValue(true);
95
+
96
+ // Execute
97
+ const result = await messageService.removeMessage(mockMessageId);
98
+
99
+ // Assert
100
+ expect(MessageModel.delete).toHaveBeenCalledWith(mockMessageId);
101
+ expect(result).toBe(true);
102
+ });
103
+ });
104
+ describe('removeMessages', () => {
105
+ it('should remove a message by id', async () => {
106
+ // Setup
107
+ (MessageModel.bulkDelete as Mock).mockResolvedValue(true);
108
+
109
+ // Execute
110
+ const result = await messageService.removeMessages([mockMessageId]);
111
+
112
+ // Assert
113
+ expect(MessageModel.bulkDelete).toHaveBeenCalledWith([mockMessageId]);
114
+ expect(result).toBe(true);
115
+ });
116
+ });
117
+
118
+ describe('getMessages', () => {
119
+ it('should retrieve messages by sessionId and topicId', async () => {
120
+ // Setup
121
+ const sessionId = 'session-id';
122
+ const topicId = 'topic-id';
123
+ (MessageModel.query as Mock).mockResolvedValue(mockMessages);
124
+
125
+ // Execute
126
+ const messages = await messageService.getMessages(sessionId, topicId);
127
+
128
+ // Assert
129
+ expect(MessageModel.query).toHaveBeenCalledWith({ sessionId, topicId });
130
+ expect(messages).toEqual(mockMessages.map((i) => ({ ...i, imageList: [] })));
131
+ });
132
+ });
133
+
134
+ describe('getAllMessagesInSession', () => {
135
+ it('should retrieve all messages in a session', async () => {
136
+ // Setup
137
+ const sessionId = 'session-id';
138
+ (MessageModel.queryBySessionId as Mock).mockResolvedValue(mockMessages);
139
+
140
+ // Execute
141
+ const messages = await messageService.getAllMessagesInSession(sessionId);
142
+
143
+ // Assert
144
+ expect(MessageModel.queryBySessionId).toHaveBeenCalledWith(sessionId);
145
+ expect(messages).toBe(mockMessages);
146
+ });
147
+ });
148
+
149
+ describe('removeMessagesByAssistant', () => {
150
+ it('should batch remove messages by assistantId and topicId', async () => {
151
+ // Setup
152
+ const assistantId = 'assistant-id';
153
+ const topicId = 'topic-id';
154
+ (MessageModel.batchDelete as Mock).mockResolvedValue(true);
155
+
156
+ // Execute
157
+ const result = await messageService.removeMessagesByAssistant(assistantId, topicId);
158
+
159
+ // Assert
160
+ expect(MessageModel.batchDelete).toHaveBeenCalledWith(assistantId, topicId);
161
+ expect(result).toBe(true);
162
+ });
163
+ });
164
+
165
+ describe('clearAllMessage', () => {
166
+ it('should clear all messages from the table', async () => {
167
+ // Setup
168
+ (MessageModel.clearTable as Mock).mockResolvedValue(true);
169
+
170
+ // Execute
171
+ const result = await messageService.removeAllMessages();
172
+
173
+ // Assert
174
+ expect(MessageModel.clearTable).toHaveBeenCalled();
175
+ expect(result).toBe(true);
176
+ });
177
+ });
178
+
179
+ describe('bindMessagesToTopic', () => {
180
+ it('should batch update messages to bind them to a topic', async () => {
181
+ // Setup
182
+ const topicId = 'topic-id';
183
+ const messageIds = [mockMessageId];
184
+ (MessageModel.batchUpdate as Mock).mockResolvedValue(mockMessages);
185
+
186
+ // Execute
187
+ const result = await messageService.bindMessagesToTopic(topicId, messageIds);
188
+
189
+ // Assert
190
+ expect(MessageModel.batchUpdate).toHaveBeenCalledWith(messageIds, { topicId });
191
+ expect(result).toBe(mockMessages);
192
+ });
193
+ });
194
+
195
+ describe('getAllMessages', () => {
196
+ it('should retrieve all messages', async () => {
197
+ // Setup
198
+ (MessageModel.queryAll as Mock).mockResolvedValue(mockMessages);
199
+
200
+ // Execute
201
+ const messages = await messageService.getAllMessages();
202
+
203
+ // Assert
204
+ expect(MessageModel.queryAll).toHaveBeenCalled();
205
+ expect(messages).toBe(mockMessages);
206
+ });
207
+ });
208
+
209
+ describe('updateMessageError', () => {
210
+ it('should update the error field of a message', async () => {
211
+ // Setup
212
+ const newError = {
213
+ type: 'InvalidProviderAPIKey',
214
+ message: 'Error occurred',
215
+ } as ChatMessageError;
216
+ (MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, error: newError });
217
+
218
+ // Execute
219
+ const result = await messageService.updateMessageError(mockMessageId, newError);
220
+
221
+ // Assert
222
+ expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { error: newError });
223
+ expect(result).toEqual({ ...mockMessage, error: newError });
224
+ });
225
+ });
226
+
227
+ // describe('updateMessagePlugin', () => {
228
+ // it('should update the plugin payload of a message', async () => {
229
+ // // Setup
230
+ // const newPlugin = {
231
+ // type: 'default',
232
+ // apiName: 'abc',
233
+ // arguments: '',
234
+ // identifier: 'plugin1',
235
+ // } as ChatPluginPayload;
236
+ //
237
+ // (MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, plugin: newPlugin });
238
+ //
239
+ // // Execute
240
+ // const result = await messageService.updateMessagePlugin(mockMessageId, newPlugin);
241
+ //
242
+ // // Assert
243
+ // expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { plugin: newPlugin });
244
+ // expect(result).toEqual({ ...mockMessage, plugin: newPlugin });
245
+ // });
246
+ // });
247
+
248
+ describe('updateMessagePluginState', () => {
249
+ it('should update the plugin state of a message', async () => {
250
+ // Setup
251
+ const key = 'stateKey';
252
+ const value = 'stateValue';
253
+ const newPluginState = { [key]: value };
254
+ (MessageModel.updatePluginState as Mock).mockResolvedValue({
255
+ ...mockMessage,
256
+ pluginState: newPluginState,
257
+ });
258
+
259
+ // Execute
260
+ const result = await messageService.updateMessagePluginState(mockMessageId, { key: value });
261
+
262
+ // Assert
263
+ expect(MessageModel.updatePluginState).toHaveBeenCalledWith(mockMessageId, { key: value });
264
+ expect(result).toEqual({ ...mockMessage, pluginState: newPluginState });
265
+ });
266
+ });
267
+
268
+ describe('updateMessagePluginArguments', () => {
269
+ it('should update the plugin arguments object of a message', async () => {
270
+ // Setup
271
+ const key = 'stateKey';
272
+ const value = 'stateValue';
273
+ (MessageModel.updatePlugin as Mock).mockResolvedValue({});
274
+
275
+ // Execute
276
+ await messageService.updateMessagePluginArguments(mockMessageId, { key: value });
277
+
278
+ // Assert
279
+ expect(MessageModel.updatePlugin).toHaveBeenCalledWith(mockMessageId, {
280
+ arguments: '{"key":"stateValue"}',
281
+ });
282
+ });
283
+ it('should update the plugin arguments string of a message', async () => {
284
+ // Setup
285
+ const key = 'stateKey';
286
+ const value = 'stateValue';
287
+ (MessageModel.updatePlugin as Mock).mockResolvedValue({});
288
+
289
+ // Execute
290
+ await messageService.updateMessagePluginArguments(
291
+ mockMessageId,
292
+ JSON.stringify({ key: value }),
293
+ );
294
+
295
+ // Assert
296
+ expect(MessageModel.updatePlugin).toHaveBeenCalledWith(mockMessageId, {
297
+ arguments: '{"key":"stateValue"}',
298
+ });
299
+ });
300
+ });
301
+
302
+ describe('countMessages', () => {
303
+ it('should count the total number of messages', async () => {
304
+ // Setup
305
+ const mockCount = 10;
306
+ (MessageModel.count as Mock).mockResolvedValue(mockCount);
307
+
308
+ // Execute
309
+ const count = await messageService.countMessages();
310
+
311
+ // Assert
312
+ expect(MessageModel.count).toHaveBeenCalled();
313
+ expect(count).toBe(mockCount);
314
+ });
315
+ });
316
+
317
+ describe('countTodayMessages', () => {
318
+ it('should count the number of messages created today', async () => {
319
+ // Setup
320
+ const today = dayjs().format('YYYY-MM-DD');
321
+ const mockMessages = [
322
+ { ...mockMessage, createdAt: today },
323
+ { ...mockMessage, createdAt: today },
324
+ { ...mockMessage, createdAt: '2023-01-01' },
325
+ ];
326
+ (MessageModel.queryAll as Mock).mockResolvedValue(mockMessages);
327
+
328
+ // Execute
329
+ const count = await messageService.countTodayMessages();
330
+
331
+ // Assert
332
+ expect(MessageModel.queryAll).toHaveBeenCalled();
333
+ expect(count).toBe(2);
334
+ });
335
+ });
336
+
337
+ describe('updateMessageTTS', () => {
338
+ it('should update the TTS field of a message', async () => {
339
+ // Setup
340
+ const newTTS: ChatTTS = {
341
+ contentMd5: 'abc',
342
+ file: 'file-abc',
343
+ };
344
+
345
+ (MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, tts: newTTS });
346
+
347
+ // Execute
348
+ const result = await messageService.updateMessageTTS(mockMessageId, newTTS);
349
+
350
+ // Assert
351
+ expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { tts: newTTS });
352
+ expect(result).toEqual({ ...mockMessage, tts: newTTS });
353
+ });
354
+ });
355
+
356
+ describe('updateMessageTranslate', () => {
357
+ it('should update the translate field of a message', async () => {
358
+ // Setup
359
+ const newTranslate: ChatTranslate = {
360
+ content: 'Translated text',
361
+ to: 'es',
362
+ };
363
+
364
+ (MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, translate: newTranslate });
365
+
366
+ // Execute
367
+ const result = await messageService.updateMessageTranslate(mockMessageId, newTranslate);
368
+
369
+ // Assert
370
+ expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { translate: newTranslate });
371
+ expect(result).toEqual({ ...mockMessage, translate: newTranslate });
372
+ });
373
+ });
374
+
375
+ describe('hasMessages', () => {
376
+ it('should return true if there are messages', async () => {
377
+ // Setup
378
+ (MessageModel.count as Mock).mockResolvedValue(1);
379
+
380
+ // Execute
381
+ const result = await messageService.hasMessages();
382
+
383
+ // Assert
384
+ expect(result).toBe(true);
385
+ });
386
+
387
+ it('should return false if there are no messages', async () => {
388
+ // Setup
389
+ (MessageModel.count as Mock).mockResolvedValue(0);
390
+
391
+ // Execute
392
+ const result = await messageService.hasMessages();
393
+
394
+ // Assert
395
+ expect(result).toBe(false);
396
+ });
397
+ });
398
+ });