@lobehub/lobehub 2.0.0-next.31 → 2.0.0-next.33

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 (83) 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/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +23 -2
  5. package/changelog/v1.json +21 -0
  6. package/docker-compose/local/.env.example +3 -0
  7. package/docs/self-hosting/server-database/docker-compose.mdx +29 -0
  8. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +29 -0
  9. package/package.json +1 -1
  10. package/packages/const/src/hotkeys.ts +3 -3
  11. package/packages/const/src/models.ts +2 -2
  12. package/packages/const/src/utils/merge.ts +3 -3
  13. package/packages/conversation-flow/package.json +13 -0
  14. package/packages/conversation-flow/src/__tests__/fixtures/index.ts +48 -0
  15. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-chain-with-followup.json +56 -0
  16. package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-with-tools.json +144 -0
  17. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/active-index-1.json +131 -0
  18. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-branch.json +96 -0
  19. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-user-branch.json +123 -0
  20. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/conversation.json +128 -0
  21. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +14 -0
  22. package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/nested.json +179 -0
  23. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/index.ts +8 -0
  24. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/simple.json +85 -0
  25. package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/with-tools.json +169 -0
  26. package/packages/conversation-flow/src/__tests__/fixtures/inputs/complex-scenario.json +107 -0
  27. package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +14 -0
  28. package/packages/conversation-flow/src/__tests__/fixtures/inputs/linear-conversation.json +59 -0
  29. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-chain-with-followup.json +135 -0
  30. package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-with-tools.json +340 -0
  31. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +242 -0
  32. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-branch.json +208 -0
  33. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-user-branch.json +254 -0
  34. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +260 -0
  35. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +14 -0
  36. package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +389 -0
  37. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/index.ts +8 -0
  38. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/simple.json +224 -0
  39. package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/with-tools.json +418 -0
  40. package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +239 -0
  41. package/packages/conversation-flow/src/__tests__/fixtures/outputs/linear-conversation.json +138 -0
  42. package/packages/conversation-flow/src/__tests__/parse.test.ts +97 -0
  43. package/packages/conversation-flow/src/index.ts +17 -0
  44. package/packages/conversation-flow/src/indexing.ts +58 -0
  45. package/packages/conversation-flow/src/parse.ts +53 -0
  46. package/packages/conversation-flow/src/structuring.ts +38 -0
  47. package/packages/conversation-flow/src/transformation/BranchResolver.ts +66 -0
  48. package/packages/conversation-flow/src/transformation/ContextTreeBuilder.ts +292 -0
  49. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +421 -0
  50. package/packages/conversation-flow/src/transformation/MessageCollector.ts +166 -0
  51. package/packages/conversation-flow/src/transformation/MessageTransformer.ts +177 -0
  52. package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +151 -0
  53. package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +385 -0
  54. package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +511 -0
  55. package/packages/conversation-flow/src/transformation/__tests__/MessageCollector.test.ts +220 -0
  56. package/packages/conversation-flow/src/transformation/__tests__/MessageTransformer.test.ts +287 -0
  57. package/packages/conversation-flow/src/transformation/index.ts +78 -0
  58. package/packages/conversation-flow/src/types/contextTree.ts +65 -0
  59. package/packages/conversation-flow/src/types/flatMessageList.ts +66 -0
  60. package/packages/conversation-flow/src/types/shared.ts +63 -0
  61. package/packages/conversation-flow/src/types.ts +36 -0
  62. package/packages/conversation-flow/vitest.config.mts +10 -0
  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/envs/__tests__/app.test.ts +47 -13
  66. package/src/envs/app.ts +6 -0
  67. package/src/server/modules/S3/index.test.ts +379 -0
  68. package/src/server/routers/async/__tests__/caller.test.ts +333 -0
  69. package/src/server/routers/async/caller.ts +2 -1
  70. package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +57 -57
  71. package/src/server/routers/lambda/message.ts +2 -2
  72. package/src/server/services/message/__tests__/index.test.ts +4 -4
  73. package/src/server/services/message/index.ts +1 -1
  74. package/src/services/message/index.ts +2 -3
  75. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +8 -8
  76. package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +8 -8
  77. package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +1 -1
  78. package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +1 -1
  79. package/src/store/chat/slices/message/action.test.ts +7 -7
  80. package/src/store/chat/slices/message/action.ts +2 -2
  81. package/src/store/chat/slices/plugin/action.test.ts +7 -7
  82. package/src/store/chat/slices/plugin/action.ts +1 -1
  83. package/packages/context-engine/ARCHITECTURE.md +0 -425
@@ -0,0 +1,239 @@
1
+ {
2
+ "contextTree": [
3
+ {
4
+ "id": "msg-501",
5
+ "type": "message"
6
+ },
7
+ {
8
+ "children": [
9
+ {
10
+ "id": "msg-502",
11
+ "type": "message",
12
+ "tools": ["msg-503", "msg-504"]
13
+ }
14
+ ],
15
+ "id": "msg-502",
16
+ "type": "assistantGroup"
17
+ },
18
+ {
19
+ "id": "msg-505",
20
+ "type": "message"
21
+ },
22
+ {
23
+ "activeBranchIndex": 0,
24
+ "branches": [
25
+ [
26
+ {
27
+ "id": "msg-506",
28
+ "type": "message"
29
+ }
30
+ ],
31
+ [
32
+ {
33
+ "id": "msg-507",
34
+ "type": "message"
35
+ }
36
+ ]
37
+ ],
38
+ "id": "branch-msg-505-0",
39
+ "parentMessageId": "msg-505",
40
+ "type": "branch"
41
+ }
42
+ ],
43
+ "flatList": [
44
+ {
45
+ "id": "msg-501",
46
+ "role": "user",
47
+ "content": "Help me debug this Python code that's throwing an error",
48
+ "parentId": null,
49
+ "createdAt": 1704067200000,
50
+ "updatedAt": 1704067200000,
51
+ "meta": {}
52
+ },
53
+ {
54
+ "id": "msg-502",
55
+ "role": "assistantGroup",
56
+ "content": "",
57
+ "parentId": "msg-501",
58
+ "createdAt": 1704067202000,
59
+ "updatedAt": 1704067202000,
60
+ "meta": {},
61
+ "children": [
62
+ {
63
+ "content": "I'll help you debug the code. Let me first check the syntax and run some analysis.",
64
+ "id": "msg-502",
65
+ "tools": [
66
+ {
67
+ "id": "tool-lint",
68
+ "identifier": "code-analyzer",
69
+ "apiName": "analyzePython",
70
+ "arguments": "{\"action\":\"lint\"}",
71
+ "type": "default",
72
+ "result": {
73
+ "content": "{\"status\":\"success\",\"issues\":[{\"line\":15,\"message\":\"undefined variable 'result'\"}]}",
74
+ "id": "msg-503",
75
+ "state": {
76
+ "cached": false
77
+ }
78
+ },
79
+ "result_msg_id": "msg-503"
80
+ },
81
+ {
82
+ "id": "tool-test",
83
+ "identifier": "code-analyzer",
84
+ "apiName": "runTests",
85
+ "arguments": "{\"action\":\"test\"}",
86
+ "type": "default",
87
+ "result": {
88
+ "content": "{\"status\":\"failed\",\"error\":\"NameError: name 'result' is not defined\"}",
89
+ "id": "msg-504",
90
+ "state": {
91
+ "cached": false
92
+ }
93
+ },
94
+ "result_msg_id": "msg-504"
95
+ }
96
+ ]
97
+ }
98
+ ]
99
+ },
100
+ {
101
+ "id": "msg-505",
102
+ "role": "user",
103
+ "content": "Here's the code:\n```python\ndef calculate():\n x = 5\n y = 10\n print(result)\n```",
104
+ "parentId": "msg-504",
105
+ "createdAt": 1704067210000,
106
+ "updatedAt": 1704067210000,
107
+ "meta": {}
108
+ },
109
+ {
110
+ "id": "msg-506",
111
+ "role": "assistant",
112
+ "content": "I found the issue! On line 15, you're trying to use `result` but it's never defined. Here's the fix:\n\n```python\ndef calculate():\n x = 5\n y = 10\n result = x + y # Define result before using it\n print(result)\n```",
113
+ "parentId": "msg-505",
114
+ "createdAt": 1704067213000,
115
+ "updatedAt": 1704067213000,
116
+ "meta": {},
117
+ "usage": {
118
+ "totalInputTokens": 156,
119
+ "totalOutputTokens": 89,
120
+ "totalTokens": 245,
121
+ "cost": 0.000735
122
+ }
123
+ }
124
+ ],
125
+ "messageMap": {
126
+ "msg-501": {
127
+ "id": "msg-501",
128
+ "role": "user",
129
+ "content": "Help me debug this Python code that's throwing an error",
130
+ "parentId": null,
131
+ "createdAt": 1704067200000,
132
+ "updatedAt": 1704067200000,
133
+ "meta": {}
134
+ },
135
+ "msg-502": {
136
+ "id": "msg-502",
137
+ "role": "assistant",
138
+ "content": "I'll help you debug the code. Let me first check the syntax and run some analysis.",
139
+ "parentId": "msg-501",
140
+ "tools": [
141
+ {
142
+ "id": "tool-lint",
143
+ "identifier": "code-analyzer",
144
+ "apiName": "analyzePython",
145
+ "arguments": "{\"action\":\"lint\"}",
146
+ "type": "default"
147
+ },
148
+ {
149
+ "id": "tool-test",
150
+ "identifier": "code-analyzer",
151
+ "apiName": "runTests",
152
+ "arguments": "{\"action\":\"test\"}",
153
+ "type": "default"
154
+ }
155
+ ],
156
+ "createdAt": 1704067202000,
157
+ "updatedAt": 1704067202000,
158
+ "meta": {},
159
+ "usage": {
160
+ "totalInputTokens": 32,
161
+ "totalOutputTokens": 48,
162
+ "totalTokens": 80,
163
+ "cost": 0.00024
164
+ },
165
+ "performance": {
166
+ "tps": 45
167
+ }
168
+ },
169
+ "msg-503": {
170
+ "id": "msg-503",
171
+ "role": "tool",
172
+ "tool_call_id": "tool-lint",
173
+ "content": "{\"status\":\"success\",\"issues\":[{\"line\":15,\"message\":\"undefined variable 'result'\"}]}",
174
+ "pluginState": {
175
+ "cached": false
176
+ },
177
+ "parentId": "msg-502",
178
+ "createdAt": 1704067203000,
179
+ "updatedAt": 1704067203000,
180
+ "meta": {}
181
+ },
182
+ "msg-504": {
183
+ "id": "msg-504",
184
+ "role": "tool",
185
+ "tool_call_id": "tool-test",
186
+ "content": "{\"status\":\"failed\",\"error\":\"NameError: name 'result' is not defined\"}",
187
+ "pluginState": {
188
+ "cached": false
189
+ },
190
+ "pluginError": {
191
+ "type": "NameError",
192
+ "message": "name 'result' is not defined"
193
+ },
194
+ "parentId": "msg-502",
195
+ "createdAt": 1704067203100,
196
+ "updatedAt": 1704067203100,
197
+ "meta": {}
198
+ },
199
+ "msg-505": {
200
+ "id": "msg-505",
201
+ "role": "user",
202
+ "content": "Here's the code:\n```python\ndef calculate():\n x = 5\n y = 10\n print(result)\n```",
203
+ "parentId": "msg-504",
204
+ "createdAt": 1704067210000,
205
+ "updatedAt": 1704067210000,
206
+ "meta": {}
207
+ },
208
+ "msg-506": {
209
+ "id": "msg-506",
210
+ "role": "assistant",
211
+ "content": "I found the issue! On line 15, you're trying to use `result` but it's never defined. Here's the fix:\n\n```python\ndef calculate():\n x = 5\n y = 10\n result = x + y # Define result before using it\n print(result)\n```",
212
+ "parentId": "msg-505",
213
+ "createdAt": 1704067213000,
214
+ "updatedAt": 1704067213000,
215
+ "meta": {},
216
+ "usage": {
217
+ "totalInputTokens": 156,
218
+ "totalOutputTokens": 89,
219
+ "totalTokens": 245,
220
+ "cost": 0.000735
221
+ }
222
+ },
223
+ "msg-507": {
224
+ "id": "msg-507",
225
+ "role": "assistant",
226
+ "content": "Alternatively, you could return the value instead:\n\n```python\ndef calculate():\n x = 5\n y = 10\n return x + y\n\nresult = calculate()\nprint(result)\n```",
227
+ "parentId": "msg-505",
228
+ "createdAt": 1704067213100,
229
+ "updatedAt": 1704067213100,
230
+ "meta": {},
231
+ "usage": {
232
+ "totalInputTokens": 156,
233
+ "totalOutputTokens": 72,
234
+ "totalTokens": 228,
235
+ "cost": 0.000684
236
+ }
237
+ }
238
+ }
239
+ }
@@ -0,0 +1,138 @@
1
+ {
2
+ "contextTree": [
3
+ {
4
+ "id": "msg-001",
5
+ "type": "message"
6
+ },
7
+ {
8
+ "id": "msg-002",
9
+ "type": "message"
10
+ },
11
+ {
12
+ "id": "msg-003",
13
+ "type": "message"
14
+ },
15
+ {
16
+ "id": "msg-004",
17
+ "type": "message"
18
+ }
19
+ ],
20
+ "flatList": [
21
+ {
22
+ "id": "msg-001",
23
+ "role": "user",
24
+ "content": "Can you help me write a Python function to check if a number is prime?",
25
+ "parentId": null,
26
+ "createdAt": 1704067200000,
27
+ "updatedAt": 1704067200000,
28
+ "meta": {}
29
+ },
30
+ {
31
+ "id": "msg-002",
32
+ "role": "assistant",
33
+ "content": "I'd be happy to help you write a function to check if a number is prime. Here's a simple and efficient implementation:\n\n```python\ndef is_prime(n):\n if n < 2:\n return False\n if n == 2:\n return True\n if n % 2 == 0:\n return False\n \n for i in range(3, int(n**0.5) + 1, 2):\n if n % i == 0:\n return False\n \n return True\n```\n\nThis function works by checking divisibility up to the square root of the number.",
34
+ "parentId": "msg-001",
35
+ "createdAt": 1704067205000,
36
+ "updatedAt": 1704067205000,
37
+ "meta": {},
38
+ "usage": {
39
+ "totalInputTokens": 24,
40
+ "totalOutputTokens": 156,
41
+ "totalTokens": 180,
42
+ "cost": 0.00054
43
+ },
44
+ "performance": {
45
+ "tps": 52,
46
+ "ttft": 234,
47
+ "duration": 3000,
48
+ "latency": 3234
49
+ }
50
+ },
51
+ {
52
+ "id": "msg-003",
53
+ "role": "user",
54
+ "content": "Thanks! Can you add some test cases?",
55
+ "parentId": "msg-002",
56
+ "createdAt": 1704067210000,
57
+ "updatedAt": 1704067210000,
58
+ "meta": {}
59
+ },
60
+ {
61
+ "id": "msg-004",
62
+ "role": "assistant",
63
+ "content": "Sure! Here are some test cases:\n\n```python\nprint(is_prime(2)) # True\nprint(is_prime(17)) # True\nprint(is_prime(100)) # False\nprint(is_prime(1)) # False\nprint(is_prime(-5)) # False\n```",
64
+ "parentId": "msg-003",
65
+ "createdAt": 1704067213000,
66
+ "updatedAt": 1704067213000,
67
+ "meta": {},
68
+ "usage": {
69
+ "totalInputTokens": 180,
70
+ "totalOutputTokens": 89,
71
+ "totalTokens": 269,
72
+ "cost": 0.000807
73
+ },
74
+ "performance": {
75
+ "tps": 44.5
76
+ }
77
+ }
78
+ ],
79
+ "messageMap": {
80
+ "msg-001": {
81
+ "id": "msg-001",
82
+ "role": "user",
83
+ "content": "Can you help me write a Python function to check if a number is prime?",
84
+ "parentId": null,
85
+ "createdAt": 1704067200000,
86
+ "updatedAt": 1704067200000,
87
+ "meta": {}
88
+ },
89
+ "msg-002": {
90
+ "id": "msg-002",
91
+ "role": "assistant",
92
+ "content": "I'd be happy to help you write a function to check if a number is prime. Here's a simple and efficient implementation:\n\n```python\ndef is_prime(n):\n if n < 2:\n return False\n if n == 2:\n return True\n if n % 2 == 0:\n return False\n \n for i in range(3, int(n**0.5) + 1, 2):\n if n % i == 0:\n return False\n \n return True\n```\n\nThis function works by checking divisibility up to the square root of the number.",
93
+ "parentId": "msg-001",
94
+ "createdAt": 1704067205000,
95
+ "updatedAt": 1704067205000,
96
+ "meta": {},
97
+ "usage": {
98
+ "totalInputTokens": 24,
99
+ "totalOutputTokens": 156,
100
+ "totalTokens": 180,
101
+ "cost": 0.00054
102
+ },
103
+ "performance": {
104
+ "tps": 52,
105
+ "ttft": 234,
106
+ "duration": 3000,
107
+ "latency": 3234
108
+ }
109
+ },
110
+ "msg-003": {
111
+ "id": "msg-003",
112
+ "role": "user",
113
+ "content": "Thanks! Can you add some test cases?",
114
+ "parentId": "msg-002",
115
+ "createdAt": 1704067210000,
116
+ "updatedAt": 1704067210000,
117
+ "meta": {}
118
+ },
119
+ "msg-004": {
120
+ "id": "msg-004",
121
+ "role": "assistant",
122
+ "content": "Sure! Here are some test cases:\n\n```python\nprint(is_prime(2)) # True\nprint(is_prime(17)) # True\nprint(is_prime(100)) # False\nprint(is_prime(1)) # False\nprint(is_prime(-5)) # False\n```",
123
+ "parentId": "msg-003",
124
+ "createdAt": 1704067213000,
125
+ "updatedAt": 1704067213000,
126
+ "meta": {},
127
+ "usage": {
128
+ "totalInputTokens": 180,
129
+ "totalOutputTokens": 89,
130
+ "totalTokens": 269,
131
+ "cost": 0.000807
132
+ },
133
+ "performance": {
134
+ "tps": 44.5
135
+ }
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { parse } from '../parse';
4
+ import { inputs, outputs } from './fixtures';
5
+
6
+ function serializeParseResult(result: ReturnType<typeof parse>) {
7
+ return {
8
+ contextTree: result.contextTree,
9
+ flatList: result.flatList,
10
+ messageMap: result.messageMap,
11
+ };
12
+ }
13
+
14
+ describe('parse', () => {
15
+ describe('Basic Conversations', () => {
16
+ it('should parse linear conversation correctly', () => {
17
+ const result = parse(inputs.linearConversation);
18
+
19
+ expect(serializeParseResult(result)).toEqual(outputs.linearConversation);
20
+ });
21
+ });
22
+
23
+ describe('Tool Usage', () => {
24
+ it('should parse assistant with tools correctly', () => {
25
+ const result = parse(inputs.assistantWithTools);
26
+
27
+ expect(serializeParseResult(result)).toEqual(outputs.assistantWithTools);
28
+ });
29
+
30
+ it('should include follow-up messages after assistant chain', () => {
31
+ const result = parse(inputs.assistantChainWithFollowup);
32
+
33
+ // The critical assertion: flatList should contain all 5 messages
34
+ // msg-1 (user) + assistantGroup (msg-2+msg-3+tool-1) + msg-4 (user follow-up)
35
+ expect(result.flatList).toHaveLength(3);
36
+ expect(result.flatList[0].id).toBe('msg-1');
37
+ expect(result.flatList[1].role).toBe('assistantGroup');
38
+ expect(result.flatList[2].id).toBe('msg-4'); // This is the critical one that might be missing
39
+
40
+ expect(serializeParseResult(result)).toEqual(outputs.assistantChainWithFollowup);
41
+ });
42
+ });
43
+
44
+ describe('Branching', () => {
45
+ it('should parse branched conversation correctly', () => {
46
+ const result = parse(inputs.branch.conversation);
47
+
48
+ expect(serializeParseResult(result)).toEqual(outputs.branch.conversation);
49
+ });
50
+
51
+ it('should respect activeBranchIndex when specified', () => {
52
+ const result = parse(inputs.branch.activeIndex1);
53
+
54
+ expect(serializeParseResult(result)).toEqual(outputs.branch.activeIndex1);
55
+ });
56
+
57
+ it('should handle assistant message with branches', () => {
58
+ const result = parse(inputs.branch.assistantBranch);
59
+
60
+ expect(serializeParseResult(result)).toEqual(outputs.branch.assistantBranch);
61
+ });
62
+
63
+ it('should handle assistant with user branches', () => {
64
+ const result = parse(inputs.branch.assistantUserBranch);
65
+
66
+ expect(serializeParseResult(result)).toEqual(outputs.branch.assistantUserBranch);
67
+ });
68
+
69
+ it('should handle deeply nested branches (4 levels)', () => {
70
+ const result = parse(inputs.branch.nested);
71
+
72
+ expect(serializeParseResult(result)).toEqual(outputs.branch.nested);
73
+ });
74
+ });
75
+
76
+ describe('Compare Mode', () => {
77
+ it('should parse simple compare mode correctly', () => {
78
+ const result = parse(inputs.compare.simple);
79
+
80
+ expect(serializeParseResult(result)).toEqual(outputs.compare.simple);
81
+ });
82
+
83
+ it('should parse compare mode with tools correctly', () => {
84
+ const result = parse(inputs.compare.withTools);
85
+
86
+ expect(serializeParseResult(result)).toEqual(outputs.compare.withTools);
87
+ });
88
+ });
89
+
90
+ describe('Complex Scenarios', () => {
91
+ it('should handle complex mixed scenarios correctly', () => {
92
+ const result = parse(inputs.complexScenario);
93
+
94
+ expect(serializeParseResult(result)).toEqual(outputs.complexScenario);
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,17 @@
1
+ // Main parse function
2
+ export { parse } from './parse';
3
+
4
+ // Context Tree Types - for navigation and context understanding
5
+ export type {
6
+ AssistantGroupNode,
7
+ BranchNode,
8
+ CompareNode,
9
+ ContextNode,
10
+ MessageNode,
11
+ } from './types';
12
+
13
+ // Flat Message List Types - for virtual list rendering
14
+ export type { BranchMetadata, FlatMessage, FlatMessageExtra, FlatMessageRole } from './types';
15
+
16
+ // Shared Types
17
+ export type { HelperMaps, IdNode, Message, MessageGroupMetadata, ParseResult } from './types';
@@ -0,0 +1,58 @@
1
+ import type { HelperMaps, Message, MessageGroupMetadata } from './types';
2
+
3
+ /**
4
+ * Phase 1: Indexing
5
+ * Builds helper maps for efficient querying during parsing
6
+ *
7
+ * @param messages - Flat array of messages from backend
8
+ * @param messageGroups - Optional array of message group metadata
9
+ * @returns Helper maps for efficient access
10
+ */
11
+ export function buildHelperMaps(
12
+ messages: Message[],
13
+ messageGroups?: MessageGroupMetadata[],
14
+ ): HelperMaps {
15
+ const messageMap = new Map<string, Message>();
16
+ const childrenMap = new Map<string | null, string[]>();
17
+ const threadMap = new Map<string, Message[]>();
18
+ const messageGroupMap = new Map<string, MessageGroupMetadata>();
19
+
20
+ // Single pass through messages to build all maps
21
+ for (const message of messages) {
22
+ // 1. Build messageMap for O(1) access
23
+ messageMap.set(message.id, message);
24
+
25
+ // 2. Build childrenMap for parent -> children lookup
26
+ const parentId = message.parentId ?? null;
27
+ const siblings = childrenMap.get(parentId);
28
+ if (siblings) {
29
+ siblings.push(message.id);
30
+ } else {
31
+ childrenMap.set(parentId, [message.id]);
32
+ }
33
+
34
+ // 3. Build threadMap for thread aggregation
35
+ if (message.threadId) {
36
+ const threadMessages = threadMap.get(message.threadId);
37
+ if (threadMessages) {
38
+ threadMessages.push(message);
39
+ } else {
40
+ threadMap.set(message.threadId, [message]);
41
+ }
42
+ }
43
+ }
44
+
45
+ // 4. Build messageGroupMap from provided metadata
46
+ if (messageGroups) {
47
+ for (const group of messageGroups) {
48
+ messageGroupMap.set(group.id, group);
49
+ }
50
+ }
51
+
52
+ return {
53
+ childrenMap,
54
+ messageGroupMap,
55
+ messageMap,
56
+ threadMap,
57
+ };
58
+ }
@@ -0,0 +1,53 @@
1
+ import { buildHelperMaps } from './indexing';
2
+ import { buildIdTree } from './structuring';
3
+ import { Transformer } from './transformation';
4
+ import type { Message, MessageGroupMetadata, ParseResult } from './types';
5
+
6
+ /**
7
+ * Main parse function - the brain of the conversation flow engine
8
+ *
9
+ * Converts a flat array of messages into:
10
+ * 1. messageMap - for O(1) message access
11
+ * 2. displayTree - semantic tree structure for navigation
12
+ * 3. flatList - flattened array optimized for virtual list rendering
13
+ *
14
+ * Uses a three-phase parsing strategy:
15
+ * 1. Indexing - build helper maps for efficient querying
16
+ * 2. Structuring - convert flat data to tree structure
17
+ * 3. Transformation - apply business logic to create semantic nodes and flat list
18
+ *
19
+ * @param messages - Flat array of messages from backend
20
+ * @param messageGroups - Optional array of message group metadata for compare/manual grouping
21
+ * @returns ParseResult containing messageMap, displayTree, and flatList
22
+ */
23
+ export function parse(messages: Message[], messageGroups?: MessageGroupMetadata[]): ParseResult {
24
+ // Phase 1: Indexing
25
+ // Build helper maps for O(1) access patterns
26
+ const helperMaps = buildHelperMaps(messages, messageGroups);
27
+
28
+ // Phase 2: Structuring
29
+ // Convert flat parent-child relationships to tree structure
30
+ // Separates main flow from threaded conversations
31
+ const idTree = buildIdTree(helperMaps);
32
+
33
+ // Phase 3: Transformation
34
+ // Apply priority-based pattern matching to create semantic display nodes
35
+ const transformer = new Transformer(helperMaps);
36
+ const contextTree = transformer.transformAll(idTree);
37
+
38
+ // Phase 3b: Generate flatList for virtual list rendering
39
+ // Implements RFC priority-based pattern matching
40
+ const flatList = transformer.flatten(messages);
41
+
42
+ // Convert messageMap from Map to plain object for serialization
43
+ const messageMapObj: Record<string, Message> = {};
44
+ helperMaps.messageMap.forEach((message, id) => {
45
+ messageMapObj[id] = message;
46
+ });
47
+
48
+ return {
49
+ contextTree,
50
+ flatList,
51
+ messageMap: messageMapObj,
52
+ };
53
+ }
@@ -0,0 +1,38 @@
1
+ import type { HelperMaps, IdNode } from './types';
2
+
3
+ /**
4
+ * Phase 2: Structuring
5
+ * Converts flat parent-child relationships into a tree structure
6
+ * Separates main flow from threaded conversations
7
+ *
8
+ * @param helperMaps - Maps built in indexing phase
9
+ * @returns Root nodes of the main conversation flow (idTree)
10
+ */
11
+ export function buildIdTree(helperMaps: HelperMaps): IdNode[] {
12
+ const { childrenMap, messageMap } = helperMaps;
13
+
14
+ // Build tree recursively starting from root messages (parentId = null)
15
+ const buildTree = (messageId: string): IdNode => {
16
+ const childIds = childrenMap.get(messageId) ?? [];
17
+
18
+ // Filter children to only include those in main flow (not in threads)
19
+ const mainFlowChildIds = childIds.filter((childId) => {
20
+ const child = messageMap.get(childId);
21
+ return child && !child.threadId;
22
+ });
23
+
24
+ return {
25
+ children: mainFlowChildIds.map((childId) => buildTree(childId)),
26
+ id: messageId,
27
+ };
28
+ };
29
+
30
+ // Get root message IDs (messages with parentId = null and no threadId)
31
+ const rootIds = childrenMap.get(null) ?? [];
32
+ const mainFlowRootIds = rootIds.filter((id) => {
33
+ const msg = messageMap.get(id);
34
+ return msg && !msg.threadId;
35
+ });
36
+
37
+ return mainFlowRootIds.map((rootId) => buildTree(rootId));
38
+ }