@lobehub/chat 1.36.46 → 1.37.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 +25 -0
- package/README.ja-JP.md +8 -8
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/changelog/v1.json +9 -0
- package/next.config.mjs +4 -1
- package/package.json +5 -3
- package/scripts/migrateClientDB/compile-migrations.ts +14 -0
- package/src/app/(main)/(mobile)/me/(home)/layout.tsx +2 -0
- package/src/app/(main)/chat/_layout/Desktop/index.tsx +3 -2
- package/src/app/(main)/chat/_layout/Mobile.tsx +5 -3
- package/src/app/(main)/chat/features/Migration/DBReader.ts +290 -0
- package/src/app/(main)/chat/features/Migration/UpgradeButton.tsx +4 -8
- package/src/app/(main)/chat/features/Migration/index.tsx +26 -15
- package/src/app/(main)/settings/_layout/Desktop/index.tsx +2 -0
- package/src/app/loading/Client/Content.tsx +11 -1
- package/src/app/loading/Client/Error.tsx +27 -0
- package/src/app/loading/stage.ts +8 -0
- package/src/components/FullscreenLoading/index.tsx +4 -3
- package/src/const/version.ts +1 -0
- package/src/database/client/db.test.ts +172 -0
- package/src/database/client/db.ts +246 -0
- package/src/database/client/migrations.json +289 -0
- package/src/features/InitClientDB/EnableModal.tsx +111 -0
- package/src/features/InitClientDB/ErrorResult.tsx +125 -0
- package/src/features/InitClientDB/InitIndicator.tsx +124 -0
- package/src/features/InitClientDB/PGliteSVG.tsx +22 -0
- package/src/features/InitClientDB/index.tsx +37 -0
- package/src/hooks/useCheckPluginsIsInstalled.ts +2 -2
- package/src/hooks/useFetchInstalledPlugins.ts +2 -2
- package/src/hooks/useFetchMessages.ts +2 -2
- package/src/hooks/useFetchSessions.ts +2 -2
- package/src/hooks/useFetchThreads.ts +2 -2
- package/src/hooks/useFetchTopics.ts +2 -2
- package/src/layout/GlobalProvider/StoreInitialization.tsx +2 -2
- package/src/services/baseClientService/index.ts +9 -0
- package/src/services/debug.ts +32 -34
- package/src/services/file/index.ts +6 -2
- package/src/services/file/pglite.test.ts +198 -0
- package/src/services/file/pglite.ts +84 -0
- package/src/services/file/type.ts +4 -3
- package/src/services/github.ts +17 -0
- package/src/services/import/index.ts +6 -2
- package/src/services/import/pglite.test.ts +997 -0
- package/src/services/import/pglite.ts +34 -0
- package/src/services/message/client.ts +2 -0
- package/src/services/message/index.ts +6 -2
- package/src/services/message/pglite.test.ts +430 -0
- package/src/services/message/pglite.ts +118 -0
- package/src/services/message/server.ts +9 -9
- package/src/services/message/type.ts +3 -4
- package/src/services/plugin/index.ts +6 -2
- package/src/services/plugin/pglite.test.ts +175 -0
- package/src/services/plugin/pglite.ts +51 -0
- package/src/services/session/client.ts +1 -1
- package/src/services/session/index.ts +6 -2
- package/src/services/session/pglite.test.ts +411 -0
- package/src/services/session/pglite.ts +184 -0
- package/src/services/session/type.ts +14 -1
- package/src/services/topic/index.ts +6 -3
- package/src/services/topic/pglite.test.ts +212 -0
- package/src/services/topic/pglite.ts +85 -0
- package/src/services/user/client.test.ts +0 -1
- package/src/services/user/index.ts +8 -2
- package/src/services/user/pglite.test.ts +98 -0
- package/src/services/user/pglite.ts +92 -0
- package/src/store/chat/slices/builtinTool/action.test.ts +3 -4
- package/src/store/global/actions/clientDb.ts +51 -0
- package/src/store/global/initialState.ts +13 -0
- package/src/store/global/selectors.ts +24 -3
- package/src/store/global/store.ts +3 -1
- package/src/store/session/slices/sessionGroup/reducer.test.ts +6 -6
- package/src/store/user/slices/common/action.ts +2 -4
- package/src/types/clientDB.ts +29 -0
- package/src/types/importer.ts +17 -5
- package/src/types/meta.ts +0 -9
- package/src/types/session/sessionGroup.ts +3 -3
- package/src/services/message/index.test.ts +0 -48
@@ -0,0 +1,34 @@
|
|
1
|
+
import { clientDB } from '@/database/client/db';
|
2
|
+
import { DataImporterRepos } from '@/database/repositories/dataImporter';
|
3
|
+
import { BaseClientService } from '@/services/baseClientService';
|
4
|
+
import { useUserStore } from '@/store/user';
|
5
|
+
import { ImportStage, ImporterEntryData, OnImportCallbacks } from '@/types/importer';
|
6
|
+
import { UserSettings } from '@/types/user/settings';
|
7
|
+
|
8
|
+
export class ClientService extends BaseClientService {
|
9
|
+
private get dataImporter(): DataImporterRepos {
|
10
|
+
return new DataImporterRepos(clientDB as any, this.userId);
|
11
|
+
}
|
12
|
+
|
13
|
+
importSettings = async (settings: UserSettings) => {
|
14
|
+
await useUserStore.getState().importAppSettings(settings);
|
15
|
+
};
|
16
|
+
|
17
|
+
importData = async (data: ImporterEntryData, callbacks?: OnImportCallbacks) => {
|
18
|
+
callbacks?.onStageChange?.(ImportStage.Importing);
|
19
|
+
const time = Date.now();
|
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 });
|
32
|
+
}
|
33
|
+
};
|
34
|
+
}
|
@@ -21,6 +21,7 @@ export class ClientService implements IMessageService {
|
|
21
21
|
return id;
|
22
22
|
}
|
23
23
|
|
24
|
+
// @ts-ignore
|
24
25
|
async batchCreateMessages(messages: ChatMessage[]) {
|
25
26
|
return MessageModel.batchCreate(messages);
|
26
27
|
}
|
@@ -70,6 +71,7 @@ export class ClientService implements IMessageService {
|
|
70
71
|
return MessageModel.update(id, { error });
|
71
72
|
}
|
72
73
|
|
74
|
+
// @ts-ignore
|
73
75
|
async updateMessage(id: string, message: Partial<DB_Message>) {
|
74
76
|
return MessageModel.update(id, message);
|
75
77
|
}
|
@@ -1,5 +1,9 @@
|
|
1
|
-
import { ClientService } from './client';
|
1
|
+
import { ClientService as DeprecatedService } from './client';
|
2
|
+
import { ClientService } from './pglite';
|
2
3
|
import { ServerService } from './server';
|
3
4
|
|
5
|
+
const clientService =
|
6
|
+
process.env.NEXT_PUBLIC_CLIENT_DB === 'pglite' ? new ClientService() : new DeprecatedService();
|
7
|
+
|
4
8
|
export const messageService =
|
5
|
-
process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() :
|
9
|
+
process.env.NEXT_PUBLIC_SERVICE_MODE === 'server' ? new ServerService() : clientService;
|
@@ -0,0 +1,430 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
import { and, eq } from 'drizzle-orm';
|
3
|
+
import { Mock, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
4
|
+
|
5
|
+
import { MessageModel } from '@/database/_deprecated/models/message';
|
6
|
+
import { clientDB, initializeDB } from '@/database/client/db';
|
7
|
+
import {
|
8
|
+
MessageItem,
|
9
|
+
files,
|
10
|
+
messagePlugins,
|
11
|
+
messageTTS,
|
12
|
+
messageTranslates,
|
13
|
+
messages,
|
14
|
+
sessions,
|
15
|
+
topics,
|
16
|
+
users,
|
17
|
+
} from '@/database/schemas';
|
18
|
+
import {
|
19
|
+
ChatMessage,
|
20
|
+
ChatMessageError,
|
21
|
+
ChatTTS,
|
22
|
+
ChatTranslate,
|
23
|
+
CreateMessageParams,
|
24
|
+
} from '@/types/message';
|
25
|
+
|
26
|
+
import { ClientService } from './pglite';
|
27
|
+
|
28
|
+
const userId = 'message-db';
|
29
|
+
const sessionId = '1';
|
30
|
+
const topicId = 'topic-id';
|
31
|
+
|
32
|
+
// Mock data
|
33
|
+
const mockMessageId = 'mock-message-id';
|
34
|
+
const mockMessage = {
|
35
|
+
id: mockMessageId,
|
36
|
+
content: 'Mock message content',
|
37
|
+
sessionId,
|
38
|
+
role: 'user',
|
39
|
+
} as ChatMessage;
|
40
|
+
|
41
|
+
const mockMessages = [mockMessage];
|
42
|
+
|
43
|
+
beforeEach(async () => {
|
44
|
+
await initializeDB();
|
45
|
+
|
46
|
+
// 在每个测试用例之前,清空表
|
47
|
+
await clientDB.transaction(async (trx) => {
|
48
|
+
await trx.delete(users);
|
49
|
+
await trx.insert(users).values([{ id: userId }, { id: '456' }]);
|
50
|
+
|
51
|
+
await trx.insert(sessions).values([{ id: sessionId, userId }]);
|
52
|
+
await trx.insert(topics).values([{ id: topicId, sessionId, userId }]);
|
53
|
+
await trx.insert(files).values({
|
54
|
+
id: 'f1',
|
55
|
+
userId: userId,
|
56
|
+
url: 'abc',
|
57
|
+
name: 'file-1',
|
58
|
+
fileType: 'image/png',
|
59
|
+
size: 1000,
|
60
|
+
});
|
61
|
+
});
|
62
|
+
});
|
63
|
+
|
64
|
+
afterEach(async () => {
|
65
|
+
// 在每个测试用例之后,清空表
|
66
|
+
await clientDB.delete(users);
|
67
|
+
});
|
68
|
+
|
69
|
+
const messageService = new ClientService(userId);
|
70
|
+
|
71
|
+
describe('MessageClientService', () => {
|
72
|
+
describe('create', () => {
|
73
|
+
it('should create a message and return its id', async () => {
|
74
|
+
// Setup
|
75
|
+
const createParams: CreateMessageParams = {
|
76
|
+
content: 'New message content',
|
77
|
+
sessionId,
|
78
|
+
role: 'user',
|
79
|
+
};
|
80
|
+
|
81
|
+
// Execute
|
82
|
+
const messageId = await messageService.createMessage(createParams);
|
83
|
+
|
84
|
+
// Assert
|
85
|
+
expect(messageId).toMatch(/^msg_/);
|
86
|
+
});
|
87
|
+
});
|
88
|
+
|
89
|
+
describe('batchCreate', () => {
|
90
|
+
it('should batch create messages', async () => {
|
91
|
+
// Execute
|
92
|
+
await messageService.batchCreateMessages([
|
93
|
+
{
|
94
|
+
content: 'Mock message content',
|
95
|
+
sessionId,
|
96
|
+
role: 'user',
|
97
|
+
},
|
98
|
+
{
|
99
|
+
content: 'Mock message content',
|
100
|
+
sessionId,
|
101
|
+
role: 'user',
|
102
|
+
},
|
103
|
+
] as MessageItem[]);
|
104
|
+
const count = await clientDB.$count(messages);
|
105
|
+
|
106
|
+
// Assert
|
107
|
+
expect(count).toBe(2);
|
108
|
+
});
|
109
|
+
});
|
110
|
+
|
111
|
+
describe('removeMessage', () => {
|
112
|
+
it('should remove a message by id', async () => {
|
113
|
+
// Execute
|
114
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
115
|
+
await messageService.removeMessage(mockMessageId);
|
116
|
+
|
117
|
+
// Assert
|
118
|
+
const count = await clientDB.$count(messages);
|
119
|
+
|
120
|
+
expect(count).toBe(0);
|
121
|
+
});
|
122
|
+
});
|
123
|
+
describe('removeMessages', () => {
|
124
|
+
it('should remove a message by id', async () => {
|
125
|
+
// Setup
|
126
|
+
await clientDB.insert(messages).values([
|
127
|
+
{ id: mockMessageId, role: 'user', userId },
|
128
|
+
{ role: 'assistant', userId },
|
129
|
+
]);
|
130
|
+
|
131
|
+
// Execute
|
132
|
+
await messageService.removeMessages([mockMessageId]);
|
133
|
+
|
134
|
+
// Assert
|
135
|
+
const count = await clientDB.$count(messages);
|
136
|
+
|
137
|
+
expect(count).toBe(1);
|
138
|
+
});
|
139
|
+
});
|
140
|
+
|
141
|
+
describe('getMessages', () => {
|
142
|
+
it('should retrieve messages by sessionId and topicId', async () => {
|
143
|
+
// Setup
|
144
|
+
await clientDB
|
145
|
+
.insert(messages)
|
146
|
+
.values({ id: mockMessageId, sessionId, topicId, role: 'user', userId });
|
147
|
+
|
148
|
+
// Execute
|
149
|
+
const data = await messageService.getMessages(sessionId, topicId);
|
150
|
+
|
151
|
+
// Assert
|
152
|
+
expect(data[0]).toMatchObject({ id: mockMessageId, role: 'user' });
|
153
|
+
});
|
154
|
+
});
|
155
|
+
|
156
|
+
describe('getAllMessagesInSession', () => {
|
157
|
+
it('should retrieve all messages in a session', async () => {
|
158
|
+
// Setup
|
159
|
+
const sessionId = 'session-id';
|
160
|
+
await clientDB.insert(sessions).values([
|
161
|
+
{ id: 'bbb', userId },
|
162
|
+
{ id: sessionId, userId },
|
163
|
+
]);
|
164
|
+
await clientDB.insert(messages).values([
|
165
|
+
{ sessionId, topicId, role: 'user', userId },
|
166
|
+
{ sessionId, topicId, role: 'assistant', userId },
|
167
|
+
{ sessionId: 'bbb', topicId, role: 'assistant', userId },
|
168
|
+
]);
|
169
|
+
|
170
|
+
// Execute
|
171
|
+
const data = await messageService.getAllMessagesInSession(sessionId);
|
172
|
+
|
173
|
+
// Assert
|
174
|
+
expect(data.length).toBe(2);
|
175
|
+
});
|
176
|
+
});
|
177
|
+
|
178
|
+
describe('removeMessagesByAssistant', () => {
|
179
|
+
it('should batch remove messages by assistantId and topicId', async () => {
|
180
|
+
// Setup
|
181
|
+
const assistantId = 'assistant-id';
|
182
|
+
const sessionId = 'session-id';
|
183
|
+
await clientDB.insert(sessions).values([
|
184
|
+
{ id: 'bbb', userId },
|
185
|
+
{ id: sessionId, userId },
|
186
|
+
]);
|
187
|
+
await clientDB.insert(messages).values([
|
188
|
+
{ sessionId, topicId, role: 'user', userId },
|
189
|
+
{ sessionId, topicId, role: 'assistant', userId },
|
190
|
+
{ sessionId: 'bbb', topicId, role: 'assistant', userId },
|
191
|
+
]);
|
192
|
+
|
193
|
+
// Execute
|
194
|
+
await messageService.removeMessagesByAssistant(sessionId, topicId);
|
195
|
+
|
196
|
+
// Assert
|
197
|
+
const result = await clientDB.query.messages.findMany({
|
198
|
+
where: and(eq(messages.sessionId, sessionId), eq(messages.topicId, topicId)),
|
199
|
+
});
|
200
|
+
|
201
|
+
expect(result.length).toBe(0);
|
202
|
+
});
|
203
|
+
});
|
204
|
+
|
205
|
+
describe('clearAllMessage', () => {
|
206
|
+
it('should clear all messages from the table', async () => {
|
207
|
+
// Setup
|
208
|
+
await clientDB.insert(users).values({ id: 'another' });
|
209
|
+
await clientDB.insert(messages).values([
|
210
|
+
{ id: mockMessageId, role: 'user', userId },
|
211
|
+
{ role: 'user', userId: 'another' },
|
212
|
+
]);
|
213
|
+
|
214
|
+
// Execute
|
215
|
+
await messageService.removeAllMessages();
|
216
|
+
|
217
|
+
// Assert
|
218
|
+
const result = await clientDB.query.messages.findMany({
|
219
|
+
where: eq(messages.userId, userId),
|
220
|
+
});
|
221
|
+
expect(result.length).toBe(0);
|
222
|
+
});
|
223
|
+
});
|
224
|
+
|
225
|
+
describe('getAllMessages', () => {
|
226
|
+
it('should retrieve all messages', async () => {
|
227
|
+
await clientDB.insert(messages).values([
|
228
|
+
{ sessionId, topicId, content: '1', role: 'user', userId },
|
229
|
+
{ sessionId, topicId, content: '2', role: 'assistant', userId },
|
230
|
+
]);
|
231
|
+
|
232
|
+
// Execute
|
233
|
+
const data = await messageService.getAllMessages();
|
234
|
+
|
235
|
+
// Assert
|
236
|
+
expect(data).toMatchObject([
|
237
|
+
{ sessionId, topicId, content: '1', role: 'user', userId },
|
238
|
+
{ sessionId, topicId, content: '2', role: 'assistant', userId },
|
239
|
+
]);
|
240
|
+
});
|
241
|
+
});
|
242
|
+
|
243
|
+
describe('updateMessageError', () => {
|
244
|
+
it('should update the error field of a message', async () => {
|
245
|
+
// Setup
|
246
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
247
|
+
const newError = {
|
248
|
+
type: 'InvalidProviderAPIKey',
|
249
|
+
message: 'Error occurred',
|
250
|
+
} as ChatMessageError;
|
251
|
+
|
252
|
+
// Execute
|
253
|
+
await messageService.updateMessageError(mockMessageId, newError);
|
254
|
+
|
255
|
+
// Assert
|
256
|
+
const result = await clientDB.query.messages.findFirst({
|
257
|
+
where: eq(messages.id, mockMessageId),
|
258
|
+
});
|
259
|
+
|
260
|
+
expect(result!.error).toEqual(newError);
|
261
|
+
});
|
262
|
+
});
|
263
|
+
|
264
|
+
// describe('updateMessagePlugin', () => {
|
265
|
+
// it('should update the plugin payload of a message', async () => {
|
266
|
+
// // Setup
|
267
|
+
// const newPlugin = {
|
268
|
+
// type: 'default',
|
269
|
+
// apiName: 'abc',
|
270
|
+
// arguments: '',
|
271
|
+
// identifier: 'plugin1',
|
272
|
+
// } as ChatPluginPayload;
|
273
|
+
//
|
274
|
+
// (MessageModel.update as Mock).mockResolvedValue({ ...mockMessage, plugin: newPlugin });
|
275
|
+
//
|
276
|
+
// // Execute
|
277
|
+
// const result = await messageService.updateMessagePlugin(mockMessageId, newPlugin);
|
278
|
+
//
|
279
|
+
// // Assert
|
280
|
+
// expect(MessageModel.update).toHaveBeenCalledWith(mockMessageId, { plugin: newPlugin });
|
281
|
+
// expect(result).toEqual({ ...mockMessage, plugin: newPlugin });
|
282
|
+
// });
|
283
|
+
// });
|
284
|
+
|
285
|
+
describe('updateMessagePluginState', () => {
|
286
|
+
it('should update the plugin state of a message', async () => {
|
287
|
+
// Setup
|
288
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
289
|
+
await clientDB.insert(messagePlugins).values({ id: mockMessageId });
|
290
|
+
const key = 'stateKey';
|
291
|
+
const value = 'stateValue';
|
292
|
+
const newPluginState = { [key]: value };
|
293
|
+
|
294
|
+
// Execute
|
295
|
+
await messageService.updateMessagePluginState(mockMessageId, { stateKey: value });
|
296
|
+
|
297
|
+
// Assert
|
298
|
+
const result = await clientDB.query.messagePlugins.findFirst({
|
299
|
+
where: eq(messagePlugins.id, mockMessageId),
|
300
|
+
});
|
301
|
+
expect(result!.state).toEqual(newPluginState);
|
302
|
+
});
|
303
|
+
});
|
304
|
+
|
305
|
+
describe('updateMessagePluginArguments', () => {
|
306
|
+
it('should update the plugin arguments object of a message', async () => {
|
307
|
+
// Setup
|
308
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
309
|
+
await clientDB.insert(messagePlugins).values({ id: mockMessageId });
|
310
|
+
const value = 'stateValue';
|
311
|
+
|
312
|
+
// Execute
|
313
|
+
await messageService.updateMessagePluginArguments(mockMessageId, { key: value });
|
314
|
+
|
315
|
+
// Assert
|
316
|
+
const result = await clientDB.query.messagePlugins.findFirst({
|
317
|
+
where: eq(messageTTS.id, mockMessageId),
|
318
|
+
});
|
319
|
+
expect(result).toMatchObject({ arguments: '{"key":"stateValue"}' });
|
320
|
+
});
|
321
|
+
it('should update the plugin arguments string of a message', async () => {
|
322
|
+
// Setup
|
323
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
324
|
+
await clientDB.insert(messagePlugins).values({ id: mockMessageId });
|
325
|
+
const value = 'stateValue';
|
326
|
+
// Execute
|
327
|
+
await messageService.updateMessagePluginArguments(
|
328
|
+
mockMessageId,
|
329
|
+
JSON.stringify({ abc: value }),
|
330
|
+
);
|
331
|
+
|
332
|
+
// Assert
|
333
|
+
const result = await clientDB.query.messagePlugins.findFirst({
|
334
|
+
where: eq(messageTTS.id, mockMessageId),
|
335
|
+
});
|
336
|
+
expect(result).toMatchObject({ arguments: '{"abc":"stateValue"}' });
|
337
|
+
});
|
338
|
+
});
|
339
|
+
|
340
|
+
describe('countMessages', () => {
|
341
|
+
it('should count the total number of messages', async () => {
|
342
|
+
// Setup
|
343
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
344
|
+
|
345
|
+
// Execute
|
346
|
+
const count = await messageService.countMessages();
|
347
|
+
|
348
|
+
// Assert
|
349
|
+
expect(count).toBe(1);
|
350
|
+
});
|
351
|
+
});
|
352
|
+
|
353
|
+
describe('countTodayMessages', () => {
|
354
|
+
it('should count the number of messages created today', async () => {
|
355
|
+
// Setup
|
356
|
+
const mockMessages = [
|
357
|
+
{ ...mockMessage, id: undefined, createdAt: new Date(), userId },
|
358
|
+
{ ...mockMessage, id: undefined, createdAt: new Date(), userId },
|
359
|
+
{ ...mockMessage, id: undefined, createdAt: new Date('2023-01-01'), userId },
|
360
|
+
];
|
361
|
+
await clientDB.insert(messages).values(mockMessages);
|
362
|
+
|
363
|
+
// Execute
|
364
|
+
const count = await messageService.countTodayMessages();
|
365
|
+
|
366
|
+
// Assert
|
367
|
+
expect(count).toBe(2);
|
368
|
+
});
|
369
|
+
});
|
370
|
+
|
371
|
+
describe('updateMessageTTS', () => {
|
372
|
+
it('should update the TTS field of a message', async () => {
|
373
|
+
// Setup
|
374
|
+
await clientDB
|
375
|
+
.insert(files)
|
376
|
+
.values({ id: 'file-abc', fileType: 'text', name: 'abc', url: 'abc', size: 100, userId });
|
377
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
378
|
+
const newTTS: ChatTTS = { contentMd5: 'abc', file: 'file-abc' };
|
379
|
+
|
380
|
+
// Execute
|
381
|
+
await messageService.updateMessageTTS(mockMessageId, newTTS);
|
382
|
+
|
383
|
+
// Assert
|
384
|
+
const result = await clientDB.query.messageTTS.findFirst({
|
385
|
+
where: eq(messageTTS.id, mockMessageId),
|
386
|
+
});
|
387
|
+
|
388
|
+
expect(result).toMatchObject({ contentMd5: 'abc', fileId: 'file-abc', id: mockMessageId });
|
389
|
+
});
|
390
|
+
});
|
391
|
+
|
392
|
+
describe('updateMessageTranslate', () => {
|
393
|
+
it('should update the translate field of a message', async () => {
|
394
|
+
// Setup
|
395
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
396
|
+
const newTranslate: ChatTranslate = { content: 'Translated text', to: 'es' };
|
397
|
+
|
398
|
+
// Execute
|
399
|
+
await messageService.updateMessageTranslate(mockMessageId, newTranslate);
|
400
|
+
|
401
|
+
// Assert
|
402
|
+
const result = await clientDB.query.messageTranslates.findFirst({
|
403
|
+
where: eq(messageTranslates.id, mockMessageId),
|
404
|
+
});
|
405
|
+
|
406
|
+
expect(result).toMatchObject(newTranslate);
|
407
|
+
});
|
408
|
+
});
|
409
|
+
|
410
|
+
describe('hasMessages', () => {
|
411
|
+
it('should return true if there are messages', async () => {
|
412
|
+
// Setup
|
413
|
+
await clientDB.insert(messages).values({ id: mockMessageId, role: 'user', userId });
|
414
|
+
|
415
|
+
// Execute
|
416
|
+
const result = await messageService.hasMessages();
|
417
|
+
|
418
|
+
// Assert
|
419
|
+
expect(result).toBe(true);
|
420
|
+
});
|
421
|
+
|
422
|
+
it('should return false if there are no messages', async () => {
|
423
|
+
// Execute
|
424
|
+
const result = await messageService.hasMessages();
|
425
|
+
|
426
|
+
// Assert
|
427
|
+
expect(result).toBe(false);
|
428
|
+
});
|
429
|
+
});
|
430
|
+
});
|
@@ -0,0 +1,118 @@
|
|
1
|
+
import dayjs from 'dayjs';
|
2
|
+
|
3
|
+
import { INBOX_SESSION_ID } from '@/const/session';
|
4
|
+
import { clientDB } from '@/database/client/db';
|
5
|
+
import { MessageItem } from '@/database/schemas';
|
6
|
+
import { MessageModel } from '@/database/server/models/message';
|
7
|
+
import { BaseClientService } from '@/services/baseClientService';
|
8
|
+
import {
|
9
|
+
ChatMessage,
|
10
|
+
ChatMessageError,
|
11
|
+
ChatTTS,
|
12
|
+
ChatTranslate,
|
13
|
+
CreateMessageParams,
|
14
|
+
} from '@/types/message';
|
15
|
+
|
16
|
+
import { IMessageService } from './type';
|
17
|
+
|
18
|
+
export class ClientService extends BaseClientService implements IMessageService {
|
19
|
+
private get messageModel(): MessageModel {
|
20
|
+
return new MessageModel(clientDB as any, this.userId);
|
21
|
+
}
|
22
|
+
|
23
|
+
async createMessage({ sessionId, ...params }: CreateMessageParams) {
|
24
|
+
const { id } = await this.messageModel.create({
|
25
|
+
...params,
|
26
|
+
sessionId: this.toDbSessionId(sessionId) as string,
|
27
|
+
});
|
28
|
+
|
29
|
+
return id;
|
30
|
+
}
|
31
|
+
|
32
|
+
async batchCreateMessages(messages: MessageItem[]) {
|
33
|
+
return this.messageModel.batchCreate(messages);
|
34
|
+
}
|
35
|
+
|
36
|
+
async getMessages(sessionId: string, topicId?: string) {
|
37
|
+
const data = await this.messageModel.query({
|
38
|
+
sessionId: this.toDbSessionId(sessionId),
|
39
|
+
topicId,
|
40
|
+
});
|
41
|
+
|
42
|
+
return data as unknown as ChatMessage[];
|
43
|
+
}
|
44
|
+
|
45
|
+
async getAllMessages() {
|
46
|
+
const data = await this.messageModel.queryAll();
|
47
|
+
|
48
|
+
return data as unknown as ChatMessage[];
|
49
|
+
}
|
50
|
+
|
51
|
+
async countMessages() {
|
52
|
+
return this.messageModel.count();
|
53
|
+
}
|
54
|
+
|
55
|
+
async countTodayMessages() {
|
56
|
+
const topics = await this.messageModel.queryAll();
|
57
|
+
return topics.filter(
|
58
|
+
(item) => dayjs(item.createdAt).format('YYYY-MM-DD') === dayjs().format('YYYY-MM-DD'),
|
59
|
+
).length;
|
60
|
+
}
|
61
|
+
|
62
|
+
async getAllMessagesInSession(sessionId: string) {
|
63
|
+
const data = this.messageModel.queryBySessionId(this.toDbSessionId(sessionId));
|
64
|
+
|
65
|
+
return data as unknown as ChatMessage[];
|
66
|
+
}
|
67
|
+
|
68
|
+
async updateMessageError(id: string, error: ChatMessageError) {
|
69
|
+
return this.messageModel.update(id, { error });
|
70
|
+
}
|
71
|
+
|
72
|
+
async updateMessage(id: string, message: Partial<MessageItem>) {
|
73
|
+
return this.messageModel.update(id, message);
|
74
|
+
}
|
75
|
+
|
76
|
+
async updateMessageTTS(id: string, tts: Partial<ChatTTS> | false) {
|
77
|
+
return this.messageModel.updateTTS(id, tts as any);
|
78
|
+
}
|
79
|
+
|
80
|
+
async updateMessageTranslate(id: string, translate: Partial<ChatTranslate> | false) {
|
81
|
+
return this.messageModel.updateTranslate(id, translate as any);
|
82
|
+
}
|
83
|
+
|
84
|
+
async updateMessagePluginState(id: string, value: Record<string, any>) {
|
85
|
+
return this.messageModel.updatePluginState(id, value);
|
86
|
+
}
|
87
|
+
|
88
|
+
async updateMessagePluginArguments(id: string, value: string | Record<string, any>) {
|
89
|
+
const args = typeof value === 'string' ? value : JSON.stringify(value);
|
90
|
+
|
91
|
+
return this.messageModel.updateMessagePlugin(id, { arguments: args });
|
92
|
+
}
|
93
|
+
|
94
|
+
async removeMessage(id: string) {
|
95
|
+
return this.messageModel.deleteMessage(id);
|
96
|
+
}
|
97
|
+
|
98
|
+
async removeMessages(ids: string[]) {
|
99
|
+
return this.messageModel.deleteMessages(ids);
|
100
|
+
}
|
101
|
+
|
102
|
+
async removeMessagesByAssistant(sessionId: string, topicId?: string) {
|
103
|
+
return this.messageModel.deleteMessagesBySession(this.toDbSessionId(sessionId), topicId);
|
104
|
+
}
|
105
|
+
|
106
|
+
async removeAllMessages() {
|
107
|
+
return this.messageModel.deleteAllMessages();
|
108
|
+
}
|
109
|
+
|
110
|
+
async hasMessages() {
|
111
|
+
const number = await this.countMessages();
|
112
|
+
return number > 0;
|
113
|
+
}
|
114
|
+
|
115
|
+
private toDbSessionId(sessionId: string | undefined) {
|
116
|
+
return sessionId === INBOX_SESSION_ID ? undefined : sessionId;
|
117
|
+
}
|
118
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
2
2
|
import { INBOX_SESSION_ID } from '@/const/session';
|
3
|
+
import { MessageItem } from '@/database/schemas';
|
3
4
|
import { lambdaClient } from '@/libs/trpc/client';
|
4
5
|
import {
|
5
6
|
ChatMessage,
|
@@ -19,20 +20,23 @@ export class ServerService implements IMessageService {
|
|
19
20
|
});
|
20
21
|
}
|
21
22
|
|
22
|
-
batchCreateMessages(messages:
|
23
|
+
batchCreateMessages(messages: MessageItem[]): Promise<any> {
|
23
24
|
return lambdaClient.message.batchCreateMessages.mutate(messages);
|
24
25
|
}
|
25
26
|
|
26
|
-
getMessages(sessionId?: string, topicId?: string | undefined)
|
27
|
-
|
27
|
+
getMessages = async (sessionId?: string, topicId?: string | undefined) => {
|
28
|
+
const data = await lambdaClient.message.getMessages.query({
|
28
29
|
sessionId: this.toDbSessionId(sessionId),
|
29
30
|
topicId,
|
30
31
|
});
|
31
|
-
|
32
|
+
|
33
|
+
return data as unknown as ChatMessage[];
|
34
|
+
};
|
32
35
|
|
33
36
|
getAllMessages(): Promise<ChatMessage[]> {
|
34
37
|
return lambdaClient.message.getAllMessages.query();
|
35
38
|
}
|
39
|
+
|
36
40
|
getAllMessagesInSession(sessionId: string): Promise<ChatMessage[]> {
|
37
41
|
return lambdaClient.message.getAllMessagesInSession.query({
|
38
42
|
sessionId: this.toDbSessionId(sessionId),
|
@@ -63,7 +67,7 @@ export class ServerService implements IMessageService {
|
|
63
67
|
return lambdaClient.message.updateMessagePlugin.mutate({ id, value: { arguments: args } });
|
64
68
|
}
|
65
69
|
|
66
|
-
updateMessage(id: string, message: Partial<
|
70
|
+
updateMessage(id: string, message: Partial<MessageItem>): Promise<any> {
|
67
71
|
return lambdaClient.message.update.mutate({ id, value: message });
|
68
72
|
}
|
69
73
|
|
@@ -79,10 +83,6 @@ export class ServerService implements IMessageService {
|
|
79
83
|
return lambdaClient.message.updatePluginState.mutate({ id, value });
|
80
84
|
}
|
81
85
|
|
82
|
-
bindMessagesToTopic(_topicId: string, _messageIds: string[]): Promise<any> {
|
83
|
-
throw new Error('Method not implemented.');
|
84
|
-
}
|
85
|
-
|
86
86
|
removeMessage(id: string): Promise<any> {
|
87
87
|
return lambdaClient.message.removeMessage.mutate({ id });
|
88
88
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { MessageItem } from '@/database/schemas';
|
2
2
|
import {
|
3
3
|
ChatMessage,
|
4
4
|
ChatMessageError,
|
@@ -11,7 +11,7 @@ import {
|
|
11
11
|
|
12
12
|
export interface IMessageService {
|
13
13
|
createMessage(data: CreateMessageParams): Promise<string>;
|
14
|
-
batchCreateMessages(messages:
|
14
|
+
batchCreateMessages(messages: MessageItem[]): Promise<any>;
|
15
15
|
|
16
16
|
getMessages(sessionId: string, topicId?: string): Promise<ChatMessage[]>;
|
17
17
|
getAllMessages(): Promise<ChatMessage[]>;
|
@@ -20,11 +20,10 @@ export interface IMessageService {
|
|
20
20
|
countTodayMessages(): Promise<number>;
|
21
21
|
|
22
22
|
updateMessageError(id: string, error: ChatMessageError): Promise<any>;
|
23
|
-
updateMessage(id: string, message: Partial<
|
23
|
+
updateMessage(id: string, message: Partial<MessageItem>): Promise<any>;
|
24
24
|
updateMessageTTS(id: string, tts: Partial<ChatTTS> | false): Promise<any>;
|
25
25
|
updateMessageTranslate(id: string, translate: Partial<ChatTranslate> | false): Promise<any>;
|
26
26
|
updateMessagePluginState(id: string, value: Record<string, any>): Promise<any>;
|
27
|
-
bindMessagesToTopic(topicId: string, messageIds: string[]): Promise<any>;
|
28
27
|
|
29
28
|
removeMessage(id: string): Promise<any>;
|
30
29
|
removeMessages(ids: string[]): Promise<any>;
|