@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.
- package/.github/workflows/test.yml +1 -0
- package/CHANGELOG.md +58 -0
- package/apps/desktop/package.json +1 -1
- package/apps/desktop/src/main/core/infrastructure/UpdaterManager.ts +23 -2
- package/changelog/v1.json +21 -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/modules/S3/index.test.ts +379 -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,66 @@
|
|
|
1
|
+
import type { IdNode, Message } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* BranchResolver - Handles branch resolution logic
|
|
5
|
+
*
|
|
6
|
+
* Determines which branch should be active based on:
|
|
7
|
+
* 1. metadata.activeBranchIndex (explicit index)
|
|
8
|
+
* 2. Inferred from which branch has children
|
|
9
|
+
* 3. Default to first branch
|
|
10
|
+
*/
|
|
11
|
+
export class BranchResolver {
|
|
12
|
+
/**
|
|
13
|
+
* Get active branch ID from IdNode structure (used in contextTree building)
|
|
14
|
+
*/
|
|
15
|
+
getActiveBranchId(message: Message, idNode: IdNode): string {
|
|
16
|
+
// Priority 1: Try to get from metadata.activeBranchIndex (index-based)
|
|
17
|
+
const activeBranchIndex = (message.metadata as any)?.activeBranchIndex;
|
|
18
|
+
if (
|
|
19
|
+
typeof activeBranchIndex === 'number' &&
|
|
20
|
+
activeBranchIndex >= 0 &&
|
|
21
|
+
activeBranchIndex < idNode.children.length
|
|
22
|
+
) {
|
|
23
|
+
return idNode.children[activeBranchIndex].id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Priority 2: Infer from which branch has children
|
|
27
|
+
for (const child of idNode.children) {
|
|
28
|
+
if (child.children.length > 0) {
|
|
29
|
+
return child.id;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Default to first branch
|
|
34
|
+
return idNode.children[0].id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get active branch ID from flat list (used in flatList building)
|
|
39
|
+
*/
|
|
40
|
+
getActiveBranchIdFromMetadata(
|
|
41
|
+
message: Message,
|
|
42
|
+
childIds: string[],
|
|
43
|
+
childrenMap: Map<string | null, string[]>,
|
|
44
|
+
): string {
|
|
45
|
+
// Priority 1: Try to get from metadata.activeBranchIndex (index-based)
|
|
46
|
+
const activeBranchIndex = (message.metadata as any)?.activeBranchIndex;
|
|
47
|
+
if (
|
|
48
|
+
typeof activeBranchIndex === 'number' &&
|
|
49
|
+
activeBranchIndex >= 0 &&
|
|
50
|
+
activeBranchIndex < childIds.length
|
|
51
|
+
) {
|
|
52
|
+
return childIds[activeBranchIndex];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Priority 2: Infer from which child has descendants
|
|
56
|
+
for (const childId of childIds) {
|
|
57
|
+
const descendants = childrenMap.get(childId);
|
|
58
|
+
if (descendants && descendants.length > 0) {
|
|
59
|
+
return childId;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Default to first child
|
|
64
|
+
return childIds[0];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AssistantGroupNode,
|
|
3
|
+
BranchNode,
|
|
4
|
+
CompareNode,
|
|
5
|
+
ContextNode,
|
|
6
|
+
IdNode,
|
|
7
|
+
Message,
|
|
8
|
+
MessageGroupMetadata,
|
|
9
|
+
MessageNode,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import type { BranchResolver } from './BranchResolver';
|
|
12
|
+
import type { MessageCollector } from './MessageCollector';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ContextTreeBuilder - Transforms IdNode tree into ContextNode tree
|
|
16
|
+
*
|
|
17
|
+
* Handles:
|
|
18
|
+
* 1. Tree traversal with priority-based node type detection
|
|
19
|
+
* 2. Creating different types of ContextNodes (Message, Branch, Compare, AssistantGroup)
|
|
20
|
+
* 3. Linear array output of the tree structure
|
|
21
|
+
*/
|
|
22
|
+
export class ContextTreeBuilder {
|
|
23
|
+
constructor(
|
|
24
|
+
private messageMap: Map<string, Message>,
|
|
25
|
+
private messageGroupMap: Map<string, MessageGroupMetadata>,
|
|
26
|
+
private branchResolver: BranchResolver,
|
|
27
|
+
private messageCollector: MessageCollector,
|
|
28
|
+
private generateNodeId: (prefix: string, messageId: string) => string,
|
|
29
|
+
) {}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Transform all root nodes to contextTree
|
|
33
|
+
* Returns a linear array of context nodes
|
|
34
|
+
*/
|
|
35
|
+
transformAll(idNodes: IdNode[]): ContextNode[] {
|
|
36
|
+
const contextTree: ContextNode[] = [];
|
|
37
|
+
|
|
38
|
+
for (const idNode of idNodes) {
|
|
39
|
+
this.transformToLinear(idNode, contextTree);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return contextTree;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Transform a single IdNode and append to contextTree array
|
|
47
|
+
*/
|
|
48
|
+
private transformToLinear(idNode: IdNode, contextTree: ContextNode[]): void {
|
|
49
|
+
const message = this.messageMap.get(idNode.id);
|
|
50
|
+
if (!message) return;
|
|
51
|
+
|
|
52
|
+
// Priority 1: Compare mode from user message metadata
|
|
53
|
+
if (this.isCompareMode(message) && idNode.children.length > 1) {
|
|
54
|
+
// Add user message node
|
|
55
|
+
const messageNode = this.createMessageNode(message);
|
|
56
|
+
contextTree.push(messageNode);
|
|
57
|
+
|
|
58
|
+
// Create compare node with children
|
|
59
|
+
const compareNode = this.createCompareNodeFromChildren(message, idNode);
|
|
60
|
+
contextTree.push(compareNode);
|
|
61
|
+
|
|
62
|
+
// Continue with active column's children (if any)
|
|
63
|
+
if (compareNode.activeColumnId) {
|
|
64
|
+
// Find the active column's IdNode in the children
|
|
65
|
+
const activeColumnIdNode = idNode.children.find(
|
|
66
|
+
(child) => child.id === compareNode.activeColumnId,
|
|
67
|
+
);
|
|
68
|
+
if (activeColumnIdNode && activeColumnIdNode.children.length > 0) {
|
|
69
|
+
this.transformToLinear(activeColumnIdNode.children[0], contextTree);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Priority 2: Compare mode (from messageGroup metadata)
|
|
76
|
+
const messageGroup = message.groupId ? this.messageGroupMap.get(message.groupId) : undefined;
|
|
77
|
+
|
|
78
|
+
if (messageGroup && messageGroup.mode === 'compare') {
|
|
79
|
+
// Create compare node
|
|
80
|
+
const compareNode = this.createCompareNode(messageGroup, message);
|
|
81
|
+
contextTree.push(compareNode);
|
|
82
|
+
|
|
83
|
+
// Continue with active column's children (if any)
|
|
84
|
+
if (compareNode.activeColumnId) {
|
|
85
|
+
// Find the active column's IdNode in the children
|
|
86
|
+
const activeColumnIdNode = idNode.children.find(
|
|
87
|
+
(child) => child.id === compareNode.activeColumnId,
|
|
88
|
+
);
|
|
89
|
+
if (activeColumnIdNode && activeColumnIdNode.children.length > 0) {
|
|
90
|
+
this.transformToLinear(activeColumnIdNode.children[0], contextTree);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Priority 3: AssistantGroup (assistant + tools)
|
|
97
|
+
if (this.isAssistantGroupNode(message, idNode)) {
|
|
98
|
+
const assistantGroupNode = this.createAssistantGroupNode(message, idNode);
|
|
99
|
+
contextTree.push(assistantGroupNode);
|
|
100
|
+
|
|
101
|
+
// Find the next message after tools
|
|
102
|
+
const nextMessage = this.messageCollector.findNextAfterTools(message, idNode);
|
|
103
|
+
if (nextMessage) {
|
|
104
|
+
this.transformToLinear(nextMessage, contextTree);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Priority 4: Branch (multiple children)
|
|
110
|
+
if (idNode.children.length > 1) {
|
|
111
|
+
// Add current message node
|
|
112
|
+
const messageNode = this.createMessageNode(message);
|
|
113
|
+
contextTree.push(messageNode);
|
|
114
|
+
|
|
115
|
+
// Create branch node
|
|
116
|
+
const branchNode = this.createBranchNode(message, idNode);
|
|
117
|
+
contextTree.push(branchNode);
|
|
118
|
+
|
|
119
|
+
// Don't continue after branch - branch is an end point
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Priority 5: Regular message
|
|
124
|
+
const messageNode = this.createMessageNode(message);
|
|
125
|
+
contextTree.push(messageNode);
|
|
126
|
+
|
|
127
|
+
// Continue with single child
|
|
128
|
+
if (idNode.children.length === 1) {
|
|
129
|
+
this.transformToLinear(idNode.children[0], contextTree);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Check if message has compare mode in metadata
|
|
135
|
+
*/
|
|
136
|
+
private isCompareMode(message: Message): boolean {
|
|
137
|
+
return (message.metadata as any)?.compare === true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if this is Assistant + Tools pattern
|
|
142
|
+
*/
|
|
143
|
+
private isAssistantGroupNode(message: Message, idNode: IdNode): boolean {
|
|
144
|
+
if (message.role !== 'assistant') return false;
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
idNode.children.length > 0 &&
|
|
148
|
+
idNode.children.every((child) => {
|
|
149
|
+
const childMsg = this.messageMap.get(child.id);
|
|
150
|
+
return childMsg?.role === 'tool';
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create MessageNode (leaf node)
|
|
157
|
+
* Uses the message's own id directly
|
|
158
|
+
*/
|
|
159
|
+
private createMessageNode(message: Message): MessageNode {
|
|
160
|
+
return {
|
|
161
|
+
id: message.id,
|
|
162
|
+
type: 'message',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Create AssistantGroupNode
|
|
168
|
+
* Collects all assistant messages in the sequence (with or without tools)
|
|
169
|
+
*/
|
|
170
|
+
private createAssistantGroupNode(message: Message, idNode: IdNode): AssistantGroupNode {
|
|
171
|
+
const children: ContextNode[] = [];
|
|
172
|
+
|
|
173
|
+
// Recursively collect all assistant messages in this group
|
|
174
|
+
this.messageCollector.collectAssistantGroupMessages(message, idNode, children);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
children,
|
|
178
|
+
id: message.id,
|
|
179
|
+
type: 'assistantGroup',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Create BranchNode
|
|
185
|
+
*/
|
|
186
|
+
private createBranchNode(message: Message, idNode: IdNode): BranchNode {
|
|
187
|
+
const activeBranchId = this.branchResolver.getActiveBranchId(message, idNode);
|
|
188
|
+
const activeBranchIndex = idNode.children.findIndex((child) => child.id === activeBranchId);
|
|
189
|
+
|
|
190
|
+
// Each branch is a tree starting from that child
|
|
191
|
+
const branches = idNode.children.map((child) => {
|
|
192
|
+
const branchTree: ContextNode[] = [];
|
|
193
|
+
this.transformToLinear(child, branchTree);
|
|
194
|
+
return branchTree;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
activeBranchIndex: activeBranchIndex >= 0 ? activeBranchIndex : 0,
|
|
199
|
+
branches,
|
|
200
|
+
id: this.generateNodeId('branch', message.id),
|
|
201
|
+
parentMessageId: message.id,
|
|
202
|
+
type: 'branch',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create CompareNode from children messages
|
|
208
|
+
*/
|
|
209
|
+
private createCompareNodeFromChildren(message: Message, idNode: IdNode): CompareNode {
|
|
210
|
+
// Find active column ID from children metadata
|
|
211
|
+
let activeColumnId: string | undefined;
|
|
212
|
+
|
|
213
|
+
// Each child is a column - need to recursively process to handle AssistantGroup
|
|
214
|
+
const columns = idNode.children.map((child) => {
|
|
215
|
+
const childMessage = this.messageMap.get(child.id);
|
|
216
|
+
if (!childMessage) {
|
|
217
|
+
return [
|
|
218
|
+
{
|
|
219
|
+
id: child.id,
|
|
220
|
+
type: 'message',
|
|
221
|
+
} as MessageNode,
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check if this message is marked as active column
|
|
226
|
+
if ((childMessage.metadata as any)?.activeColumn === true) {
|
|
227
|
+
activeColumnId = child.id;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check if this column should be an AssistantGroup
|
|
231
|
+
if (this.isAssistantGroupNode(childMessage, child)) {
|
|
232
|
+
const assistantGroupNode = this.createAssistantGroupNode(childMessage, child);
|
|
233
|
+
return [assistantGroupNode];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Otherwise, just a simple MessageNode
|
|
237
|
+
return [
|
|
238
|
+
{
|
|
239
|
+
id: child.id,
|
|
240
|
+
type: 'message',
|
|
241
|
+
} as MessageNode,
|
|
242
|
+
];
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Generate ID by joining parent message id and all column message ids
|
|
246
|
+
const columnIds = idNode.children.map((child) => child.id).join('-');
|
|
247
|
+
const compareId = `compare-${message.id}-${columnIds}`;
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
activeColumnId,
|
|
251
|
+
columns,
|
|
252
|
+
id: compareId,
|
|
253
|
+
messageId: message.id,
|
|
254
|
+
type: 'compare',
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Create CompareNode from message group
|
|
260
|
+
*/
|
|
261
|
+
private createCompareNode(group: MessageGroupMetadata, message: Message): CompareNode {
|
|
262
|
+
// Collect all messages in this group
|
|
263
|
+
const groupMessages: Message[] = [];
|
|
264
|
+
for (const msg of this.messageMap.values()) {
|
|
265
|
+
if (msg.groupId === group.id) {
|
|
266
|
+
groupMessages.push(msg);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Find active column ID from group messages metadata
|
|
271
|
+
const activeColumnId = groupMessages.find(
|
|
272
|
+
(msg) => (msg.metadata as any)?.activeColumn === true,
|
|
273
|
+
)?.id;
|
|
274
|
+
|
|
275
|
+
// Each column is a message tree
|
|
276
|
+
const columns = groupMessages.map((msg) => {
|
|
277
|
+
const messageNode: MessageNode = {
|
|
278
|
+
id: msg.id,
|
|
279
|
+
type: 'message',
|
|
280
|
+
};
|
|
281
|
+
return [messageNode];
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
activeColumnId,
|
|
286
|
+
columns,
|
|
287
|
+
id: this.generateNodeId('compare', group.id),
|
|
288
|
+
messageId: group.parentMessageId || message.id,
|
|
289
|
+
type: 'compare',
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|