@capgo/capacitor-llm 8.0.19 → 8.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.
- package/CapgoCapacitorLlm.podspec +1 -9
- package/Package.swift +21 -3
- package/README.md +137 -163
- package/android/build.gradle +3 -2
- package/android/src/main/java/ee/forgr/capgo_llm/LLM.java +605 -283
- package/android/src/main/java/ee/forgr/capgo_llm/LLMPlugin.java +36 -63
- package/dist/docs.json +66 -35
- package/dist/esm/definitions.d.ts +28 -20
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +68 -39
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +68 -39
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +68 -39
- package/dist/plugin.js.map +1 -1
- package/ios/Sources/LLMPlugin/LLMPlugin.swift +384 -613
- package/ios/Sources/LiteRTLM/Benchmark.swift +83 -0
- package/ios/Sources/LiteRTLM/Capabilities.swift +41 -0
- package/ios/Sources/LiteRTLM/Config.swift +172 -0
- package/ios/Sources/LiteRTLM/Conversation.swift +472 -0
- package/ios/Sources/LiteRTLM/Engine.swift +208 -0
- package/ios/Sources/LiteRTLM/ExperimentalFlags.swift +142 -0
- package/ios/Sources/LiteRTLM/LiteRTLMError.swift +156 -0
- package/ios/Sources/LiteRTLM/Message.swift +225 -0
- package/ios/Sources/LiteRTLM/Tool.swift +291 -0
- package/ios/Sources/LiteRTLM/ToolManager.swift +152 -0
- package/package.json +6 -5
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
package ee.forgr.capgo_llm;
|
|
2
2
|
|
|
3
|
-
import com.getcapacitor.JSArray;
|
|
4
3
|
import com.getcapacitor.JSObject;
|
|
5
4
|
import com.getcapacitor.Plugin;
|
|
6
5
|
import com.getcapacitor.PluginCall;
|
|
7
6
|
import com.getcapacitor.PluginMethod;
|
|
8
7
|
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
9
|
-
import java.util.ArrayList;
|
|
10
|
-
import java.util.List;
|
|
11
8
|
|
|
12
9
|
@CapacitorPlugin(name = "CapgoLLM")
|
|
13
10
|
public class LLMPlugin extends Plugin {
|
|
14
11
|
|
|
15
|
-
private final String pluginVersion = "8.0
|
|
12
|
+
private final String pluginVersion = "8.1.0";
|
|
16
13
|
|
|
17
14
|
private LLM llm;
|
|
18
15
|
|
|
@@ -44,34 +41,41 @@ public class LLMPlugin extends Plugin {
|
|
|
44
41
|
return;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
try {
|
|
45
|
+
llm.sendMessage(
|
|
46
|
+
chatId,
|
|
47
|
+
message,
|
|
48
|
+
new LLM.MessageCallback() {
|
|
49
|
+
@Override
|
|
50
|
+
public void onTextReceived(String chatId, String text, boolean isChunk) {
|
|
51
|
+
JSObject event = new JSObject();
|
|
52
|
+
event.put("text", text);
|
|
53
|
+
event.put("chatId", chatId);
|
|
54
|
+
event.put("isChunk", isChunk);
|
|
55
|
+
notifyListeners("textFromAi", event);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Override
|
|
59
|
+
public void onComplete(String chatId) {
|
|
60
|
+
JSObject event = new JSObject();
|
|
61
|
+
event.put("chatId", chatId);
|
|
62
|
+
notifyListeners("aiFinished", event);
|
|
63
|
+
}
|
|
66
64
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
@Override
|
|
66
|
+
public void onError(String error) {
|
|
67
|
+
JSObject event = new JSObject();
|
|
68
|
+
event.put("chatId", chatId);
|
|
69
|
+
event.put("error", error);
|
|
70
|
+
notifyListeners("generationError", event);
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
|
-
|
|
72
|
-
);
|
|
73
|
+
);
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
call.resolve();
|
|
76
|
+
} catch (IllegalStateException exception) {
|
|
77
|
+
call.reject(exception.getMessage());
|
|
78
|
+
}
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
@PluginMethod
|
|
@@ -97,35 +101,19 @@ public class LLMPlugin extends Plugin {
|
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
// Extract model parameters
|
|
100
|
-
String engine = call.getString("engine", "auto");
|
|
101
|
-
String modelType = call.getString("modelType");
|
|
102
|
-
String tokenizerPath = call.getString("tokenizerPath");
|
|
103
104
|
Integer maxTokens = call.getInt("maxTokens", 2048);
|
|
104
|
-
Integer sequenceLength = call.getInt("sequenceLength", maxTokens);
|
|
105
105
|
Integer topk = call.getInt("topk", 40);
|
|
106
106
|
Float temperature = call.getFloat("temperature", 0.8f);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (sequenceLength <= 0) {
|
|
110
|
-
call.reject("sequenceLength must be > 0");
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!specialTokens.isEmpty()) {
|
|
115
|
-
call.reject("specialTokens are only supported by ExecuTorch on iOS");
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
107
|
+
Integer randomSeed = call.getInt("randomSeed", 0);
|
|
108
|
+
String modelType = call.getString("modelType");
|
|
118
109
|
|
|
119
110
|
llm.setModel(
|
|
120
111
|
path,
|
|
121
|
-
engine,
|
|
122
112
|
modelType,
|
|
123
|
-
tokenizerPath,
|
|
124
|
-
specialTokens,
|
|
125
113
|
maxTokens,
|
|
126
|
-
sequenceLength,
|
|
127
114
|
topk,
|
|
128
115
|
temperature,
|
|
116
|
+
randomSeed,
|
|
129
117
|
new LLM.ModelLoadCallback() {
|
|
130
118
|
@Override
|
|
131
119
|
public void onSuccess() {
|
|
@@ -204,21 +192,6 @@ public class LLMPlugin extends Plugin {
|
|
|
204
192
|
.start();
|
|
205
193
|
}
|
|
206
194
|
|
|
207
|
-
private List<String> getStringList(JSArray values) {
|
|
208
|
-
List<String> result = new ArrayList<>();
|
|
209
|
-
if (values == null) {
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
for (int i = 0; i < values.length(); i++) {
|
|
214
|
-
String value = values.optString(i, null);
|
|
215
|
-
if (value != null) {
|
|
216
|
-
result.add(value);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return result;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
195
|
private void downloadFile(String urlString, java.io.File outputFile, ProgressCallback callback) throws Exception {
|
|
223
196
|
java.net.URL url = new java.net.URL(urlString);
|
|
224
197
|
java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection();
|
package/dist/docs.json
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"text": "Promise that resolves when model is loaded"
|
|
82
82
|
}
|
|
83
83
|
],
|
|
84
|
-
"docs": "Sets the model configuration\n- iOS: Use \"Apple Intelligence\" as path for system model
|
|
84
|
+
"docs": "Sets the model configuration\n- iOS: Use \"Apple Intelligence\" as path for the system model. Custom LiteRT-LM `.litertlm` models are supported on iOS only when this plugin is integrated through Swift Package Manager.\n- Android: Use \"Gemini Nano\" for the AICore system model on supported devices where it is already available. Prefer LiteRT-LM `.litertlm` bundles for custom models; legacy MediaPipe `.task` models are still supported\n- Web: Provide a web-ready model asset for `@mediapipe/tasks-genai` such as Gemma 4 `*-web.task`",
|
|
85
85
|
"complexTypes": [
|
|
86
86
|
"ModelOptions"
|
|
87
87
|
],
|
|
@@ -187,6 +187,42 @@
|
|
|
187
187
|
],
|
|
188
188
|
"slug": "addlisteneraifinished-"
|
|
189
189
|
},
|
|
190
|
+
{
|
|
191
|
+
"name": "addListener",
|
|
192
|
+
"signature": "(eventName: 'generationError', listenerFunc: (event: GenerationErrorEvent) => void) => Promise<{ remove: () => Promise<void>; }>",
|
|
193
|
+
"parameters": [
|
|
194
|
+
{
|
|
195
|
+
"name": "eventName",
|
|
196
|
+
"docs": "- Event name 'generationError'",
|
|
197
|
+
"type": "'generationError'"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "listenerFunc",
|
|
201
|
+
"docs": "- Callback function for generation errors",
|
|
202
|
+
"type": "(event: GenerationErrorEvent) => void"
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
"returns": "Promise<{ remove: () => Promise<void>; }>",
|
|
206
|
+
"tags": [
|
|
207
|
+
{
|
|
208
|
+
"name": "param",
|
|
209
|
+
"text": "eventName - Event name 'generationError'"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "param",
|
|
213
|
+
"text": "listenerFunc - Callback function for generation errors"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "returns",
|
|
217
|
+
"text": "Promise with remove function to unsubscribe"
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"docs": "Adds a listener for generation failures that happen after streaming starts",
|
|
221
|
+
"complexTypes": [
|
|
222
|
+
"GenerationErrorEvent"
|
|
223
|
+
],
|
|
224
|
+
"slug": "addlistenergenerationerror-"
|
|
225
|
+
},
|
|
190
226
|
{
|
|
191
227
|
"name": "addListener",
|
|
192
228
|
"signature": "(eventName: 'downloadProgress', listenerFunc: (event: DownloadProgressEvent) => void) => Promise<{ remove: () => Promise<void>; }>",
|
|
@@ -293,45 +329,24 @@
|
|
|
293
329
|
{
|
|
294
330
|
"name": "ModelOptions",
|
|
295
331
|
"slug": "modeloptions",
|
|
296
|
-
"docs": "Model configuration options",
|
|
332
|
+
"docs": "Model configuration options\nOnly `path` is required. All other properties are optional overrides.",
|
|
297
333
|
"tags": [],
|
|
298
334
|
"methods": [],
|
|
299
335
|
"properties": [
|
|
300
336
|
{
|
|
301
337
|
"name": "path",
|
|
302
338
|
"tags": [],
|
|
303
|
-
"docs": "Model path
|
|
339
|
+
"docs": "Model path, \"Apple Intelligence\" for the Apple system model on iOS, or \"Gemini Nano\" for the Android AICore system model when already available on the device. On iOS, custom `.litertlm` models require the plugin to be integrated through Swift Package Manager. Gemma 4 examples use `.litertlm` on mobile and `*-web.task` on web.",
|
|
304
340
|
"complexTypes": [],
|
|
305
341
|
"type": "string"
|
|
306
342
|
},
|
|
307
|
-
{
|
|
308
|
-
"name": "engine",
|
|
309
|
-
"tags": [],
|
|
310
|
-
"docs": "Runtime engine to use.\n- \"auto\": uses Apple Intelligence on iOS when path is \"Apple Intelligence\", ExecuTorch for `.pte` or tokenizerPath, otherwise MediaPipe\n- \"apple\": iOS Foundation Models / Apple Intelligence\n- \"mediapipe\": MediaPipe GenAI `.task` models\n- \"executorch\": ExecuTorch `.pte` models with a tokenizer file",
|
|
311
|
-
"complexTypes": [],
|
|
312
|
-
"type": "'auto' | 'apple' | 'mediapipe' | 'executorch' | undefined"
|
|
313
|
-
},
|
|
314
343
|
{
|
|
315
344
|
"name": "modelType",
|
|
316
345
|
"tags": [],
|
|
317
|
-
"docs": "Model file type/extension (
|
|
346
|
+
"docs": "Optional. Model file type/extension (for example `task`, `bin`, `litertlm`, or `gemini-nano`). If not provided, it is extracted from the path. Use `litertlm` on iOS only in SwiftPM integrations.",
|
|
318
347
|
"complexTypes": [],
|
|
319
348
|
"type": "string | undefined"
|
|
320
349
|
},
|
|
321
|
-
{
|
|
322
|
-
"name": "tokenizerPath",
|
|
323
|
-
"tags": [],
|
|
324
|
-
"docs": "Tokenizer path for ExecuTorch models. Required when engine is \"executorch\".",
|
|
325
|
-
"complexTypes": [],
|
|
326
|
-
"type": "string | undefined"
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
"name": "specialTokens",
|
|
330
|
-
"tags": [],
|
|
331
|
-
"docs": "Optional special tokens passed to iOS ExecuTorch tokenizers.",
|
|
332
|
-
"complexTypes": [],
|
|
333
|
-
"type": "string[] | undefined"
|
|
334
|
-
},
|
|
335
350
|
{
|
|
336
351
|
"name": "maxTokens",
|
|
337
352
|
"tags": [],
|
|
@@ -339,13 +354,6 @@
|
|
|
339
354
|
"complexTypes": [],
|
|
340
355
|
"type": "number | undefined"
|
|
341
356
|
},
|
|
342
|
-
{
|
|
343
|
-
"name": "sequenceLength",
|
|
344
|
-
"tags": [],
|
|
345
|
-
"docs": "Sequence length for ExecuTorch generation. Defaults to maxTokens when omitted.",
|
|
346
|
-
"complexTypes": [],
|
|
347
|
-
"type": "number | undefined"
|
|
348
|
-
},
|
|
349
357
|
{
|
|
350
358
|
"name": "topk",
|
|
351
359
|
"tags": [],
|
|
@@ -363,7 +371,7 @@
|
|
|
363
371
|
{
|
|
364
372
|
"name": "randomSeed",
|
|
365
373
|
"tags": [],
|
|
366
|
-
"docs": "Random seed for generation",
|
|
374
|
+
"docs": "Optional. Random seed for generation.",
|
|
367
375
|
"complexTypes": [],
|
|
368
376
|
"type": "number | undefined"
|
|
369
377
|
}
|
|
@@ -395,7 +403,7 @@
|
|
|
395
403
|
{
|
|
396
404
|
"name": "DownloadModelOptions",
|
|
397
405
|
"slug": "downloadmodeloptions",
|
|
398
|
-
"docs": "Options for downloading a model",
|
|
406
|
+
"docs": "Options for downloading a model\nOnly `url` is required. `companionUrl` and `filename` are optional.",
|
|
399
407
|
"tags": [],
|
|
400
408
|
"methods": [],
|
|
401
409
|
"properties": [
|
|
@@ -409,7 +417,7 @@
|
|
|
409
417
|
{
|
|
410
418
|
"name": "companionUrl",
|
|
411
419
|
"tags": [],
|
|
412
|
-
"docs": "Optional: URL of companion file
|
|
420
|
+
"docs": "Optional: URL of a companion file for legacy model formats",
|
|
413
421
|
"complexTypes": [],
|
|
414
422
|
"type": "string | undefined"
|
|
415
423
|
},
|
|
@@ -468,6 +476,29 @@
|
|
|
468
476
|
}
|
|
469
477
|
]
|
|
470
478
|
},
|
|
479
|
+
{
|
|
480
|
+
"name": "GenerationErrorEvent",
|
|
481
|
+
"slug": "generationerrorevent",
|
|
482
|
+
"docs": "Event data for generation failures\n`chatId` is optional and may be omitted when the failure is not tied to a specific chat.",
|
|
483
|
+
"tags": [],
|
|
484
|
+
"methods": [],
|
|
485
|
+
"properties": [
|
|
486
|
+
{
|
|
487
|
+
"name": "chatId",
|
|
488
|
+
"tags": [],
|
|
489
|
+
"docs": "Optional. The chat session ID that failed, when available.",
|
|
490
|
+
"complexTypes": [],
|
|
491
|
+
"type": "string | undefined"
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
"name": "error",
|
|
495
|
+
"tags": [],
|
|
496
|
+
"docs": "Error message describing the failure",
|
|
497
|
+
"complexTypes": [],
|
|
498
|
+
"type": "string"
|
|
499
|
+
}
|
|
500
|
+
]
|
|
501
|
+
},
|
|
471
502
|
{
|
|
472
503
|
"name": "DownloadProgressEvent",
|
|
473
504
|
"slug": "downloadprogressevent",
|
|
@@ -28,8 +28,9 @@ export interface LLMPlugin {
|
|
|
28
28
|
}>;
|
|
29
29
|
/**
|
|
30
30
|
* Sets the model configuration
|
|
31
|
-
* - iOS: Use "Apple Intelligence" as path for system model
|
|
32
|
-
* - Android:
|
|
31
|
+
* - iOS: Use "Apple Intelligence" as path for the system model. Custom LiteRT-LM `.litertlm` models are supported on iOS only when this plugin is integrated through Swift Package Manager.
|
|
32
|
+
* - Android: Use "Gemini Nano" for the AICore system model on supported devices where it is already available. Prefer LiteRT-LM `.litertlm` bundles for custom models; legacy MediaPipe `.task` models are still supported
|
|
33
|
+
* - Web: Provide a web-ready model asset for `@mediapipe/tasks-genai` such as Gemma 4 `*-web.task`
|
|
33
34
|
* @param options - The model configuration
|
|
34
35
|
* @returns Promise that resolves when model is loaded
|
|
35
36
|
*/
|
|
@@ -60,6 +61,15 @@ export interface LLMPlugin {
|
|
|
60
61
|
addListener(eventName: 'aiFinished', listenerFunc: (event: AiFinishedEvent) => void): Promise<{
|
|
61
62
|
remove: () => Promise<void>;
|
|
62
63
|
}>;
|
|
64
|
+
/**
|
|
65
|
+
* Adds a listener for generation failures that happen after streaming starts
|
|
66
|
+
* @param eventName - Event name 'generationError'
|
|
67
|
+
* @param listenerFunc - Callback function for generation errors
|
|
68
|
+
* @returns Promise with remove function to unsubscribe
|
|
69
|
+
*/
|
|
70
|
+
addListener(eventName: 'generationError', listenerFunc: (event: GenerationErrorEvent) => void): Promise<{
|
|
71
|
+
remove: () => Promise<void>;
|
|
72
|
+
}>;
|
|
63
73
|
/**
|
|
64
74
|
* Adds a listener for model download progress events
|
|
65
75
|
* @param eventName - Event name 'downloadProgress'
|
|
@@ -112,13 +122,24 @@ export interface AiFinishedEvent {
|
|
|
112
122
|
/** The chat session ID that finished */
|
|
113
123
|
chatId: string;
|
|
114
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Event data for generation failures
|
|
127
|
+
* `chatId` is optional and may be omitted when the failure is not tied to a specific chat.
|
|
128
|
+
*/
|
|
129
|
+
export interface GenerationErrorEvent {
|
|
130
|
+
/** Optional. The chat session ID that failed, when available. */
|
|
131
|
+
chatId?: string;
|
|
132
|
+
/** Error message describing the failure */
|
|
133
|
+
error: string;
|
|
134
|
+
}
|
|
115
135
|
/**
|
|
116
136
|
* Options for downloading a model
|
|
137
|
+
* Only `url` is required. `companionUrl` and `filename` are optional.
|
|
117
138
|
*/
|
|
118
139
|
export interface DownloadModelOptions {
|
|
119
140
|
/** URL of the model file to download */
|
|
120
141
|
url: string;
|
|
121
|
-
/** Optional: URL of companion file
|
|
142
|
+
/** Optional: URL of a companion file for legacy model formats */
|
|
122
143
|
companionUrl?: string;
|
|
123
144
|
/** Optional: Custom filename (defaults to filename from URL) */
|
|
124
145
|
filename?: string;
|
|
@@ -152,32 +173,19 @@ export interface ReadinessChangeEvent {
|
|
|
152
173
|
}
|
|
153
174
|
/**
|
|
154
175
|
* Model configuration options
|
|
176
|
+
* Only `path` is required. All other properties are optional overrides.
|
|
155
177
|
*/
|
|
156
178
|
export interface ModelOptions {
|
|
157
|
-
/** Model path
|
|
179
|
+
/** Model path, "Apple Intelligence" for the Apple system model on iOS, or "Gemini Nano" for the Android AICore system model when already available on the device. On iOS, custom `.litertlm` models require the plugin to be integrated through Swift Package Manager. Gemma 4 examples use `.litertlm` on mobile and `*-web.task` on web. */
|
|
158
180
|
path: string;
|
|
159
|
-
/**
|
|
160
|
-
* Runtime engine to use.
|
|
161
|
-
* - "auto": uses Apple Intelligence on iOS when path is "Apple Intelligence", ExecuTorch for `.pte` or tokenizerPath, otherwise MediaPipe
|
|
162
|
-
* - "apple": iOS Foundation Models / Apple Intelligence
|
|
163
|
-
* - "mediapipe": MediaPipe GenAI `.task` models
|
|
164
|
-
* - "executorch": ExecuTorch `.pte` models with a tokenizer file
|
|
165
|
-
*/
|
|
166
|
-
engine?: 'auto' | 'apple' | 'mediapipe' | 'executorch';
|
|
167
|
-
/** Model file type/extension (e.g., "task", "bin", "litertlm"). If not provided, will be extracted from path. */
|
|
181
|
+
/** Optional. Model file type/extension (for example `task`, `bin`, `litertlm`, or `gemini-nano`). If not provided, it is extracted from the path. Use `litertlm` on iOS only in SwiftPM integrations. */
|
|
168
182
|
modelType?: string;
|
|
169
|
-
/** Tokenizer path for ExecuTorch models. Required when engine is "executorch". */
|
|
170
|
-
tokenizerPath?: string;
|
|
171
|
-
/** Optional special tokens passed to iOS ExecuTorch tokenizers. */
|
|
172
|
-
specialTokens?: string[];
|
|
173
183
|
/** Maximum number of tokens the model handles */
|
|
174
184
|
maxTokens?: number;
|
|
175
|
-
/** Sequence length for ExecuTorch generation. Defaults to maxTokens when omitted. */
|
|
176
|
-
sequenceLength?: number;
|
|
177
185
|
/** Number of tokens the model considers at each step */
|
|
178
186
|
topk?: number;
|
|
179
187
|
/** Amount of randomness in generation (0.0-1.0) */
|
|
180
188
|
temperature?: number;
|
|
181
|
-
/** Random seed for generation */
|
|
189
|
+
/** Optional. Random seed for generation. */
|
|
182
190
|
randomSeed?: number;
|
|
183
191
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * LLM Plugin interface for interacting with on-device language models\n */\nexport interface LLMPlugin {\n /**\n * Creates a new chat session\n * @returns Promise with chat id and optional instructions\n */\n createChat(): Promise<{ id: string; instructions?: string }>;\n\n /**\n * Sends a message to the AI in a specific chat session\n * @param options - The chat id and message to send\n * @returns Promise that resolves when message is sent\n */\n sendMessage(options: { chatId: string; message: string }): Promise<void>;\n\n /**\n * Gets the readiness status of the LLM\n * @returns Promise with readiness status string\n */\n getReadiness(): Promise<{ readiness: string }>;\n\n /**\n * Sets the model configuration\n * - iOS: Use \"Apple Intelligence\" as path for system model
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * LLM Plugin interface for interacting with on-device language models\n */\nexport interface LLMPlugin {\n /**\n * Creates a new chat session\n * @returns Promise with chat id and optional instructions\n */\n createChat(): Promise<{ id: string; instructions?: string }>;\n\n /**\n * Sends a message to the AI in a specific chat session\n * @param options - The chat id and message to send\n * @returns Promise that resolves when message is sent\n */\n sendMessage(options: { chatId: string; message: string }): Promise<void>;\n\n /**\n * Gets the readiness status of the LLM\n * @returns Promise with readiness status string\n */\n getReadiness(): Promise<{ readiness: string }>;\n\n /**\n * Sets the model configuration\n * - iOS: Use \"Apple Intelligence\" as path for the system model. Custom LiteRT-LM `.litertlm` models are supported on iOS only when this plugin is integrated through Swift Package Manager.\n * - Android: Use \"Gemini Nano\" for the AICore system model on supported devices where it is already available. Prefer LiteRT-LM `.litertlm` bundles for custom models; legacy MediaPipe `.task` models are still supported\n * - Web: Provide a web-ready model asset for `@mediapipe/tasks-genai` such as Gemma 4 `*-web.task`\n * @param options - The model configuration\n * @returns Promise that resolves when model is loaded\n */\n setModel(options: ModelOptions): Promise<void>;\n\n /**\n * Downloads a model from a URL and saves it to the appropriate location\n * - iOS: Downloads to the app's documents directory\n * - Android: Downloads to the app's files directory\n * @param options - The download configuration\n * @returns Promise with download result including the saved path\n */\n downloadModel(options: DownloadModelOptions): Promise<DownloadModelResult>;\n\n /**\n * Adds a listener for text received from AI\n * @param eventName - Event name 'textFromAi'\n * @param listenerFunc - Callback function for text events\n * @returns Promise with remove function to unsubscribe\n */\n addListener(\n eventName: 'textFromAi',\n listenerFunc: (event: TextFromAiEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Adds a listener for AI completion events\n * @param eventName - Event name 'aiFinished'\n * @param listenerFunc - Callback function for finish events\n * @returns Promise with remove function to unsubscribe\n */\n addListener(\n eventName: 'aiFinished',\n listenerFunc: (event: AiFinishedEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Adds a listener for generation failures that happen after streaming starts\n * @param eventName - Event name 'generationError'\n * @param listenerFunc - Callback function for generation errors\n * @returns Promise with remove function to unsubscribe\n */\n addListener(\n eventName: 'generationError',\n listenerFunc: (event: GenerationErrorEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Adds a listener for model download progress events\n * @param eventName - Event name 'downloadProgress'\n * @param listenerFunc - Callback function for progress events\n * @returns Promise with remove function to unsubscribe\n */\n addListener(\n eventName: 'downloadProgress',\n listenerFunc: (event: DownloadProgressEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Adds a listener for readiness status changes\n * @param eventName - Event name 'readinessChange'\n * @param listenerFunc - Callback function for readiness events\n * @returns Promise with remove function to unsubscribe\n */\n addListener(\n eventName: 'readinessChange',\n listenerFunc: (event: ReadinessChangeEvent) => void,\n ): Promise<{ remove: () => Promise<void> }>;\n\n /**\n * Get the native Capacitor plugin version.\n *\n * @returns Promise that resolves with the plugin version\n * @throws Error if getting the version fails\n * @since 1.0.0\n * @example\n * ```typescript\n * const { version } = await CapgoLLM.getPluginVersion();\n * console.log('Plugin version:', version);\n * ```\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\n\n/**\n * Event data for text received from AI\n */\nexport interface TextFromAiEvent {\n /** The text content from AI - this is an incremental chunk, not the full text */\n text: string;\n /** The chat session ID */\n chatId: string;\n /** Whether this is a complete chunk (true) or partial streaming data (false) */\n isChunk?: boolean;\n}\n\n/**\n * Event data for AI completion\n */\nexport interface AiFinishedEvent {\n /** The chat session ID that finished */\n chatId: string;\n}\n\n/**\n * Event data for generation failures\n * `chatId` is optional and may be omitted when the failure is not tied to a specific chat.\n */\nexport interface GenerationErrorEvent {\n /** Optional. The chat session ID that failed, when available. */\n chatId?: string;\n /** Error message describing the failure */\n error: string;\n}\n\n/**\n * Options for downloading a model\n * Only `url` is required. `companionUrl` and `filename` are optional.\n */\nexport interface DownloadModelOptions {\n /** URL of the model file to download */\n url: string;\n /** Optional: URL of a companion file for legacy model formats */\n companionUrl?: string;\n /** Optional: Custom filename (defaults to filename from URL) */\n filename?: string;\n}\n\n/**\n * Result of model download\n */\nexport interface DownloadModelResult {\n /** Path where the model was saved */\n path: string;\n /** Path where the companion file was saved (if applicable) */\n companionPath?: string;\n}\n\n/**\n * Event data for download progress\n */\nexport interface DownloadProgressEvent {\n /** Percentage of download completed (0-100) */\n progress: number;\n /** Total bytes to download */\n totalBytes?: number;\n /** Bytes downloaded so far */\n downloadedBytes?: number;\n}\n\n/**\n * Event data for readiness status changes\n */\nexport interface ReadinessChangeEvent {\n /** The readiness status */\n readiness: string;\n}\n\n/**\n * Model configuration options\n * Only `path` is required. All other properties are optional overrides.\n */\nexport interface ModelOptions {\n /** Model path, \"Apple Intelligence\" for the Apple system model on iOS, or \"Gemini Nano\" for the Android AICore system model when already available on the device. On iOS, custom `.litertlm` models require the plugin to be integrated through Swift Package Manager. Gemma 4 examples use `.litertlm` on mobile and `*-web.task` on web. */\n path: string;\n /** Optional. Model file type/extension (for example `task`, `bin`, `litertlm`, or `gemini-nano`). If not provided, it is extracted from the path. Use `litertlm` on iOS only in SwiftPM integrations. */\n modelType?: string;\n /** Maximum number of tokens the model handles */\n maxTokens?: number;\n /** Number of tokens the model considers at each step */\n topk?: number;\n /** Amount of randomness in generation (0.0-1.0) */\n temperature?: number;\n /** Optional. Random seed for generation. */\n randomSeed?: number;\n}\n"]}
|
package/dist/esm/web.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export declare class CapgoLLMWeb extends WebPlugin implements LLMPlugin {
|
|
|
4
4
|
private llm;
|
|
5
5
|
private chatSessions;
|
|
6
6
|
private readiness;
|
|
7
|
+
private modelPath;
|
|
8
|
+
private modelType;
|
|
7
9
|
getReadiness(): Promise<{
|
|
8
10
|
readiness: string;
|
|
9
11
|
}>;
|
|
@@ -20,4 +22,8 @@ export declare class CapgoLLMWeb extends WebPlugin implements LLMPlugin {
|
|
|
20
22
|
getPluginVersion(): Promise<{
|
|
21
23
|
version: string;
|
|
22
24
|
}>;
|
|
25
|
+
private closeCurrentModel;
|
|
26
|
+
private resolveModelType;
|
|
27
|
+
private buildPrompt;
|
|
28
|
+
private usesGemmaChatTemplate;
|
|
23
29
|
}
|
package/dist/esm/web.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
2
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
3
|
-
var m = o[Symbol.asyncIterator], i;
|
|
4
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
5
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
6
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
7
|
-
};
|
|
8
1
|
import { WebPlugin } from '@capacitor/core';
|
|
9
2
|
import { FilesetResolver, LlmInference } from '@mediapipe/tasks-genai';
|
|
10
3
|
export class CapgoLLMWeb extends WebPlugin {
|
|
@@ -13,6 +6,8 @@ export class CapgoLLMWeb extends WebPlugin {
|
|
|
13
6
|
this.llm = null;
|
|
14
7
|
this.chatSessions = new Map();
|
|
15
8
|
this.readiness = 'not-loaded';
|
|
9
|
+
this.modelPath = '';
|
|
10
|
+
this.modelType = 'task';
|
|
16
11
|
}
|
|
17
12
|
async getReadiness() {
|
|
18
13
|
return { readiness: this.readiness };
|
|
@@ -26,11 +21,13 @@ export class CapgoLLMWeb extends WebPlugin {
|
|
|
26
21
|
id: chatId,
|
|
27
22
|
llm: this.llm,
|
|
28
23
|
isActive: true,
|
|
24
|
+
modelPath: this.modelPath,
|
|
25
|
+
modelType: this.modelType,
|
|
26
|
+
history: [],
|
|
29
27
|
});
|
|
30
28
|
return { id: chatId };
|
|
31
29
|
}
|
|
32
30
|
async sendMessage(options) {
|
|
33
|
-
var _a, e_1, _b, _c;
|
|
34
31
|
const session = this.chatSessions.get(options.chatId);
|
|
35
32
|
if (!session) {
|
|
36
33
|
throw new Error(`Chat session ${options.chatId} not found`);
|
|
@@ -38,29 +35,31 @@ export class CapgoLLMWeb extends WebPlugin {
|
|
|
38
35
|
if (!session.isActive) {
|
|
39
36
|
throw new Error(`Chat session ${options.chatId} is not active`);
|
|
40
37
|
}
|
|
38
|
+
let hasStreamed = false;
|
|
39
|
+
let responseText = '';
|
|
41
40
|
try {
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
_c = response_1_1.value;
|
|
47
|
-
_d = false;
|
|
48
|
-
const partialResponse = _c;
|
|
49
|
-
// Send incremental text
|
|
50
|
-
this.notifyListeners('textFromAi', {
|
|
51
|
-
text: partialResponse,
|
|
52
|
-
chatId: options.chatId,
|
|
53
|
-
isChunk: true,
|
|
54
|
-
});
|
|
41
|
+
const prompt = this.buildPrompt(session, options.message);
|
|
42
|
+
const finalResponse = await session.llm.generateResponse(prompt, (partialResponse, done) => {
|
|
43
|
+
if (done || !partialResponse) {
|
|
44
|
+
return;
|
|
55
45
|
}
|
|
46
|
+
hasStreamed = true;
|
|
47
|
+
responseText += partialResponse;
|
|
48
|
+
this.notifyListeners('textFromAi', {
|
|
49
|
+
text: partialResponse,
|
|
50
|
+
chatId: options.chatId,
|
|
51
|
+
isChunk: true,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
if (!hasStreamed && finalResponse) {
|
|
55
|
+
responseText = finalResponse;
|
|
56
|
+
this.notifyListeners('textFromAi', {
|
|
57
|
+
text: finalResponse,
|
|
58
|
+
chatId: options.chatId,
|
|
59
|
+
isChunk: true,
|
|
60
|
+
});
|
|
56
61
|
}
|
|
57
|
-
|
|
58
|
-
finally {
|
|
59
|
-
try {
|
|
60
|
-
if (!_d && !_a && (_b = response_1.return)) await _b.call(response_1);
|
|
61
|
-
}
|
|
62
|
-
finally { if (e_1) throw e_1.error; }
|
|
63
|
-
}
|
|
62
|
+
session.history.push({ role: 'user', content: options.message }, { role: 'model', content: responseText || finalResponse });
|
|
64
63
|
// Notify completion
|
|
65
64
|
this.notifyListeners('aiFinished', {
|
|
66
65
|
chatId: options.chatId,
|
|
@@ -68,22 +67,20 @@ export class CapgoLLMWeb extends WebPlugin {
|
|
|
68
67
|
}
|
|
69
68
|
catch (error) {
|
|
70
69
|
console.error('Error generating response:', error);
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
if (hasStreamed) {
|
|
72
|
+
this.notifyListeners('generationError', {
|
|
73
|
+
chatId: options.chatId,
|
|
74
|
+
error: message,
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
71
78
|
throw error;
|
|
72
79
|
}
|
|
73
80
|
}
|
|
74
81
|
async setModel(options) {
|
|
75
|
-
var _a;
|
|
76
|
-
const engine = (_a = options.engine) !== null && _a !== void 0 ? _a : 'auto';
|
|
77
|
-
const normalizedPath = options.path.toLowerCase();
|
|
78
|
-
const wantsApple = engine === 'apple' || (engine === 'auto' && normalizedPath === 'apple intelligence');
|
|
79
|
-
const wantsExecuTorch = engine === 'executorch' || (engine === 'auto' && (options.tokenizerPath || normalizedPath.endsWith('.pte')));
|
|
80
|
-
if (wantsApple) {
|
|
81
|
-
throw new Error('Apple Intelligence is only available on native iOS.');
|
|
82
|
-
}
|
|
83
|
-
if (wantsExecuTorch) {
|
|
84
|
-
throw new Error('ExecuTorch is only available on native iOS and Android.');
|
|
85
|
-
}
|
|
86
82
|
try {
|
|
83
|
+
this.closeCurrentModel();
|
|
87
84
|
// Update readiness
|
|
88
85
|
this.readiness = 'loading';
|
|
89
86
|
this.notifyListeners('readinessChange', { readiness: this.readiness });
|
|
@@ -100,11 +97,15 @@ export class CapgoLLMWeb extends WebPlugin {
|
|
|
100
97
|
const genai = await FilesetResolver.forGenAiTasks('https://cdn.jsdelivr.net/npm/@mediapipe/tasks-genai@latest/wasm');
|
|
101
98
|
// Create LLM instance
|
|
102
99
|
this.llm = await LlmInference.createFromOptions(genai, config);
|
|
100
|
+
this.modelPath = options.path;
|
|
101
|
+
this.modelType = this.resolveModelType(options);
|
|
102
|
+
this.chatSessions.clear();
|
|
103
103
|
// Update readiness
|
|
104
104
|
this.readiness = 'ready';
|
|
105
105
|
this.notifyListeners('readinessChange', { readiness: this.readiness });
|
|
106
106
|
}
|
|
107
107
|
catch (error) {
|
|
108
|
+
this.closeCurrentModel();
|
|
108
109
|
this.readiness = 'error';
|
|
109
110
|
this.notifyListeners('readinessChange', { readiness: this.readiness });
|
|
110
111
|
throw error;
|
|
@@ -168,5 +169,33 @@ export class CapgoLLMWeb extends WebPlugin {
|
|
|
168
169
|
async getPluginVersion() {
|
|
169
170
|
return { version: 'web' };
|
|
170
171
|
}
|
|
172
|
+
closeCurrentModel() {
|
|
173
|
+
var _a;
|
|
174
|
+
this.chatSessions.clear();
|
|
175
|
+
(_a = this.llm) === null || _a === void 0 ? void 0 : _a.close();
|
|
176
|
+
this.llm = null;
|
|
177
|
+
this.modelPath = '';
|
|
178
|
+
this.modelType = 'task';
|
|
179
|
+
}
|
|
180
|
+
resolveModelType(options) {
|
|
181
|
+
var _a;
|
|
182
|
+
if ((_a = options.modelType) === null || _a === void 0 ? void 0 : _a.trim()) {
|
|
183
|
+
return options.modelType.trim().toLowerCase();
|
|
184
|
+
}
|
|
185
|
+
const extension = options.path.split('.').pop();
|
|
186
|
+
return extension ? extension.toLowerCase() : 'task';
|
|
187
|
+
}
|
|
188
|
+
buildPrompt(session, message) {
|
|
189
|
+
if (!this.usesGemmaChatTemplate(session)) {
|
|
190
|
+
return message;
|
|
191
|
+
}
|
|
192
|
+
const history = [...session.history, { role: 'user', content: message }];
|
|
193
|
+
return `${history
|
|
194
|
+
.map((turn) => `<start_of_turn>${turn.role}\n${turn.content}<end_of_turn>`)
|
|
195
|
+
.join('\n')}\n<start_of_turn>model\n`;
|
|
196
|
+
}
|
|
197
|
+
usesGemmaChatTemplate(session) {
|
|
198
|
+
return /gemma/i.test(session.modelPath) || session.modelType === 'litertlm';
|
|
199
|
+
}
|
|
171
200
|
}
|
|
172
201
|
//# sourceMappingURL=web.js.map
|