expo-ai-kit 0.3.6 → 0.4.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 +68 -1106
- package/build/ExpoAiKitModule.d.ts +1 -1
- package/build/ExpoAiKitModule.d.ts.map +1 -1
- package/build/ExpoAiKitModule.js.map +1 -1
- package/build/index.d.ts +1 -436
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -737
- package/build/index.js.map +1 -1
- package/build/types.d.ts +3 -244
- package/build/types.d.ts.map +1 -1
- package/build/types.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoAiKitModule.ts +2 -2
- package/src/index.ts +1 -943
- package/src/types.ts +3 -284
- package/build/hooks.d.ts +0 -147
- package/build/hooks.d.ts.map +0 -1
- package/build/hooks.js +0 -426
- package/build/hooks.js.map +0 -1
- package/build/memory.d.ts +0 -235
- package/build/memory.d.ts.map +0 -1
- package/build/memory.js +0 -353
- package/build/memory.js.map +0 -1
- package/src/__tests__/index.test.js +0 -630
- package/src/hooks.ts +0 -502
- package/src/memory.ts +0 -394
package/src/types.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Role in a conversation message.
|
|
3
|
-
*/
|
|
4
1
|
/**
|
|
5
2
|
* Hardware backend for on-device model inference.
|
|
6
3
|
*
|
|
@@ -18,6 +15,9 @@ export type SetModelOptions = {
|
|
|
18
15
|
backend?: InferenceBackend;
|
|
19
16
|
};
|
|
20
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Role in a conversation message.
|
|
20
|
+
*/
|
|
21
21
|
export type LLMRole = 'system' | 'user' | 'assistant';
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -77,246 +77,6 @@ export type LLMStreamEvent = {
|
|
|
77
77
|
*/
|
|
78
78
|
export type LLMStreamCallback = (event: LLMStreamEvent) => void;
|
|
79
79
|
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Prompt Helper Types
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Options for the summarize helper.
|
|
86
|
-
*/
|
|
87
|
-
export type LLMSummarizeOptions = {
|
|
88
|
-
/**
|
|
89
|
-
* Target length for the summary.
|
|
90
|
-
* @default 'medium'
|
|
91
|
-
*/
|
|
92
|
-
length?: 'short' | 'medium' | 'long';
|
|
93
|
-
/**
|
|
94
|
-
* Style of summary to generate.
|
|
95
|
-
* @default 'paragraph'
|
|
96
|
-
*/
|
|
97
|
-
style?: 'paragraph' | 'bullets' | 'tldr';
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Options for the translate helper.
|
|
102
|
-
*/
|
|
103
|
-
export type LLMTranslateOptions = {
|
|
104
|
-
/**
|
|
105
|
-
* Target language to translate to.
|
|
106
|
-
*/
|
|
107
|
-
to: string;
|
|
108
|
-
/**
|
|
109
|
-
* Source language (auto-detected if not provided).
|
|
110
|
-
*/
|
|
111
|
-
from?: string;
|
|
112
|
-
/**
|
|
113
|
-
* Tone/formality of the translation.
|
|
114
|
-
* @default 'neutral'
|
|
115
|
-
*/
|
|
116
|
-
tone?: 'formal' | 'informal' | 'neutral';
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Options for the rewrite helper.
|
|
121
|
-
*/
|
|
122
|
-
export type LLMRewriteOptions = {
|
|
123
|
-
/**
|
|
124
|
-
* Style to rewrite in.
|
|
125
|
-
*/
|
|
126
|
-
style:
|
|
127
|
-
| 'formal'
|
|
128
|
-
| 'casual'
|
|
129
|
-
| 'professional'
|
|
130
|
-
| 'friendly'
|
|
131
|
-
| 'concise'
|
|
132
|
-
| 'detailed'
|
|
133
|
-
| 'simple'
|
|
134
|
-
| 'academic';
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Options for the extractKeyPoints helper.
|
|
139
|
-
*/
|
|
140
|
-
export type LLMExtractKeyPointsOptions = {
|
|
141
|
-
/**
|
|
142
|
-
* Maximum number of key points to extract.
|
|
143
|
-
* @default 5
|
|
144
|
-
*/
|
|
145
|
-
maxPoints?: number;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Options for the answerQuestion helper.
|
|
150
|
-
*/
|
|
151
|
-
export type LLMAnswerQuestionOptions = {
|
|
152
|
-
/**
|
|
153
|
-
* How detailed the answer should be.
|
|
154
|
-
* @default 'medium'
|
|
155
|
-
*/
|
|
156
|
-
detail?: 'brief' | 'medium' | 'detailed';
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// ============================================================================
|
|
160
|
-
// Smart Suggestions Types
|
|
161
|
-
// ============================================================================
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Options for the suggest helper.
|
|
165
|
-
*/
|
|
166
|
-
export type LLMSuggestOptions = {
|
|
167
|
-
/**
|
|
168
|
-
* Number of suggestions to generate.
|
|
169
|
-
* @default 3
|
|
170
|
-
*/
|
|
171
|
-
count?: number;
|
|
172
|
-
/**
|
|
173
|
-
* Optional context to inform the suggestions (e.g., surrounding text, app context).
|
|
174
|
-
*/
|
|
175
|
-
context?: string;
|
|
176
|
-
/**
|
|
177
|
-
* Tone of the suggestions.
|
|
178
|
-
* @default 'neutral'
|
|
179
|
-
*/
|
|
180
|
-
tone?: 'formal' | 'casual' | 'professional' | 'friendly' | 'neutral';
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* A single suggestion item.
|
|
185
|
-
*/
|
|
186
|
-
export type LLMSuggestion = {
|
|
187
|
-
/** The suggested text */
|
|
188
|
-
text: string;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Response from suggest/smartReply containing multiple suggestions.
|
|
193
|
-
*/
|
|
194
|
-
export type LLMSuggestResponse = {
|
|
195
|
-
/** Array of generated suggestions */
|
|
196
|
-
suggestions: LLMSuggestion[];
|
|
197
|
-
/** Raw response text from the model */
|
|
198
|
-
raw: string;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Options for the smartReply helper.
|
|
203
|
-
*/
|
|
204
|
-
export type LLMSmartReplyOptions = {
|
|
205
|
-
/**
|
|
206
|
-
* Number of reply suggestions to generate.
|
|
207
|
-
* @default 3
|
|
208
|
-
*/
|
|
209
|
-
count?: number;
|
|
210
|
-
/**
|
|
211
|
-
* Tone of the reply suggestions.
|
|
212
|
-
* @default 'neutral'
|
|
213
|
-
*/
|
|
214
|
-
tone?: 'formal' | 'casual' | 'professional' | 'friendly' | 'neutral';
|
|
215
|
-
/**
|
|
216
|
-
* Optional persona/context for the replier (e.g., "customer support agent", "friendly colleague").
|
|
217
|
-
*/
|
|
218
|
-
persona?: string;
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Options for the autocomplete helper.
|
|
223
|
-
*/
|
|
224
|
-
export type LLMAutocompleteOptions = {
|
|
225
|
-
/**
|
|
226
|
-
* Number of completions to generate.
|
|
227
|
-
* @default 3
|
|
228
|
-
*/
|
|
229
|
-
count?: number;
|
|
230
|
-
/**
|
|
231
|
-
* Maximum length of each completion in words.
|
|
232
|
-
* @default 10
|
|
233
|
-
*/
|
|
234
|
-
maxWords?: number;
|
|
235
|
-
/**
|
|
236
|
-
* Optional context to inform the completions (e.g., what the user is writing about).
|
|
237
|
-
*/
|
|
238
|
-
context?: string;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// ============================================================================
|
|
242
|
-
// Hook Types
|
|
243
|
-
// ============================================================================
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Options for the useChat hook.
|
|
247
|
-
*/
|
|
248
|
-
export type UseChatOptions = {
|
|
249
|
-
/** System prompt for the AI assistant */
|
|
250
|
-
systemPrompt?: string;
|
|
251
|
-
/** Maximum conversation turns to keep in memory (default: 10) */
|
|
252
|
-
maxTurns?: number;
|
|
253
|
-
/** Initial messages to populate the chat */
|
|
254
|
-
initialMessages?: LLMMessage[];
|
|
255
|
-
/** Callback when a response is complete */
|
|
256
|
-
onFinish?: (response: LLMResponse) => void;
|
|
257
|
-
/** Callback when an error occurs */
|
|
258
|
-
onError?: (error: Error) => void;
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Return type for the useChat hook.
|
|
263
|
-
*/
|
|
264
|
-
export type UseChatReturn = {
|
|
265
|
-
/** All messages in the conversation */
|
|
266
|
-
messages: LLMMessage[];
|
|
267
|
-
/** Current input text value */
|
|
268
|
-
input: string;
|
|
269
|
-
/** Set the input text value */
|
|
270
|
-
setInput: (input: string) => void;
|
|
271
|
-
/** Send the current input (or provided text) as a message */
|
|
272
|
-
sendMessage: (text?: string) => Promise<void>;
|
|
273
|
-
/** Whether the AI is currently streaming a response */
|
|
274
|
-
isStreaming: boolean;
|
|
275
|
-
/** Stop the current streaming response */
|
|
276
|
-
stop: () => void;
|
|
277
|
-
/** Clear all messages and reset the conversation */
|
|
278
|
-
clear: () => void;
|
|
279
|
-
/** The most recent error, if any */
|
|
280
|
-
error: Error | null;
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Options for the useCompletion hook.
|
|
285
|
-
*/
|
|
286
|
-
export type UseCompletionOptions = {
|
|
287
|
-
/** System prompt for the AI */
|
|
288
|
-
systemPrompt?: string;
|
|
289
|
-
/** Callback when completion is done */
|
|
290
|
-
onFinish?: (response: LLMResponse) => void;
|
|
291
|
-
/** Callback when an error occurs */
|
|
292
|
-
onError?: (error: Error) => void;
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Return type for the useCompletion hook.
|
|
297
|
-
*/
|
|
298
|
-
export type UseCompletionReturn = {
|
|
299
|
-
/** The current completion text */
|
|
300
|
-
completion: string;
|
|
301
|
-
/** Whether a completion is in progress */
|
|
302
|
-
isLoading: boolean;
|
|
303
|
-
/** Request a completion for the given prompt */
|
|
304
|
-
complete: (prompt: string) => Promise<string>;
|
|
305
|
-
/** Stop the current completion */
|
|
306
|
-
stop: () => void;
|
|
307
|
-
/** The most recent error, if any */
|
|
308
|
-
error: Error | null;
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Return type for the useOnDeviceAI hook.
|
|
313
|
-
*/
|
|
314
|
-
export type UseOnDeviceAIReturn = {
|
|
315
|
-
/** Whether on-device AI is available */
|
|
316
|
-
isAvailable: boolean;
|
|
317
|
-
/** Whether the availability check is still in progress */
|
|
318
|
-
isChecking: boolean;
|
|
319
|
-
};
|
|
320
80
|
|
|
321
81
|
// ============================================================================
|
|
322
82
|
// Model Types
|
|
@@ -424,44 +184,3 @@ export type ModelStateChangeEvent = {
|
|
|
424
184
|
/** New status */
|
|
425
185
|
status: DownloadableModelStatus;
|
|
426
186
|
};
|
|
427
|
-
|
|
428
|
-
// ============================================================================
|
|
429
|
-
// Chat Memory Types
|
|
430
|
-
// ============================================================================
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Options for creating a ChatMemoryManager.
|
|
434
|
-
*/
|
|
435
|
-
export type ChatMemoryOptions = {
|
|
436
|
-
/**
|
|
437
|
-
* Maximum number of conversation turns to keep in memory.
|
|
438
|
-
* A turn is one user message + one assistant response.
|
|
439
|
-
* @default 10
|
|
440
|
-
*/
|
|
441
|
-
maxTurns?: number;
|
|
442
|
-
/**
|
|
443
|
-
* Optional system prompt to prepend to every generated prompt.
|
|
444
|
-
*/
|
|
445
|
-
systemPrompt?: string;
|
|
446
|
-
/**
|
|
447
|
-
* Maximum context window in tokens. When set, trimHistory will also
|
|
448
|
-
* trim messages that exceed this token budget (in addition to maxTurns).
|
|
449
|
-
* Whichever limit is hit first triggers trimming.
|
|
450
|
-
* @default Infinity (no token-based trimming)
|
|
451
|
-
*/
|
|
452
|
-
contextWindow?: number;
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* A snapshot of the current chat memory state.
|
|
457
|
-
*/
|
|
458
|
-
export type ChatMemorySnapshot = {
|
|
459
|
-
/** All messages currently in memory */
|
|
460
|
-
messages: LLMMessage[];
|
|
461
|
-
/** The system prompt (if any) */
|
|
462
|
-
systemPrompt: string | undefined;
|
|
463
|
-
/** Current number of turns stored */
|
|
464
|
-
turnCount: number;
|
|
465
|
-
/** Maximum turns allowed */
|
|
466
|
-
maxTurns: number;
|
|
467
|
-
};
|
package/build/hooks.d.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type { UseChatOptions, UseChatReturn, UseCompletionOptions, UseCompletionReturn, UseOnDeviceAIReturn, BuiltInModel, DownloadableModel, DownloadableModelStatus, ModelError } from './types';
|
|
2
|
-
/**
|
|
3
|
-
* React hook to check if on-device AI is available.
|
|
4
|
-
*
|
|
5
|
-
* Caches the result so multiple components don't re-check.
|
|
6
|
-
*
|
|
7
|
-
* @returns Object with isAvailable and isChecking states
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```tsx
|
|
11
|
-
* function MyComponent() {
|
|
12
|
-
* const { isAvailable, isChecking } = useOnDeviceAI();
|
|
13
|
-
*
|
|
14
|
-
* if (isChecking) return <Text>Checking AI availability...</Text>;
|
|
15
|
-
* if (!isAvailable) return <Text>On-device AI not available</Text>;
|
|
16
|
-
*
|
|
17
|
-
* return <ChatComponent />;
|
|
18
|
-
* }
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export declare function useOnDeviceAI(): UseOnDeviceAIReturn;
|
|
22
|
-
/**
|
|
23
|
-
* React hook for building chat interfaces with on-device AI.
|
|
24
|
-
*
|
|
25
|
-
* Manages messages, input state, streaming, and conversation memory automatically.
|
|
26
|
-
* Built on top of ChatMemoryManager and streamMessage.
|
|
27
|
-
*
|
|
28
|
-
* @param options - Configuration options for the chat
|
|
29
|
-
* @returns Chat state and control functions
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```tsx
|
|
33
|
-
* function ChatScreen() {
|
|
34
|
-
* const { messages, input, setInput, sendMessage, isStreaming, stop } = useChat({
|
|
35
|
-
* systemPrompt: 'You are a helpful assistant.',
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* return (
|
|
39
|
-
* <View>
|
|
40
|
-
* <FlatList
|
|
41
|
-
* data={messages}
|
|
42
|
-
* renderItem={({ item }) => (
|
|
43
|
-
* <Text>{item.role}: {item.content}</Text>
|
|
44
|
-
* )}
|
|
45
|
-
* />
|
|
46
|
-
* <TextInput value={input} onChangeText={setInput} />
|
|
47
|
-
* {isStreaming ? (
|
|
48
|
-
* <Button title="Stop" onPress={stop} />
|
|
49
|
-
* ) : (
|
|
50
|
-
* <Button title="Send" onPress={() => sendMessage()} />
|
|
51
|
-
* )}
|
|
52
|
-
* </View>
|
|
53
|
-
* );
|
|
54
|
-
* }
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export declare function useChat(options?: UseChatOptions): UseChatReturn;
|
|
58
|
-
/**
|
|
59
|
-
* React hook for single-shot AI completions.
|
|
60
|
-
*
|
|
61
|
-
* Unlike useChat (which manages a conversation), useCompletion is for
|
|
62
|
-
* one-off tasks like summarization, translation, or content generation.
|
|
63
|
-
*
|
|
64
|
-
* @param options - Configuration options
|
|
65
|
-
* @returns Completion state and control functions
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```tsx
|
|
69
|
-
* function SummarizerScreen() {
|
|
70
|
-
* const { completion, isLoading, complete, stop } = useCompletion({
|
|
71
|
-
* systemPrompt: 'You are a summarization assistant. Summarize the given text concisely.',
|
|
72
|
-
* });
|
|
73
|
-
*
|
|
74
|
-
* return (
|
|
75
|
-
* <View>
|
|
76
|
-
* <Button
|
|
77
|
-
* title="Summarize"
|
|
78
|
-
* onPress={() => complete('Long article text here...')}
|
|
79
|
-
* />
|
|
80
|
-
* {isLoading && <Button title="Stop" onPress={stop} />}
|
|
81
|
-
* <Text>{completion}</Text>
|
|
82
|
-
* </View>
|
|
83
|
-
* );
|
|
84
|
-
* }
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
export declare function useCompletion(options?: UseCompletionOptions): UseCompletionReturn;
|
|
88
|
-
/**
|
|
89
|
-
* React hook for managing a downloadable model's lifecycle.
|
|
90
|
-
*
|
|
91
|
-
* Tracks download status, progress, and provides download/delete controls.
|
|
92
|
-
*
|
|
93
|
-
* @param modelId - The downloadable model ID (e.g. 'gemma-e2b')
|
|
94
|
-
* @returns Model status, progress, and control functions
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```tsx
|
|
98
|
-
* function ModelManager() {
|
|
99
|
-
* const { status, progress, download, delete: remove, error } = useModel('gemma-e2b');
|
|
100
|
-
*
|
|
101
|
-
* if (status === 'not-downloaded') {
|
|
102
|
-
* return <Button title="Download" onPress={download} />;
|
|
103
|
-
* }
|
|
104
|
-
* if (status === 'downloading') {
|
|
105
|
-
* return <Text>Downloading: {Math.round(progress * 100)}%</Text>;
|
|
106
|
-
* }
|
|
107
|
-
* if (status === 'loading') {
|
|
108
|
-
* return <Text>Loading model...</Text>;
|
|
109
|
-
* }
|
|
110
|
-
* return <Text>Model ready!</Text>;
|
|
111
|
-
* }
|
|
112
|
-
* ```
|
|
113
|
-
*/
|
|
114
|
-
export declare function useModel(modelId: string): {
|
|
115
|
-
status: DownloadableModelStatus;
|
|
116
|
-
progress: number;
|
|
117
|
-
download: () => Promise<void>;
|
|
118
|
-
delete: () => Promise<void>;
|
|
119
|
-
error: ModelError | null;
|
|
120
|
-
};
|
|
121
|
-
/**
|
|
122
|
-
* React hook to discover all available models (built-in and downloadable).
|
|
123
|
-
*
|
|
124
|
-
* @returns Built-in models, downloadable models, and loading state
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```tsx
|
|
128
|
-
* function ModelPicker() {
|
|
129
|
-
* const { builtIn, downloadable, isLoading } = useAvailableModels();
|
|
130
|
-
*
|
|
131
|
-
* if (isLoading) return <Text>Loading models...</Text>;
|
|
132
|
-
*
|
|
133
|
-
* return (
|
|
134
|
-
* <View>
|
|
135
|
-
* <Text>Built-in: {builtIn.map(m => m.name).join(', ')}</Text>
|
|
136
|
-
* <Text>Downloadable: {downloadable.map(m => m.name).join(', ')}</Text>
|
|
137
|
-
* </View>
|
|
138
|
-
* );
|
|
139
|
-
* }
|
|
140
|
-
* ```
|
|
141
|
-
*/
|
|
142
|
-
export declare function useAvailableModels(): {
|
|
143
|
-
builtIn: BuiltInModel[];
|
|
144
|
-
downloadable: DownloadableModel[];
|
|
145
|
-
isLoading: boolean;
|
|
146
|
-
};
|
|
147
|
-
//# sourceMappingURL=hooks.d.ts.map
|
package/build/hooks.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAEV,cAAc,EACd,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,YAAY,EACZ,iBAAiB,EACjB,uBAAuB,EACvB,UAAU,EACX,MAAM,SAAS,CAAC;AAmBjB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,IAAI,mBAAmB,CAwBnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,aAAa,CA8GnE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CAgFrF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IACzC,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B,CA0EA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,IAAI;IACpC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,iBAAiB,EAAE,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;CACpB,CAwBA"}
|