@lobehub/lobehub 2.0.0-next.32 → 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.
- package/.github/workflows/test.yml +1 -0
- package/CHANGELOG.md +33 -0
- package/apps/desktop/package.json +1 -1
- package/changelog/v1.json +12 -0
- package/docker-compose/local/.env.example +3 -0
- package/docs/self-hosting/server-database/docker-compose.mdx +29 -0
- package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +29 -0
- package/package.json +1 -1
- package/packages/const/src/hotkeys.ts +3 -3
- package/packages/const/src/models.ts +2 -2
- package/packages/const/src/utils/merge.ts +3 -3
- package/packages/conversation-flow/package.json +13 -0
- package/packages/conversation-flow/src/__tests__/fixtures/index.ts +48 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-chain-with-followup.json +56 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/assistant-with-tools.json +144 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/active-index-1.json +131 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-branch.json +96 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/assistant-user-branch.json +123 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/conversation.json +128 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/index.ts +14 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/branch/nested.json +179 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/index.ts +8 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/simple.json +85 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/compare/with-tools.json +169 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/complex-scenario.json +107 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/index.ts +14 -0
- package/packages/conversation-flow/src/__tests__/fixtures/inputs/linear-conversation.json +59 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-chain-with-followup.json +135 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/assistant-with-tools.json +340 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/active-index-1.json +242 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-branch.json +208 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/assistant-user-branch.json +254 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/conversation.json +260 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/index.ts +14 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/branch/nested.json +389 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/index.ts +8 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/simple.json +224 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/compare/with-tools.json +418 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/complex-scenario.json +239 -0
- package/packages/conversation-flow/src/__tests__/fixtures/outputs/linear-conversation.json +138 -0
- package/packages/conversation-flow/src/__tests__/parse.test.ts +97 -0
- package/packages/conversation-flow/src/index.ts +17 -0
- package/packages/conversation-flow/src/indexing.ts +58 -0
- package/packages/conversation-flow/src/parse.ts +53 -0
- package/packages/conversation-flow/src/structuring.ts +38 -0
- package/packages/conversation-flow/src/transformation/BranchResolver.ts +66 -0
- package/packages/conversation-flow/src/transformation/ContextTreeBuilder.ts +292 -0
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +421 -0
- package/packages/conversation-flow/src/transformation/MessageCollector.ts +166 -0
- package/packages/conversation-flow/src/transformation/MessageTransformer.ts +177 -0
- package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +151 -0
- package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +385 -0
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +511 -0
- package/packages/conversation-flow/src/transformation/__tests__/MessageCollector.test.ts +220 -0
- package/packages/conversation-flow/src/transformation/__tests__/MessageTransformer.test.ts +287 -0
- package/packages/conversation-flow/src/transformation/index.ts +78 -0
- package/packages/conversation-flow/src/types/contextTree.ts +65 -0
- package/packages/conversation-flow/src/types/flatMessageList.ts +66 -0
- package/packages/conversation-flow/src/types/shared.ts +63 -0
- package/packages/conversation-flow/src/types.ts +36 -0
- package/packages/conversation-flow/vitest.config.mts +10 -0
- package/packages/types/src/message/common/metadata.ts +5 -1
- package/src/app/[variants]/(main)/chat/@session/features/SessionListContent/List/Item/index.tsx +3 -4
- package/src/envs/__tests__/app.test.ts +47 -13
- package/src/envs/app.ts +6 -0
- package/src/server/routers/async/__tests__/caller.test.ts +333 -0
- package/src/server/routers/async/caller.ts +2 -1
- package/src/server/routers/lambda/__tests__/integration/message.integration.test.ts +57 -57
- package/src/server/routers/lambda/message.ts +2 -2
- package/src/server/services/message/__tests__/index.test.ts +4 -4
- package/src/server/services/message/index.ts +1 -1
- package/src/services/message/index.ts +2 -3
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChat.test.ts +8 -8
- package/src/store/chat/slices/aiChat/actions/__tests__/generateAIChatV2.test.ts +8 -8
- package/src/store/chat/slices/aiChat/actions/__tests__/helpers.ts +1 -1
- package/src/store/chat/slices/aiChat/actions/generateAIChatV2.ts +1 -1
- package/src/store/chat/slices/message/action.test.ts +7 -7
- package/src/store/chat/slices/message/action.ts +2 -2
- package/src/store/chat/slices/plugin/action.test.ts +7 -7
- package/src/store/chat/slices/plugin/action.ts +1 -1
- 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
|
+
}
|