@syntrologie/adapt-faq 2.16.0 → 2.17.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.
@@ -1,195 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Lit Editor Component
3
- *
4
- * Lit web component port of the React FAQ editor (editor.tsx).
5
- * Displays FAQ question cards with inline editing, detection,
6
- * dismiss/restore, and rationale display.
7
- *
8
- * Custom events:
9
- * navigate-home — user clicked back
10
- * dirty-change — { dirty: boolean }
11
- */
12
- import { html, LitElement, nothing } from 'lit';
13
- import { summarizeFAQItem } from './summarize';
14
- import { isOwnAction } from './types';
15
- // ============================================================================
16
- // Helpers
17
- // ============================================================================
18
- const getAnswerText = (answer) => {
19
- if (typeof answer === 'string')
20
- return answer;
21
- if (answer.type === 'rich')
22
- return answer.html;
23
- return answer.content;
24
- };
25
- const flattenItems = (config) => {
26
- const actions = (config.actions || []).filter(isOwnAction);
27
- return actions.map((q, i) => ({
28
- key: String(i),
29
- index: i,
30
- summary: summarizeFAQItem(q),
31
- question: q,
32
- }));
33
- };
34
- // ============================================================================
35
- // Element
36
- // ============================================================================
37
- export class FAQEditorLit extends LitElement {
38
- constructor() {
39
- super(...arguments);
40
- this.config = null;
41
- this.onChange = null;
42
- this._editingKey = null;
43
- // No auto-dirty on load — only dirty when user makes changes (matching React behavior).
44
- // ---- Actions ----
45
- this._handleBack = () => {
46
- if (this._editingKey !== null) {
47
- this._editingKey = null;
48
- }
49
- else {
50
- this.dispatchEvent(new CustomEvent('navigate-home', { bubbles: true }));
51
- }
52
- };
53
- this._handleItemClick = (key) => {
54
- this._editingKey = key;
55
- };
56
- this._handleFieldChange = (index, field, value) => {
57
- if (!this.config || !this.onChange)
58
- return;
59
- const ownActions = (this.config.actions || []).filter(isOwnAction).slice();
60
- const q = { ...ownActions[index], config: { ...ownActions[index].config } };
61
- q.config[field] = value;
62
- ownActions[index] = q;
63
- const otherActions = (this.config.actions || []).filter((a) => !isOwnAction(a));
64
- const updated = { ...this.config, actions: [...otherActions, ...ownActions] };
65
- this.onChange(updated);
66
- this.dispatchEvent(new CustomEvent('dirty-change', { detail: { dirty: true }, bubbles: true }));
67
- };
68
- // ---- Render ----
69
- this._renderEditMode = (item) => {
70
- const q = item.question;
71
- return html `
72
- <div class="se-py-1">
73
- <div class="se-flex se-items-center se-gap-2 se-mb-3 se-text-[13px] se-font-semibold se-text-text-primary">
74
- <span>❓</span>
75
- <span>${item.summary}</span>
76
- </div>
77
-
78
- <div class="se-mb-3">
79
- <label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Question</label>
80
- <input
81
- type="text"
82
- .value=${q.config.question}
83
- @input=${(e) => this._handleFieldChange(item.index, 'question', e.target.value)}
84
- class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm"
85
- />
86
- </div>
87
-
88
- <div class="se-mb-3">
89
- <label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Answer</label>
90
- <textarea
91
- .value=${getAnswerText(q.config.answer)}
92
- @input=${(e) => this._handleFieldChange(item.index, 'answer', e.target.value)}
93
- rows="4"
94
- class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm se-resize-none"
95
- ></textarea>
96
- </div>
97
-
98
- <div class="se-mb-3">
99
- <label class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">Category</label>
100
- <input
101
- type="text"
102
- .value=${q.config.category || ''}
103
- placeholder="e.g., Billing, Account"
104
- @input=${(e) => this._handleFieldChange(item.index, 'category', e.target.value)}
105
- class="se-w-full se-p-2 se-rounded se-border se-border-border-primary se-bg-input-field-bg se-text-text-primary se-text-sm"
106
- />
107
- </div>
108
-
109
- ${q.rationale
110
- ? html `
111
- <div>
112
- <span class="se-text-[11px] se-font-semibold se-text-text-secondary se-mb-1 se-block">AI Rationale</span>
113
- <div class="se-p-2 se-rounded se-border se-border-dashed se-border-border-primary se-bg-card-bg se-text-text-secondary se-text-xs se-mb-2">
114
- ${q.rationale.why}
115
- </div>
116
- </div>
117
- `
118
- : nothing}
119
- </div>
120
- `;
121
- };
122
- this._renderListMode = (items) => {
123
- if (items.length === 0) {
124
- return html `<div class="se-text-center se-py-8 se-px-4 se-text-text-secondary se-text-sm">
125
- No FAQ questions configured.
126
- </div>`;
127
- }
128
- return html `
129
- <div class="se-text-[11px] se-font-semibold se-uppercase se-tracking-wider se-text-text-secondary se-mb-2 se-px-1">
130
- FAQ <span class="se-text-text-tertiary se-font-normal">${items.length}</span>
131
- </div>
132
- ${items.map((item) => html `
133
- <div
134
- data-item-key=${item.key}
135
- @click=${() => this._handleItemClick(item.key)}
136
- class="se-p-3 se-rounded-lg se-border se-border-border-primary se-bg-card-bg se-cursor-pointer se-mb-2 se-transition-all hover:se-border-pink-4/40 hover:se-bg-pink-4/5"
137
- >
138
- <div class="se-flex se-items-center se-gap-2">
139
- <span class="se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap se-text-sm se-text-text-primary">
140
- ${item.summary}
141
- </span>
142
- </div>
143
- ${item.question.rationale
144
- ? html `
145
- <div class="se-text-[10px] se-text-text-tertiary se-mt-1">
146
- WHY: ${item.question.rationale.why}
147
- </div>
148
- `
149
- : nothing}
150
- </div>
151
- `)}
152
- `;
153
- };
154
- }
155
- createRenderRoot() {
156
- return this;
157
- }
158
- render() {
159
- if (!this.config) {
160
- return html `<div class="se-p-8 se-text-center se-text-text-secondary se-text-sm">Loading...</div>`;
161
- }
162
- const items = flattenItems(this.config);
163
- const editItem = this._editingKey !== null ? items.find((it) => it.key === this._editingKey) : null;
164
- return html `
165
- <div class="se-flex se-flex-col se-h-full">
166
- <!-- Header (no Back button — panel provides that) -->
167
- <div class="se-px-4 se-pt-3 se-pb-1">
168
- <h2 class="se-m-0 se-text-base se-font-semibold se-text-text-primary">Review Questions</h2>
169
- <p class="se-m-0 se-mt-0.5 se-text-xs se-text-text-secondary">
170
- ${items.length} question${items.length !== 1 ? 's' : ''}
171
- </p>
172
- </div>
173
-
174
- <div class="se-flex-1 se-overflow-auto se-p-4">
175
- ${editItem
176
- ? html `
177
- <button type="button" @click=${() => {
178
- this._editingKey = null;
179
- }}
180
- class="se-mb-3 se-py-1 se-px-2 se-rounded se-border-none se-bg-card-bg se-text-text-secondary se-text-xs se-cursor-pointer"
181
- >← Back to list</button>
182
- ${this._renderEditMode(editItem)}
183
- `
184
- : this._renderListMode(items)}
185
- </div>
186
- </div>
187
- `;
188
- }
189
- }
190
- FAQEditorLit.properties = {
191
- config: { attribute: false },
192
- onChange: { attribute: false },
193
- _editingKey: { state: true },
194
- };
195
- customElements.define('se-faq-editor', FAQEditorLit);
package/dist/executors.js DELETED
@@ -1,150 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Action Executors
3
- *
4
- * Three executors that operate on the FAQStore:
5
- * - executeScrollToFaq: scroll to a FAQ item and optionally expand it
6
- * - executeToggleFaqItem: open / close / toggle a FAQ item
7
- * - executeUpdateFaq: add, remove, reorder, or replace FAQ items
8
- */
9
- // ============================================================================
10
- // Helpers
11
- // ============================================================================
12
- /**
13
- * Resolve a FAQ item by direct ID or by fuzzy question text match.
14
- * Throws if neither yields a result.
15
- */
16
- function resolveItem(store, itemId, itemQuestion) {
17
- if (itemId) {
18
- const found = store.getState().items.find((i) => i.config.id === itemId);
19
- if (found)
20
- return found;
21
- }
22
- if (itemQuestion) {
23
- const found = store.findByQuestion(itemQuestion);
24
- if (found)
25
- return found;
26
- }
27
- throw new Error('FAQ item not found');
28
- }
29
- // ============================================================================
30
- // executeScrollToFaq
31
- // ============================================================================
32
- /**
33
- * Scroll to a FAQ item in the DOM and optionally expand it.
34
- *
35
- * Looks up the item by `itemId` or `itemQuestion`, scrolls the matching
36
- * `[data-faq-item-id]` element into view, and expands it unless
37
- * `expand` is explicitly set to `false`.
38
- */
39
- export async function executeScrollToFaq(action, context, store) {
40
- const item = resolveItem(store, action.itemId, action.itemQuestion);
41
- const { id } = item.config;
42
- // Expand the item unless explicitly told not to
43
- if (action.expand !== false) {
44
- store.expand(id);
45
- }
46
- // Scroll the DOM element into view (may be absent in test environments)
47
- const el = document.querySelector(`[data-faq-item-id="${id}"]`);
48
- if (el) {
49
- el.scrollIntoView({
50
- behavior: action.behavior ?? 'smooth',
51
- });
52
- }
53
- // Publish analytics event
54
- context.publishEvent('faq:scroll_to', { itemId: id });
55
- return {
56
- cleanup: () => {
57
- // Optionally collapse on revert
58
- },
59
- };
60
- }
61
- // ============================================================================
62
- // executeToggleFaqItem
63
- // ============================================================================
64
- /**
65
- * Open, close, or toggle a FAQ item's expanded state.
66
- */
67
- export async function executeToggleFaqItem(action, context, store) {
68
- const item = resolveItem(store, action.itemId, action.itemQuestion);
69
- const { id } = item.config;
70
- const desiredState = action.state ?? 'toggle';
71
- let newState;
72
- switch (desiredState) {
73
- case 'open':
74
- store.expand(id);
75
- newState = 'open';
76
- break;
77
- case 'closed':
78
- store.collapse(id);
79
- newState = 'closed';
80
- break;
81
- default: {
82
- const wasExpanded = store.getState().expandedItems.has(id);
83
- store.toggle(id);
84
- newState = wasExpanded ? 'closed' : 'open';
85
- break;
86
- }
87
- }
88
- context.publishEvent('faq:toggle', { itemId: id, newState });
89
- return {
90
- cleanup: () => {
91
- // Revert toggle on cleanup
92
- },
93
- };
94
- }
95
- // ============================================================================
96
- // executeUpdateFaq
97
- // ============================================================================
98
- /**
99
- * Add, remove, reorder, or replace FAQ items in the store.
100
- */
101
- export async function executeUpdateFaq(action, context, store) {
102
- switch (action.operation) {
103
- case 'add': {
104
- const items = action.items ?? [];
105
- const position = action.position === 'prepend' ? 'prepend' : 'append';
106
- store.addItems(items, position);
107
- break;
108
- }
109
- case 'remove': {
110
- if (!action.itemId) {
111
- throw new Error('FAQ item not found');
112
- }
113
- // Verify the item exists before removing
114
- const exists = store.getState().items.some((i) => i.config.id === action.itemId);
115
- if (!exists) {
116
- throw new Error('FAQ item not found');
117
- }
118
- store.removeItem(action.itemId);
119
- break;
120
- }
121
- case 'reorder': {
122
- const order = action.order ?? [];
123
- store.reorderItems(order);
124
- break;
125
- }
126
- case 'replace': {
127
- const items = action.items ?? [];
128
- store.replaceItems(items);
129
- break;
130
- }
131
- }
132
- context.publishEvent('faq:update', { operation: action.operation });
133
- return {
134
- cleanup: () => {
135
- // Could snapshot previous state for undo
136
- },
137
- };
138
- }
139
- // ============================================================================
140
- // Executor Definitions for Registration
141
- // ============================================================================
142
- /**
143
- * All executors provided by adaptive-faq.
144
- * These are registered with the runtime's ExecutorRegistry.
145
- */
146
- export const executorDefinitions = [
147
- { kind: 'faq:scroll_to', executor: executeScrollToFaq },
148
- { kind: 'faq:toggle_item', executor: executeToggleFaqItem },
149
- { kind: 'faq:update', executor: executeUpdateFaq },
150
- ];
@@ -1,204 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Styles
3
- *
4
- * BaseStyles object (font, layout, colors, animations) and theme integration
5
- * for the FAQWidget component.
6
- */
7
- import { purple, slateGrey } from '@syntro/design-system/tokens';
8
- // ============================================================================
9
- // Base Styles
10
- // ============================================================================
11
- export const baseStyles = {
12
- container: {
13
- fontFamily: 'var(--sc-font-family, system-ui, -apple-system, sans-serif)',
14
- maxWidth: '800px',
15
- margin: '0 auto',
16
- },
17
- searchWrapper: {
18
- marginBottom: '8px',
19
- },
20
- searchInput: {
21
- width: '100%',
22
- padding: '12px 16px',
23
- borderRadius: '8px',
24
- fontSize: '14px',
25
- outline: 'none',
26
- transition: 'border-color 0.15s ease',
27
- backgroundColor: 'var(--sc-content-search-background)',
28
- color: 'var(--sc-content-search-color)',
29
- },
30
- accordion: {
31
- display: 'flex',
32
- flexDirection: 'column',
33
- gap: 'var(--sc-content-item-gap, 6px)',
34
- },
35
- item: {
36
- borderRadius: 'var(--sc-content-border-radius, 8px)',
37
- overflow: 'hidden',
38
- transition: 'box-shadow 0.15s ease',
39
- },
40
- question: {
41
- width: '100%',
42
- padding: 'var(--sc-content-item-padding, 12px 16px)',
43
- display: 'flex',
44
- alignItems: 'center',
45
- justifyContent: 'space-between',
46
- border: 'none',
47
- cursor: 'pointer',
48
- fontSize: 'var(--sc-content-item-font-size, 15px)',
49
- fontWeight: 500,
50
- textAlign: 'left',
51
- transition: 'background-color 0.15s ease',
52
- },
53
- chevron: {
54
- fontSize: '20px',
55
- transition: 'transform 0.2s ease',
56
- color: 'var(--sc-content-chevron-color, currentColor)',
57
- },
58
- answer: {
59
- padding: 'var(--sc-content-body-padding, 0 16px 12px 16px)',
60
- fontSize: 'var(--sc-content-body-font-size, 14px)',
61
- lineHeight: 1.6,
62
- overflow: 'hidden',
63
- transition: 'max-height 0.2s ease, padding 0.2s ease',
64
- },
65
- category: {
66
- display: 'inline-block',
67
- fontSize: '11px',
68
- fontWeight: 600,
69
- textTransform: 'uppercase',
70
- letterSpacing: '0.05em',
71
- padding: '4px 8px',
72
- borderRadius: '4px',
73
- marginBottom: '8px',
74
- },
75
- categoryHeader: {
76
- fontSize: 'var(--sc-content-category-font-size, 12px)',
77
- fontWeight: 700,
78
- textTransform: 'uppercase',
79
- letterSpacing: '0.05em',
80
- padding: 'var(--sc-content-category-padding, 8px 4px 4px 4px)',
81
- marginTop: 'var(--sc-content-category-gap, 4px)',
82
- },
83
- feedback: {
84
- display: 'flex',
85
- alignItems: 'center',
86
- gap: '8px',
87
- marginTop: '12px',
88
- paddingTop: '10px',
89
- borderTop: '1px solid rgba(0, 0, 0, 0.08)',
90
- fontSize: '13px',
91
- },
92
- feedbackButton: {
93
- background: 'none',
94
- border: '1px solid transparent',
95
- cursor: 'pointer',
96
- fontSize: '16px',
97
- padding: '4px 8px',
98
- borderRadius: '4px',
99
- transition: 'background-color 0.15s ease, border-color 0.15s ease',
100
- },
101
- feedbackButtonSelected: {
102
- borderColor: 'rgba(0, 0, 0, 0.2)',
103
- backgroundColor: 'rgba(0, 0, 0, 0.04)',
104
- },
105
- emptyState: {
106
- textAlign: 'center',
107
- padding: '48px 24px',
108
- fontSize: '14px',
109
- },
110
- noResults: {
111
- textAlign: 'center',
112
- padding: '32px 16px',
113
- fontSize: '14px',
114
- },
115
- };
116
- // ============================================================================
117
- // Theme Styles
118
- // ============================================================================
119
- export const themeStyles = {
120
- light: {
121
- container: {
122
- backgroundColor: 'transparent',
123
- color: 'inherit',
124
- },
125
- searchInput: {
126
- border: `1px solid ${slateGrey[11]}`,
127
- },
128
- item: {
129
- backgroundColor: 'var(--sc-content-background)',
130
- borderTop: 'var(--sc-content-border)',
131
- borderRight: 'var(--sc-content-border)',
132
- borderBottom: 'var(--sc-content-border)',
133
- borderLeft: 'var(--sc-content-border)',
134
- },
135
- itemExpanded: {
136
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
137
- },
138
- question: {
139
- backgroundColor: 'transparent',
140
- color: 'var(--sc-content-text-color)',
141
- },
142
- questionHover: {
143
- backgroundColor: 'var(--sc-content-background-hover)',
144
- },
145
- answer: {
146
- color: 'var(--sc-content-text-secondary-color)',
147
- },
148
- category: {
149
- backgroundColor: purple[8],
150
- color: purple[2],
151
- },
152
- categoryHeader: {
153
- color: slateGrey[7],
154
- },
155
- emptyState: {
156
- color: slateGrey[8],
157
- },
158
- feedbackPrompt: {
159
- color: slateGrey[7],
160
- },
161
- },
162
- dark: {
163
- container: {
164
- backgroundColor: 'transparent',
165
- color: 'inherit',
166
- },
167
- searchInput: {
168
- border: `1px solid ${slateGrey[5]}`,
169
- },
170
- item: {
171
- backgroundColor: 'var(--sc-content-background)',
172
- borderTop: 'var(--sc-content-border)',
173
- borderRight: 'var(--sc-content-border)',
174
- borderBottom: 'var(--sc-content-border)',
175
- borderLeft: 'var(--sc-content-border)',
176
- },
177
- itemExpanded: {
178
- boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
179
- },
180
- question: {
181
- backgroundColor: 'transparent',
182
- color: 'var(--sc-content-text-color)',
183
- },
184
- questionHover: {
185
- backgroundColor: 'var(--sc-content-background-hover)',
186
- },
187
- answer: {
188
- color: 'var(--sc-content-text-secondary-color)',
189
- },
190
- category: {
191
- backgroundColor: purple[0],
192
- color: purple[6],
193
- },
194
- categoryHeader: {
195
- color: slateGrey[8],
196
- },
197
- emptyState: {
198
- color: slateGrey[7],
199
- },
200
- feedbackPrompt: {
201
- color: slateGrey[8],
202
- },
203
- },
204
- };
package/dist/faq-types.js DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Widget Runtime & Props Types
3
- *
4
- * Runtime interface, component props, and helper type definitions
5
- * used by the FAQWidget component.
6
- */
7
- export {};
@@ -1,85 +0,0 @@
1
- /**
2
- * Adaptive FAQ - Runtime Module (Lit)
3
- *
4
- * Runtime manifest for the FAQ accordion adaptive.
5
- * Uses the Lit web component mountable instead of the React one.
6
- * Provides action executors and widget registration.
7
- */
8
- import './FAQWidgetLit';
9
- import type { FAQWidgetRuntime } from './faq-types';
10
- import type { FAQConfig } from './types';
11
- /**
12
- * Mountable widget interface for <syntro-faq-accordion> (Lit web component).
13
- *
14
- * Mirrors FAQMountableWidget but mounts the Lit element instead of a React
15
- * root — no React dependency required at mount time.
16
- */
17
- export declare const FAQWidgetLitMountable: {
18
- mount(container: HTMLElement, config?: FAQConfig & {
19
- runtime?: FAQWidgetRuntime;
20
- instanceId?: string;
21
- }): () => void;
22
- };
23
- /**
24
- * Runtime manifest for adaptive-faq (Lit variant).
25
- *
26
- * Provides:
27
- * - FAQ action executors (scroll_to, toggle_item, update)
28
- * - Widget-based accordion using the Lit web component
29
- */
30
- export declare const runtime: {
31
- id: string;
32
- version: string;
33
- name: string;
34
- description: string;
35
- /**
36
- * Action executors for programmatic FAQ interaction.
37
- */
38
- executors: readonly [{
39
- readonly kind: "faq:scroll_to";
40
- readonly executor: typeof import("./executors").executeScrollToFaq;
41
- }, {
42
- readonly kind: "faq:toggle_item";
43
- readonly executor: typeof import("./executors").executeToggleFaqItem;
44
- }, {
45
- readonly kind: "faq:update";
46
- readonly executor: typeof import("./executors").executeUpdateFaq;
47
- }];
48
- /**
49
- * Widget definitions for the runtime's WidgetRegistry.
50
- */
51
- widgets: {
52
- id: string;
53
- component: {
54
- mount(container: HTMLElement, config?: FAQConfig & {
55
- runtime?: FAQWidgetRuntime;
56
- instanceId?: string;
57
- }): () => void;
58
- };
59
- metadata: {
60
- name: string;
61
- description: string;
62
- icon: string;
63
- subtitle: string;
64
- };
65
- }[];
66
- /**
67
- * Extract notify watcher entries from tile config props.
68
- * The runtime evaluates these continuously (even with drawer closed)
69
- * and publishes faq:question_revealed when triggerWhen transitions false → true.
70
- */
71
- notifyWatchers(props: Record<string, unknown>): {
72
- id: string;
73
- strategy: import("./types").DecisionStrategy<boolean>;
74
- eventName: string;
75
- eventProps: {
76
- questionId: string;
77
- question: string;
78
- title: string | undefined;
79
- body: string | undefined;
80
- icon: string | undefined;
81
- };
82
- }[];
83
- };
84
- export default runtime;
85
- //# sourceMappingURL=runtime-lit.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runtime-lit.d.ts","sourceRoot":"","sources":["../src/runtime-lit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAqB,MAAM,SAAS,CAAC;AAM5D;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB;qBAEnB,WAAW,WACb,SAAS,GAAG;QAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;CA4B3E,CAAC;AAMF;;;;;;GAMG;AACH,eAAO,MAAM,OAAO;;;;;IAOlB;;OAEG;;;;;;;;;;;IAGH;;OAEG;;;;6BAxDU,WAAW,WACb,SAAS,GAAG;gBAAE,OAAO,CAAC,EAAE,gBAAgB,CAAC;gBAAC,UAAU,CAAC,EAAE,MAAM,CAAA;aAAE;;;;;;;;;IAqE1E;;;;OAIG;0BACmB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;;;;CAiB9C,CAAC;AAEF,eAAe,OAAO,CAAC"}