n8n-nodes-smart-memory 1.0.14 → 1.0.16

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +356 -32
  3. package/dist/SmartMemory.d.ts +11 -3
  4. package/dist/SmartMemory.d.ts.map +1 -1
  5. package/dist/SmartMemory.js +144 -68
  6. package/dist/SmartMemory.js.map +1 -1
  7. package/dist/SmartMemory.node.d.ts +6 -8
  8. package/dist/SmartMemory.node.d.ts.map +1 -1
  9. package/dist/SmartMemory.node.js +179 -177
  10. package/dist/SmartMemory.node.js.map +1 -1
  11. package/dist/index.d.ts +0 -0
  12. package/dist/index.d.ts.map +0 -0
  13. package/dist/index.js +0 -0
  14. package/dist/index.js.map +0 -0
  15. package/dist/interfaces/MemoryInterfaces.d.ts +18 -13
  16. package/dist/interfaces/MemoryInterfaces.d.ts.map +1 -1
  17. package/dist/interfaces/MemoryInterfaces.js +0 -0
  18. package/dist/interfaces/MemoryInterfaces.js.map +0 -0
  19. package/dist/storage/InMemoryStorage.d.ts +2 -1
  20. package/dist/storage/InMemoryStorage.d.ts.map +1 -1
  21. package/dist/storage/InMemoryStorage.js +7 -6
  22. package/dist/storage/InMemoryStorage.js.map +1 -1
  23. package/dist/storage/MemoryStorage.d.ts +0 -0
  24. package/dist/storage/MemoryStorage.d.ts.map +0 -0
  25. package/dist/storage/MemoryStorage.js +0 -0
  26. package/dist/storage/MemoryStorage.js.map +0 -0
  27. package/dist/storage/RedisStorage.d.ts +35 -0
  28. package/dist/storage/RedisStorage.d.ts.map +1 -0
  29. package/dist/storage/RedisStorage.js +150 -0
  30. package/dist/storage/RedisStorage.js.map +1 -0
  31. package/dist/utils/EncryptionUtils.d.ts +51 -0
  32. package/dist/utils/EncryptionUtils.d.ts.map +1 -0
  33. package/dist/utils/EncryptionUtils.js +141 -0
  34. package/dist/utils/EncryptionUtils.js.map +1 -0
  35. package/dist/utils/ExpressionEvaluator.d.ts +5 -2
  36. package/dist/utils/ExpressionEvaluator.d.ts.map +1 -1
  37. package/dist/utils/ExpressionEvaluator.js +36 -21
  38. package/dist/utils/ExpressionEvaluator.js.map +1 -1
  39. package/dist/utils/OutputCleaner.d.ts +22 -0
  40. package/dist/utils/OutputCleaner.d.ts.map +1 -0
  41. package/dist/utils/OutputCleaner.js +112 -0
  42. package/dist/utils/OutputCleaner.js.map +1 -0
  43. package/dist/utils/ValidationUtils.d.ts +44 -0
  44. package/dist/utils/ValidationUtils.d.ts.map +1 -0
  45. package/dist/utils/ValidationUtils.js +88 -0
  46. package/dist/utils/ValidationUtils.js.map +1 -0
  47. package/package.json +54 -54
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ivan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,369 @@
1
- # Smart Memory for n8n
1
+ # n8n Smart Memory node: Conversational Memory Management System
2
2
 
3
- A flexible, configuration-driven memory management node for n8n that supports multiple storage backends and chat types.
3
+ ## Overview
4
4
 
5
- ## Features
5
+ Smart Memory is a sophisticated memory management system designed for conversational AI applications, particularly chatbots. It provides structured storage and retrieval of conversation histories, enabling language models to maintain context across multiple interactions while optimizing memory usage through intelligent windowing and organization strategies.
6
6
 
7
- - **Chat-Type Aware**: Different handling for private chats vs group chats
8
- - **Smart User Mapping**: Configure how user names are displayed
9
- - **Multiple Storage Backends**: In-memory, Redis
10
- - **Configurable Assistant Identity**: Customize how the assistant is identified
11
- - **Optional Features**: Reply context, semantic/episodic memory extraction
12
- - **Token Efficient**: Only stores essential information
7
+ ## Conceptual Architecture
13
8
 
14
- ## Installation
9
+ ### Core Purpose
15
10
 
16
- 1. Clone this repository
17
- 2. Install dependencies: `npm install`
18
- 3. Build the node: `npm run build`
19
- 4. Start n8n with the node: `n8n start --nodes ./dist`
11
+ The system addresses a fundamental challenge in conversational AI: maintaining coherent, contextually-aware conversations across multiple messages while managing memory constraints. It transforms raw conversational data into a structured format optimized for language model consumption, handling the complexity of different chat types (private conversations vs. group discussions) and maintaining relevant metadata.
20
12
 
21
- ## Usage
13
+ ### High-Level Design Philosophy
22
14
 
23
- ### Basic Setup
15
+ The architecture follows these key principles:
24
16
 
25
- 1. Add the "Smart Memory" node to your workflow
26
- 2. Configure the basic settings:
27
- - Memory Window Size: Number of messages to keep in memory
28
- - Assistant Role: How the assistant should be identified (e.g., "Assistant", "Bot")
29
- - User Display Name Expression: Expression to combine user name fields
30
- - Storage Backend: Choose where to store memory data
17
+ 1. **Chat-Centric Organization**: Memory is organized by conversation (chat) rather than by user, reflecting natural conversation boundaries
18
+ 2. **Adaptive Structure**: Different chat types (private, group, supergroup, channel) receive appropriate memory structures
19
+ 3. **Sliding Window Strategy**: Maintains recent context while preventing unbounded memory growth
20
+ 4. **Storage Abstraction**: Separates memory logic from storage implementation, enabling different backends
21
+ 5. **LLM-Ready Output**: Structures data in formats that language models can directly consume
31
22
 
32
- ### Advanced Configuration
23
+ ## How Smart Memory Functions
33
24
 
34
- #### User Field Mapping
25
+ ### Memory Structure Design
35
26
 
36
- The node allows flexible mapping of user fields:
27
+ The system organizes conversational memory into a hierarchical structure:
37
28
 
38
- ```javascript
39
- // Default display name expression
40
- ={{ $json.message.from.first_name + " " + $json.message.from.last_name }}
29
+ **Chat Level Information**:
30
+ - Unique chat identifier (for memory retrieval)
31
+ - Chat type classification (private, group, supergroup, channel)
32
+ - Chat name/title (for context)
41
33
 
42
- // Alternative expressions
43
- ={{ $json.message.from.username }}
44
- ={{ $json.customFields.displayName }}
45
- ={{ "User" + $json.message.from.id }}
34
+ **User Information Strategy**:
35
+ - **Private Chats**: User information stored once at the chat level (avoiding redundancy since only two participants exist)
36
+ - **Group Chats**: User information attached to each message (necessary since multiple users participate)
37
+
38
+ **Message Collection**:
39
+ - Ordered sequence of conversation turns
40
+ - Each message contains:
41
+ - Role identifier (user or assistant)
42
+ - Message content
43
+ - Optional timestamp (when temporal context matters)
44
+ - Optional user information (for group contexts)
45
+ - Optional reply context (tracking conversational threads)
46
+
47
+ ### Processing Pipeline
48
+
49
+ When new conversational data arrives, the system executes the following sequence:
50
+
51
+ #### 1. Input Validation Phase
52
+ The system validates incoming data to ensure:
53
+ - Chat identifier exists and is valid
54
+ - Message content is present and non-empty
55
+ - User information is complete (when required)
56
+ - Data types match expected formats
57
+
58
+ #### 2. Memory Retrieval Phase
59
+ Based on the chat identifier, the system:
60
+ - Queries the storage layer for existing conversation memory
61
+ - Returns null if this is the first interaction for this chat
62
+ - Otherwise, returns the complete memory structure
63
+
64
+ #### 3. Message Processing Phase
65
+
66
+ **For New Conversations**:
67
+ - Creates a fresh memory structure
68
+ - Initializes chat metadata (ID, type, name)
69
+ - For private chats: extracts and stores user information at the chat level
70
+ - Prepares an empty message collection
71
+
72
+ **For Existing Conversations**:
73
+ - Retrieves current memory state
74
+ - Validates consistency of chat metadata
75
+ - Prepares to append new message
76
+
77
+ #### 4. Message Construction Phase
78
+ The system builds a structured message object:
79
+
80
+ **Role Assignment**:
81
+ - Determines if the message is from the user or the assistant
82
+ - Uses configurable role identifiers (allowing customization)
83
+
84
+ **Content Extraction**:
85
+ - Extracts text content from various possible fields
86
+ - Handles different message types (text, captions, bot responses)
87
+ - Preserves original content without modification
88
+
89
+ **Metadata Attachment**:
90
+ - Conditionally adds timestamp (if temporal tracking is enabled)
91
+ - For group chats: attaches user identification (display name, username)
92
+ - For replies: captures reply context (who was replied to, what was said)
93
+
94
+ #### 5. Reply Context Processing
95
+ When messages are replies to previous messages:
96
+ - Extracts information about the original message
97
+ - Identifies the user being replied to
98
+ - Captures the content of the replied-to message
99
+ - Structures this as contextual metadata attached to the new message
100
+
101
+ This enables language models to understand conversational threading without requiring the entire conversation history to be repeatedly processed.
102
+
103
+ #### 6. Memory Windowing Phase
104
+ After adding the new message, the system applies a sliding window strategy:
105
+
106
+ **Window Size Enforcement**:
107
+ - Maintains only the most recent N messages
108
+ - Older messages are automatically pruned
109
+ - Window size is configurable per use case
110
+
111
+ **Rationale**:
112
+ - Prevents unbounded memory growth
113
+ - Focuses language model attention on recent context
114
+ - Reduces token consumption in LLM API calls
115
+ - Balances context retention with computational efficiency
116
+
117
+ #### 7. Storage Persistence Phase
118
+ The updated memory structure is persisted to the storage backend:
119
+ - Uses the chat identifier as the storage key
120
+ - Completely replaces previous memory state
121
+ - Storage operation is atomic (succeeds or fails completely)
122
+
123
+ #### 8. Output Formatting Phase
124
+ The system prepares output optimized for downstream consumption:
125
+ - Structures data for direct LLM prompt injection
126
+ - Optionally estimates token count (for API planning)
127
+ - Removes empty/null fields (reducing payload size)
128
+ - Maintains consistent JSON structure
129
+
130
+ ### Operation Modes
131
+
132
+ The system supports three primary operations:
133
+
134
+ #### Insert Operation
135
+ Adds new messages to conversation memory with two modes:
136
+
137
+ **Append Mode**:
138
+ - Adds the new message to the existing message sequence
139
+ - Preserves all previous messages (within the window)
140
+ - Standard operation for ongoing conversations
141
+
142
+ **Replace Mode**:
143
+ - Discards existing memory completely
144
+ - Creates fresh memory with just the new message
145
+ - Useful for conversation resets or context clearing
146
+
147
+ #### Get Operation
148
+ Retrieves the current memory state for a conversation:
149
+ - Returns complete memory structure
150
+ - Includes all messages within the window
151
+ - Provides metadata for context understanding
152
+
153
+ #### Clear Operation
154
+ Removes all memory for a conversation:
155
+ - Deletes the entire memory structure
156
+ - Used for privacy compliance or session termination
157
+ - Irreversible operation
158
+
159
+ ### Field Mapping and Expression Evaluation
160
+
161
+ The system handles diverse input formats through configurable field mapping:
162
+
163
+ **Dynamic Field Resolution**:
164
+ - Maps generic field identifiers to actual data locations
165
+ - Evaluates simple expressions to extract nested data
166
+ - Constructs composite fields (e.g., full name from first_name + last_name)
167
+
168
+ **Example Transformations**:
169
+ - Combining separate name fields into a single display name
170
+ - Extracting optional fields with fallback defaults
171
+ - Handling missing data gracefully
172
+
173
+ This abstraction allows the system to work with various messaging platforms without hardcoding platform-specific structures.
174
+
175
+ ### Chat Type Differentiation
176
+
177
+ The system optimizes memory structure based on conversation context:
178
+
179
+ **Private Conversations**:
180
+ - Single user information stored at chat level
181
+ - No need to identify message authors (only two participants)
182
+ - More compact memory structure
183
+ - Reduced redundancy
184
+
185
+ **Group Conversations**:
186
+ - User information attached to each message
187
+ - Essential for multi-participant context
188
+ - Enables "who said what" tracking
189
+ - Supports natural language references ("as John mentioned...")
190
+
191
+ This adaptive approach reduces memory overhead while maintaining necessary context.
192
+
193
+ ### Token Estimation
194
+
195
+ For LLM API planning, the system provides token estimation:
196
+
197
+ **Estimation Method**:
198
+ - Converts entire memory structure to JSON string
199
+ - Applies a heuristic ratio (approximately 4 characters per token)
200
+ - Returns conservative estimate
201
+
202
+ **Use Cases**:
203
+ - Pre-flight checks before LLM API calls
204
+ - Monitoring memory size growth
205
+ - Implementing dynamic windowing based on token limits
206
+ - Cost estimation for API usage
207
+
208
+ ### Output Cleaning
209
+
210
+ Before returning memory structures, the system removes noise:
211
+
212
+ **Recursive Cleaning Process**:
213
+ - Removes undefined and null values
214
+ - Eliminates empty strings
215
+ - Filters empty arrays and objects
216
+ - Processes nested structures recursively
217
+
218
+ **Benefits**:
219
+ - Reduces payload size
220
+ - Simplifies downstream processing
221
+ - Prevents language models from processing empty fields
222
+ - Creates cleaner prompt context
223
+
224
+ ## Integration Pattern
225
+
226
+ The system operates as a stateful service within workflow environments:
227
+
228
+ **Typical Flow**:
229
+ 1. Conversational input arrives (user message or bot response)
230
+ 2. Smart Memory processes and stores the message
231
+ 3. System retrieves current memory state
232
+ 4. Memory is formatted into LLM prompt
233
+ 5. LLM generates response using full context
234
+ 6. Bot response is stored back into memory
235
+ 7. Cycle repeats for next interaction
236
+
237
+ **Session Management**:
238
+ - Each conversation has a unique identifier (chat ID)
239
+ - Memory persists across workflow executions
240
+ - Storage backend handles persistence layer
241
+ - Memory lifecycle is independent of workflow lifecycle
242
+
243
+ ## Configuration Philosophy
244
+
245
+ The system is designed to be configuration-driven rather than code-driven:
246
+
247
+ **Configurable Aspects**:
248
+ - Memory window size (how much history to retain)
249
+ - Storage backend selection (where to persist memory)
250
+ - Field mapping rules (how to extract data)
251
+ - Feature toggles (timestamps, reply context, token estimation)
252
+ - Role identifiers (how to label participants)
253
+
254
+ This approach enables adapting the system to different use cases without code modifications.
255
+
256
+ ## Memory Consistency and Isolation
257
+
258
+ **Isolation Guarantees**:
259
+ - Each chat maintains independent memory
260
+ - No cross-conversation contamination
261
+ - Concurrent conversations are safely isolated
262
+
263
+ **Consistency Model**:
264
+ - Last-write-wins for memory updates
265
+ - No transaction support (appropriate for conversational context)
266
+ - Storage layer responsible for atomicity
267
+
268
+ ## Use Case Scenarios
269
+
270
+ ### Scenario 1: Customer Support Bot
271
+ A user initiates a support conversation:
272
+ - First message creates new memory with user context
273
+ - Subsequent questions append to conversation history
274
+ - Support agent (bot) responses are marked with assistant role
275
+ - Recent 15 messages maintained as sliding window
276
+ - When resolved, memory can be cleared
277
+
278
+ ### Scenario 2: Group Discussion Bot
279
+ Multiple users discuss in a group chat:
280
+ - Each message includes user identification
281
+ - Bot can reference who said what
282
+ - Reply context maintains conversation threads
283
+ - Window keeps last 20 messages across all users
284
+ - Bot maintains coherent multi-user context
285
+
286
+ ### Scenario 3: Long-Running Personal Assistant
287
+ A user has extended conversation with AI:
288
+ - Private chat structure used (compact format)
289
+ - Large window size (30-40 messages) for deeper context
290
+ - Timestamps enabled for temporal reasoning
291
+ - Token estimation prevents API limit issues
292
+ - Memory persists across days/weeks
293
+
294
+ ## Implementation-Agnostic Concepts
295
+
296
+ ### Storage Abstraction Layer
297
+ The system defines a storage interface that any backend can implement:
298
+
299
+ **Required Operations**:
300
+ - Retrieve memory by chat identifier
301
+ - Save/update memory for chat identifier
302
+ - Delete memory for chat identifier
303
+
304
+ **Supported Backends**:
305
+ - In-memory storage (volatile, for development/testing)
306
+ - Persistent storage solutions (databases, etc.)
307
+
308
+ The core logic is entirely independent of storage implementation, enabling deployment flexibility.
309
+
310
+ ### Validation Strategy
311
+ Input validation occurs at boundaries:
312
+ - Validates chat identifiers are non-empty
313
+ - Ensures message content exists
314
+ - Verifies window size is within acceptable range
315
+ - Type-checks critical fields
316
+
317
+ This defensive approach prevents corrupted memory states.
318
+
319
+ ### Error Handling Philosophy
320
+ The system follows a fail-fast approach:
321
+ - Invalid input throws errors immediately
322
+ - Storage failures propagate to caller
323
+ - No silent data corruption
324
+ - Errors include descriptive messages
325
+
326
+ This makes debugging and monitoring straightforward.
327
+
328
+ ## Design Rationale
329
+
330
+ ### Why Sliding Windows?
331
+ Language models have token limits and attention limitations. By maintaining a sliding window, the system:
332
+ - Keeps memory bounded and predictable
333
+ - Focuses on recent, relevant context
334
+ - Prevents token limit violations
335
+ - Improves response quality through focused context
336
+
337
+ ### Why Chat-Centric Storage?
338
+ Organizing by conversation rather than user:
339
+ - Reflects natural conversation boundaries
340
+ - Simplifies retrieval (one key lookup)
341
+ - Supports both individual and group contexts
342
+ - Aligns with how messaging platforms organize data
343
+
344
+ ### Why Different Structures for Chat Types?
345
+ Private and group chats have fundamentally different characteristics:
346
+ - Private: Two participants, no ambiguity about speakers
347
+ - Group: Multiple participants, must track "who said what"
348
+
349
+ Adapting the structure optimizes both memory efficiency and context clarity.
350
+
351
+ ### Why Optional Fields?
352
+ Not all use cases need all features:
353
+ - Timestamps add overhead but enable temporal reasoning
354
+ - Reply context increases complexity but improves coherence
355
+ - Token estimation has computational cost but aids planning
356
+
357
+ Optional fields allow tuning the system to specific requirements.
358
+
359
+ ## Summary
360
+
361
+ Smart Memory transforms the challenge of conversational context management into a solved problem through:
362
+
363
+ 1. **Structured Memory**: Organizing conversations into LLM-ready formats
364
+ 2. **Intelligent Windowing**: Balancing context retention with resource constraints
365
+ 3. **Adaptive Architecture**: Optimizing structure based on conversation type
366
+ 4. **Storage Abstraction**: Enabling flexible deployment options
367
+ 5. **Configuration-Driven**: Adapting to diverse use cases without code changes
368
+
369
+ The system serves as an intermediary layer between raw conversational data and language model consumption, handling the complexity of context management so that developers can focus on building conversational experiences rather than managing conversation state.
@@ -1,13 +1,21 @@
1
1
  import { CleanMemoryStructure, MemoryManagerConfig } from './interfaces/MemoryInterfaces';
2
+ import { MemoryStorage } from './storage/MemoryStorage';
2
3
  export declare class PureMemoryManager {
3
4
  private config;
4
5
  private storage;
5
- constructor(config: MemoryManagerConfig);
6
- insertMessage(inputData: any, insertMode: string): Promise<CleanMemoryStructure>;
7
- processMessage(inputData: any): Promise<CleanMemoryStructure>;
6
+ constructor(config: MemoryManagerConfig, storage?: MemoryStorage);
7
+ private validateConfig;
8
+ insertMessage(inputData: Record<string, unknown>, insertMode: string): Promise<CleanMemoryStructure>;
9
+ private validateAndParseInput;
10
+ private isValidInputData;
8
11
  getMemory(chatId: string): Promise<CleanMemoryStructure | null>;
9
12
  clearMemory(chatId: string): Promise<void>;
10
13
  private createNewMemory;
14
+ private normalizeChatType;
11
15
  private addMessageToMemory;
16
+ private buildMessage;
17
+ private extractUserInfo;
18
+ private extractReplyInfo;
19
+ private applyMemoryWindow;
12
20
  }
13
21
  //# sourceMappingURL=SmartMemory.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SmartMemory.d.ts","sourceRoot":"","sources":["../src/SmartMemory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAI1F,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,OAAO,CAAgB;gBAEnB,MAAM,EAAE,mBAAmB;IAWjC,aAAa,CAAC,SAAS,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAkBhF,cAAc,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAI7D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAI/D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAIlC,eAAe;YA2Bf,kBAAkB;CAiEjC"}
1
+ {"version":3,"file":"SmartMemory.d.ts","sourceRoot":"","sources":["../src/SmartMemory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAE1F,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AA8BxD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,OAAO,CAAgB;gBAEnB,MAAM,EAAE,mBAAmB,EAAE,OAAO,CAAC,EAAE,aAAa;IAMhE,OAAO,CAAC,cAAc;IAShB,aAAa,CACjB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC;IAgBhC,OAAO,CAAC,qBAAqB;IA+B7B,OAAO,CAAC,gBAAgB;IAUlB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAO/D,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD,OAAO,CAAC,eAAe;IAuCvB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,kBAAkB;IA2B1B,OAAO,CAAC,YAAY;IAwCpB,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,gBAAgB;IAgCxB,OAAO,CAAC,iBAAiB;CAQ1B"}