@lobehub/lobehub 2.0.0-next.40 → 2.0.0-next.41
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/changelog/v1.json +9 -0
- package/locales/ar/chat.json +1 -0
- package/locales/bg-BG/chat.json +1 -0
- package/locales/de-DE/chat.json +1 -0
- package/locales/en-US/chat.json +1 -0
- package/locales/es-ES/chat.json +1 -0
- package/locales/fa-IR/chat.json +1 -0
- package/locales/fr-FR/chat.json +1 -0
- package/locales/it-IT/chat.json +1 -0
- package/locales/ja-JP/chat.json +1 -0
- package/locales/ko-KR/chat.json +1 -0
- package/locales/nl-NL/chat.json +1 -0
- package/locales/pl-PL/chat.json +1 -0
- package/locales/pt-BR/chat.json +1 -0
- package/locales/ru-RU/chat.json +1 -0
- package/locales/tr-TR/chat.json +1 -0
- package/locales/vi-VN/chat.json +1 -0
- package/locales/zh-CN/chat.json +1 -0
- package/locales/zh-TW/chat.json +1 -0
- package/package.json +1 -1
- package/packages/database/src/models/__tests__/messages/message.create.test.ts +492 -0
- package/packages/database/src/models/__tests__/messages/message.delete.test.ts +481 -0
- package/packages/database/src/models/__tests__/messages/message.query.test.ts +970 -0
- package/packages/database/src/models/__tests__/messages/message.stats.test.ts +584 -0
- package/packages/database/src/models/__tests__/messages/message.update.test.ts +716 -0
- package/packages/database/src/models/message.ts +2 -50
- package/src/server/services/message/index.ts +14 -4
- package/packages/database/src/models/__tests__/message.test.ts +0 -2632
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { uuid } from '@/utils/uuid';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
agents,
|
|
9
|
+
chatGroups,
|
|
10
|
+
chunks,
|
|
11
|
+
embeddings,
|
|
12
|
+
fileChunks,
|
|
13
|
+
files,
|
|
14
|
+
messagePlugins,
|
|
15
|
+
messageQueries,
|
|
16
|
+
messageQueryChunks,
|
|
17
|
+
messageTTS,
|
|
18
|
+
messageTranslates,
|
|
19
|
+
messages,
|
|
20
|
+
messagesFiles,
|
|
21
|
+
sessions,
|
|
22
|
+
topics,
|
|
23
|
+
users,
|
|
24
|
+
} from '../../../schemas';
|
|
25
|
+
import { LobeChatDatabase } from '../../../type';
|
|
26
|
+
import { MessageModel } from '../../message';
|
|
27
|
+
import { getTestDB } from '../_util';
|
|
28
|
+
import { codeEmbedding } from '../fixtures/embedding';
|
|
29
|
+
|
|
30
|
+
const serverDB: LobeChatDatabase = await getTestDB();
|
|
31
|
+
|
|
32
|
+
const userId = 'message-query-test';
|
|
33
|
+
const otherUserId = 'message-query-test-other';
|
|
34
|
+
const messageModel = new MessageModel(serverDB, userId);
|
|
35
|
+
const embeddingsId = uuid();
|
|
36
|
+
|
|
37
|
+
beforeEach(async () => {
|
|
38
|
+
// Clear tables before each test case
|
|
39
|
+
await serverDB.transaction(async (trx) => {
|
|
40
|
+
await trx.delete(users).where(eq(users.id, userId));
|
|
41
|
+
await trx.delete(users).where(eq(users.id, otherUserId));
|
|
42
|
+
await trx.insert(users).values([{ id: userId }, { id: otherUserId }]);
|
|
43
|
+
|
|
44
|
+
await trx.insert(sessions).values([
|
|
45
|
+
// { id: 'session1', userId },
|
|
46
|
+
// { id: 'session2', userId },
|
|
47
|
+
{ id: '1', userId },
|
|
48
|
+
]);
|
|
49
|
+
await trx.insert(files).values({
|
|
50
|
+
id: 'f1',
|
|
51
|
+
userId: userId,
|
|
52
|
+
url: 'abc',
|
|
53
|
+
name: 'file-1',
|
|
54
|
+
fileType: 'image/png',
|
|
55
|
+
size: 1000,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await trx.insert(embeddings).values({
|
|
59
|
+
id: embeddingsId,
|
|
60
|
+
embeddings: codeEmbedding,
|
|
61
|
+
userId,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
afterEach(async () => {
|
|
67
|
+
// Clear tables after each test case
|
|
68
|
+
await serverDB.delete(users).where(eq(users.id, userId));
|
|
69
|
+
await serverDB.delete(users).where(eq(users.id, otherUserId));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('MessageModel Query Tests', () => {
|
|
73
|
+
describe('query', () => {
|
|
74
|
+
it('should query messages by user ID', async () => {
|
|
75
|
+
// Create test data
|
|
76
|
+
await serverDB.insert(messages).values([
|
|
77
|
+
{ id: '1', userId, role: 'user', content: 'message 1', createdAt: new Date('2023-01-01') },
|
|
78
|
+
{ id: '2', userId, role: 'user', content: 'message 2', createdAt: new Date('2023-02-01') },
|
|
79
|
+
{
|
|
80
|
+
id: '3',
|
|
81
|
+
userId: otherUserId,
|
|
82
|
+
role: 'user',
|
|
83
|
+
content: 'message 3',
|
|
84
|
+
createdAt: new Date('2023-03-01'),
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Call query method
|
|
89
|
+
const result = await messageModel.query();
|
|
90
|
+
|
|
91
|
+
// Assert result
|
|
92
|
+
expect(result).toHaveLength(2);
|
|
93
|
+
expect(result[0].id).toBe('1');
|
|
94
|
+
expect(result[1].id).toBe('2');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should return empty messages if not match the user ID', async () => {
|
|
98
|
+
// Create test data
|
|
99
|
+
await serverDB.insert(messages).values([
|
|
100
|
+
{
|
|
101
|
+
id: '1',
|
|
102
|
+
userId: otherUserId,
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: '1',
|
|
105
|
+
createdAt: new Date('2023-01-01'),
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: '2',
|
|
109
|
+
userId: otherUserId,
|
|
110
|
+
role: 'user',
|
|
111
|
+
content: '2',
|
|
112
|
+
createdAt: new Date('2023-02-01'),
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: '3',
|
|
116
|
+
userId: otherUserId,
|
|
117
|
+
role: 'user',
|
|
118
|
+
content: '3',
|
|
119
|
+
createdAt: new Date('2023-03-01'),
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
// Call query method
|
|
124
|
+
const result = await messageModel.query();
|
|
125
|
+
|
|
126
|
+
// Assert result
|
|
127
|
+
expect(result).toHaveLength(0);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should query messages with pagination', async () => {
|
|
131
|
+
// Create test data
|
|
132
|
+
await serverDB.insert(messages).values([
|
|
133
|
+
{ id: '1', userId, role: 'user', content: 'message 1', createdAt: new Date('2023-01-01') },
|
|
134
|
+
{ id: '2', userId, role: 'user', content: 'message 2', createdAt: new Date('2023-02-01') },
|
|
135
|
+
{ id: '3', userId, role: 'user', content: 'message 3', createdAt: new Date('2023-03-01') },
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
// Test pagination
|
|
139
|
+
const result1 = await messageModel.query({ current: 0, pageSize: 2 });
|
|
140
|
+
expect(result1).toHaveLength(2);
|
|
141
|
+
|
|
142
|
+
const result2 = await messageModel.query({ current: 1, pageSize: 1 });
|
|
143
|
+
expect(result2).toHaveLength(1);
|
|
144
|
+
expect(result2[0].id).toBe('2');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should filter messages by sessionId', async () => {
|
|
148
|
+
// Create test data
|
|
149
|
+
await serverDB.insert(sessions).values([
|
|
150
|
+
{ id: 'session1', userId },
|
|
151
|
+
{ id: 'session2', userId },
|
|
152
|
+
]);
|
|
153
|
+
await serverDB.insert(messages).values([
|
|
154
|
+
{
|
|
155
|
+
id: '1',
|
|
156
|
+
userId,
|
|
157
|
+
role: 'user',
|
|
158
|
+
sessionId: 'session1',
|
|
159
|
+
content: 'message 1',
|
|
160
|
+
createdAt: new Date('2022-02-01'),
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: '2',
|
|
164
|
+
userId,
|
|
165
|
+
role: 'user',
|
|
166
|
+
sessionId: 'session1',
|
|
167
|
+
content: 'message 2',
|
|
168
|
+
createdAt: new Date('2023-02-02'),
|
|
169
|
+
},
|
|
170
|
+
{ id: '3', userId, role: 'user', sessionId: 'session2', content: 'message 3' },
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
// Test filtering by sessionId
|
|
174
|
+
const result = await messageModel.query({ sessionId: 'session1' });
|
|
175
|
+
expect(result).toHaveLength(2);
|
|
176
|
+
expect(result[0].id).toBe('1');
|
|
177
|
+
expect(result[1].id).toBe('2');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should filter messages by topicId', async () => {
|
|
181
|
+
// Create test data
|
|
182
|
+
const sessionId = 'session1';
|
|
183
|
+
await serverDB.insert(sessions).values([{ id: sessionId, userId }]);
|
|
184
|
+
const topicId = 'topic1';
|
|
185
|
+
await serverDB.insert(topics).values([
|
|
186
|
+
{ id: topicId, sessionId, userId },
|
|
187
|
+
{ id: 'topic2', sessionId, userId },
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
await serverDB.insert(messages).values([
|
|
191
|
+
{ id: '1', userId, role: 'user', topicId, content: '1', createdAt: new Date('2022-04-01') },
|
|
192
|
+
{ id: '2', userId, role: 'user', topicId, content: '2', createdAt: new Date('2023-02-01') },
|
|
193
|
+
{ id: '3', userId, role: 'user', topicId: 'topic2', content: 'message 3' },
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
// Test filtering by topicId
|
|
197
|
+
const result = await messageModel.query({ topicId });
|
|
198
|
+
expect(result).toHaveLength(2);
|
|
199
|
+
expect(result[0].id).toBe('1');
|
|
200
|
+
expect(result[1].id).toBe('2');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should filter messages by groupId and expose group metadata', async () => {
|
|
204
|
+
await serverDB.transaction(async (trx) => {
|
|
205
|
+
await trx.insert(chatGroups).values([
|
|
206
|
+
{ id: 'group-1', userId, title: 'Group 1' },
|
|
207
|
+
{ id: 'group-2', userId, title: 'Group 2' },
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
await trx.insert(agents).values([
|
|
211
|
+
{ id: 'agent-group', userId, title: 'Agent Group' },
|
|
212
|
+
{ id: 'agent-other', userId, title: 'Agent Other' },
|
|
213
|
+
]);
|
|
214
|
+
|
|
215
|
+
await trx.insert(messages).values([
|
|
216
|
+
{
|
|
217
|
+
id: 'group-message',
|
|
218
|
+
userId,
|
|
219
|
+
role: 'assistant',
|
|
220
|
+
content: 'group message',
|
|
221
|
+
groupId: 'group-1',
|
|
222
|
+
agentId: 'agent-group',
|
|
223
|
+
targetId: 'user',
|
|
224
|
+
createdAt: new Date('2024-01-01'),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'other-message',
|
|
228
|
+
userId,
|
|
229
|
+
role: 'assistant',
|
|
230
|
+
content: 'other group message',
|
|
231
|
+
groupId: 'group-2',
|
|
232
|
+
agentId: 'agent-other',
|
|
233
|
+
targetId: 'user',
|
|
234
|
+
createdAt: new Date('2024-01-02'),
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const result = await messageModel.query({ groupId: 'group-1' });
|
|
240
|
+
|
|
241
|
+
expect(result).toHaveLength(1);
|
|
242
|
+
expect(result[0].id).toBe('group-message');
|
|
243
|
+
expect(result[0].groupId).toBe('group-1');
|
|
244
|
+
expect(result[0].agentId).toBe('agent-group');
|
|
245
|
+
expect(result[0].targetId).toBe('user');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('should query messages with join', async () => {
|
|
249
|
+
// Create test data
|
|
250
|
+
await serverDB.transaction(async (trx) => {
|
|
251
|
+
await trx.insert(messages).values([
|
|
252
|
+
{
|
|
253
|
+
id: '1',
|
|
254
|
+
userId,
|
|
255
|
+
role: 'user',
|
|
256
|
+
content: 'message 1',
|
|
257
|
+
createdAt: new Date('2023-01-01'),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
id: '2',
|
|
261
|
+
userId,
|
|
262
|
+
role: 'user',
|
|
263
|
+
content: 'message 2',
|
|
264
|
+
createdAt: new Date('2023-02-01'),
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
id: '3',
|
|
268
|
+
userId: otherUserId,
|
|
269
|
+
role: 'user',
|
|
270
|
+
content: 'message 3',
|
|
271
|
+
createdAt: new Date('2023-03-01'),
|
|
272
|
+
},
|
|
273
|
+
]);
|
|
274
|
+
await trx.insert(files).values([
|
|
275
|
+
{ id: 'f-0', url: 'abc', name: 'file-1', userId, fileType: 'image/png', size: 1000 },
|
|
276
|
+
{ id: 'f-1', url: 'abc', name: 'file-1', userId, fileType: 'image/png', size: 100 },
|
|
277
|
+
{ id: 'f-3', url: 'abc', name: 'file-3', userId, fileType: 'image/png', size: 400 },
|
|
278
|
+
]);
|
|
279
|
+
await trx.insert(messageTTS).values([
|
|
280
|
+
{ id: '1', userId },
|
|
281
|
+
{ id: '2', voice: 'a', fileId: 'f-1', contentMd5: 'abc', userId },
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
await trx.insert(messagesFiles).values([
|
|
285
|
+
{ fileId: 'f-0', messageId: '1', userId },
|
|
286
|
+
{ fileId: 'f-3', messageId: '1', userId },
|
|
287
|
+
]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const domain = 'http://abc.com';
|
|
291
|
+
// Call query method
|
|
292
|
+
const result = await messageModel.query(
|
|
293
|
+
{},
|
|
294
|
+
{ postProcessUrl: async (path) => `${domain}/${path}` },
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Assert result
|
|
298
|
+
expect(result).toHaveLength(2);
|
|
299
|
+
expect(result[0].id).toBe('1');
|
|
300
|
+
expect(result[0].imageList).toEqual([
|
|
301
|
+
{ alt: 'file-1', id: 'f-0', url: `${domain}/abc` },
|
|
302
|
+
{ alt: 'file-3', id: 'f-3', url: `${domain}/abc` },
|
|
303
|
+
]);
|
|
304
|
+
|
|
305
|
+
expect(result[1].id).toBe('2');
|
|
306
|
+
expect(result[1].imageList).toEqual([]);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should include translate, tts and other extra fields in query result', async () => {
|
|
310
|
+
// Create test data
|
|
311
|
+
await serverDB.transaction(async (trx) => {
|
|
312
|
+
await trx.insert(messages).values([
|
|
313
|
+
{
|
|
314
|
+
id: '1',
|
|
315
|
+
userId,
|
|
316
|
+
role: 'user',
|
|
317
|
+
content: 'message 1',
|
|
318
|
+
createdAt: new Date('2023-01-01'),
|
|
319
|
+
},
|
|
320
|
+
]);
|
|
321
|
+
await trx
|
|
322
|
+
.insert(messageTranslates)
|
|
323
|
+
.values([{ id: '1', content: 'translated', from: 'en', to: 'zh', userId }]);
|
|
324
|
+
await trx
|
|
325
|
+
.insert(messageTTS)
|
|
326
|
+
.values([{ id: '1', voice: 'voice1', fileId: 'f1', contentMd5: 'md5', userId }]);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Call query method
|
|
330
|
+
const result = await messageModel.query();
|
|
331
|
+
|
|
332
|
+
// Assert result
|
|
333
|
+
expect(result[0].extra!.translate).toEqual({ content: 'translated', from: 'en', to: 'zh' });
|
|
334
|
+
expect(result[0].extra!.tts).toEqual({
|
|
335
|
+
contentMd5: 'md5',
|
|
336
|
+
file: 'f1',
|
|
337
|
+
voice: 'voice1',
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should handle edge cases of pagination parameters', async () => {
|
|
342
|
+
// Create test data
|
|
343
|
+
await serverDB.insert(messages).values([
|
|
344
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
345
|
+
{ id: '2', userId, role: 'user', content: 'message 2' },
|
|
346
|
+
{ id: '3', userId, role: 'user', content: 'message 3' },
|
|
347
|
+
]);
|
|
348
|
+
|
|
349
|
+
// 测试 current 和 pageSize 的边界情况
|
|
350
|
+
const result1 = await messageModel.query({ current: 0, pageSize: 2 });
|
|
351
|
+
expect(result1).toHaveLength(2);
|
|
352
|
+
|
|
353
|
+
const result2 = await messageModel.query({ current: 1, pageSize: 2 });
|
|
354
|
+
expect(result2).toHaveLength(1);
|
|
355
|
+
|
|
356
|
+
const result3 = await messageModel.query({ current: 2, pageSize: 2 });
|
|
357
|
+
expect(result3).toHaveLength(0);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe('query with messageQueries', () => {
|
|
361
|
+
it('should include ragQuery, ragQueryId and ragRawQuery in query results', async () => {
|
|
362
|
+
// Create test data
|
|
363
|
+
const messageId = 'msg-with-query';
|
|
364
|
+
const queryId = uuid();
|
|
365
|
+
|
|
366
|
+
await serverDB.insert(messages).values({
|
|
367
|
+
id: messageId,
|
|
368
|
+
userId,
|
|
369
|
+
role: 'user',
|
|
370
|
+
content: 'test message',
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
await serverDB.insert(messageQueries).values({
|
|
374
|
+
id: queryId,
|
|
375
|
+
messageId,
|
|
376
|
+
userQuery: 'original query',
|
|
377
|
+
rewriteQuery: 'rewritten query',
|
|
378
|
+
userId,
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Call query method
|
|
382
|
+
const result = await messageModel.query();
|
|
383
|
+
|
|
384
|
+
// Assert result
|
|
385
|
+
expect(result).toHaveLength(1);
|
|
386
|
+
expect(result[0].id).toBe(messageId);
|
|
387
|
+
expect(result[0].ragQueryId).toBe(queryId);
|
|
388
|
+
expect(result[0].ragQuery).toBe('rewritten query');
|
|
389
|
+
expect(result[0].ragRawQuery).toBe('original query');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it.skip('should handle multiple message queries for the same message', async () => {
|
|
393
|
+
// Create test data
|
|
394
|
+
const messageId = 'msg-multi-query';
|
|
395
|
+
const queryId1 = uuid();
|
|
396
|
+
const queryId2 = uuid();
|
|
397
|
+
|
|
398
|
+
await serverDB.insert(messages).values({
|
|
399
|
+
id: messageId,
|
|
400
|
+
userId,
|
|
401
|
+
role: 'user',
|
|
402
|
+
content: 'test message',
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// 创建两个查询,但查询结果应该只包含一个(最新的)
|
|
406
|
+
await serverDB.insert(messageQueries).values([
|
|
407
|
+
{
|
|
408
|
+
id: queryId1,
|
|
409
|
+
messageId,
|
|
410
|
+
userQuery: 'original query 1',
|
|
411
|
+
rewriteQuery: 'rewritten query 1',
|
|
412
|
+
userId,
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
id: queryId2,
|
|
416
|
+
messageId,
|
|
417
|
+
userQuery: 'original query 2',
|
|
418
|
+
rewriteQuery: 'rewritten query 2',
|
|
419
|
+
userId,
|
|
420
|
+
},
|
|
421
|
+
]);
|
|
422
|
+
|
|
423
|
+
// Call query method
|
|
424
|
+
const result = await messageModel.query();
|
|
425
|
+
|
|
426
|
+
// Assert result - 应该只包含最新的查询
|
|
427
|
+
expect(result).toHaveLength(1);
|
|
428
|
+
expect(result[0].id).toBe(messageId);
|
|
429
|
+
expect(result[0].ragQueryId).toBe(queryId2);
|
|
430
|
+
expect(result[0].ragQuery).toBe('rewritten query 2');
|
|
431
|
+
expect(result[0].ragRawQuery).toBe('original query 2');
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should handle complex query with multiple joins and file chunks', async () => {
|
|
436
|
+
await serverDB.transaction(async (trx) => {
|
|
437
|
+
const chunk1Id = uuid();
|
|
438
|
+
const query1Id = uuid();
|
|
439
|
+
// 创建基础消息
|
|
440
|
+
await trx.insert(messages).values({
|
|
441
|
+
id: 'msg1',
|
|
442
|
+
userId,
|
|
443
|
+
role: 'user',
|
|
444
|
+
content: 'test message',
|
|
445
|
+
createdAt: new Date('2023-01-01'),
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// 创建文件
|
|
449
|
+
await trx.insert(files).values([
|
|
450
|
+
{
|
|
451
|
+
id: 'file1',
|
|
452
|
+
userId,
|
|
453
|
+
name: 'test.txt',
|
|
454
|
+
url: 'test-url',
|
|
455
|
+
fileType: 'text/plain',
|
|
456
|
+
size: 100,
|
|
457
|
+
},
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
// 创建文件块
|
|
461
|
+
await trx.insert(chunks).values({
|
|
462
|
+
id: chunk1Id,
|
|
463
|
+
text: 'chunk content',
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// 关联消息和文件
|
|
467
|
+
await trx.insert(messagesFiles).values({
|
|
468
|
+
messageId: 'msg1',
|
|
469
|
+
userId,
|
|
470
|
+
fileId: 'file1',
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// 创建文件块关联
|
|
474
|
+
await trx.insert(fileChunks).values({
|
|
475
|
+
fileId: 'file1',
|
|
476
|
+
userId,
|
|
477
|
+
chunkId: chunk1Id,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// 创建消息查询
|
|
481
|
+
await trx.insert(messageQueries).values({
|
|
482
|
+
id: query1Id,
|
|
483
|
+
messageId: 'msg1',
|
|
484
|
+
userId,
|
|
485
|
+
userQuery: 'original query',
|
|
486
|
+
rewriteQuery: 'rewritten query',
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// 创建消息查询块关联
|
|
490
|
+
await trx.insert(messageQueryChunks).values({
|
|
491
|
+
messageId: 'msg1',
|
|
492
|
+
queryId: query1Id,
|
|
493
|
+
chunkId: chunk1Id,
|
|
494
|
+
similarity: '0.95',
|
|
495
|
+
userId,
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const result = await messageModel.query();
|
|
500
|
+
|
|
501
|
+
expect(result).toHaveLength(1);
|
|
502
|
+
expect(result[0].chunksList).toHaveLength(1);
|
|
503
|
+
expect(result[0].chunksList![0]).toMatchObject({
|
|
504
|
+
text: 'chunk content',
|
|
505
|
+
similarity: 0.95,
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should return empty arrays for files and chunks if none exist', async () => {
|
|
510
|
+
await serverDB.insert(messages).values({
|
|
511
|
+
id: 'msg1',
|
|
512
|
+
userId,
|
|
513
|
+
role: 'user',
|
|
514
|
+
content: 'test message',
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const result = await messageModel.query();
|
|
518
|
+
|
|
519
|
+
expect(result).toHaveLength(1);
|
|
520
|
+
expect(result[0].fileList).toEqual([]);
|
|
521
|
+
expect(result[0].imageList).toEqual([]);
|
|
522
|
+
expect(result[0].chunksList).toEqual([]);
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should query messages in session with null topicId (only non-topic messages)', async () => {
|
|
526
|
+
await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
|
|
527
|
+
await serverDB.insert(topics).values([
|
|
528
|
+
{ id: 'topic1', sessionId: 'session1', userId },
|
|
529
|
+
{ id: 'topic2', sessionId: 'session1', userId },
|
|
530
|
+
]);
|
|
531
|
+
|
|
532
|
+
await serverDB.insert(messages).values([
|
|
533
|
+
{
|
|
534
|
+
id: 'msg-no-topic-1',
|
|
535
|
+
userId,
|
|
536
|
+
sessionId: 'session1',
|
|
537
|
+
topicId: null,
|
|
538
|
+
role: 'user',
|
|
539
|
+
content: 'message without topic 1',
|
|
540
|
+
createdAt: new Date('2023-01-01'),
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
id: 'msg-no-topic-2',
|
|
544
|
+
userId,
|
|
545
|
+
sessionId: 'session1',
|
|
546
|
+
topicId: null,
|
|
547
|
+
role: 'assistant',
|
|
548
|
+
content: 'message without topic 2',
|
|
549
|
+
createdAt: new Date('2023-01-02'),
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
id: 'msg-topic1',
|
|
553
|
+
userId,
|
|
554
|
+
sessionId: 'session1',
|
|
555
|
+
topicId: 'topic1',
|
|
556
|
+
role: 'user',
|
|
557
|
+
content: 'message in topic1',
|
|
558
|
+
createdAt: new Date('2023-01-03'),
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: 'msg-topic2',
|
|
562
|
+
userId,
|
|
563
|
+
sessionId: 'session1',
|
|
564
|
+
topicId: 'topic2',
|
|
565
|
+
role: 'assistant',
|
|
566
|
+
content: 'message in topic2',
|
|
567
|
+
createdAt: new Date('2023-01-04'),
|
|
568
|
+
},
|
|
569
|
+
]);
|
|
570
|
+
|
|
571
|
+
// Query with explicit null topicId should return only non-topic messages
|
|
572
|
+
const result = await messageModel.query({ sessionId: 'session1', topicId: null });
|
|
573
|
+
|
|
574
|
+
expect(result).toHaveLength(2);
|
|
575
|
+
expect(result[0].id).toBe('msg-no-topic-1');
|
|
576
|
+
expect(result[1].id).toBe('msg-no-topic-2');
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it('should query messages in session with null groupId (only non-group messages)', async () => {
|
|
580
|
+
await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
|
|
581
|
+
await serverDB.insert(chatGroups).values([
|
|
582
|
+
{ id: 'group1', userId, title: 'Group 1' },
|
|
583
|
+
{ id: 'group2', userId, title: 'Group 2' },
|
|
584
|
+
]);
|
|
585
|
+
|
|
586
|
+
await serverDB.insert(messages).values([
|
|
587
|
+
{
|
|
588
|
+
id: 'msg-no-group-1',
|
|
589
|
+
userId,
|
|
590
|
+
sessionId: 'session1',
|
|
591
|
+
groupId: null,
|
|
592
|
+
role: 'user',
|
|
593
|
+
content: 'message without group 1',
|
|
594
|
+
createdAt: new Date('2023-01-01'),
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
id: 'msg-no-group-2',
|
|
598
|
+
userId,
|
|
599
|
+
sessionId: 'session1',
|
|
600
|
+
groupId: null,
|
|
601
|
+
role: 'assistant',
|
|
602
|
+
content: 'message without group 2',
|
|
603
|
+
createdAt: new Date('2023-01-02'),
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
id: 'msg-group1',
|
|
607
|
+
userId,
|
|
608
|
+
sessionId: 'session1',
|
|
609
|
+
groupId: 'group1',
|
|
610
|
+
role: 'user',
|
|
611
|
+
content: 'message in group1',
|
|
612
|
+
createdAt: new Date('2023-01-03'),
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
id: 'msg-group2',
|
|
616
|
+
userId,
|
|
617
|
+
sessionId: 'session1',
|
|
618
|
+
groupId: 'group2',
|
|
619
|
+
role: 'assistant',
|
|
620
|
+
content: 'message in group2',
|
|
621
|
+
createdAt: new Date('2023-01-04'),
|
|
622
|
+
},
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
// Query with explicit null groupId should return only non-group messages
|
|
626
|
+
const result = await messageModel.query({ sessionId: 'session1', groupId: null });
|
|
627
|
+
|
|
628
|
+
expect(result).toHaveLength(2);
|
|
629
|
+
expect(result[0].id).toBe('msg-no-group-1');
|
|
630
|
+
expect(result[1].id).toBe('msg-no-group-2');
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should query inbox messages with null topicId when no sessionId specified', async () => {
|
|
634
|
+
await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
|
|
635
|
+
await serverDB.insert(topics).values([{ id: 'topic1', sessionId: 'session1', userId }]);
|
|
636
|
+
|
|
637
|
+
await serverDB.insert(messages).values([
|
|
638
|
+
{
|
|
639
|
+
id: 'msg-inbox-no-topic',
|
|
640
|
+
userId,
|
|
641
|
+
sessionId: null, // inbox message
|
|
642
|
+
topicId: null,
|
|
643
|
+
role: 'user',
|
|
644
|
+
content: 'inbox message without topic',
|
|
645
|
+
createdAt: new Date('2023-01-01'),
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: 'msg-session-no-topic',
|
|
649
|
+
userId,
|
|
650
|
+
sessionId: 'session1',
|
|
651
|
+
topicId: null,
|
|
652
|
+
role: 'user',
|
|
653
|
+
content: 'session message without topic',
|
|
654
|
+
createdAt: new Date('2023-01-02'),
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
id: 'msg-session-with-topic',
|
|
658
|
+
userId,
|
|
659
|
+
sessionId: 'session1',
|
|
660
|
+
topicId: 'topic1',
|
|
661
|
+
role: 'user',
|
|
662
|
+
content: 'session message with topic',
|
|
663
|
+
createdAt: new Date('2023-01-03'),
|
|
664
|
+
},
|
|
665
|
+
]);
|
|
666
|
+
|
|
667
|
+
// When no sessionId specified (defaults to inbox), query with topicId null
|
|
668
|
+
// should return only inbox messages without topics
|
|
669
|
+
const result = await messageModel.query({ topicId: null });
|
|
670
|
+
|
|
671
|
+
expect(result).toHaveLength(1);
|
|
672
|
+
expect(result[0].id).toBe('msg-inbox-no-topic');
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should query messages with combined sessionId and topicId filters', async () => {
|
|
676
|
+
await serverDB.insert(sessions).values([
|
|
677
|
+
{ id: 'session1', userId },
|
|
678
|
+
{ id: 'session2', userId },
|
|
679
|
+
]);
|
|
680
|
+
await serverDB.insert(topics).values([
|
|
681
|
+
{ id: 'topic1', sessionId: 'session1', userId },
|
|
682
|
+
{ id: 'topic2', sessionId: 'session1', userId },
|
|
683
|
+
]);
|
|
684
|
+
|
|
685
|
+
await serverDB.insert(messages).values([
|
|
686
|
+
{
|
|
687
|
+
id: 'msg-s1-t1',
|
|
688
|
+
userId,
|
|
689
|
+
sessionId: 'session1',
|
|
690
|
+
topicId: 'topic1',
|
|
691
|
+
role: 'user',
|
|
692
|
+
content: 'session1 topic1',
|
|
693
|
+
createdAt: new Date('2023-01-01'),
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
id: 'msg-s1-t2',
|
|
697
|
+
userId,
|
|
698
|
+
sessionId: 'session1',
|
|
699
|
+
topicId: 'topic2',
|
|
700
|
+
role: 'user',
|
|
701
|
+
content: 'session1 topic2',
|
|
702
|
+
createdAt: new Date('2023-01-02'),
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: 'msg-s2',
|
|
706
|
+
userId,
|
|
707
|
+
sessionId: 'session2',
|
|
708
|
+
topicId: null,
|
|
709
|
+
role: 'user',
|
|
710
|
+
content: 'session2 no topic',
|
|
711
|
+
createdAt: new Date('2023-01-03'),
|
|
712
|
+
},
|
|
713
|
+
]);
|
|
714
|
+
|
|
715
|
+
// Query specific session and topic combination
|
|
716
|
+
const result = await messageModel.query({ sessionId: 'session1', topicId: 'topic1' });
|
|
717
|
+
|
|
718
|
+
expect(result).toHaveLength(1);
|
|
719
|
+
expect(result[0].id).toBe('msg-s1-t1');
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
describe('queryAll', () => {
|
|
724
|
+
it('should return all messages belonging to the user in ascending order', async () => {
|
|
725
|
+
// Create test data
|
|
726
|
+
await serverDB.insert(messages).values([
|
|
727
|
+
{
|
|
728
|
+
id: '1',
|
|
729
|
+
userId,
|
|
730
|
+
role: 'user',
|
|
731
|
+
content: 'message 1',
|
|
732
|
+
createdAt: new Date('2023-01-01'),
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id: '2',
|
|
736
|
+
userId,
|
|
737
|
+
role: 'user',
|
|
738
|
+
content: 'message 2',
|
|
739
|
+
createdAt: new Date('2023-02-01'),
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: '3',
|
|
743
|
+
userId: otherUserId,
|
|
744
|
+
role: 'user',
|
|
745
|
+
content: 'message 3',
|
|
746
|
+
createdAt: new Date('2023-03-01'),
|
|
747
|
+
},
|
|
748
|
+
]);
|
|
749
|
+
|
|
750
|
+
// Call queryAll method
|
|
751
|
+
const result = await messageModel.queryAll();
|
|
752
|
+
|
|
753
|
+
// Assert result
|
|
754
|
+
expect(result).toHaveLength(2);
|
|
755
|
+
expect(result[0].id).toBe('1');
|
|
756
|
+
expect(result[1].id).toBe('2');
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
describe('findById', () => {
|
|
761
|
+
it('should find message by ID', async () => {
|
|
762
|
+
// Create test data
|
|
763
|
+
await serverDB.insert(messages).values([
|
|
764
|
+
{ id: '1', userId, role: 'user', content: 'message 1' },
|
|
765
|
+
{ id: '2', userId: otherUserId, role: 'user', content: 'message 2' },
|
|
766
|
+
]);
|
|
767
|
+
|
|
768
|
+
// Call findById method
|
|
769
|
+
const result = await messageModel.findById('1');
|
|
770
|
+
|
|
771
|
+
// Assert result
|
|
772
|
+
expect(result?.id).toBe('1');
|
|
773
|
+
expect(result?.content).toBe('message 1');
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('should return undefined if message does not belong to user', async () => {
|
|
777
|
+
// Create test data
|
|
778
|
+
await serverDB
|
|
779
|
+
.insert(messages)
|
|
780
|
+
.values([{ id: '1', userId: otherUserId, role: 'user', content: 'message 1' }]);
|
|
781
|
+
|
|
782
|
+
// Call findById method
|
|
783
|
+
const result = await messageModel.findById('1');
|
|
784
|
+
|
|
785
|
+
// Assert result
|
|
786
|
+
expect(result).toBeUndefined();
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
describe('queryBySessionId', () => {
|
|
791
|
+
it('should query messages by sessionId', async () => {
|
|
792
|
+
// Create test data
|
|
793
|
+
const sessionId = 'session1';
|
|
794
|
+
await serverDB.insert(sessions).values([
|
|
795
|
+
{ id: 'session1', userId },
|
|
796
|
+
{ id: 'session2', userId },
|
|
797
|
+
]);
|
|
798
|
+
await serverDB.insert(messages).values([
|
|
799
|
+
{
|
|
800
|
+
id: '1',
|
|
801
|
+
userId,
|
|
802
|
+
role: 'user',
|
|
803
|
+
sessionId,
|
|
804
|
+
content: 'message 1',
|
|
805
|
+
createdAt: new Date('2022-01-01'),
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
id: '2',
|
|
809
|
+
userId,
|
|
810
|
+
role: 'user',
|
|
811
|
+
sessionId,
|
|
812
|
+
content: 'message 2',
|
|
813
|
+
createdAt: new Date('2023-02-01'),
|
|
814
|
+
},
|
|
815
|
+
{ id: '3', userId, role: 'user', sessionId: 'session2', content: 'message 3' },
|
|
816
|
+
]);
|
|
817
|
+
|
|
818
|
+
// Call queryBySessionId method
|
|
819
|
+
const result = await messageModel.queryBySessionId(sessionId);
|
|
820
|
+
|
|
821
|
+
// Assert result
|
|
822
|
+
expect(result).toHaveLength(2);
|
|
823
|
+
expect(result[0].id).toBe('1');
|
|
824
|
+
expect(result[1].id).toBe('2');
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
it('should query inbox messages when sessionId is null', async () => {
|
|
828
|
+
await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
|
|
829
|
+
|
|
830
|
+
await serverDB.insert(messages).values([
|
|
831
|
+
{
|
|
832
|
+
id: 'inbox-msg-1',
|
|
833
|
+
userId,
|
|
834
|
+
sessionId: null, // inbox message
|
|
835
|
+
role: 'user',
|
|
836
|
+
content: 'inbox message 1',
|
|
837
|
+
createdAt: new Date('2023-01-01'),
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
id: 'inbox-msg-2',
|
|
841
|
+
userId,
|
|
842
|
+
sessionId: null, // inbox message
|
|
843
|
+
role: 'assistant',
|
|
844
|
+
content: 'inbox message 2',
|
|
845
|
+
createdAt: new Date('2023-01-02'),
|
|
846
|
+
},
|
|
847
|
+
{
|
|
848
|
+
id: 'session-msg',
|
|
849
|
+
userId,
|
|
850
|
+
sessionId: 'session1',
|
|
851
|
+
role: 'user',
|
|
852
|
+
content: 'session message',
|
|
853
|
+
createdAt: new Date('2023-01-03'),
|
|
854
|
+
},
|
|
855
|
+
]);
|
|
856
|
+
|
|
857
|
+
// Query with null sessionId should return only inbox messages
|
|
858
|
+
const result = await messageModel.queryBySessionId(null);
|
|
859
|
+
|
|
860
|
+
expect(result).toHaveLength(2);
|
|
861
|
+
expect(result[0].id).toBe('inbox-msg-1');
|
|
862
|
+
expect(result[1].id).toBe('inbox-msg-2');
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('should query inbox messages when sessionId is undefined', async () => {
|
|
866
|
+
await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
|
|
867
|
+
|
|
868
|
+
await serverDB.insert(messages).values([
|
|
869
|
+
{
|
|
870
|
+
id: 'inbox-msg',
|
|
871
|
+
userId,
|
|
872
|
+
sessionId: null,
|
|
873
|
+
role: 'user',
|
|
874
|
+
content: 'inbox message',
|
|
875
|
+
createdAt: new Date('2023-01-01'),
|
|
876
|
+
},
|
|
877
|
+
{
|
|
878
|
+
id: 'session-msg',
|
|
879
|
+
userId,
|
|
880
|
+
sessionId: 'session1',
|
|
881
|
+
role: 'user',
|
|
882
|
+
content: 'session message',
|
|
883
|
+
createdAt: new Date('2023-01-02'),
|
|
884
|
+
},
|
|
885
|
+
]);
|
|
886
|
+
|
|
887
|
+
// Query with undefined sessionId should also return inbox messages
|
|
888
|
+
const result = await messageModel.queryBySessionId(undefined);
|
|
889
|
+
|
|
890
|
+
expect(result).toHaveLength(1);
|
|
891
|
+
expect(result[0].id).toBe('inbox-msg');
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
describe('queryByKeyWord', () => {
|
|
896
|
+
it('should query messages by keyword', async () => {
|
|
897
|
+
// Create test data
|
|
898
|
+
await serverDB.insert(messages).values([
|
|
899
|
+
{ id: '1', userId, role: 'user', content: 'apple', createdAt: new Date('2022-02-01') },
|
|
900
|
+
{ id: '2', userId, role: 'user', content: 'banana' },
|
|
901
|
+
{ id: '3', userId, role: 'user', content: 'pear' },
|
|
902
|
+
{ id: '4', userId, role: 'user', content: 'apple pie', createdAt: new Date('2024-02-01') },
|
|
903
|
+
]);
|
|
904
|
+
|
|
905
|
+
// Test querying messages with specific keyword
|
|
906
|
+
const result = await messageModel.queryByKeyword('apple');
|
|
907
|
+
|
|
908
|
+
// Assert result
|
|
909
|
+
expect(result).toHaveLength(2);
|
|
910
|
+
expect(result[0].id).toBe('4');
|
|
911
|
+
expect(result[1].id).toBe('1');
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
it('should return empty array when keyword is empty', async () => {
|
|
915
|
+
// Create test data
|
|
916
|
+
await serverDB.insert(messages).values([
|
|
917
|
+
{ id: '1', userId, role: 'user', content: 'apple' },
|
|
918
|
+
{ id: '2', userId, role: 'user', content: 'banana' },
|
|
919
|
+
{ id: '3', userId, role: 'user', content: 'pear' },
|
|
920
|
+
{ id: '4', userId, role: 'user', content: 'apple pie' },
|
|
921
|
+
]);
|
|
922
|
+
|
|
923
|
+
// Test returning empty array when keyword is empty
|
|
924
|
+
const result = await messageModel.queryByKeyword('');
|
|
925
|
+
|
|
926
|
+
// Assert result
|
|
927
|
+
expect(result).toHaveLength(0);
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
describe('findMessageQueriesById', () => {
|
|
932
|
+
it('should return undefined for non-existent message query', async () => {
|
|
933
|
+
const result = await messageModel.findMessageQueriesById('non-existent-id');
|
|
934
|
+
expect(result).toBeUndefined();
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
it('should return message query with embeddings', async () => {
|
|
938
|
+
const query1Id = uuid();
|
|
939
|
+
const embeddings1Id = uuid();
|
|
940
|
+
|
|
941
|
+
await serverDB.transaction(async (trx) => {
|
|
942
|
+
await trx.insert(messages).values({ id: 'msg1', userId, role: 'user', content: 'abc' });
|
|
943
|
+
|
|
944
|
+
await trx.insert(embeddings).values({
|
|
945
|
+
id: embeddings1Id,
|
|
946
|
+
embeddings: codeEmbedding,
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
await trx.insert(messageQueries).values({
|
|
950
|
+
id: query1Id,
|
|
951
|
+
messageId: 'msg1',
|
|
952
|
+
userQuery: 'test query',
|
|
953
|
+
rewriteQuery: 'rewritten query',
|
|
954
|
+
embeddingsId: embeddings1Id,
|
|
955
|
+
userId,
|
|
956
|
+
});
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
const result = await messageModel.findMessageQueriesById('msg1');
|
|
960
|
+
|
|
961
|
+
expect(result).toBeDefined();
|
|
962
|
+
expect(result).toMatchObject({
|
|
963
|
+
id: query1Id,
|
|
964
|
+
userQuery: 'test query',
|
|
965
|
+
rewriteQuery: 'rewritten query',
|
|
966
|
+
embeddings: codeEmbedding,
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
});
|
|
970
|
+
});
|