@mariozechner/pi-tui 0.5.0

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/README.md ADDED
@@ -0,0 +1,655 @@
1
+ # @mariozechner/pi-tui
2
+
3
+ Terminal UI framework with differential rendering for building interactive CLI applications.
4
+
5
+ ## Features
6
+
7
+ - **Differential Rendering**: Only re-renders content that has changed for optimal performance
8
+ - **Interactive Components**: Text editor, autocomplete, selection lists, and markdown rendering
9
+ - **Composable Architecture**: Container-based component system with proper lifecycle management
10
+ - **Text Editor Autocomplete System**: File completion and slash commands with provider interface
11
+
12
+ ## Quick Start
13
+
14
+ ```typescript
15
+ import { TUI, Container, TextComponent, TextEditor } from "@mariozechner/pi-tui";
16
+
17
+ // Create TUI manager
18
+ const ui = new TUI();
19
+
20
+ // Create components
21
+ const header = new TextComponent("🚀 My TUI App");
22
+ const chatContainer = new Container();
23
+ const editor = new TextEditor();
24
+
25
+ // Add components to UI
26
+ ui.addChild(header);
27
+ ui.addChild(chatContainer);
28
+ ui.addChild(editor);
29
+
30
+ // Set focus to the editor
31
+ ui.setFocus(editor);
32
+
33
+ // Handle editor submissions
34
+ editor.onSubmit = (text: string) => {
35
+ if (text.trim()) {
36
+ const message = new TextComponent(`💬 ${text}`);
37
+ chatContainer.addChild(message);
38
+ ui.requestRender();
39
+ }
40
+ };
41
+
42
+ // Start the UI
43
+ ui.start();
44
+ ```
45
+
46
+ ## Core Components
47
+
48
+ ### TUI
49
+
50
+ Main TUI manager that handles rendering, input, and component coordination.
51
+
52
+ **Methods:**
53
+
54
+ - `addChild(component)` - Add a component to the TUI
55
+ - `removeChild(component)` - Remove a component from the TUI
56
+ - `setFocus(component)` - Set which component receives keyboard input
57
+ - `start()` - Start the TUI (enables raw mode)
58
+ - `stop()` - Stop the TUI (disables raw mode)
59
+ - `requestRender()` - Request a re-render on next tick
60
+ - `configureLogging(config)` - Configure debug logging
61
+ - `cleanupSentinels()` - Remove placeholder components after removal operations
62
+ - `findComponent(component)` - Check if a component exists in the hierarchy (private)
63
+ - `findInContainer(container, component)` - Search for component in container (private)
64
+
65
+ ### Container
66
+
67
+ Component that manages child components with differential rendering.
68
+
69
+ **Constructor:**
70
+
71
+ ```typescript
72
+ new Container(parentTui?: TUI | undefined)
73
+ ```
74
+
75
+ **Methods:**
76
+
77
+ - `addChild(component)` - Add a child component
78
+ - `removeChild(component)` - Remove a child component
79
+ - `getChild(index)` - Get a specific child component
80
+ - `getChildCount()` - Get the number of child components
81
+ - `clear()` - Remove all child components
82
+ - `setParentTui(tui)` - Set the parent TUI reference
83
+ - `cleanupSentinels()` - Clean up removed component placeholders
84
+ - `render(width)` - Render all child components (returns ContainerRenderResult)
85
+
86
+ ### TextEditor
87
+
88
+ Interactive multiline text editor with cursor support and comprehensive keyboard shortcuts.
89
+
90
+ **Constructor:**
91
+
92
+ ```typescript
93
+ new TextEditor(config?: TextEditorConfig)
94
+ ```
95
+
96
+ **Configuration:**
97
+
98
+ ```typescript
99
+ interface TextEditorConfig {
100
+ // Configuration options for text editor
101
+ }
102
+
103
+ editor.configure(config: Partial<TextEditorConfig>)
104
+ ```
105
+
106
+ **Properties:**
107
+
108
+ - `onSubmit?: (text: string) => void` - Callback when user presses Enter
109
+ - `onChange?: (text: string) => void` - Callback when text content changes
110
+
111
+ **Methods:**
112
+
113
+ - `getText()` - Get current text content
114
+ - `setText(text)` - Set text content and move cursor to end
115
+ - `setAutocompleteProvider(provider)` - Set autocomplete provider for Tab completion
116
+ - `render(width)` - Render the editor with current state
117
+ - `handleInput(data)` - Process keyboard input
118
+
119
+ **Keyboard Shortcuts:**
120
+
121
+ **Navigation:**
122
+
123
+ - `Arrow Keys` - Move cursor
124
+ - `Home` / `Ctrl+A` - Move to start of line
125
+ - `End` / `Ctrl+E` - Move to end of line
126
+
127
+ **Editing:**
128
+
129
+ - `Backspace` - Delete character before cursor
130
+ - `Delete` / `Fn+Backspace` - Delete character at cursor
131
+ - `Ctrl+K` - Delete current line
132
+ - `Enter` - Submit text (calls onSubmit)
133
+ - `Shift+Enter` / `Option+Enter` - Add new line
134
+ - `Tab` - Trigger autocomplete
135
+
136
+ **Autocomplete (when active):**
137
+
138
+ - `Tab` - Apply selected completion
139
+ - `Arrow Up/Down` - Navigate suggestions
140
+ - `Escape` - Cancel autocomplete
141
+ - `Enter` - Cancel autocomplete and submit
142
+
143
+ **Paste Detection:**
144
+
145
+ - Automatically handles multi-line paste
146
+ - Converts tabs to 4 spaces
147
+ - Filters non-printable characters
148
+
149
+ ### TextComponent
150
+
151
+ Simple text component with automatic text wrapping and differential rendering.
152
+
153
+ **Constructor:**
154
+
155
+ ```typescript
156
+ new TextComponent(text: string, padding?: Padding)
157
+
158
+ interface Padding {
159
+ top?: number;
160
+ bottom?: number;
161
+ left?: number;
162
+ right?: number;
163
+ }
164
+ ```
165
+
166
+ **Methods:**
167
+
168
+ - `setText(text)` - Update the text content
169
+ - `getText()` - Get current text content
170
+ - `render(width)` - Render with word wrapping
171
+
172
+ **Features:**
173
+
174
+ - Automatic text wrapping to fit terminal width
175
+ - Configurable padding on all sides
176
+ - Preserves line breaks in source text
177
+ - Uses differential rendering to avoid unnecessary updates
178
+
179
+ ### MarkdownComponent
180
+
181
+ Renders markdown content with syntax highlighting and proper formatting.
182
+
183
+ **Constructor:**
184
+
185
+ ```typescript
186
+ new MarkdownComponent(text?: string)
187
+ ```
188
+
189
+ **Methods:**
190
+
191
+ - `setText(text)` - Update markdown content
192
+ - `render(width)` - Render parsed markdown
193
+
194
+ **Features:**
195
+
196
+ - **Headings**: Styled with colors and formatting
197
+ - **Code blocks**: Syntax highlighting with gray background
198
+ - **Lists**: Bullet points (•) and numbered lists
199
+ - **Emphasis**: **Bold** and _italic_ text
200
+ - **Links**: Underlined with URL display
201
+ - **Blockquotes**: Styled with left border
202
+ - **Inline code**: Highlighted with background
203
+ - **Horizontal rules**: Terminal-width separator lines
204
+ - Differential rendering for performance
205
+
206
+ ### SelectList
207
+
208
+ Interactive selection component for choosing from options.
209
+
210
+ **Constructor:**
211
+
212
+ ```typescript
213
+ new SelectList(items: SelectItem[], maxVisible?: number)
214
+
215
+ interface SelectItem {
216
+ value: string;
217
+ label: string;
218
+ description?: string;
219
+ }
220
+ ```
221
+
222
+ **Properties:**
223
+
224
+ - `onSelect?: (item: SelectItem) => void` - Called when item is selected
225
+ - `onCancel?: () => void` - Called when selection is cancelled
226
+
227
+ **Methods:**
228
+
229
+ - `setFilter(filter)` - Filter items by value
230
+ - `getSelectedItem()` - Get currently selected item
231
+ - `handleInput(keyData)` - Handle keyboard navigation
232
+ - `render(width)` - Render the selection list
233
+
234
+ **Features:**
235
+
236
+ - Keyboard navigation (arrow keys, Enter)
237
+ - Search/filter functionality
238
+ - Scrolling for long lists
239
+ - Custom option rendering with descriptions
240
+ - Visual selection indicator (→)
241
+ - Scroll position indicator
242
+
243
+ ### Autocomplete System
244
+
245
+ Comprehensive autocomplete system supporting slash commands and file paths.
246
+
247
+ #### AutocompleteProvider Interface
248
+
249
+ ```typescript
250
+ interface AutocompleteProvider {
251
+ getSuggestions(
252
+ lines: string[],
253
+ cursorLine: number,
254
+ cursorCol: number,
255
+ ): {
256
+ items: AutocompleteItem[];
257
+ prefix: string;
258
+ } | null;
259
+
260
+ applyCompletion(
261
+ lines: string[],
262
+ cursorLine: number,
263
+ cursorCol: number,
264
+ item: AutocompleteItem,
265
+ prefix: string,
266
+ ): {
267
+ lines: string[];
268
+ cursorLine: number;
269
+ cursorCol: number;
270
+ };
271
+ }
272
+
273
+ interface AutocompleteItem {
274
+ value: string;
275
+ label: string;
276
+ description?: string;
277
+ }
278
+ ```
279
+
280
+ #### CombinedAutocompleteProvider
281
+
282
+ Built-in provider supporting slash commands and file completion.
283
+
284
+ **Constructor:**
285
+
286
+ ```typescript
287
+ new CombinedAutocompleteProvider(
288
+ commands: (SlashCommand | AutocompleteItem)[] = [],
289
+ basePath: string = process.cwd()
290
+ )
291
+
292
+ interface SlashCommand {
293
+ name: string;
294
+ description?: string;
295
+ getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
296
+ }
297
+ ```
298
+
299
+ **Features:**
300
+
301
+ **Slash Commands:**
302
+
303
+ - Type `/` to trigger command completion
304
+ - Auto-completion for command names
305
+ - Argument completion for commands that support it
306
+ - Space after command name for argument input
307
+
308
+ **File Completion:**
309
+
310
+ - `Tab` key triggers file completion
311
+ - `@` prefix for file attachments
312
+ - Home directory expansion (`~/`)
313
+ - Relative and absolute path support
314
+ - Directory-first sorting
315
+ - Filters to attachable files for `@` prefix
316
+
317
+ **Path Patterns:**
318
+
319
+ - `./` and `../` - Relative paths
320
+ - `~/` - Home directory
321
+ - `@path` - File attachment syntax
322
+ - Tab completion from any context
323
+
324
+ **Methods:**
325
+
326
+ - `getSuggestions()` - Get completions for current context
327
+ - `getForceFileSuggestions()` - Force file completion (Tab key)
328
+ - `shouldTriggerFileCompletion()` - Check if file completion should trigger
329
+ - `applyCompletion()` - Apply selected completion
330
+
331
+ ## Differential Rendering
332
+
333
+ The core concept: components return `{lines: string[], changed: boolean, keepLines?: number}`:
334
+
335
+ - `lines`: All lines the component should display
336
+ - `changed`: Whether the component has changed since last render
337
+ - `keepLines`: (Containers only) How many lines from the beginning are unchanged
338
+
339
+ **How it works:**
340
+
341
+ 1. TUI calculates total unchanged lines from top (`keepLines`)
342
+ 2. Moves cursor up by `(totalLines - keepLines)` positions
343
+ 3. Clears from cursor position down with `\x1b[0J`
344
+ 4. Prints only the changing lines: `result.lines.slice(keepLines)`
345
+
346
+ This approach minimizes screen updates and provides smooth performance even with large amounts of text.
347
+
348
+ **Important:** Don't add extra cursor positioning after printing - it interferes with terminal scrolling and causes rendering artifacts.
349
+
350
+ ## Advanced Examples
351
+
352
+ ### Chat Application with Autocomplete
353
+
354
+ ```typescript
355
+ import { TUI, Container, TextEditor, MarkdownComponent, CombinedAutocompleteProvider } from "@mariozechner/pi-tui";
356
+
357
+ const ui = new TUI();
358
+ const chatHistory = new Container();
359
+ const editor = new TextEditor();
360
+
361
+ // Set up autocomplete with slash commands
362
+ const autocompleteProvider = new CombinedAutocompleteProvider([
363
+ { name: "clear", description: "Clear chat history" },
364
+ { name: "help", description: "Show help information" },
365
+ {
366
+ name: "attach",
367
+ description: "Attach a file",
368
+ getArgumentCompletions: (prefix) => {
369
+ // Return file suggestions for attach command
370
+ return null; // Use default file completion
371
+ },
372
+ },
373
+ ]);
374
+
375
+ editor.setAutocompleteProvider(autocompleteProvider);
376
+
377
+ editor.onSubmit = (text) => {
378
+ // Handle slash commands
379
+ if (text.startsWith("/")) {
380
+ const [command, ...args] = text.slice(1).split(" ");
381
+ if (command === "clear") {
382
+ chatHistory.clear();
383
+ return;
384
+ }
385
+ if (command === "help") {
386
+ const help = new MarkdownComponent(`
387
+ ## Available Commands
388
+ - \`/clear\` - Clear chat history
389
+ - \`/help\` - Show this help
390
+ - \`/attach <file>\` - Attach a file
391
+ `);
392
+ chatHistory.addChild(help);
393
+ ui.requestRender();
394
+ return;
395
+ }
396
+ }
397
+
398
+ // Regular message
399
+ const message = new MarkdownComponent(`**You:** ${text}`);
400
+ chatHistory.addChild(message);
401
+
402
+ // Add AI response (simulated)
403
+ setTimeout(() => {
404
+ const response = new MarkdownComponent(`**AI:** Response to "${text}"`);
405
+ chatHistory.addChild(response);
406
+ ui.requestRender();
407
+ }, 1000);
408
+ };
409
+
410
+ ui.addChild(chatHistory);
411
+ ui.addChild(editor);
412
+ ui.setFocus(editor);
413
+ ui.start();
414
+ ```
415
+
416
+ ### File Browser
417
+
418
+ ```typescript
419
+ import { TUI, SelectList } from "@mariozechner/pi-tui";
420
+ import { readdirSync, statSync } from "fs";
421
+ import { join } from "path";
422
+
423
+ const ui = new TUI();
424
+ let currentPath = process.cwd();
425
+
426
+ function createFileList(path: string) {
427
+ const entries = readdirSync(path).map((entry) => {
428
+ const fullPath = join(path, entry);
429
+ const isDir = statSync(fullPath).isDirectory();
430
+ return {
431
+ value: entry,
432
+ label: entry,
433
+ description: isDir ? "directory" : "file",
434
+ };
435
+ });
436
+
437
+ // Add parent directory option
438
+ if (path !== "/") {
439
+ entries.unshift({
440
+ value: "..",
441
+ label: "..",
442
+ description: "parent directory",
443
+ });
444
+ }
445
+
446
+ return entries;
447
+ }
448
+
449
+ function showDirectory(path: string) {
450
+ ui.clear();
451
+
452
+ const entries = createFileList(path);
453
+ const fileList = new SelectList(entries, 10);
454
+
455
+ fileList.onSelect = (item) => {
456
+ if (item.value === "..") {
457
+ currentPath = join(currentPath, "..");
458
+ showDirectory(currentPath);
459
+ } else if (item.description === "directory") {
460
+ currentPath = join(currentPath, item.value);
461
+ showDirectory(currentPath);
462
+ } else {
463
+ console.log(`Selected file: ${join(currentPath, item.value)}`);
464
+ ui.stop();
465
+ }
466
+ };
467
+
468
+ ui.addChild(fileList);
469
+ ui.setFocus(fileList);
470
+ }
471
+
472
+ showDirectory(currentPath);
473
+ ui.start();
474
+ ```
475
+
476
+ ### Multi-Component Layout
477
+
478
+ ```typescript
479
+ import { TUI, Container, TextComponent, TextEditor, MarkdownComponent } from "@mariozechner/pi-tui";
480
+
481
+ const ui = new TUI();
482
+
483
+ // Create layout containers
484
+ const header = new TextComponent("📝 Advanced TUI Demo", { bottom: 1 });
485
+ const mainContent = new Container();
486
+ const sidebar = new Container();
487
+ const footer = new TextComponent("Press Ctrl+C to exit", { top: 1 });
488
+
489
+ // Sidebar content
490
+ sidebar.addChild(new TextComponent("📁 Files:", { bottom: 1 }));
491
+ sidebar.addChild(new TextComponent("- config.json"));
492
+ sidebar.addChild(new TextComponent("- README.md"));
493
+ sidebar.addChild(new TextComponent("- package.json"));
494
+
495
+ // Main content area
496
+ const chatArea = new Container();
497
+ const inputArea = new TextEditor();
498
+
499
+ // Add welcome message
500
+ chatArea.addChild(
501
+ new MarkdownComponent(`
502
+ # Welcome to the TUI Demo
503
+
504
+ This demonstrates multiple components working together:
505
+
506
+ - **Header**: Static title with padding
507
+ - **Sidebar**: File list (simulated)
508
+ - **Chat Area**: Scrollable message history
509
+ - **Input**: Interactive text editor
510
+ - **Footer**: Status information
511
+
512
+ Try typing a message and pressing Enter!
513
+ `),
514
+ );
515
+
516
+ inputArea.onSubmit = (text) => {
517
+ if (text.trim()) {
518
+ const message = new MarkdownComponent(`
519
+ **${new Date().toLocaleTimeString()}:** ${text}
520
+ `);
521
+ chatArea.addChild(message);
522
+ ui.requestRender();
523
+ }
524
+ };
525
+
526
+ // Build layout
527
+ mainContent.addChild(chatArea);
528
+ mainContent.addChild(inputArea);
529
+
530
+ ui.addChild(header);
531
+ ui.addChild(mainContent);
532
+ ui.addChild(footer);
533
+ ui.setFocus(inputArea);
534
+
535
+ // Configure debug logging
536
+ ui.configureLogging({
537
+ enabled: true,
538
+ level: "info",
539
+ logFile: "tui-debug.log",
540
+ });
541
+
542
+ ui.start();
543
+ ```
544
+
545
+ ## Interfaces and Types
546
+
547
+ ### Core Types
548
+
549
+ ```typescript
550
+ interface ComponentRenderResult {
551
+ lines: string[];
552
+ changed: boolean;
553
+ }
554
+
555
+ interface ContainerRenderResult extends ComponentRenderResult {
556
+ keepLines: number;
557
+ }
558
+
559
+ interface Component {
560
+ render(width: number): ComponentRenderResult;
561
+ handleInput?(keyData: string): void;
562
+ }
563
+
564
+ interface Padding {
565
+ top?: number;
566
+ bottom?: number;
567
+ left?: number;
568
+ right?: number;
569
+ }
570
+ ```
571
+
572
+ ### Autocomplete Types
573
+
574
+ ```typescript
575
+ interface AutocompleteItem {
576
+ value: string;
577
+ label: string;
578
+ description?: string;
579
+ }
580
+
581
+ interface SlashCommand {
582
+ name: string;
583
+ description?: string;
584
+ getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
585
+ }
586
+
587
+ interface AutocompleteProvider {
588
+ getSuggestions(
589
+ lines: string[],
590
+ cursorLine: number,
591
+ cursorCol: number,
592
+ ): {
593
+ items: AutocompleteItem[];
594
+ prefix: string;
595
+ } | null;
596
+
597
+ applyCompletion(
598
+ lines: string[],
599
+ cursorLine: number,
600
+ cursorCol: number,
601
+ item: AutocompleteItem,
602
+ prefix: string,
603
+ ): {
604
+ lines: string[];
605
+ cursorLine: number;
606
+ cursorCol: number;
607
+ };
608
+ }
609
+ ```
610
+
611
+ ### Selection Types
612
+
613
+ ```typescript
614
+ interface SelectItem {
615
+ value: string;
616
+ label: string;
617
+ description?: string;
618
+ }
619
+ ```
620
+
621
+ ## Development
622
+
623
+ ```bash
624
+ # Install dependencies (from monorepo root)
625
+ npm install
626
+
627
+ # Build the package
628
+ npm run build
629
+
630
+ # Run type checking
631
+ npm run check
632
+ ```
633
+
634
+ **Testing:**
635
+ Create a test file and run it with tsx:
636
+
637
+ ```bash
638
+ # From packages/tui directory
639
+ npx tsx test/demo.ts
640
+ ```
641
+
642
+ Special input keywords for simulation: "TAB", "ENTER", "SPACE", "ESC"
643
+
644
+ **Debugging:**
645
+ Enable logging to see detailed component behavior:
646
+
647
+ ```typescript
648
+ ui.configureLogging({
649
+ enabled: true,
650
+ level: "debug", // "error" | "warn" | "info" | "debug"
651
+ logFile: "tui-debug.log",
652
+ });
653
+ ```
654
+
655
+ Check the log file to debug rendering issues, input handling, and component lifecycle.
@@ -0,0 +1,44 @@
1
+ export interface AutocompleteItem {
2
+ value: string;
3
+ label: string;
4
+ description?: string;
5
+ }
6
+ export interface SlashCommand {
7
+ name: string;
8
+ description?: string;
9
+ getArgumentCompletions?(argumentPrefix: string): AutocompleteItem[] | null;
10
+ }
11
+ export interface AutocompleteProvider {
12
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
13
+ items: AutocompleteItem[];
14
+ prefix: string;
15
+ } | null;
16
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
17
+ lines: string[];
18
+ cursorLine: number;
19
+ cursorCol: number;
20
+ };
21
+ }
22
+ export declare class CombinedAutocompleteProvider implements AutocompleteProvider {
23
+ private commands;
24
+ private basePath;
25
+ constructor(commands?: (SlashCommand | AutocompleteItem)[], basePath?: string);
26
+ getSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
27
+ items: AutocompleteItem[];
28
+ prefix: string;
29
+ } | null;
30
+ applyCompletion(lines: string[], cursorLine: number, cursorCol: number, item: AutocompleteItem, prefix: string): {
31
+ lines: string[];
32
+ cursorLine: number;
33
+ cursorCol: number;
34
+ };
35
+ private extractPathPrefix;
36
+ private expandHomePath;
37
+ private getFileSuggestions;
38
+ getForceFileSuggestions(lines: string[], cursorLine: number, cursorCol: number): {
39
+ items: AutocompleteItem[];
40
+ prefix: string;
41
+ } | null;
42
+ shouldTriggerFileCompletion(lines: string[], cursorLine: number, cursorCol: number): boolean;
43
+ }
44
+ //# sourceMappingURL=autocomplete.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autocomplete.d.ts","sourceRoot":"","sources":["../src/autocomplete.ts"],"names":[],"mappings":"AAwFA,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IAGrB,sBAAsB,CAAC,CAAC,cAAc,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAAC;CAC3E;AAED,MAAM,WAAW,oBAAoB;IAGpC,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QACF,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAC1B,MAAM,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAIT,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QACF,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KAClB,CAAC;CACF;AAGD,qBAAa,4BAA6B,YAAW,oBAAoB;IACxE,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,GAAE,CAAC,YAAY,GAAG,gBAAgB,CAAC,EAAO,EAAE,QAAQ,GAAE,MAAsB;IAKhG,cAAc,CACb,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IA+EvD,eAAe,CACd,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACZ;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IA6D7D,OAAO,CAAC,iBAAiB;IA0CzB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,kBAAkB;IA0J1B,uBAAuB,CACtB,KAAK,EAAE,MAAM,EAAE,EACf,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GACf;QAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAoCvD,2BAA2B,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;CAW5F"}