@lobehub/lobehub 2.0.0-next.29 → 2.0.0-next.30

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.
@@ -11,6 +11,7 @@ import { getServerDB } from '@/database/server';
11
11
  import { authedProcedure, publicProcedure, router } from '@/libs/trpc/lambda';
12
12
  import { serverDatabase } from '@/libs/trpc/lambda/middleware';
13
13
  import { FileService } from '@/server/services/file';
14
+ import { MessageService } from '@/server/services/message';
14
15
 
15
16
  const messageProcedure = authedProcedure.use(serverDatabase).use(async (opts) => {
16
17
  const { ctx } = opts;
@@ -19,6 +20,7 @@ const messageProcedure = authedProcedure.use(serverDatabase).use(async (opts) =>
19
20
  ctx: {
20
21
  fileService: new FileService(ctx.serverDB, ctx.userId),
21
22
  messageModel: new MessageModel(ctx.serverDB, ctx.userId),
23
+ messageService: new MessageService(ctx.serverDB, ctx.userId),
22
24
  },
23
25
  });
24
26
  });
@@ -53,11 +55,10 @@ export const messageRouter = router({
53
55
  }),
54
56
 
55
57
  createNewMessage: messageProcedure
56
- .input(CreateNewMessageParamsSchema)
58
+ .input(CreateNewMessageParamsSchema.extend({ useGroup: z.boolean().optional() }))
57
59
  .mutation(async ({ input, ctx }) => {
58
- return ctx.messageModel.createNewMessage(input as any, {
59
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
60
- });
60
+ const { useGroup, ...params } = input;
61
+ return ctx.messageService.createNewMessage(params as any, { useGroup });
61
62
  }),
62
63
 
63
64
  getHeatmaps: messageProcedure.query(async ({ ctx }) => {
@@ -109,23 +110,8 @@ export const messageRouter = router({
109
110
  }),
110
111
  )
111
112
  .mutation(async ({ input, ctx }) => {
112
- await ctx.messageModel.deleteMessage(input.id);
113
-
114
- // If sessionId or topicId is provided, return the full message list
115
- if (input.sessionId !== undefined || input.topicId !== undefined) {
116
- const messageList = await ctx.messageModel.query(
117
- {
118
- sessionId: input.sessionId,
119
- topicId: input.topicId,
120
- },
121
- {
122
- groupAssistantMessages: input.useGroup ?? false,
123
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
124
- },
125
- );
126
- return { messages: messageList, success: true };
127
- }
128
- return { success: true };
113
+ const { id, ...options } = input;
114
+ return ctx.messageService.removeMessage(id, options);
129
115
  }),
130
116
 
131
117
  removeMessageQuery: messageProcedure
@@ -144,23 +130,8 @@ export const messageRouter = router({
144
130
  }),
145
131
  )
146
132
  .mutation(async ({ input, ctx }) => {
147
- await ctx.messageModel.deleteMessages(input.ids);
148
-
149
- // If sessionId or topicId is provided, return the full message list
150
- if (input.sessionId !== undefined || input.topicId !== undefined) {
151
- const messageList = await ctx.messageModel.query(
152
- {
153
- sessionId: input.sessionId,
154
- topicId: input.topicId,
155
- },
156
- {
157
- groupAssistantMessages: input.useGroup ?? false,
158
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
159
- },
160
- );
161
- return { messages: messageList, success: true };
162
- }
163
- return { success: true };
133
+ const { ids, ...options } = input;
134
+ return ctx.messageService.removeMessages(ids, options);
164
135
  }),
165
136
 
166
137
  removeMessagesByAssistant: messageProcedure
@@ -207,12 +178,8 @@ export const messageRouter = router({
207
178
  }),
208
179
  )
209
180
  .mutation(async ({ input, ctx }) => {
210
- return ctx.messageModel.update(input.id, input.value as any, {
211
- groupAssistantMessages: input.useGroup ?? false,
212
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
213
- sessionId: input.sessionId,
214
- topicId: input.topicId,
215
- });
181
+ const { id, value, ...options } = input;
182
+ return ctx.messageService.updateMessage(id, value as any, options);
216
183
  }),
217
184
 
218
185
  updateMessagePlugin: messageProcedure
@@ -235,23 +202,8 @@ export const messageRouter = router({
235
202
  }),
236
203
  )
237
204
  .mutation(async ({ input, ctx }) => {
238
- await ctx.messageModel.updateMessageRAG(input.id, input.value);
239
-
240
- // If sessionId or topicId is provided, return the full message list
241
- if (input.sessionId !== undefined || input.topicId !== undefined) {
242
- const messageList = await ctx.messageModel.query(
243
- {
244
- sessionId: input.sessionId,
245
- topicId: input.topicId,
246
- },
247
- {
248
- groupAssistantMessages: input.useGroup ?? false,
249
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
250
- },
251
- );
252
- return { messages: messageList, success: true };
253
- }
254
- return { success: true };
205
+ const { id, value, ...options } = input;
206
+ return ctx.messageService.updateMessageRAG(id, value, options);
255
207
  }),
256
208
 
257
209
  updateMetadata: messageProcedure
@@ -276,23 +228,8 @@ export const messageRouter = router({
276
228
  }),
277
229
  )
278
230
  .mutation(async ({ input, ctx }) => {
279
- // If sessionId or topicId is provided, we need to return the full message list
280
- if (input.sessionId !== undefined || input.topicId !== undefined) {
281
- await ctx.messageModel.updateMessagePlugin(input.id, { error: input.value });
282
- const messageList = await ctx.messageModel.query(
283
- {
284
- sessionId: input.sessionId,
285
- topicId: input.topicId,
286
- },
287
- {
288
- groupAssistantMessages: input.useGroup ?? false,
289
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
290
- },
291
- );
292
- return { messages: messageList, success: true };
293
- }
294
- await ctx.messageModel.updateMessagePlugin(input.id, { error: input.value });
295
- return { success: true };
231
+ const { id, value, ...options } = input;
232
+ return ctx.messageService.updatePluginError(id, value, options);
296
233
  }),
297
234
 
298
235
  updatePluginState: messageProcedure
@@ -306,12 +243,8 @@ export const messageRouter = router({
306
243
  }),
307
244
  )
308
245
  .mutation(async ({ input, ctx }) => {
309
- return ctx.messageModel.updatePluginState(input.id, input.value, {
310
- groupAssistantMessages: input.useGroup ?? false,
311
- postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),
312
- sessionId: input.sessionId,
313
- topicId: input.topicId,
314
- });
246
+ const { id, value, ...options } = input;
247
+ return ctx.messageService.updatePluginState(id, value, options);
315
248
  }),
316
249
 
317
250
  updateTTS: messageProcedure
@@ -0,0 +1,348 @@
1
+ import { LobeChatDatabase } from '@lobechat/database';
2
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { MessageModel } from '@/database/models/message';
5
+ import { FileService } from '@/server/services/file';
6
+
7
+ import { MessageService } from '../index';
8
+
9
+ vi.mock('@/database/models/message');
10
+ vi.mock('@/server/services/file');
11
+
12
+ describe('MessageService', () => {
13
+ let messageService: MessageService;
14
+ let mockDB: LobeChatDatabase;
15
+ let mockMessageModel: MessageModel;
16
+ let mockFileService: FileService;
17
+ const userId = 'test-user-id';
18
+
19
+ beforeEach(() => {
20
+ mockDB = {} as LobeChatDatabase;
21
+ mockMessageModel = {
22
+ create: vi.fn(),
23
+ deleteMessage: vi.fn(),
24
+ deleteMessages: vi.fn(),
25
+ query: vi.fn(),
26
+ update: vi.fn(),
27
+ updateMessagePlugin: vi.fn(),
28
+ updateMessageRAG: vi.fn(),
29
+ updatePluginState: vi.fn(),
30
+ } as any;
31
+
32
+ mockFileService = {
33
+ getFullFileUrl: vi.fn().mockImplementation((path) => Promise.resolve(`/files${path}`)),
34
+ } as any;
35
+
36
+ // Mock constructors
37
+ vi.mocked(MessageModel).mockImplementation(() => mockMessageModel);
38
+ vi.mocked(FileService).mockImplementation(() => mockFileService);
39
+
40
+ messageService = new MessageService(mockDB, userId);
41
+ });
42
+
43
+ describe('removeMessage', () => {
44
+ it('should delete message and return { success: true } when no sessionId/topicId provided', async () => {
45
+ const messageId = 'msg-1';
46
+
47
+ const result = await messageService.removeMessage(messageId);
48
+
49
+ expect(mockMessageModel.deleteMessage).toHaveBeenCalledWith(messageId);
50
+ expect(result).toEqual({ success: true });
51
+ expect(mockMessageModel.query).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it('should delete message and return message list when sessionId provided', async () => {
55
+ const messageId = 'msg-1';
56
+ const mockMessages = [{ id: 'msg-2', content: 'test' }];
57
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
58
+
59
+ const result = await messageService.removeMessage(messageId, { sessionId: 'session-1' });
60
+
61
+ expect(mockMessageModel.deleteMessage).toHaveBeenCalledWith(messageId);
62
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
63
+ { groupId: undefined, sessionId: 'session-1', topicId: undefined },
64
+ expect.objectContaining({
65
+ groupAssistantMessages: false,
66
+ }),
67
+ );
68
+ expect(result).toEqual({ messages: mockMessages, success: true });
69
+ });
70
+
71
+ it('should delete message and return message list when topicId provided', async () => {
72
+ const messageId = 'msg-1';
73
+ const mockMessages = [{ id: 'msg-2', content: 'test' }];
74
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
75
+
76
+ const result = await messageService.removeMessage(messageId, { topicId: 'topic-1' });
77
+
78
+ expect(mockMessageModel.deleteMessage).toHaveBeenCalledWith(messageId);
79
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
80
+ { groupId: undefined, sessionId: undefined, topicId: 'topic-1' },
81
+ expect.objectContaining({
82
+ groupAssistantMessages: false,
83
+ }),
84
+ );
85
+ expect(result).toEqual({ messages: mockMessages, success: true });
86
+ });
87
+ });
88
+
89
+ describe('removeMessages', () => {
90
+ it('should delete messages and return { success: true } when no sessionId/topicId provided', async () => {
91
+ const messageIds = ['msg-1', 'msg-2'];
92
+
93
+ const result = await messageService.removeMessages(messageIds);
94
+
95
+ expect(mockMessageModel.deleteMessages).toHaveBeenCalledWith(messageIds);
96
+ expect(result).toEqual({ success: true });
97
+ expect(mockMessageModel.query).not.toHaveBeenCalled();
98
+ });
99
+
100
+ it('should delete messages and return message list when sessionId provided', async () => {
101
+ const messageIds = ['msg-1', 'msg-2'];
102
+ const mockMessages = [{ id: 'msg-3', content: 'test' }];
103
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
104
+
105
+ const result = await messageService.removeMessages(messageIds, { sessionId: 'session-1' });
106
+
107
+ expect(mockMessageModel.deleteMessages).toHaveBeenCalledWith(messageIds);
108
+ expect(mockMessageModel.query).toHaveBeenCalled();
109
+ expect(result).toEqual({ messages: mockMessages, success: true });
110
+ });
111
+ });
112
+
113
+ describe('updateMessageRAG', () => {
114
+ it('should update RAG and return { success: true } when no sessionId/topicId provided', async () => {
115
+ const messageId = 'msg-1';
116
+ const ragValue = { fileChunks: [{ id: 'chunk-1', similarity: 0.95 }] };
117
+
118
+ const result = await messageService.updateMessageRAG(messageId, ragValue);
119
+
120
+ expect(mockMessageModel.updateMessageRAG).toHaveBeenCalledWith(messageId, ragValue);
121
+ expect(result).toEqual({ success: true });
122
+ expect(mockMessageModel.query).not.toHaveBeenCalled();
123
+ });
124
+
125
+ it('should update RAG and return message list when sessionId provided', async () => {
126
+ const messageId = 'msg-1';
127
+ const ragValue = { fileChunks: [{ id: 'chunk-1', similarity: 0.95 }] };
128
+ const mockMessages = [{ id: 'msg-1', content: 'test' }];
129
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
130
+
131
+ const result = await messageService.updateMessageRAG(messageId, ragValue, {
132
+ sessionId: 'session-1',
133
+ });
134
+
135
+ expect(mockMessageModel.updateMessageRAG).toHaveBeenCalledWith(messageId, ragValue);
136
+ expect(mockMessageModel.query).toHaveBeenCalled();
137
+ expect(result).toEqual({ messages: mockMessages, success: true });
138
+ });
139
+ });
140
+
141
+ describe('updatePluginError', () => {
142
+ it('should update plugin error and return { success: true } when no sessionId/topicId provided', async () => {
143
+ const messageId = 'msg-1';
144
+ const error = { type: 'TestError', message: 'Test error message' };
145
+
146
+ const result = await messageService.updatePluginError(messageId, error);
147
+
148
+ expect(mockMessageModel.updateMessagePlugin).toHaveBeenCalledWith(messageId, { error });
149
+ expect(result).toEqual({ success: true });
150
+ expect(mockMessageModel.query).not.toHaveBeenCalled();
151
+ });
152
+
153
+ it('should update plugin error and return message list when sessionId provided', async () => {
154
+ const messageId = 'msg-1';
155
+ const error = { type: 'TestError', message: 'Test error message' };
156
+ const mockMessages = [{ id: 'msg-1', content: 'test' }];
157
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
158
+
159
+ const result = await messageService.updatePluginError(messageId, error, {
160
+ sessionId: 'session-1',
161
+ });
162
+
163
+ expect(mockMessageModel.updateMessagePlugin).toHaveBeenCalledWith(messageId, { error });
164
+ expect(mockMessageModel.query).toHaveBeenCalled();
165
+ expect(result).toEqual({ messages: mockMessages, success: true });
166
+ });
167
+ });
168
+
169
+ describe('updatePluginState', () => {
170
+ it('should update plugin state and return { success: true } when no sessionId/topicId provided', async () => {
171
+ const messageId = 'msg-1';
172
+ const state = { key: 'value' };
173
+
174
+ const result = await messageService.updatePluginState(messageId, state, {});
175
+
176
+ expect(mockMessageModel.updatePluginState).toHaveBeenCalledWith(messageId, state);
177
+ expect(result).toEqual({ success: true });
178
+ expect(mockMessageModel.query).not.toHaveBeenCalled();
179
+ });
180
+
181
+ it('should update plugin state and return message list when sessionId provided', async () => {
182
+ const messageId = 'msg-1';
183
+ const state = { key: 'value' };
184
+ const mockMessages = [{ id: 'msg-1', content: 'test' }];
185
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
186
+
187
+ const result = await messageService.updatePluginState(messageId, state, {
188
+ sessionId: 'session-1',
189
+ });
190
+
191
+ expect(mockMessageModel.updatePluginState).toHaveBeenCalledWith(messageId, state);
192
+ expect(mockMessageModel.query).toHaveBeenCalled();
193
+ expect(result).toEqual({ messages: mockMessages, success: true });
194
+ });
195
+ });
196
+
197
+ describe('updateMessage', () => {
198
+ it('should update message and return { success: true } when no sessionId/topicId provided', async () => {
199
+ const messageId = 'msg-1';
200
+ const value = { content: 'updated content' };
201
+
202
+ const result = await messageService.updateMessage(messageId, value as any, {});
203
+
204
+ expect(mockMessageModel.update).toHaveBeenCalledWith(messageId, value);
205
+ expect(result).toEqual({ success: true });
206
+ expect(mockMessageModel.query).not.toHaveBeenCalled();
207
+ });
208
+
209
+ it('should update message and return message list when sessionId provided', async () => {
210
+ const messageId = 'msg-1';
211
+ const value = { content: 'updated content' };
212
+ const mockMessages = [{ id: 'msg-1', content: 'updated content' }];
213
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
214
+
215
+ const result = await messageService.updateMessage(messageId, value as any, {
216
+ sessionId: 'session-1',
217
+ });
218
+
219
+ expect(mockMessageModel.update).toHaveBeenCalledWith(messageId, value);
220
+ expect(mockMessageModel.query).toHaveBeenCalled();
221
+ expect(result).toEqual({ messages: mockMessages, success: true });
222
+ });
223
+ });
224
+
225
+ describe('useGroup option', () => {
226
+ it('should pass useGroup option to query', async () => {
227
+ const mockMessages = [{ id: 'msg-1', content: 'test' }];
228
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
229
+
230
+ await messageService.removeMessage('msg-1', {
231
+ sessionId: 'session-1',
232
+ useGroup: true,
233
+ });
234
+
235
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
236
+ expect.anything(),
237
+ expect.objectContaining({
238
+ groupAssistantMessages: true,
239
+ }),
240
+ );
241
+ });
242
+
243
+ it('should default useGroup to false', async () => {
244
+ const mockMessages = [{ id: 'msg-1', content: 'test' }];
245
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
246
+
247
+ await messageService.removeMessage('msg-1', { sessionId: 'session-1' });
248
+
249
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
250
+ expect.anything(),
251
+ expect.objectContaining({
252
+ groupAssistantMessages: false,
253
+ }),
254
+ );
255
+ });
256
+ });
257
+
258
+ describe('createNewMessage', () => {
259
+ it('should create message and return message list', async () => {
260
+ const params = {
261
+ content: 'Hello',
262
+ role: 'user' as const,
263
+ sessionId: 'session-1',
264
+ };
265
+ const createdMessage = { id: 'msg-1', ...params };
266
+ const mockMessages = [createdMessage, { id: 'msg-2', content: 'Hi' }];
267
+
268
+ vi.mocked(mockMessageModel.create).mockResolvedValue(createdMessage as any);
269
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
270
+
271
+ const result = await messageService.createNewMessage(params as any);
272
+
273
+ expect(mockMessageModel.create).toHaveBeenCalledWith(params);
274
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
275
+ {
276
+ current: 0,
277
+ groupId: undefined,
278
+ pageSize: 9999,
279
+ sessionId: 'session-1',
280
+ topicId: undefined,
281
+ },
282
+ expect.objectContaining({
283
+ groupAssistantMessages: false,
284
+ }),
285
+ );
286
+ expect(result).toEqual({
287
+ id: 'msg-1',
288
+ messages: mockMessages,
289
+ });
290
+ });
291
+
292
+ it('should create message with useGroup option', async () => {
293
+ const params = {
294
+ content: 'Hello',
295
+ role: 'assistant' as const,
296
+ sessionId: 'session-1',
297
+ };
298
+ const createdMessage = { id: 'msg-1', ...params };
299
+ const mockMessages = [createdMessage];
300
+
301
+ vi.mocked(mockMessageModel.create).mockResolvedValue(createdMessage as any);
302
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
303
+
304
+ const result = await messageService.createNewMessage(params as any, { useGroup: true });
305
+
306
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
307
+ expect.anything(),
308
+ expect.objectContaining({
309
+ groupAssistantMessages: true,
310
+ }),
311
+ );
312
+ expect(result).toEqual({
313
+ id: 'msg-1',
314
+ messages: mockMessages,
315
+ });
316
+ });
317
+
318
+ it('should create message with topicId and groupId', async () => {
319
+ const params = {
320
+ content: 'Hello',
321
+ groupId: 'group-1',
322
+ role: 'user' as const,
323
+ sessionId: 'session-1',
324
+ topicId: 'topic-1',
325
+ };
326
+ const createdMessage = { id: 'msg-1', ...params };
327
+ const mockMessages = [createdMessage];
328
+
329
+ vi.mocked(mockMessageModel.create).mockResolvedValue(createdMessage as any);
330
+ vi.mocked(mockMessageModel.query).mockResolvedValue(mockMessages as any);
331
+
332
+ const result = await messageService.createNewMessage(params as any);
333
+
334
+ expect(mockMessageModel.query).toHaveBeenCalledWith(
335
+ {
336
+ current: 0,
337
+ groupId: 'group-1',
338
+ pageSize: 9999,
339
+ sessionId: 'session-1',
340
+ topicId: 'topic-1',
341
+ },
342
+ expect.anything(),
343
+ );
344
+ expect(result.id).toBe('msg-1');
345
+ expect(result.messages).toEqual(mockMessages);
346
+ });
347
+ });
348
+ });
@@ -0,0 +1,159 @@
1
+ import { LobeChatDatabase } from '@lobechat/database';
2
+ import { CreateMessageParams, UpdateMessageParams } from '@lobechat/types';
3
+
4
+ import { MessageModel } from '@/database/models/message';
5
+
6
+ import { FileService } from '../file';
7
+
8
+ interface QueryOptions {
9
+ groupId?: string | null;
10
+ sessionId?: string | null;
11
+ topicId?: string | null;
12
+ useGroup?: boolean;
13
+ }
14
+
15
+ interface CreateMessageResult {
16
+ id: string;
17
+ messages: any[];
18
+ }
19
+
20
+ /**
21
+ * Message Service
22
+ *
23
+ * Encapsulates repeated "mutation + conditional query" logic.
24
+ * After performing update/delete operations, conditionally returns message list based on sessionId/topicId.
25
+ */
26
+ export class MessageService {
27
+ private messageModel: MessageModel;
28
+ private fileService: FileService;
29
+
30
+ constructor(db: LobeChatDatabase, userId: string) {
31
+ this.messageModel = new MessageModel(db, userId);
32
+ this.fileService = new FileService(db, userId);
33
+ }
34
+
35
+ /**
36
+ * Unified URL processing function
37
+ */
38
+ private get postProcessUrl() {
39
+ return (path: string | null) => this.fileService.getFullFileUrl(path);
40
+ }
41
+
42
+ /**
43
+ * Unified query options
44
+ */
45
+ private getQueryOptions(options: QueryOptions) {
46
+ return {
47
+ groupAssistantMessages: options.useGroup ?? false,
48
+ postProcessUrl: this.postProcessUrl,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Query messages and return response with success status (used after mutations)
54
+ */
55
+ private async queryWithSuccess(options?: QueryOptions) {
56
+ if (!options || (options.sessionId === undefined && options.topicId === undefined)) {
57
+ return { success: true };
58
+ }
59
+
60
+ const { sessionId, topicId, groupId } = options;
61
+
62
+ const messages = await this.messageModel.query(
63
+ { groupId, sessionId, topicId },
64
+ this.getQueryOptions(options),
65
+ );
66
+
67
+ return { messages, success: true };
68
+ }
69
+
70
+ /**
71
+ * Remove messages with optional message list return
72
+ * Pattern: delete + conditional query
73
+ */
74
+ async removeMessages(ids: string[], options?: QueryOptions) {
75
+ await this.messageModel.deleteMessages(ids);
76
+ return this.queryWithSuccess(options);
77
+ }
78
+
79
+ /**
80
+ * Remove single message with optional message list return
81
+ * Pattern: delete + conditional query
82
+ */
83
+ async removeMessage(id: string, options?: QueryOptions) {
84
+ await this.messageModel.deleteMessage(id);
85
+ return this.queryWithSuccess(options);
86
+ }
87
+
88
+ /**
89
+ * Update message RAG with optional message list return
90
+ * Pattern: update + conditional query
91
+ */
92
+ async updateMessageRAG(id: string, value: any, options?: QueryOptions) {
93
+ await this.messageModel.updateMessageRAG(id, value);
94
+ return this.queryWithSuccess(options);
95
+ }
96
+
97
+ /**
98
+ * Update plugin error with optional message list return
99
+ * Pattern: update + conditional query
100
+ */
101
+ async updatePluginError(id: string, value: any, options?: QueryOptions) {
102
+ await this.messageModel.updateMessagePlugin(id, { error: value });
103
+ return this.queryWithSuccess(options);
104
+ }
105
+
106
+ /**
107
+ * Update plugin state and return message list
108
+ * Pattern: update + conditional query
109
+ */
110
+ async updatePluginState(id: string, value: any, options: QueryOptions): Promise<any> {
111
+ await this.messageModel.updatePluginState(id, value);
112
+ return this.queryWithSuccess(options);
113
+ }
114
+
115
+ /**
116
+ * Update message and return message list
117
+ * Pattern: update + conditional query
118
+ */
119
+ async updateMessage(id: string, value: UpdateMessageParams, options: QueryOptions): Promise<any> {
120
+ await this.messageModel.update(id, value as any);
121
+ return this.queryWithSuccess(options);
122
+ }
123
+
124
+ /**
125
+ * Create a new message and return the complete message list
126
+ * Pattern: create + query
127
+ *
128
+ * This method combines message creation and querying into a single operation,
129
+ * reducing the need for separate refresh calls and improving performance.
130
+ */
131
+ async createNewMessage(
132
+ params: CreateMessageParams,
133
+ options?: QueryOptions,
134
+ ): Promise<CreateMessageResult> {
135
+ // 1. Create the message
136
+ const item = await this.messageModel.create(params);
137
+
138
+ // 2. Query all messages for this session/topic
139
+ const messages = await this.messageModel.query(
140
+ {
141
+ current: 0,
142
+ groupId: params.groupId,
143
+ pageSize: 9999,
144
+ sessionId: params.sessionId,
145
+ topicId: params.topicId,
146
+ },
147
+ {
148
+ groupAssistantMessages: options?.useGroup ?? false,
149
+ postProcessUrl: this.postProcessUrl,
150
+ },
151
+ );
152
+
153
+ // 3. Return the result
154
+ return {
155
+ id: item.id,
156
+ messages,
157
+ };
158
+ }
159
+ }
@@ -31,6 +31,7 @@ export class MessageService {
31
31
  return lambdaClient.message.createNewMessage.mutate({
32
32
  ...params,
33
33
  sessionId: sessionId ? this.toDbSessionId(sessionId) : undefined,
34
+ useGroup: this.useGroup,
34
35
  });
35
36
  };
36
37