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/build/hooks.js DELETED
@@ -1,426 +0,0 @@
1
- import { useState, useEffect, useRef, useCallback } from 'react';
2
- import { isAvailable, streamMessage, getBuiltInModels as getBuiltInModelsApi, getDownloadableModels as getDownloadableModelsApi, downloadModel as downloadModelApi, deleteModel as deleteModelApi, } from './index';
3
- import { ChatMemoryManager } from './memory';
4
- import ExpoAiKitModule from './ExpoAiKitModule';
5
- // Cache availability result across all hook instances
6
- let availabilityCache = null;
7
- let availabilityPromise = null;
8
- function checkAvailability() {
9
- if (availabilityCache !== null) {
10
- return Promise.resolve(availabilityCache);
11
- }
12
- if (!availabilityPromise) {
13
- availabilityPromise = isAvailable().then((result) => {
14
- availabilityCache = result;
15
- return result;
16
- });
17
- }
18
- return availabilityPromise;
19
- }
20
- /**
21
- * React hook to check if on-device AI is available.
22
- *
23
- * Caches the result so multiple components don't re-check.
24
- *
25
- * @returns Object with isAvailable and isChecking states
26
- *
27
- * @example
28
- * ```tsx
29
- * function MyComponent() {
30
- * const { isAvailable, isChecking } = useOnDeviceAI();
31
- *
32
- * if (isChecking) return <Text>Checking AI availability...</Text>;
33
- * if (!isAvailable) return <Text>On-device AI not available</Text>;
34
- *
35
- * return <ChatComponent />;
36
- * }
37
- * ```
38
- */
39
- export function useOnDeviceAI() {
40
- const [available, setAvailable] = useState(availabilityCache ?? false);
41
- const [checking, setChecking] = useState(availabilityCache === null);
42
- useEffect(() => {
43
- if (availabilityCache !== null) {
44
- setAvailable(availabilityCache);
45
- setChecking(false);
46
- return;
47
- }
48
- let mounted = true;
49
- checkAvailability().then((result) => {
50
- if (mounted) {
51
- setAvailable(result);
52
- setChecking(false);
53
- }
54
- });
55
- return () => {
56
- mounted = false;
57
- };
58
- }, []);
59
- return { isAvailable: available, isChecking: checking };
60
- }
61
- /**
62
- * React hook for building chat interfaces with on-device AI.
63
- *
64
- * Manages messages, input state, streaming, and conversation memory automatically.
65
- * Built on top of ChatMemoryManager and streamMessage.
66
- *
67
- * @param options - Configuration options for the chat
68
- * @returns Chat state and control functions
69
- *
70
- * @example
71
- * ```tsx
72
- * function ChatScreen() {
73
- * const { messages, input, setInput, sendMessage, isStreaming, stop } = useChat({
74
- * systemPrompt: 'You are a helpful assistant.',
75
- * });
76
- *
77
- * return (
78
- * <View>
79
- * <FlatList
80
- * data={messages}
81
- * renderItem={({ item }) => (
82
- * <Text>{item.role}: {item.content}</Text>
83
- * )}
84
- * />
85
- * <TextInput value={input} onChangeText={setInput} />
86
- * {isStreaming ? (
87
- * <Button title="Stop" onPress={stop} />
88
- * ) : (
89
- * <Button title="Send" onPress={() => sendMessage()} />
90
- * )}
91
- * </View>
92
- * );
93
- * }
94
- * ```
95
- */
96
- export function useChat(options = {}) {
97
- const { systemPrompt, maxTurns, initialMessages, onFinish, onError } = options;
98
- const memoryRef = useRef(new ChatMemoryManager({ maxTurns, systemPrompt }));
99
- // Initialize with initial messages
100
- const initializedRef = useRef(false);
101
- if (!initializedRef.current && initialMessages) {
102
- for (const msg of initialMessages) {
103
- memoryRef.current.addMessage(msg);
104
- }
105
- initializedRef.current = true;
106
- }
107
- const [messages, setMessages] = useState(memoryRef.current.getMessages());
108
- const [input, setInput] = useState('');
109
- const [isStreaming, setIsStreaming] = useState(false);
110
- const [error, setError] = useState(null);
111
- const stopRef = useRef(null);
112
- const mountedRef = useRef(true);
113
- const inputRef = useRef(input);
114
- inputRef.current = input;
115
- const streamingRef = useRef(isStreaming);
116
- streamingRef.current = isStreaming;
117
- const onFinishRef = useRef(onFinish);
118
- onFinishRef.current = onFinish;
119
- const onErrorRef = useRef(onError);
120
- onErrorRef.current = onError;
121
- useEffect(() => {
122
- return () => {
123
- mountedRef.current = false;
124
- stopRef.current?.();
125
- };
126
- }, []);
127
- const send = useCallback(async (text) => {
128
- const content = (text ?? inputRef.current).trim();
129
- if (!content || streamingRef.current)
130
- return;
131
- setError(null);
132
- // Add user message
133
- memoryRef.current.addUserMessage(content);
134
- setMessages([...memoryRef.current.getMessages()]);
135
- if (!text)
136
- setInput('');
137
- setIsStreaming(true);
138
- try {
139
- const allMessages = memoryRef.current.getAllMessages();
140
- // Use a temporary message for streaming display
141
- let accumulatedText = '';
142
- const { promise, stop } = streamMessage(allMessages, (event) => {
143
- if (!mountedRef.current)
144
- return;
145
- accumulatedText = event.accumulatedText;
146
- // Update messages with streaming assistant response
147
- const currentMessages = memoryRef.current.getMessages();
148
- setMessages([
149
- ...currentMessages,
150
- { role: 'assistant', content: accumulatedText },
151
- ]);
152
- });
153
- stopRef.current = stop;
154
- const response = await promise;
155
- if (!mountedRef.current)
156
- return;
157
- // Add the final assistant message to memory
158
- memoryRef.current.addAssistantMessage(response.text);
159
- setMessages([...memoryRef.current.getMessages()]);
160
- onFinishRef.current?.(response);
161
- }
162
- catch (err) {
163
- if (!mountedRef.current)
164
- return;
165
- const e = err instanceof Error ? err : new Error(String(err));
166
- setError(e);
167
- onErrorRef.current?.(e);
168
- }
169
- finally {
170
- stopRef.current = null;
171
- if (mountedRef.current) {
172
- setIsStreaming(false);
173
- }
174
- }
175
- }, []);
176
- const stop = useCallback(() => {
177
- stopRef.current?.();
178
- }, []);
179
- const clear = useCallback(() => {
180
- memoryRef.current.clear();
181
- setMessages([]);
182
- setError(null);
183
- }, []);
184
- return {
185
- messages,
186
- input,
187
- setInput,
188
- sendMessage: send,
189
- isStreaming,
190
- stop,
191
- clear,
192
- error,
193
- };
194
- }
195
- /**
196
- * React hook for single-shot AI completions.
197
- *
198
- * Unlike useChat (which manages a conversation), useCompletion is for
199
- * one-off tasks like summarization, translation, or content generation.
200
- *
201
- * @param options - Configuration options
202
- * @returns Completion state and control functions
203
- *
204
- * @example
205
- * ```tsx
206
- * function SummarizerScreen() {
207
- * const { completion, isLoading, complete, stop } = useCompletion({
208
- * systemPrompt: 'You are a summarization assistant. Summarize the given text concisely.',
209
- * });
210
- *
211
- * return (
212
- * <View>
213
- * <Button
214
- * title="Summarize"
215
- * onPress={() => complete('Long article text here...')}
216
- * />
217
- * {isLoading && <Button title="Stop" onPress={stop} />}
218
- * <Text>{completion}</Text>
219
- * </View>
220
- * );
221
- * }
222
- * ```
223
- */
224
- export function useCompletion(options = {}) {
225
- const { systemPrompt, onFinish, onError } = options;
226
- const [completion, setCompletion] = useState('');
227
- const [isLoading, setIsLoading] = useState(false);
228
- const [error, setError] = useState(null);
229
- const stopRef = useRef(null);
230
- const mountedRef = useRef(true);
231
- const loadingRef = useRef(isLoading);
232
- loadingRef.current = isLoading;
233
- const completionRef = useRef(completion);
234
- completionRef.current = completion;
235
- const systemPromptRef = useRef(systemPrompt);
236
- systemPromptRef.current = systemPrompt;
237
- const onFinishRef = useRef(onFinish);
238
- onFinishRef.current = onFinish;
239
- const onErrorRef = useRef(onError);
240
- onErrorRef.current = onError;
241
- useEffect(() => {
242
- return () => {
243
- mountedRef.current = false;
244
- stopRef.current?.();
245
- };
246
- }, []);
247
- const complete = useCallback(async (prompt) => {
248
- if (loadingRef.current)
249
- return completionRef.current;
250
- setError(null);
251
- setCompletion('');
252
- setIsLoading(true);
253
- try {
254
- const messages = [{ role: 'user', content: prompt }];
255
- const { promise, stop } = streamMessage(messages, (event) => {
256
- if (!mountedRef.current)
257
- return;
258
- setCompletion(event.accumulatedText);
259
- }, systemPromptRef.current ? { systemPrompt: systemPromptRef.current } : undefined);
260
- stopRef.current = stop;
261
- const response = await promise;
262
- if (!mountedRef.current)
263
- return '';
264
- setCompletion(response.text);
265
- onFinishRef.current?.(response);
266
- return response.text;
267
- }
268
- catch (err) {
269
- if (!mountedRef.current)
270
- return '';
271
- const e = err instanceof Error ? err : new Error(String(err));
272
- setError(e);
273
- onErrorRef.current?.(e);
274
- return '';
275
- }
276
- finally {
277
- stopRef.current = null;
278
- if (mountedRef.current) {
279
- setIsLoading(false);
280
- }
281
- }
282
- }, []);
283
- const stop = useCallback(() => {
284
- stopRef.current?.();
285
- }, []);
286
- return {
287
- completion,
288
- isLoading,
289
- complete,
290
- stop,
291
- error,
292
- };
293
- }
294
- /**
295
- * React hook for managing a downloadable model's lifecycle.
296
- *
297
- * Tracks download status, progress, and provides download/delete controls.
298
- *
299
- * @param modelId - The downloadable model ID (e.g. 'gemma-e2b')
300
- * @returns Model status, progress, and control functions
301
- *
302
- * @example
303
- * ```tsx
304
- * function ModelManager() {
305
- * const { status, progress, download, delete: remove, error } = useModel('gemma-e2b');
306
- *
307
- * if (status === 'not-downloaded') {
308
- * return <Button title="Download" onPress={download} />;
309
- * }
310
- * if (status === 'downloading') {
311
- * return <Text>Downloading: {Math.round(progress * 100)}%</Text>;
312
- * }
313
- * if (status === 'loading') {
314
- * return <Text>Loading model...</Text>;
315
- * }
316
- * return <Text>Model ready!</Text>;
317
- * }
318
- * ```
319
- */
320
- export function useModel(modelId) {
321
- const [status, setStatus] = useState('not-downloaded');
322
- const [progress, setProgress] = useState(0);
323
- const [error, setError] = useState(null);
324
- const mountedRef = useRef(true);
325
- useEffect(() => {
326
- // Query initial status
327
- try {
328
- const initialStatus = ExpoAiKitModule.getDownloadableModelStatus(modelId);
329
- setStatus(initialStatus);
330
- }
331
- catch {
332
- // Model not found or native module not ready
333
- }
334
- // Listen for state changes
335
- const stateSubscription = ExpoAiKitModule.addListener('onModelStateChange', (event) => {
336
- if (event.modelId === modelId && mountedRef.current) {
337
- setStatus(event.status);
338
- }
339
- });
340
- // Listen for download progress
341
- const progressSubscription = ExpoAiKitModule.addListener('onDownloadProgress', (event) => {
342
- if (event.modelId === modelId && mountedRef.current) {
343
- setProgress(event.progress);
344
- }
345
- });
346
- return () => {
347
- mountedRef.current = false;
348
- stateSubscription.remove();
349
- progressSubscription.remove();
350
- };
351
- }, [modelId]);
352
- const download = useCallback(async () => {
353
- setError(null);
354
- setProgress(0);
355
- try {
356
- await downloadModelApi(modelId, {
357
- onProgress: (p) => {
358
- if (mountedRef.current)
359
- setProgress(p);
360
- },
361
- });
362
- }
363
- catch (err) {
364
- if (mountedRef.current) {
365
- setError(err);
366
- }
367
- }
368
- }, [modelId]);
369
- const del = useCallback(async () => {
370
- setError(null);
371
- try {
372
- await deleteModelApi(modelId);
373
- if (mountedRef.current) {
374
- setStatus('not-downloaded');
375
- setProgress(0);
376
- }
377
- }
378
- catch (err) {
379
- if (mountedRef.current) {
380
- setError(err);
381
- }
382
- }
383
- }, [modelId]);
384
- return { status, progress, download, delete: del, error };
385
- }
386
- /**
387
- * React hook to discover all available models (built-in and downloadable).
388
- *
389
- * @returns Built-in models, downloadable models, and loading state
390
- *
391
- * @example
392
- * ```tsx
393
- * function ModelPicker() {
394
- * const { builtIn, downloadable, isLoading } = useAvailableModels();
395
- *
396
- * if (isLoading) return <Text>Loading models...</Text>;
397
- *
398
- * return (
399
- * <View>
400
- * <Text>Built-in: {builtIn.map(m => m.name).join(', ')}</Text>
401
- * <Text>Downloadable: {downloadable.map(m => m.name).join(', ')}</Text>
402
- * </View>
403
- * );
404
- * }
405
- * ```
406
- */
407
- export function useAvailableModels() {
408
- const [builtIn, setBuiltIn] = useState([]);
409
- const [downloadable, setDownloadable] = useState([]);
410
- const [isLoading, setIsLoading] = useState(true);
411
- useEffect(() => {
412
- let mounted = true;
413
- Promise.all([getBuiltInModelsApi(), getDownloadableModelsApi()]).then(([bi, dl]) => {
414
- if (mounted) {
415
- setBuiltIn(bi);
416
- setDownloadable(dl);
417
- setIsLoading(false);
418
- }
419
- });
420
- return () => {
421
- mounted = false;
422
- };
423
- }, []);
424
- return { builtIn, downloadable, isLoading };
425
- }
426
- //# sourceMappingURL=hooks.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { useState, useEffect, useRef, useCallback } from 'react';\nimport {\n isAvailable,\n streamMessage,\n getBuiltInModels as getBuiltInModelsApi,\n getDownloadableModels as getDownloadableModelsApi,\n downloadModel as downloadModelApi,\n deleteModel as deleteModelApi,\n} from './index';\nimport { ChatMemoryManager } from './memory';\nimport ExpoAiKitModule from './ExpoAiKitModule';\nimport type {\n LLMMessage,\n UseChatOptions,\n UseChatReturn,\n UseCompletionOptions,\n UseCompletionReturn,\n UseOnDeviceAIReturn,\n BuiltInModel,\n DownloadableModel,\n DownloadableModelStatus,\n ModelError,\n} from './types';\n\n// Cache availability result across all hook instances\nlet availabilityCache: boolean | null = null;\nlet availabilityPromise: Promise<boolean> | null = null;\n\nfunction checkAvailability(): Promise<boolean> {\n if (availabilityCache !== null) {\n return Promise.resolve(availabilityCache);\n }\n if (!availabilityPromise) {\n availabilityPromise = isAvailable().then((result) => {\n availabilityCache = result;\n return result;\n });\n }\n return availabilityPromise;\n}\n\n/**\n * React hook to check if on-device AI is available.\n *\n * Caches the result so multiple components don't re-check.\n *\n * @returns Object with isAvailable and isChecking states\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { isAvailable, isChecking } = useOnDeviceAI();\n *\n * if (isChecking) return <Text>Checking AI availability...</Text>;\n * if (!isAvailable) return <Text>On-device AI not available</Text>;\n *\n * return <ChatComponent />;\n * }\n * ```\n */\nexport function useOnDeviceAI(): UseOnDeviceAIReturn {\n const [available, setAvailable] = useState(availabilityCache ?? false);\n const [checking, setChecking] = useState(availabilityCache === null);\n\n useEffect(() => {\n if (availabilityCache !== null) {\n setAvailable(availabilityCache);\n setChecking(false);\n return;\n }\n\n let mounted = true;\n checkAvailability().then((result) => {\n if (mounted) {\n setAvailable(result);\n setChecking(false);\n }\n });\n return () => {\n mounted = false;\n };\n }, []);\n\n return { isAvailable: available, isChecking: checking };\n}\n\n/**\n * React hook for building chat interfaces with on-device AI.\n *\n * Manages messages, input state, streaming, and conversation memory automatically.\n * Built on top of ChatMemoryManager and streamMessage.\n *\n * @param options - Configuration options for the chat\n * @returns Chat state and control functions\n *\n * @example\n * ```tsx\n * function ChatScreen() {\n * const { messages, input, setInput, sendMessage, isStreaming, stop } = useChat({\n * systemPrompt: 'You are a helpful assistant.',\n * });\n *\n * return (\n * <View>\n * <FlatList\n * data={messages}\n * renderItem={({ item }) => (\n * <Text>{item.role}: {item.content}</Text>\n * )}\n * />\n * <TextInput value={input} onChangeText={setInput} />\n * {isStreaming ? (\n * <Button title=\"Stop\" onPress={stop} />\n * ) : (\n * <Button title=\"Send\" onPress={() => sendMessage()} />\n * )}\n * </View>\n * );\n * }\n * ```\n */\nexport function useChat(options: UseChatOptions = {}): UseChatReturn {\n const { systemPrompt, maxTurns, initialMessages, onFinish, onError } = options;\n\n const memoryRef = useRef(new ChatMemoryManager({ maxTurns, systemPrompt }));\n\n // Initialize with initial messages\n const initializedRef = useRef(false);\n if (!initializedRef.current && initialMessages) {\n for (const msg of initialMessages) {\n memoryRef.current.addMessage(msg);\n }\n initializedRef.current = true;\n }\n\n const [messages, setMessages] = useState<LLMMessage[]>(memoryRef.current.getMessages());\n const [input, setInput] = useState('');\n const [isStreaming, setIsStreaming] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const stopRef = useRef<(() => void) | null>(null);\n const mountedRef = useRef(true);\n const inputRef = useRef(input);\n inputRef.current = input;\n const streamingRef = useRef(isStreaming);\n streamingRef.current = isStreaming;\n const onFinishRef = useRef(onFinish);\n onFinishRef.current = onFinish;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n stopRef.current?.();\n };\n }, []);\n\n const send = useCallback(\n async (text?: string) => {\n const content = (text ?? inputRef.current).trim();\n if (!content || streamingRef.current) return;\n\n setError(null);\n\n // Add user message\n memoryRef.current.addUserMessage(content);\n setMessages([...memoryRef.current.getMessages()]);\n if (!text) setInput('');\n\n setIsStreaming(true);\n\n try {\n const allMessages = memoryRef.current.getAllMessages();\n\n // Use a temporary message for streaming display\n let accumulatedText = '';\n const { promise, stop } = streamMessage(allMessages, (event) => {\n if (!mountedRef.current) return;\n accumulatedText = event.accumulatedText;\n // Update messages with streaming assistant response\n const currentMessages = memoryRef.current.getMessages();\n setMessages([\n ...currentMessages,\n { role: 'assistant' as const, content: accumulatedText },\n ]);\n });\n\n stopRef.current = stop;\n const response = await promise;\n\n if (!mountedRef.current) return;\n\n // Add the final assistant message to memory\n memoryRef.current.addAssistantMessage(response.text);\n setMessages([...memoryRef.current.getMessages()]);\n onFinishRef.current?.(response);\n } catch (err) {\n if (!mountedRef.current) return;\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n onErrorRef.current?.(e);\n } finally {\n stopRef.current = null;\n if (mountedRef.current) {\n setIsStreaming(false);\n }\n }\n },\n []\n );\n\n const stop = useCallback(() => {\n stopRef.current?.();\n }, []);\n\n const clear = useCallback(() => {\n memoryRef.current.clear();\n setMessages([]);\n setError(null);\n }, []);\n\n return {\n messages,\n input,\n setInput,\n sendMessage: send,\n isStreaming,\n stop,\n clear,\n error,\n };\n}\n\n/**\n * React hook for single-shot AI completions.\n *\n * Unlike useChat (which manages a conversation), useCompletion is for\n * one-off tasks like summarization, translation, or content generation.\n *\n * @param options - Configuration options\n * @returns Completion state and control functions\n *\n * @example\n * ```tsx\n * function SummarizerScreen() {\n * const { completion, isLoading, complete, stop } = useCompletion({\n * systemPrompt: 'You are a summarization assistant. Summarize the given text concisely.',\n * });\n *\n * return (\n * <View>\n * <Button\n * title=\"Summarize\"\n * onPress={() => complete('Long article text here...')}\n * />\n * {isLoading && <Button title=\"Stop\" onPress={stop} />}\n * <Text>{completion}</Text>\n * </View>\n * );\n * }\n * ```\n */\nexport function useCompletion(options: UseCompletionOptions = {}): UseCompletionReturn {\n const { systemPrompt, onFinish, onError } = options;\n\n const [completion, setCompletion] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const stopRef = useRef<(() => void) | null>(null);\n const mountedRef = useRef(true);\n const loadingRef = useRef(isLoading);\n loadingRef.current = isLoading;\n const completionRef = useRef(completion);\n completionRef.current = completion;\n const systemPromptRef = useRef(systemPrompt);\n systemPromptRef.current = systemPrompt;\n const onFinishRef = useRef(onFinish);\n onFinishRef.current = onFinish;\n const onErrorRef = useRef(onError);\n onErrorRef.current = onError;\n\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n stopRef.current?.();\n };\n }, []);\n\n const complete = useCallback(\n async (prompt: string) => {\n if (loadingRef.current) return completionRef.current;\n\n setError(null);\n setCompletion('');\n setIsLoading(true);\n\n try {\n const messages: LLMMessage[] = [{ role: 'user', content: prompt }];\n const { promise, stop } = streamMessage(\n messages,\n (event) => {\n if (!mountedRef.current) return;\n setCompletion(event.accumulatedText);\n },\n systemPromptRef.current ? { systemPrompt: systemPromptRef.current } : undefined\n );\n\n stopRef.current = stop;\n const response = await promise;\n\n if (!mountedRef.current) return '';\n\n setCompletion(response.text);\n onFinishRef.current?.(response);\n return response.text;\n } catch (err) {\n if (!mountedRef.current) return '';\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n onErrorRef.current?.(e);\n return '';\n } finally {\n stopRef.current = null;\n if (mountedRef.current) {\n setIsLoading(false);\n }\n }\n },\n []\n );\n\n const stop = useCallback(() => {\n stopRef.current?.();\n }, []);\n\n return {\n completion,\n isLoading,\n complete,\n stop,\n error,\n };\n}\n\n/**\n * React hook for managing a downloadable model's lifecycle.\n *\n * Tracks download status, progress, and provides download/delete controls.\n *\n * @param modelId - The downloadable model ID (e.g. 'gemma-e2b')\n * @returns Model status, progress, and control functions\n *\n * @example\n * ```tsx\n * function ModelManager() {\n * const { status, progress, download, delete: remove, error } = useModel('gemma-e2b');\n *\n * if (status === 'not-downloaded') {\n * return <Button title=\"Download\" onPress={download} />;\n * }\n * if (status === 'downloading') {\n * return <Text>Downloading: {Math.round(progress * 100)}%</Text>;\n * }\n * if (status === 'loading') {\n * return <Text>Loading model...</Text>;\n * }\n * return <Text>Model ready!</Text>;\n * }\n * ```\n */\nexport function useModel(modelId: string): {\n status: DownloadableModelStatus;\n progress: number;\n download: () => Promise<void>;\n delete: () => Promise<void>;\n error: ModelError | null;\n} {\n const [status, setStatus] = useState<DownloadableModelStatus>('not-downloaded');\n const [progress, setProgress] = useState(0);\n const [error, setError] = useState<ModelError | null>(null);\n const mountedRef = useRef(true);\n\n useEffect(() => {\n // Query initial status\n try {\n const initialStatus = ExpoAiKitModule.getDownloadableModelStatus(modelId);\n setStatus(initialStatus);\n } catch {\n // Model not found or native module not ready\n }\n\n // Listen for state changes\n const stateSubscription = ExpoAiKitModule.addListener(\n 'onModelStateChange',\n (event) => {\n if (event.modelId === modelId && mountedRef.current) {\n setStatus(event.status);\n }\n }\n );\n\n // Listen for download progress\n const progressSubscription = ExpoAiKitModule.addListener(\n 'onDownloadProgress',\n (event) => {\n if (event.modelId === modelId && mountedRef.current) {\n setProgress(event.progress);\n }\n }\n );\n\n return () => {\n mountedRef.current = false;\n stateSubscription.remove();\n progressSubscription.remove();\n };\n }, [modelId]);\n\n const download = useCallback(async () => {\n setError(null);\n setProgress(0);\n try {\n await downloadModelApi(modelId, {\n onProgress: (p) => {\n if (mountedRef.current) setProgress(p);\n },\n });\n } catch (err) {\n if (mountedRef.current) {\n setError(err as ModelError);\n }\n }\n }, [modelId]);\n\n const del = useCallback(async () => {\n setError(null);\n try {\n await deleteModelApi(modelId);\n if (mountedRef.current) {\n setStatus('not-downloaded');\n setProgress(0);\n }\n } catch (err) {\n if (mountedRef.current) {\n setError(err as ModelError);\n }\n }\n }, [modelId]);\n\n return { status, progress, download, delete: del, error };\n}\n\n/**\n * React hook to discover all available models (built-in and downloadable).\n *\n * @returns Built-in models, downloadable models, and loading state\n *\n * @example\n * ```tsx\n * function ModelPicker() {\n * const { builtIn, downloadable, isLoading } = useAvailableModels();\n *\n * if (isLoading) return <Text>Loading models...</Text>;\n *\n * return (\n * <View>\n * <Text>Built-in: {builtIn.map(m => m.name).join(', ')}</Text>\n * <Text>Downloadable: {downloadable.map(m => m.name).join(', ')}</Text>\n * </View>\n * );\n * }\n * ```\n */\nexport function useAvailableModels(): {\n builtIn: BuiltInModel[];\n downloadable: DownloadableModel[];\n isLoading: boolean;\n} {\n const [builtIn, setBuiltIn] = useState<BuiltInModel[]>([]);\n const [downloadable, setDownloadable] = useState<DownloadableModel[]>([]);\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() => {\n let mounted = true;\n\n Promise.all([getBuiltInModelsApi(), getDownloadableModelsApi()]).then(\n ([bi, dl]) => {\n if (mounted) {\n setBuiltIn(bi);\n setDownloadable(dl);\n setIsLoading(false);\n }\n }\n );\n\n return () => {\n mounted = false;\n };\n }, []);\n\n return { builtIn, downloadable, isLoading };\n}\n"]}