@lobehub/lobehub 2.0.0-next.32 → 2.0.0-next.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.github/workflows/test.yml +1 -0
  2. package/CHANGELOG.md +58 -0
  3. package/apps/desktop/package.json +1 -1
  4. package/changelog/v1.json +21 -0
  5. package/docker-compose/local/.env.example +3 -0
  6. package/docs/self-hosting/server-database/docker-compose.mdx +29 -0
  7. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +29 -0
  8. package/package.json +1 -1
  9. package/packages/const/src/hotkeys.ts +3 -3
  10. package/packages/const/src/models.ts +2 -2
  11. package/packages/const/src/utils/merge.ts +3 -3
  12. package/packages/conversation-flow/package.json +13 -0
  13. package/packages/conversation-flow/src/__tests__/fixtures/index.ts +48 -0
  14. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-chain-with-followup.json +56 -0
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-with-tools.json +144 -0
  16. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/active-index-1.json +131 -0
  17. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-branch.json +96 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-user-branch.json +123 -0
  19. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/conversation.json +128 -0
  20. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +14 -0
  21. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/nested.json +179 -0
  22. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/index.ts +8 -0
  23. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/simple.json +85 -0
  24. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/with-tools.json +169 -0
  25. package/packages/conversation-flow/src/__tests__/fixtures/inputs/complex-scenario.json +107 -0
  26. package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +14 -0
  27. package/packages/conversation-flow/src/__tests__/fixtures/inputs/linear-conversation.json +59 -0
  28. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-chain-with-followup.json +135 -0
  29. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-with-tools.json +340 -0
  30. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +242 -0
  31. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-branch.json +208 -0
  32. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-user-branch.json +254 -0
  33. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +260 -0
  34. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +14 -0
  35. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +389 -0
  36. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/index.ts +8 -0
  37. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/simple.json +224 -0
  38. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/with-tools.json +418 -0
  39. package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +239 -0
  40. package/packages/conversation-flow/src/__tests__/fixtures/outputs/linear-conversation.json +138 -0
  41. package/packages/conversation-flow/src/__tests__/parse.test.ts +97 -0
  42. package/packages/conversation-flow/src/index.ts +17 -0
  43. package/packages/conversation-flow/src/indexing.ts +58 -0
  44. package/packages/conversation-flow/src/parse.ts +53 -0
  45. package/packages/conversation-flow/src/structuring.ts +38 -0
  46. package/packages/conversation-flow/src/transformation/BranchResolver.ts +66 -0
  47. package/packages/conversation-flow/src/transformation/ContextTreeBuilder.ts +292 -0
  48. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +421 -0
  49. package/packages/conversation-flow/src/transformation/MessageCollector.ts +166 -0
  50. package/packages/conversation-flow/src/transformation/MessageTransformer.ts +177 -0
  51. package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +151 -0
  52. package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +385 -0
  53. package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +511 -0
  54. package/packages/conversation-flow/src/transformation/__tests__/MessageCollector.test.ts +220 -0
  55. package/packages/conversation-flow/src/transformation/__tests__/MessageTransformer.test.ts +287 -0
  56. package/packages/conversation-flow/src/transformation/index.ts +78 -0
  57. package/packages/conversation-flow/src/types/contextTree.ts +65 -0
  58. package/packages/conversation-flow/src/types/flatMessageList.ts +66 -0
  59. package/packages/conversation-flow/src/types/shared.ts +63 -0
  60. package/packages/conversation-flow/src/types.ts +36 -0
  61. package/packages/conversation-flow/vitest.config.mts +10 -0
  62. package/packages/model-bank/src/aiModels/google.ts +1 -1
  63. package/packages/types/src/message/common/metadata.ts +5 -1
  64. package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +3 -4
  65. package/src/app/[variants]/(main)/settings/provider/ProviderMenu/List.tsx +97 -7
  66. package/src/app/[variants]/(main)/settings/provider/features/ModelList/DisabledModels.tsx +144 -8
  67. package/src/envs/__tests__/app.test.ts +47 -13
  68. package/src/envs/app.ts +6 -0
  69. package/src/locales/default/modelProvider.ts +15 -1
  70. package/src/server/routers/async/__tests__/caller.test.ts +333 -0
  71. package/src/server/routers/async/caller.ts +2 -1
  72. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +57 -57
  73. package/src/server/routers/lambda/message.ts +2 -2
  74. package/src/server/services/mcp/deps/checkers/ManualInstallationChecker.test.ts +162 -0
  75. package/src/server/services/mcp/deps/checkers/NpmInstallationChecker.test.ts +374 -0
  76. package/src/server/services/mcp/deps/checkers/PythonInstallationChecker.test.ts +368 -0
  77. package/src/server/services/message/__tests__/index.test.ts +4 -4
  78. package/src/server/services/message/index.ts +1 -1
  79. package/src/services/message/index.ts +2 -3
  80. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +8 -8
  81. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +8 -8
  82. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +1 -1
  83. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +1 -1
  84. package/src/store/chat/slices/message/action.test.ts +7 -7
  85. package/src/store/chat/slices/message/action.ts +2 -2
  86. package/src/store/chat/slices/plugin/action.test.ts +7 -7
  87. package/src/store/chat/slices/plugin/action.ts +1 -1
  88. package/src/store/global/initialState.ts +4 -0
  89. package/src/store/global/selectors/systemStatus.ts +6 -0
  90. package/packages/context-engine/ARCHITECTURE.md +0 -425
@@ -0,0 +1,511 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import type { Message, MessageGroupMetadata } from '../../types';
4
+ import { BranchResolver } from '../BranchResolver';
5
+ import { FlatListBuilder } from '../FlatListBuilder';
6
+ import { MessageCollector } from '../MessageCollector';
7
+ import { MessageTransformer } from '../MessageTransformer';
8
+
9
+ describe('FlatListBuilder', () => {
10
+ const createBuilder = (
11
+ messages: Message[],
12
+ messageGroupMap: Map<string, MessageGroupMetadata> = new Map(),
13
+ ) => {
14
+ const messageMap = new Map<string, Message>();
15
+ const childrenMap = new Map<string | null, string[]>();
16
+
17
+ // Build maps
18
+ messages.forEach((msg) => {
19
+ messageMap.set(msg.id, msg);
20
+ const parentId = msg.parentId || null;
21
+ if (!childrenMap.has(parentId)) {
22
+ childrenMap.set(parentId, []);
23
+ }
24
+ childrenMap.get(parentId)!.push(msg.id);
25
+ });
26
+
27
+ const branchResolver = new BranchResolver();
28
+ const messageCollector = new MessageCollector(messageMap, childrenMap);
29
+ const messageTransformer = new MessageTransformer();
30
+
31
+ return new FlatListBuilder(
32
+ messageMap,
33
+ messageGroupMap,
34
+ childrenMap,
35
+ branchResolver,
36
+ messageCollector,
37
+ messageTransformer,
38
+ );
39
+ };
40
+
41
+ describe('flatten', () => {
42
+ it('should flatten simple message chain', () => {
43
+ const messages: Message[] = [
44
+ {
45
+ content: 'Hello',
46
+ createdAt: 0,
47
+ id: 'msg-1',
48
+ meta: {},
49
+ role: 'user',
50
+ updatedAt: 0,
51
+ },
52
+ {
53
+ content: 'Hi',
54
+ createdAt: 0,
55
+ id: 'msg-2',
56
+ meta: {},
57
+ parentId: 'msg-1',
58
+ role: 'assistant',
59
+ updatedAt: 0,
60
+ },
61
+ ];
62
+
63
+ const builder = createBuilder(messages);
64
+ const result = builder.flatten(messages);
65
+
66
+ expect(result).toHaveLength(2);
67
+ expect(result[0].id).toBe('msg-1');
68
+ expect(result[1].id).toBe('msg-2');
69
+ });
70
+
71
+ it('should create assistant group virtual message', () => {
72
+ const messages: Message[] = [
73
+ {
74
+ content: 'Request',
75
+ createdAt: 0,
76
+ id: 'msg-1',
77
+ meta: {},
78
+ role: 'user',
79
+ updatedAt: 0,
80
+ },
81
+ {
82
+ content: 'Using tool',
83
+ createdAt: 0,
84
+ id: 'msg-2',
85
+ meta: {},
86
+ metadata: { totalInputTokens: 10, totalOutputTokens: 20 },
87
+ parentId: 'msg-1',
88
+ role: 'assistant',
89
+ tools: [
90
+ { apiName: 'test', arguments: '{}', id: 'tool-1', identifier: 'test', type: 'default' },
91
+ ],
92
+ updatedAt: 0,
93
+ },
94
+ {
95
+ content: 'Tool result',
96
+ createdAt: 0,
97
+ id: 'tool-1',
98
+ meta: {},
99
+ parentId: 'msg-2',
100
+ role: 'tool',
101
+ tool_call_id: 'tool-1',
102
+ updatedAt: 0,
103
+ },
104
+ ];
105
+
106
+ const builder = createBuilder(messages);
107
+ const result = builder.flatten(messages);
108
+
109
+ expect(result).toHaveLength(2);
110
+ expect(result[0].id).toBe('msg-1');
111
+ expect(result[1].role).toBe('assistantGroup');
112
+ expect(result[1].children).toHaveLength(1);
113
+ expect(result[1].usage).toBeDefined();
114
+ });
115
+
116
+ it('should handle user message with branches', () => {
117
+ const messages: Message[] = [
118
+ {
119
+ content: 'User',
120
+ createdAt: 0,
121
+ id: 'msg-1',
122
+ meta: {},
123
+ metadata: { activeBranchIndex: 0 },
124
+ role: 'user',
125
+ updatedAt: 0,
126
+ },
127
+ {
128
+ content: 'Branch 1',
129
+ createdAt: 0,
130
+ id: 'msg-2',
131
+ meta: {},
132
+ parentId: 'msg-1',
133
+ role: 'assistant',
134
+ updatedAt: 0,
135
+ },
136
+ {
137
+ content: 'Branch 2',
138
+ createdAt: 0,
139
+ id: 'msg-3',
140
+ meta: {},
141
+ parentId: 'msg-1',
142
+ role: 'assistant',
143
+ updatedAt: 0,
144
+ },
145
+ ];
146
+
147
+ const builder = createBuilder(messages);
148
+ const result = builder.flatten(messages);
149
+
150
+ expect(result).toHaveLength(2);
151
+ expect(result[0].id).toBe('msg-1');
152
+ expect(result[1].id).toBe('msg-2'); // active branch
153
+ });
154
+
155
+ it('should handle assistant message with branches', () => {
156
+ const messages: Message[] = [
157
+ {
158
+ content: 'User',
159
+ createdAt: 0,
160
+ id: 'msg-1',
161
+ meta: {},
162
+ role: 'user',
163
+ updatedAt: 0,
164
+ },
165
+ {
166
+ content: 'Assistant',
167
+ createdAt: 0,
168
+ id: 'msg-2',
169
+ meta: {},
170
+ metadata: { activeBranchIndex: 1 },
171
+ parentId: 'msg-1',
172
+ role: 'assistant',
173
+ updatedAt: 0,
174
+ },
175
+ {
176
+ content: 'Branch 1',
177
+ createdAt: 0,
178
+ id: 'msg-3',
179
+ meta: {},
180
+ parentId: 'msg-2',
181
+ role: 'user',
182
+ updatedAt: 0,
183
+ },
184
+ {
185
+ content: 'Branch 2',
186
+ createdAt: 0,
187
+ id: 'msg-4',
188
+ meta: {},
189
+ parentId: 'msg-2',
190
+ role: 'user',
191
+ updatedAt: 0,
192
+ },
193
+ ];
194
+
195
+ const builder = createBuilder(messages);
196
+ const result = builder.flatten(messages);
197
+
198
+ expect(result).toHaveLength(3);
199
+ expect(result[0].id).toBe('msg-1');
200
+ expect(result[1].id).toBe('msg-2');
201
+ expect(result[2].id).toBe('msg-4'); // active branch (index 1)
202
+ });
203
+
204
+ it('should create compare message from message group', () => {
205
+ const messages: Message[] = [
206
+ {
207
+ content: 'Compare 1',
208
+ createdAt: 0,
209
+ groupId: 'group-1',
210
+ id: 'msg-1',
211
+ meta: {},
212
+ metadata: { activeColumn: true },
213
+ role: 'assistant',
214
+ updatedAt: 0,
215
+ },
216
+ {
217
+ content: 'Compare 2',
218
+ createdAt: 0,
219
+ groupId: 'group-1',
220
+ id: 'msg-2',
221
+ meta: {},
222
+ role: 'assistant',
223
+ updatedAt: 0,
224
+ },
225
+ ];
226
+
227
+ const messageGroupMap = new Map<string, MessageGroupMetadata>([
228
+ ['group-1', { id: 'group-1', mode: 'compare' }],
229
+ ]);
230
+
231
+ const builder = createBuilder(messages, messageGroupMap);
232
+ const result = builder.flatten(messages);
233
+
234
+ expect(result).toHaveLength(1);
235
+ expect(result[0].id).toBe('group-1');
236
+ expect(result[0].role).toBe('compare');
237
+ expect((result[0] as any).columns).toHaveLength(2);
238
+ expect((result[0] as any).activeColumnId).toBe('msg-1');
239
+ });
240
+
241
+ it('should create compare message from user metadata', () => {
242
+ const messages: Message[] = [
243
+ {
244
+ content: 'User',
245
+ createdAt: 0,
246
+ id: 'msg-1',
247
+ meta: {},
248
+ metadata: { compare: true },
249
+ role: 'user',
250
+ updatedAt: 0,
251
+ },
252
+ {
253
+ content: 'Assistant 1',
254
+ createdAt: 0,
255
+ id: 'msg-2',
256
+ meta: {},
257
+ metadata: { activeColumn: true },
258
+ parentId: 'msg-1',
259
+ role: 'assistant',
260
+ updatedAt: 0,
261
+ },
262
+ {
263
+ content: 'Assistant 2',
264
+ createdAt: 0,
265
+ id: 'msg-3',
266
+ meta: {},
267
+ parentId: 'msg-1',
268
+ role: 'assistant',
269
+ updatedAt: 0,
270
+ },
271
+ ];
272
+
273
+ const builder = createBuilder(messages);
274
+ const result = builder.flatten(messages);
275
+
276
+ expect(result).toHaveLength(2);
277
+ expect(result[0].id).toBe('msg-1');
278
+ expect(result[1].role).toBe('compare');
279
+ expect((result[1] as any).activeColumnId).toBe('msg-2');
280
+ });
281
+
282
+ it('should handle empty messages array', () => {
283
+ const builder = createBuilder([]);
284
+ const result = builder.flatten([]);
285
+
286
+ expect(result).toHaveLength(0);
287
+ });
288
+
289
+ it('should follow active branch correctly', () => {
290
+ const messages: Message[] = [
291
+ {
292
+ content: 'User',
293
+ createdAt: 0,
294
+ id: 'msg-1',
295
+ meta: {},
296
+ metadata: { activeBranchIndex: 0 },
297
+ role: 'user',
298
+ updatedAt: 0,
299
+ },
300
+ {
301
+ content: 'Branch 1',
302
+ createdAt: 0,
303
+ id: 'msg-2',
304
+ meta: {},
305
+ parentId: 'msg-1',
306
+ role: 'assistant',
307
+ updatedAt: 0,
308
+ },
309
+ {
310
+ content: 'Branch 2',
311
+ createdAt: 0,
312
+ id: 'msg-3',
313
+ meta: {},
314
+ parentId: 'msg-1',
315
+ role: 'assistant',
316
+ updatedAt: 0,
317
+ },
318
+ {
319
+ content: 'Follow-up',
320
+ createdAt: 0,
321
+ id: 'msg-4',
322
+ meta: {},
323
+ parentId: 'msg-2',
324
+ role: 'user',
325
+ updatedAt: 0,
326
+ },
327
+ ];
328
+
329
+ const builder = createBuilder(messages);
330
+ const result = builder.flatten(messages);
331
+
332
+ expect(result).toHaveLength(3);
333
+ expect(result[0].id).toBe('msg-1');
334
+ expect(result[1].id).toBe('msg-2');
335
+ expect(result[2].id).toBe('msg-4');
336
+ });
337
+
338
+ it('should handle assistant group in compare columns', () => {
339
+ const messages: Message[] = [
340
+ {
341
+ content: 'User',
342
+ createdAt: 0,
343
+ id: 'msg-1',
344
+ meta: {},
345
+ metadata: { compare: true },
346
+ role: 'user',
347
+ updatedAt: 0,
348
+ },
349
+ {
350
+ content: 'Assistant 1',
351
+ createdAt: 0,
352
+ id: 'msg-2',
353
+ meta: {},
354
+ parentId: 'msg-1',
355
+ role: 'assistant',
356
+ tools: [
357
+ { apiName: 'test', arguments: '{}', id: 'tool-1', identifier: 'test', type: 'default' },
358
+ ],
359
+ updatedAt: 0,
360
+ },
361
+ {
362
+ content: 'Tool result',
363
+ createdAt: 0,
364
+ id: 'tool-1',
365
+ meta: {},
366
+ parentId: 'msg-2',
367
+ role: 'tool',
368
+ tool_call_id: 'tool-1',
369
+ updatedAt: 0,
370
+ },
371
+ {
372
+ content: 'Assistant 2',
373
+ createdAt: 0,
374
+ id: 'msg-3',
375
+ meta: {},
376
+ parentId: 'msg-1',
377
+ role: 'assistant',
378
+ updatedAt: 0,
379
+ },
380
+ ];
381
+
382
+ const builder = createBuilder(messages);
383
+ const result = builder.flatten(messages);
384
+
385
+ expect(result).toHaveLength(2);
386
+ expect(result[0].id).toBe('msg-1');
387
+ expect(result[1].role).toBe('compare');
388
+ const columns = (result[1] as any).columns;
389
+ expect(columns).toHaveLength(2);
390
+ // First column should be an assistant group
391
+ expect(columns[0][0].role).toBe('assistantGroup');
392
+ // Second column should be a regular message
393
+ expect(columns[1][0].id).toBe('msg-3');
394
+ });
395
+
396
+ it('should include follow-up messages after assistant chain', () => {
397
+ const messages: Message[] = [
398
+ {
399
+ content: 'User request',
400
+ createdAt: 0,
401
+ id: 'msg-1',
402
+ meta: {},
403
+ role: 'user',
404
+ updatedAt: 0,
405
+ },
406
+ {
407
+ content: 'Using tool',
408
+ createdAt: 0,
409
+ id: 'msg-2',
410
+ meta: {},
411
+ parentId: 'msg-1',
412
+ role: 'assistant',
413
+ tools: [
414
+ { apiName: 'test', arguments: '{}', id: 'tool-1', identifier: 'test', type: 'default' },
415
+ ],
416
+ updatedAt: 0,
417
+ },
418
+ {
419
+ content: 'Tool result',
420
+ createdAt: 0,
421
+ id: 'tool-1',
422
+ meta: {},
423
+ parentId: 'msg-2',
424
+ role: 'tool',
425
+ tool_call_id: 'tool-1',
426
+ updatedAt: 0,
427
+ },
428
+ {
429
+ content: 'Response based on tool',
430
+ createdAt: 0,
431
+ id: 'msg-3',
432
+ meta: {},
433
+ parentId: 'tool-1',
434
+ role: 'assistant',
435
+ updatedAt: 0,
436
+ },
437
+ {
438
+ content: 'User follow-up',
439
+ createdAt: 0,
440
+ id: 'msg-4',
441
+ meta: {},
442
+ parentId: 'msg-3',
443
+ role: 'user',
444
+ updatedAt: 0,
445
+ },
446
+ ];
447
+
448
+ const builder = createBuilder(messages);
449
+ const result = builder.flatten(messages);
450
+
451
+ // Critical: msg-4 should be included
452
+ expect(result).toHaveLength(3);
453
+ expect(result[0].id).toBe('msg-1');
454
+ expect(result[1].role).toBe('assistantGroup');
455
+ expect(result[2].id).toBe('msg-4');
456
+ });
457
+
458
+ it('should handle user reply to tool message', () => {
459
+ const messages: Message[] = [
460
+ {
461
+ content: 'User request',
462
+ createdAt: 0,
463
+ id: 'msg-1',
464
+ meta: {},
465
+ role: 'user',
466
+ updatedAt: 0,
467
+ },
468
+ {
469
+ content: 'Using tool',
470
+ createdAt: 0,
471
+ id: 'msg-2',
472
+ meta: {},
473
+ parentId: 'msg-1',
474
+ role: 'assistant',
475
+ tools: [
476
+ { apiName: 'test', arguments: '{}', id: 'tool-1', identifier: 'test', type: 'default' },
477
+ ],
478
+ updatedAt: 0,
479
+ },
480
+ {
481
+ content: 'Tool result',
482
+ createdAt: 0,
483
+ id: 'tool-1',
484
+ meta: {},
485
+ parentId: 'msg-2',
486
+ role: 'tool',
487
+ tool_call_id: 'tool-1',
488
+ updatedAt: 0,
489
+ },
490
+ {
491
+ content: 'User reply to tool',
492
+ createdAt: 0,
493
+ id: 'msg-3',
494
+ meta: {},
495
+ parentId: 'tool-1',
496
+ role: 'user',
497
+ updatedAt: 0,
498
+ },
499
+ ];
500
+
501
+ const builder = createBuilder(messages);
502
+ const result = builder.flatten(messages);
503
+
504
+ // msg-3 should be included even though it's a reply to tool
505
+ expect(result).toHaveLength(3);
506
+ expect(result[0].id).toBe('msg-1');
507
+ expect(result[1].role).toBe('assistantGroup');
508
+ expect(result[2].id).toBe('msg-3');
509
+ });
510
+ });
511
+ });