@lobehub/lobehub 2.0.0-next.41 → 2.0.0-next.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.42](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.41...v2.0.0-next.42)
6
+
7
+ <sup>Released on **2025-11-09**</sup>
8
+
9
+ #### 🐛 Bug Fixes
10
+
11
+ - **misc**: Fix missing messages when finish runtime.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### What's fixed
19
+
20
+ - **misc**: Fix missing messages when finish runtime, closes [#10138](https://github.com/lobehub/lobe-chat/issues/10138) ([b94d477](https://github.com/lobehub/lobe-chat/commit/b94d477))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
5
30
  ## [Version 2.0.0-next.41](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.40...v2.0.0-next.41)
6
31
 
7
32
  <sup>Released on **2025-11-09**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "fixes": [
5
+ "Fix missing messages when finish runtime."
6
+ ]
7
+ },
8
+ "date": "2025-11-09",
9
+ "version": "2.0.0-next.42"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "improvements": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.41",
3
+ "version": "2.0.0-next.42",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
5
  import { uuid } from '@/utils/uuid';
6
6
 
7
7
  import {
8
+ chatGroups,
8
9
  chunks,
9
10
  embeddings,
10
11
  files,
@@ -224,31 +225,53 @@ describe('MessageModel Create Tests', () => {
224
225
  expect(dbResult[0].id).toBe(customId);
225
226
  });
226
227
 
227
- it.skip('should create a message with file chunks and RAG query ID', async () => {
228
- // Create test data
228
+ it('should create a message with file chunks and RAG query ID', async () => {
229
+ // Create test data following proper order: message -> query -> message with chunks
229
230
  const chunkId1 = uuid();
230
231
  const chunkId2 = uuid();
231
- const ragQueryId = uuid();
232
+ const firstMessageId = uuid();
233
+ const secondMessageId = uuid();
232
234
 
235
+ // 1. Create chunks first
233
236
  await serverDB.insert(chunks).values([
234
- { id: chunkId1, text: 'chunk text 1' },
235
- { id: chunkId2, text: 'chunk text 2' },
237
+ { id: chunkId1, text: 'chunk text 1', userId },
238
+ { id: chunkId2, text: 'chunk text 2', userId },
236
239
  ]);
237
240
 
238
- // Call create method
239
- const result = await messageModel.create({
240
- role: 'assistant',
241
- content: 'message with file chunks',
242
- fileChunks: [
243
- { id: chunkId1, similarity: 0.95 },
244
- { id: chunkId2, similarity: 0.85 },
245
- ],
246
- ragQueryId,
241
+ // 2. Create first message (required for messageQuery FK)
242
+ await serverDB.insert(messages).values({
243
+ id: firstMessageId,
244
+ userId,
245
+ role: 'user',
246
+ content: 'user query',
247
247
  sessionId: '1',
248
248
  });
249
249
 
250
+ // 3. Create message query linked to first message
251
+ const messageQuery = await messageModel.createMessageQuery({
252
+ messageId: firstMessageId,
253
+ rewriteQuery: 'test query',
254
+ userQuery: 'original query',
255
+ embeddingsId,
256
+ });
257
+
258
+ // 4. Create second message with file chunks referencing the query
259
+ const result = await messageModel.create(
260
+ {
261
+ role: 'assistant',
262
+ content: 'message with file chunks',
263
+ fileChunks: [
264
+ { id: chunkId1, similarity: 0.95 },
265
+ { id: chunkId2, similarity: 0.85 },
266
+ ],
267
+ ragQueryId: messageQuery.id,
268
+ sessionId: '1',
269
+ },
270
+ secondMessageId,
271
+ );
272
+
250
273
  // Verify message created successfully
251
- expect(result.id).toBeDefined();
274
+ expect(result.id).toBe(secondMessageId);
252
275
 
253
276
  // Verify message query chunk associations created successfully
254
277
  const queryChunks = await serverDB
@@ -258,10 +281,10 @@ describe('MessageModel Create Tests', () => {
258
281
 
259
282
  expect(queryChunks).toHaveLength(2);
260
283
  expect(queryChunks[0].chunkId).toBe(chunkId1);
261
- expect(queryChunks[0].queryId).toBe(ragQueryId);
262
- expect(queryChunks[0].similarity).toBe('0.95');
284
+ expect(queryChunks[0].queryId).toBe(messageQuery.id);
285
+ expect(queryChunks[0].similarity).toBe('0.95000');
263
286
  expect(queryChunks[1].chunkId).toBe(chunkId2);
264
- expect(queryChunks[1].similarity).toBe('0.85');
287
+ expect(queryChunks[1].similarity).toBe('0.85000');
265
288
  });
266
289
 
267
290
  it('should create a message with files', async () => {
@@ -350,6 +373,40 @@ describe('MessageModel Create Tests', () => {
350
373
  expect(result[0].content).toBe('message 1');
351
374
  expect(result[1].content).toBe('message 2');
352
375
  });
376
+
377
+ it('should handle messages with and without groupId', async () => {
378
+ await serverDB.insert(sessions).values({ id: 'session1', userId });
379
+ await serverDB.insert(chatGroups).values({ id: 'group1', userId, title: 'Group 1' });
380
+
381
+ // Message without groupId - should keep sessionId
382
+ const msgWithoutGroup = await messageModel.create({
383
+ role: 'user',
384
+ content: 'message without group',
385
+ sessionId: 'session1',
386
+ });
387
+
388
+ // Message with groupId - sessionId should be set to null
389
+ const msgWithGroup = await messageModel.create({
390
+ role: 'user',
391
+ content: 'message with group',
392
+ sessionId: 'session1',
393
+ groupId: 'group1',
394
+ });
395
+
396
+ // Verify from database
397
+ const dbMsgWithoutGroup = await serverDB.query.messages.findFirst({
398
+ where: eq(messages.id, msgWithoutGroup.id),
399
+ });
400
+ const dbMsgWithGroup = await serverDB.query.messages.findFirst({
401
+ where: eq(messages.id, msgWithGroup.id),
402
+ });
403
+
404
+ expect(dbMsgWithoutGroup?.sessionId).toBe('session1');
405
+ expect(dbMsgWithoutGroup?.groupId).toBeNull();
406
+
407
+ expect(dbMsgWithGroup?.sessionId).toBeNull();
408
+ expect(dbMsgWithGroup?.groupId).toBe('group1');
409
+ });
353
410
  });
354
411
 
355
412
  describe('createMessageQuery', () => {
@@ -1,3 +1,4 @@
1
+ import { INBOX_SESSION_ID } from '@lobechat/const';
1
2
  import dayjs from 'dayjs';
2
3
  import { eq } from 'drizzle-orm';
3
4
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
@@ -8,6 +9,7 @@ import {
8
9
  agents,
9
10
  chatGroups,
10
11
  chunks,
12
+ documents,
11
13
  embeddings,
12
14
  fileChunks,
13
15
  files,
@@ -389,7 +391,7 @@ describe('MessageModel Query Tests', () => {
389
391
  expect(result[0].ragRawQuery).toBe('original query');
390
392
  });
391
393
 
392
- it.skip('should handle multiple message queries for the same message', async () => {
394
+ it('should handle multiple message queries for the same message', async () => {
393
395
  // Create test data
394
396
  const messageId = 'msg-multi-query';
395
397
  const queryId1 = uuid();
@@ -402,7 +404,9 @@ describe('MessageModel Query Tests', () => {
402
404
  content: 'test message',
403
405
  });
404
406
 
405
- // 创建两个查询,但查询结果应该只包含一个(最新的)
407
+ // 创建两个查询,查询结果应该只包含其中一个
408
+ // Note: 由于 messageQueries 表没有排序字段,返回哪个 query 是不确定的
409
+ // 但应该只返回一个
406
410
  await serverDB.insert(messageQueries).values([
407
411
  {
408
412
  id: queryId1,
@@ -423,12 +427,13 @@ describe('MessageModel Query Tests', () => {
423
427
  // Call query method
424
428
  const result = await messageModel.query();
425
429
 
426
- // Assert result - 应该只包含最新的查询
430
+ // Assert result - 应该只包含一个查询(具体是哪个取决于数据库实现)
427
431
  expect(result).toHaveLength(1);
428
432
  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');
433
+ // 验证返回的是两个 query 中的一个
434
+ expect([queryId1, queryId2]).toContain(result[0].ragQueryId);
435
+ expect(['rewritten query 1', 'rewritten query 2']).toContain(result[0].ragQuery);
436
+ expect(['original query 1', 'original query 2']).toContain(result[0].ragRawQuery);
432
437
  });
433
438
  });
434
439
 
@@ -506,6 +511,64 @@ describe('MessageModel Query Tests', () => {
506
511
  });
507
512
  });
508
513
 
514
+ it('should handle null similarity in chunks and convert to number', async () => {
515
+ await serverDB.transaction(async (trx) => {
516
+ const chunk1Id = uuid();
517
+ const query1Id = uuid();
518
+
519
+ await trx.insert(messages).values({
520
+ id: 'msg1',
521
+ userId,
522
+ role: 'user',
523
+ content: 'test message',
524
+ });
525
+
526
+ await trx.insert(files).values({
527
+ id: 'file1',
528
+ userId,
529
+ name: 'test.txt',
530
+ url: 'test-url',
531
+ fileType: 'text/plain',
532
+ size: 100,
533
+ });
534
+
535
+ await trx.insert(chunks).values({
536
+ id: chunk1Id,
537
+ text: 'chunk content',
538
+ });
539
+
540
+ await trx.insert(fileChunks).values({
541
+ fileId: 'file1',
542
+ userId,
543
+ chunkId: chunk1Id,
544
+ });
545
+
546
+ await trx.insert(messageQueries).values({
547
+ id: query1Id,
548
+ messageId: 'msg1',
549
+ userId,
550
+ userQuery: 'query',
551
+ rewriteQuery: 'rewritten',
552
+ });
553
+
554
+ // Insert chunk with null similarity
555
+ await trx.insert(messageQueryChunks).values({
556
+ messageId: 'msg1',
557
+ queryId: query1Id,
558
+ chunkId: chunk1Id,
559
+ similarity: null as any,
560
+ userId,
561
+ });
562
+ });
563
+
564
+ const result = await messageModel.query();
565
+
566
+ expect(result).toHaveLength(1);
567
+ expect(result[0].chunksList).toHaveLength(1);
568
+ // null should be converted to undefined
569
+ expect(result[0].chunksList![0].similarity).toBeUndefined();
570
+ });
571
+
509
572
  it('should return empty arrays for files and chunks if none exist', async () => {
510
573
  await serverDB.insert(messages).values({
511
574
  id: 'msg1',
@@ -890,6 +953,44 @@ describe('MessageModel Query Tests', () => {
890
953
  expect(result).toHaveLength(1);
891
954
  expect(result[0].id).toBe('inbox-msg');
892
955
  });
956
+
957
+ it('should query inbox messages when sessionId is INBOX_SESSION_ID', async () => {
958
+ await serverDB.insert(sessions).values([{ id: 'session1', userId }]);
959
+
960
+ await serverDB.insert(messages).values([
961
+ {
962
+ id: 'inbox-msg-1',
963
+ userId,
964
+ sessionId: null,
965
+ role: 'user',
966
+ content: 'inbox message 1',
967
+ createdAt: new Date('2023-01-01'),
968
+ },
969
+ {
970
+ id: 'inbox-msg-2',
971
+ userId,
972
+ sessionId: null,
973
+ role: 'assistant',
974
+ content: 'inbox message 2',
975
+ createdAt: new Date('2023-01-02'),
976
+ },
977
+ {
978
+ id: 'session-msg',
979
+ userId,
980
+ sessionId: 'session1',
981
+ role: 'user',
982
+ content: 'session message',
983
+ createdAt: new Date('2023-01-03'),
984
+ },
985
+ ]);
986
+
987
+ // Query with INBOX_SESSION_ID should return only inbox messages
988
+ const result = await messageModel.queryBySessionId(INBOX_SESSION_ID);
989
+
990
+ expect(result).toHaveLength(2);
991
+ expect(result[0].id).toBe('inbox-msg-1');
992
+ expect(result[1].id).toBe('inbox-msg-2');
993
+ });
893
994
  });
894
995
 
895
996
  describe('queryByKeyWord', () => {
@@ -928,6 +1029,122 @@ describe('MessageModel Query Tests', () => {
928
1029
  });
929
1030
  });
930
1031
 
1032
+ describe('query with files edge cases', () => {
1033
+ it('should handle files with empty fileType', async () => {
1034
+ await serverDB.transaction(async (trx) => {
1035
+ await trx.insert(sessions).values({ id: 'session1', userId });
1036
+
1037
+ // Create files with empty string fileType (tests the || '' branch)
1038
+ await trx.insert(files).values([
1039
+ {
1040
+ id: 'file-empty-type',
1041
+ userId,
1042
+ url: 'unknown.bin',
1043
+ name: 'unknown file',
1044
+ fileType: '',
1045
+ size: 1000,
1046
+ },
1047
+ {
1048
+ id: 'file-image',
1049
+ userId,
1050
+ url: 'image.png',
1051
+ name: 'test image',
1052
+ fileType: 'image/png',
1053
+ size: 2000,
1054
+ },
1055
+ {
1056
+ id: 'file-video',
1057
+ userId,
1058
+ url: 'video.mp4',
1059
+ name: 'test video',
1060
+ fileType: 'video/mp4',
1061
+ size: 3000,
1062
+ },
1063
+ ]);
1064
+
1065
+ const messageId = uuid();
1066
+ await trx.insert(messages).values({
1067
+ id: messageId,
1068
+ userId,
1069
+ role: 'user',
1070
+ content: 'Message with various fileTypes',
1071
+ sessionId: 'session1',
1072
+ });
1073
+
1074
+ await trx.insert(messagesFiles).values([
1075
+ { messageId, fileId: 'file-empty-type', userId },
1076
+ { messageId, fileId: 'file-image', userId },
1077
+ { messageId, fileId: 'file-video', userId },
1078
+ ]);
1079
+ });
1080
+
1081
+ const result = await messageModel.query({ sessionId: 'session1' });
1082
+
1083
+ expect(result).toHaveLength(1);
1084
+ expect(result[0].fileList).toHaveLength(1); // empty fileType should go to fileList
1085
+ expect(result[0].fileList![0].id).toBe('file-empty-type');
1086
+ expect(result[0].imageList).toHaveLength(1);
1087
+ expect(result[0].imageList![0].id).toBe('file-image');
1088
+ expect(result[0].videoList).toHaveLength(1);
1089
+ expect(result[0].videoList![0].id).toBe('file-video');
1090
+ });
1091
+ });
1092
+
1093
+ describe('query with documents', () => {
1094
+ it('should include document content when files have associated documents', async () => {
1095
+ // Create a file with an associated document
1096
+ const fileId = uuid();
1097
+
1098
+ await serverDB.transaction(async (trx) => {
1099
+ await trx.insert(sessions).values({ id: 'session1', userId });
1100
+
1101
+ await trx.insert(files).values({
1102
+ id: fileId,
1103
+ userId,
1104
+ url: 'document.pdf',
1105
+ name: 'test.pdf',
1106
+ fileType: 'application/pdf',
1107
+ size: 5000,
1108
+ });
1109
+
1110
+ await trx.insert(documents).values({
1111
+ fileId,
1112
+ userId,
1113
+ content: 'This is the document content for testing',
1114
+ fileType: 'application/pdf',
1115
+ sourceType: 'file',
1116
+ source: 'document.pdf',
1117
+ totalCharCount: 42,
1118
+ totalLineCount: 1,
1119
+ });
1120
+
1121
+ const messageId = uuid();
1122
+ await trx.insert(messages).values({
1123
+ id: messageId,
1124
+ userId,
1125
+ role: 'user',
1126
+ content: 'Message with document',
1127
+ sessionId: 'session1',
1128
+ });
1129
+
1130
+ await trx.insert(messagesFiles).values({
1131
+ messageId,
1132
+ fileId,
1133
+ userId,
1134
+ });
1135
+ });
1136
+
1137
+ // Query messages - this should trigger the documents processing code
1138
+ const result = await messageModel.query({ sessionId: 'session1' });
1139
+
1140
+ expect(result).toHaveLength(1);
1141
+ expect(result[0].fileList).toBeDefined();
1142
+ expect(result[0].fileList).toHaveLength(1);
1143
+ expect(result[0].fileList![0].id).toBe(fileId);
1144
+ expect(result[0].fileList![0].content).toBe('This is the document content for testing');
1145
+ });
1146
+ });
1147
+
931
1148
  describe('findMessageQueriesById', () => {
932
1149
  it('should return undefined for non-existent message query', async () => {
933
1150
  const result = await messageModel.findMessageQueriesById('non-existent-id');
@@ -223,6 +223,54 @@ describe('MessageModel Statistics Tests', () => {
223
223
  // Assert result
224
224
  expect(result).toEqual(0);
225
225
  });
226
+
227
+ it('should count words with startDate filter', async () => {
228
+ await serverDB.insert(messages).values([
229
+ {
230
+ id: '1',
231
+ userId,
232
+ role: 'user',
233
+ content: 'old message',
234
+ createdAt: new Date('2023-01-01'),
235
+ },
236
+ {
237
+ id: '2',
238
+ userId,
239
+ role: 'user',
240
+ content: 'new message',
241
+ createdAt: new Date('2023-03-01'),
242
+ },
243
+ ]);
244
+
245
+ const result = await messageModel.countWords({ startDate: '2023-02-01' });
246
+
247
+ // Only 'new message' should be counted = 11 characters
248
+ expect(result).toEqual(11);
249
+ });
250
+
251
+ it('should count words with endDate filter', async () => {
252
+ await serverDB.insert(messages).values([
253
+ {
254
+ id: '1',
255
+ userId,
256
+ role: 'user',
257
+ content: 'old message',
258
+ createdAt: new Date('2023-01-01'),
259
+ },
260
+ {
261
+ id: '2',
262
+ userId,
263
+ role: 'user',
264
+ content: 'new message',
265
+ createdAt: new Date('2023-03-01'),
266
+ },
267
+ ]);
268
+
269
+ const result = await messageModel.countWords({ endDate: '2023-02-01' });
270
+
271
+ // Only 'old message' should be counted = 11 characters
272
+ expect(result).toEqual(11);
273
+ });
226
274
  });
227
275
 
228
276
  describe('getHeatmaps', () => {
@@ -379,10 +427,11 @@ describe('MessageModel Statistics Tests', () => {
379
427
  vi.useRealTimers();
380
428
  });
381
429
 
382
- it.skip('should return time count correctly when 19:00 time', async () => {
383
- // 使用固定日期进行测试
430
+ it('should return time count correctly when 19:00 time', async () => {
431
+ // 使用固定日期进行测试,使用本地时间避免时区问题
384
432
  vi.useFakeTimers();
385
- const fixedDate = new Date('2025-04-02T19:00:00Z');
433
+ // Use local time at noon to avoid timezone edge cases
434
+ const fixedDate = new Date('2025-04-02T12:00:00');
386
435
  vi.setSystemTime(fixedDate);
387
436
 
388
437
  const today = dayjs(fixedDate);
@@ -390,28 +439,28 @@ describe('MessageModel Statistics Tests', () => {
390
439
  const oneDayAgoDate = today.subtract(1, 'day').format('YYYY-MM-DD');
391
440
  const todayDate = today.format('YYYY-MM-DD');
392
441
 
393
- // Create test data
442
+ // Create test data using explicit dates to avoid timezone issues
394
443
  await serverDB.insert(messages).values([
395
444
  {
396
445
  id: '1',
397
446
  userId,
398
447
  role: 'user',
399
448
  content: 'message 1',
400
- createdAt: today.subtract(2, 'day').toDate(),
449
+ createdAt: new Date(twoDaysAgoDate + 'T10:00:00'),
401
450
  },
402
451
  {
403
452
  id: '2',
404
453
  userId,
405
454
  role: 'user',
406
455
  content: 'message 2',
407
- createdAt: today.subtract(2, 'day').toDate(),
456
+ createdAt: new Date(twoDaysAgoDate + 'T14:00:00'),
408
457
  },
409
458
  {
410
459
  id: '3',
411
460
  userId,
412
461
  role: 'user',
413
462
  content: 'message 3',
414
- createdAt: today.subtract(1, 'day').toDate(),
463
+ createdAt: new Date(oneDayAgoDate + 'T10:00:00'),
415
464
  },
416
465
  ]);
417
466
 
@@ -367,6 +367,25 @@ describe('MessageModel Update Tests', () => {
367
367
 
368
368
  expect(otherResult).toHaveLength(1);
369
369
  });
370
+
371
+ it('should handle database errors gracefully', async () => {
372
+ // Create test message
373
+ await serverDB.insert(messages).values({
374
+ id: '1',
375
+ content: 'test message',
376
+ role: 'user',
377
+ userId,
378
+ });
379
+
380
+ // Mock database to throw error by trying to update with invalid sessionId reference
381
+ // This should trigger the catch block in the update method
382
+ const result = await messageModel.update('1', {
383
+ // @ts-expect-error - intentionally passing invalid sessionId to trigger error
384
+ sessionId: 'non-existent-session-that-violates-fk',
385
+ });
386
+
387
+ expect(result.success).toBe(false);
388
+ });
370
389
  });
371
390
 
372
391
  describe('updatePluginState', () => {
@@ -392,6 +411,28 @@ describe('MessageModel Update Tests', () => {
392
411
  expect(result[0].state).toEqual({ key1: 'value1', key2: 'value2' });
393
412
  });
394
413
 
414
+ it('should handle null state in plugin', async () => {
415
+ // Create test data with null state
416
+ await serverDB.insert(messages).values({ id: '1', content: 'abc', role: 'user', userId });
417
+ await serverDB.insert(messagePlugins).values([
418
+ {
419
+ id: '1',
420
+ toolCallId: 'tool1',
421
+ identifier: 'plugin1',
422
+ state: null,
423
+ userId,
424
+ },
425
+ ]);
426
+
427
+ // Call updatePluginState method
428
+ await messageModel.updatePluginState('1', { key1: 'value1' });
429
+
430
+ // Assert result - should merge with empty object when state is null
431
+ const result = await serverDB.select().from(messagePlugins).where(eq(messagePlugins.id, '1'));
432
+
433
+ expect(result[0].state).toEqual({ key1: 'value1' });
434
+ });
435
+
395
436
  it('should throw an error if plugin does not exist', async () => {
396
437
  // 调用 updatePluginState 方法
397
438
  await expect(messageModel.updatePluginState('1', { key: 'value' })).rejects.toThrowError(
@@ -423,10 +464,10 @@ describe('MessageModel Update Tests', () => {
423
464
  });
424
465
 
425
466
  it('should throw an error if plugin does not exist', async () => {
426
- // 调用 updatePluginState 方法
427
- await expect(messageModel.updatePluginState('1', { key: 'value' })).rejects.toThrowError(
428
- 'Plugin not found',
429
- );
467
+ // 调用 updateMessagePlugin 方法(修复:之前错误地调用了 updatePluginState
468
+ await expect(
469
+ messageModel.updateMessagePlugin('non-existent-id', { identifier: 'test' }),
470
+ ).rejects.toThrowError('Plugin not found');
430
471
  });
431
472
  });
432
473
 
@@ -212,7 +212,7 @@ export class MessageModel {
212
212
  .from(messageQueries)
213
213
  .where(inArray(messageQueries.messageId, messageIds));
214
214
 
215
- const mappedMessages = result.map(
215
+ return result.map(
216
216
  ({ model, provider, translate, ttsId, ttsFile, ttsContentMd5, ttsVoice, ...item }) => {
217
217
  const messageQuery = messageQueriesList.find((relation) => relation.messageId === item.id);
218
218
  return {
@@ -221,7 +221,7 @@ export class MessageModel {
221
221
  .filter((relation) => relation.messageId === item.id)
222
222
  .map((c) => ({
223
223
  ...c,
224
- similarity: Number(c.similarity) ?? undefined,
224
+ similarity: c.similarity === null ? undefined : Number(c.similarity),
225
225
  })),
226
226
 
227
227
  extra: {
@@ -266,8 +266,6 @@ export class MessageModel {
266
266
  } as unknown as UIChatMessage;
267
267
  },
268
268
  );
269
-
270
- return mappedMessages;
271
269
  };
272
270
 
273
271
  findById = async (id: string) => {
@@ -423,7 +421,7 @@ export class MessageModel {
423
421
  for (const item of result) {
424
422
  if (item?.date) {
425
423
  const dateStr = dayjs(item.date as string).format('YYYY-MM-DD');
426
- dateCountMap.set(dateStr, Number(item.count) || 0);
424
+ dateCountMap.set(dateStr, item.count);
427
425
  }
428
426
  }
429
427