expo-local-llm 0.1.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/android/build.gradle +36 -0
  4. package/android/src/main/AndroidManifest.xml +2 -0
  5. package/android/src/main/java/expo/modules/localllm/ConversationHistory.kt +39 -0
  6. package/android/src/main/java/expo/modules/localllm/Enums.kt +13 -0
  7. package/android/src/main/java/expo/modules/localllm/Exceptions.kt +12 -0
  8. package/android/src/main/java/expo/modules/localllm/ExpoLocalLlmModule.kt +57 -0
  9. package/android/src/main/java/expo/modules/localllm/LLMSession.kt +119 -0
  10. package/android/src/main/java/expo/modules/localllm/Records.kt +23 -0
  11. package/build/ExpoLocalLlm.types.d.ts +35 -0
  12. package/build/ExpoLocalLlm.types.d.ts.map +1 -0
  13. package/build/ExpoLocalLlm.types.js +2 -0
  14. package/build/ExpoLocalLlm.types.js.map +1 -0
  15. package/build/ExpoLocalLlmModule.d.ts +10 -0
  16. package/build/ExpoLocalLlmModule.d.ts.map +1 -0
  17. package/build/ExpoLocalLlmModule.js +10 -0
  18. package/build/ExpoLocalLlmModule.js.map +1 -0
  19. package/build/LLMSession.d.ts +9 -0
  20. package/build/LLMSession.d.ts.map +1 -0
  21. package/build/LLMSession.js +16 -0
  22. package/build/LLMSession.js.map +1 -0
  23. package/build/index.d.ts +5 -0
  24. package/build/index.d.ts.map +1 -0
  25. package/build/index.js +5 -0
  26. package/build/index.js.map +1 -0
  27. package/build/useLocalLLM.d.ts +18 -0
  28. package/build/useLocalLLM.d.ts.map +1 -0
  29. package/build/useLocalLLM.js +121 -0
  30. package/build/useLocalLLM.js.map +1 -0
  31. package/expo-module.config.json +9 -0
  32. package/ios/Enums.swift +11 -0
  33. package/ios/Exceptions.swift +19 -0
  34. package/ios/ExpoLocalLlm.podspec +26 -0
  35. package/ios/ExpoLocalLlmModule.swift +56 -0
  36. package/ios/FoundationModelBridge.swift +59 -0
  37. package/ios/LLMSession.swift +68 -0
  38. package/ios/Records.swift +12 -0
  39. package/package.json +61 -0
  40. package/src/ExpoLocalLlm.types.ts +47 -0
  41. package/src/ExpoLocalLlmModule.ts +19 -0
  42. package/src/LLMSession.ts +19 -0
  43. package/src/__tests__/ExpoLocalLlm.test.ts +82 -0
  44. package/src/__tests__/useLocalLLM.test.ts +30 -0
  45. package/src/index.ts +4 -0
  46. package/src/useLocalLLM.ts +176 -0
@@ -0,0 +1,176 @@
1
+ import { useEffect, useState, useCallback, useMemo } from "react";
2
+
3
+ import type {
4
+ ModelAvailability,
5
+ SessionConfig,
6
+ TokenEvent,
7
+ StreamCompleteEvent,
8
+ StreamErrorEvent,
9
+ DownloadProgress,
10
+ } from "./ExpoLocalLlm.types";
11
+ import ExpoLocalLlmModule from "./ExpoLocalLlmModule";
12
+ import { createLLMSession, LLMSession } from "./LLMSession";
13
+
14
+ type UseLocalLLMOptions = SessionConfig;
15
+
16
+ type UseLocalLLMResult = {
17
+ availability: ModelAvailability;
18
+ session: LLMSession | null;
19
+ isGenerating: boolean;
20
+ streamedText: string;
21
+ downloadProgress: number | null;
22
+ error: string | null;
23
+ respond?: (prompt: string) => Promise<string>;
24
+ streamResponse?: (prompt: string) => Promise<void>;
25
+ cancelStream?: () => Promise<void>;
26
+ downloadModel?: () => Promise<void>;
27
+ };
28
+
29
+ export function useLocalLLM(
30
+ options: UseLocalLLMOptions = {}
31
+ ): UseLocalLLMResult {
32
+ const [availability, setAvailability] =
33
+ useState<ModelAvailability>("unknown");
34
+ const [isGenerating, setIsGenerating] = useState(false);
35
+ const [streamedText, setStreamedText] = useState("");
36
+ const [downloadProgress, setDownloadProgress] = useState<number | null>(null);
37
+ const [error, setError] = useState<string | null>(null);
38
+
39
+ // Stabilize options to avoid unnecessary session recreation
40
+ const stableConfig = useMemo(
41
+ () => options,
42
+ [
43
+ options.instructions,
44
+ options.options?.temperature,
45
+ options.options?.maxTokens,
46
+ options.options?.topK,
47
+ ]
48
+ );
49
+
50
+ const session = useMemo(() => {
51
+ if (!ExpoLocalLlmModule) return null;
52
+ try {
53
+ return createLLMSession(stableConfig);
54
+ } catch (e: any) {
55
+ return null;
56
+ }
57
+ }, [stableConfig]);
58
+
59
+ useEffect(() => {
60
+ if (!ExpoLocalLlmModule) return;
61
+
62
+ setAvailability(ExpoLocalLlmModule.getAvailability() as ModelAvailability);
63
+
64
+ const subs: { remove(): void }[] = [];
65
+
66
+ subs.push(
67
+ ExpoLocalLlmModule.addListener(
68
+ "downloadProgress",
69
+ (event: DownloadProgress) => {
70
+ setDownloadProgress(event.progress);
71
+ }
72
+ )
73
+ );
74
+ subs.push(
75
+ ExpoLocalLlmModule.addListener("availabilityChange", (event) => {
76
+ setAvailability(event.availability);
77
+ })
78
+ );
79
+
80
+ if (session) {
81
+ subs.push(
82
+ session.addListener("token", (event: TokenEvent) => {
83
+ setStreamedText(event.accumulated);
84
+ })
85
+ );
86
+ subs.push(
87
+ session.addListener("streamComplete", (_event: StreamCompleteEvent) => {
88
+ setIsGenerating(false);
89
+ })
90
+ );
91
+ subs.push(
92
+ session.addListener("streamError", (event: StreamErrorEvent) => {
93
+ setError(event.error);
94
+ setIsGenerating(false);
95
+ })
96
+ );
97
+ }
98
+
99
+ return () => {
100
+ subs.forEach((s) => s.remove());
101
+ session?.release();
102
+ };
103
+ }, [session]);
104
+
105
+ const respond = useCallback(
106
+ async (prompt: string): Promise<string> => {
107
+ if (!session) throw new Error("Session not available");
108
+ setError(null);
109
+ setIsGenerating(true);
110
+ try {
111
+ const result = await session.respond(prompt);
112
+ return result;
113
+ } catch (e: any) {
114
+ setError(e.message);
115
+ throw e;
116
+ } finally {
117
+ setIsGenerating(false);
118
+ }
119
+ },
120
+ [session]
121
+ );
122
+
123
+ const streamResponse = useCallback(
124
+ async (prompt: string): Promise<void> => {
125
+ if (!session) throw new Error("Session not available");
126
+ setError(null);
127
+ setStreamedText("");
128
+ setIsGenerating(true);
129
+ try {
130
+ await session.streamResponse(prompt);
131
+ } catch (e: any) {
132
+ setError(e.message);
133
+ setIsGenerating(false);
134
+ throw e;
135
+ }
136
+ },
137
+ [session]
138
+ );
139
+
140
+ const cancelStream = useCallback(async (): Promise<void> => {
141
+ if (!session) return;
142
+ await session.cancelStream();
143
+ setIsGenerating(false);
144
+ }, [session]);
145
+
146
+ const downloadModel = useCallback(async (): Promise<void> => {
147
+ if (!ExpoLocalLlmModule) return;
148
+ await ExpoLocalLlmModule.downloadModel();
149
+ }, []);
150
+
151
+ if (!ExpoLocalLlmModule) {
152
+ return {
153
+ availability: "notEligible",
154
+ session: null,
155
+ isGenerating: false,
156
+ streamedText: "",
157
+ downloadProgress: null,
158
+ error: null,
159
+ };
160
+ }
161
+
162
+ return {
163
+ availability,
164
+ session,
165
+ isGenerating,
166
+ streamedText,
167
+ downloadProgress,
168
+ error,
169
+ respond: session && availability === "available" ? respond : undefined,
170
+ streamResponse:
171
+ session && availability === "available" ? streamResponse : undefined,
172
+ cancelStream:
173
+ session && availability === "available" ? cancelStream : undefined,
174
+ downloadModel,
175
+ };
176
+ }