react-native-litert-lm 0.2.0 → 0.2.1

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 (30) hide show
  1. package/README.md +147 -28
  2. package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +254 -58
  3. package/cpp/HybridLiteRTLM.cpp +30 -9
  4. package/cpp/HybridLiteRTLM.hpp +4 -0
  5. package/lib/hooks.d.ts +16 -0
  6. package/lib/hooks.js +114 -0
  7. package/lib/index.d.ts +27 -2
  8. package/lib/index.js +50 -6
  9. package/lib/modelFactory.d.ts +5 -0
  10. package/lib/modelFactory.js +42 -0
  11. package/lib/specs/LiteRTLM.nitro.d.ts +19 -0
  12. package/lib/templates.d.ts +51 -0
  13. package/lib/templates.js +81 -0
  14. package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +2 -0
  15. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
  16. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +33 -1
  17. package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +2 -0
  18. package/nitrogen/generated/android/c++/JLLMConfig.hpp +6 -1
  19. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Func_void_double.kt +80 -0
  20. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +13 -0
  21. package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LLMConfig.kt +5 -2
  22. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +2 -0
  23. package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +2 -0
  24. package/nitrogen/generated/shared/c++/LLMConfig.hpp +7 -2
  25. package/package.json +1 -1
  26. package/src/hooks.ts +152 -0
  27. package/src/index.ts +41 -3
  28. package/src/modelFactory.ts +49 -0
  29. package/src/specs/LiteRTLM.nitro.ts +26 -0
  30. package/src/templates.ts +105 -0
@@ -0,0 +1,80 @@
1
+ ///
2
+ /// Func_void_double.kt
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright © Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ package com.margelo.nitro.dev.litert.litertlm
9
+
10
+ import androidx.annotation.Keep
11
+ import com.facebook.jni.HybridData
12
+ import com.facebook.proguard.annotations.DoNotStrip
13
+ import dalvik.annotation.optimization.FastNative
14
+
15
+
16
+ /**
17
+ * Represents the JavaScript callback `(progress: number) => void`.
18
+ * This can be either implemented in C++ (in which case it might be a callback coming from JS),
19
+ * or in Kotlin/Java (in which case it is a native callback).
20
+ */
21
+ @DoNotStrip
22
+ @Keep
23
+ @Suppress("ClassName", "RedundantUnitReturnType")
24
+ fun interface Func_void_double: (Double) -> Unit {
25
+ /**
26
+ * Call the given JS callback.
27
+ * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted.
28
+ */
29
+ @DoNotStrip
30
+ @Keep
31
+ override fun invoke(progress: Double): Unit
32
+ }
33
+
34
+ /**
35
+ * Represents the JavaScript callback `(progress: number) => void`.
36
+ * This is implemented in C++, via a `std::function<...>`.
37
+ * The callback might be coming from JS.
38
+ */
39
+ @DoNotStrip
40
+ @Keep
41
+ @Suppress(
42
+ "KotlinJniMissingFunction", "unused",
43
+ "RedundantSuppression", "RedundantUnitReturnType", "FunctionName",
44
+ "ConvertSecondaryConstructorToPrimary", "ClassName", "LocalVariableName",
45
+ )
46
+ class Func_void_double_cxx: Func_void_double {
47
+ @DoNotStrip
48
+ @Keep
49
+ private val mHybridData: HybridData
50
+
51
+ @DoNotStrip
52
+ @Keep
53
+ private constructor(hybridData: HybridData) {
54
+ mHybridData = hybridData
55
+ }
56
+
57
+ @DoNotStrip
58
+ @Keep
59
+ override fun invoke(progress: Double): Unit
60
+ = invoke_cxx(progress)
61
+
62
+ @FastNative
63
+ private external fun invoke_cxx(progress: Double): Unit
64
+ }
65
+
66
+ /**
67
+ * Represents the JavaScript callback `(progress: number) => void`.
68
+ * This is implemented in Java/Kotlin, via a `(Double) -> Unit`.
69
+ * The callback is always coming from native.
70
+ */
71
+ @DoNotStrip
72
+ @Keep
73
+ @Suppress("ClassName", "RedundantUnitReturnType", "unused")
74
+ class Func_void_double_java(private val function: (Double) -> Unit): Func_void_double {
75
+ @DoNotStrip
76
+ @Keep
77
+ override fun invoke(progress: Double): Unit {
78
+ return this.function(progress)
79
+ }
80
+ }
@@ -58,6 +58,19 @@ abstract class HybridLiteRTLMSpec: HybridObject() {
58
58
  @Keep
59
59
  abstract fun sendMessageWithImage(message: String, imagePath: String): Promise<String>
60
60
 
61
+ abstract fun downloadModel(url: String, fileName: String, onProgress: ((progress: Double) -> Unit)?): Promise<String>
62
+
63
+ @DoNotStrip
64
+ @Keep
65
+ private fun downloadModel_cxx(url: String, fileName: String, onProgress: Func_void_double?): Promise<String> {
66
+ val __result = downloadModel(url, fileName, onProgress?.let { it })
67
+ return __result
68
+ }
69
+
70
+ @DoNotStrip
71
+ @Keep
72
+ abstract fun deleteModel(fileName: String): Promise<Unit>
73
+
61
74
  @DoNotStrip
62
75
  @Keep
63
76
  abstract fun sendMessageWithAudio(message: String, audioPath: String): Promise<String>
@@ -17,6 +17,9 @@ import com.facebook.proguard.annotations.DoNotStrip
17
17
  @DoNotStrip
18
18
  @Keep
19
19
  data class LLMConfig(
20
+ @DoNotStrip
21
+ @Keep
22
+ val systemPrompt: String?,
20
23
  @DoNotStrip
21
24
  @Keep
22
25
  val backend: Backend?,
@@ -43,8 +46,8 @@ data class LLMConfig(
43
46
  @Keep
44
47
  @Suppress("unused")
45
48
  @JvmStatic
46
- private fun fromCpp(backend: Backend?, maxTokens: Double?, temperature: Double?, topK: Double?, topP: Double?): LLMConfig {
47
- return LLMConfig(backend, maxTokens, temperature, topK, topP)
49
+ private fun fromCpp(systemPrompt: String?, backend: Backend?, maxTokens: Double?, temperature: Double?, topK: Double?, topP: Double?): LLMConfig {
50
+ return LLMConfig(systemPrompt, backend, maxTokens, temperature, topK, topP)
48
51
  }
49
52
  }
50
53
  }
@@ -17,6 +17,8 @@ namespace margelo::nitro::litertlm {
17
17
  prototype.registerHybridMethod("loadModel", &HybridLiteRTLMSpec::loadModel);
18
18
  prototype.registerHybridMethod("sendMessage", &HybridLiteRTLMSpec::sendMessage);
19
19
  prototype.registerHybridMethod("sendMessageWithImage", &HybridLiteRTLMSpec::sendMessageWithImage);
20
+ prototype.registerHybridMethod("downloadModel", &HybridLiteRTLMSpec::downloadModel);
21
+ prototype.registerHybridMethod("deleteModel", &HybridLiteRTLMSpec::deleteModel);
20
22
  prototype.registerHybridMethod("sendMessageWithAudio", &HybridLiteRTLMSpec::sendMessageWithAudio);
21
23
  prototype.registerHybridMethod("sendMessageAsync", &HybridLiteRTLMSpec::sendMessageAsync);
22
24
  prototype.registerHybridMethod("getHistory", &HybridLiteRTLMSpec::getHistory);
@@ -63,6 +63,8 @@ namespace margelo::nitro::litertlm {
63
63
  virtual std::shared_ptr<Promise<void>> loadModel(const std::string& modelPath, const std::optional<LLMConfig>& config) = 0;
64
64
  virtual std::shared_ptr<Promise<std::string>> sendMessage(const std::string& message) = 0;
65
65
  virtual std::shared_ptr<Promise<std::string>> sendMessageWithImage(const std::string& message, const std::string& imagePath) = 0;
66
+ virtual std::shared_ptr<Promise<std::string>> downloadModel(const std::string& url, const std::string& fileName, const std::optional<std::function<void(double /* progress */)>>& onProgress) = 0;
67
+ virtual std::shared_ptr<Promise<void>> deleteModel(const std::string& fileName) = 0;
66
68
  virtual std::shared_ptr<Promise<std::string>> sendMessageWithAudio(const std::string& message, const std::string& audioPath) = 0;
67
69
  virtual void sendMessageAsync(const std::string& message, const std::function<void(const std::string& /* token */, bool /* done */)>& onToken) = 0;
68
70
  virtual std::vector<Message> getHistory() = 0;
@@ -31,8 +31,9 @@
31
31
  // Forward declaration of `Backend` to properly resolve imports.
32
32
  namespace margelo::nitro::litertlm { enum class Backend; }
33
33
 
34
- #include "Backend.hpp"
34
+ #include <string>
35
35
  #include <optional>
36
+ #include "Backend.hpp"
36
37
 
37
38
  namespace margelo::nitro::litertlm {
38
39
 
@@ -41,6 +42,7 @@ namespace margelo::nitro::litertlm {
41
42
  */
42
43
  struct LLMConfig final {
43
44
  public:
45
+ std::optional<std::string> systemPrompt SWIFT_PRIVATE;
44
46
  std::optional<Backend> backend SWIFT_PRIVATE;
45
47
  std::optional<double> maxTokens SWIFT_PRIVATE;
46
48
  std::optional<double> temperature SWIFT_PRIVATE;
@@ -49,7 +51,7 @@ namespace margelo::nitro::litertlm {
49
51
 
50
52
  public:
51
53
  LLMConfig() = default;
52
- explicit LLMConfig(std::optional<Backend> backend, std::optional<double> maxTokens, std::optional<double> temperature, std::optional<double> topK, std::optional<double> topP): backend(backend), maxTokens(maxTokens), temperature(temperature), topK(topK), topP(topP) {}
54
+ explicit LLMConfig(std::optional<std::string> systemPrompt, std::optional<Backend> backend, std::optional<double> maxTokens, std::optional<double> temperature, std::optional<double> topK, std::optional<double> topP): systemPrompt(systemPrompt), backend(backend), maxTokens(maxTokens), temperature(temperature), topK(topK), topP(topP) {}
53
55
 
54
56
  public:
55
57
  friend bool operator==(const LLMConfig& lhs, const LLMConfig& rhs) = default;
@@ -65,6 +67,7 @@ namespace margelo::nitro {
65
67
  static inline margelo::nitro::litertlm::LLMConfig fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
66
68
  jsi::Object obj = arg.asObject(runtime);
67
69
  return margelo::nitro::litertlm::LLMConfig(
70
+ JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "systemPrompt"))),
68
71
  JSIConverter<std::optional<margelo::nitro::litertlm::Backend>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "backend"))),
69
72
  JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "maxTokens"))),
70
73
  JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "temperature"))),
@@ -74,6 +77,7 @@ namespace margelo::nitro {
74
77
  }
75
78
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::litertlm::LLMConfig& arg) {
76
79
  jsi::Object obj(runtime);
80
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "systemPrompt"), JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.systemPrompt));
77
81
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "backend"), JSIConverter<std::optional<margelo::nitro::litertlm::Backend>>::toJSI(runtime, arg.backend));
78
82
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "maxTokens"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.maxTokens));
79
83
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "temperature"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.temperature));
@@ -89,6 +93,7 @@ namespace margelo::nitro {
89
93
  if (!nitro::isPlainObject(runtime, obj)) {
90
94
  return false;
91
95
  }
96
+ if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "systemPrompt")))) return false;
92
97
  if (!JSIConverter<std::optional<margelo::nitro::litertlm::Backend>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "backend")))) return false;
93
98
  if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "maxTokens")))) return false;
94
99
  if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "temperature")))) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-litert-lm",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "High-performance LLM inference for React Native using LiteRT-LM. Optimized for Gemma 3n and other on-device language models.",
5
5
  "license": "MIT",
6
6
  "author": "Hugh Chen (https://github.com/hung-yueh)",
package/src/hooks.ts ADDED
@@ -0,0 +1,152 @@
1
+ import { useState, useEffect, useRef, useCallback } from "react";
2
+ import { LiteRTLM, LLMConfig } from "./index";
3
+ import { createLLM } from "./modelFactory";
4
+
5
+ export interface UseModelConfig extends LLMConfig {
6
+ autoLoad?: boolean;
7
+ }
8
+
9
+ export interface UseModelResult {
10
+ model: LiteRTLM | null;
11
+ isReady: boolean;
12
+ isGenerating: boolean;
13
+ downloadProgress: number;
14
+ error: string | null;
15
+ generate: (prompt: string) => Promise<string>;
16
+ reset: () => void;
17
+ deleteModel: (fileName: string) => Promise<void>;
18
+ load: () => Promise<void>;
19
+ }
20
+
21
+ export function useModel(
22
+ pathOrUrl: string,
23
+ config?: UseModelConfig,
24
+ ): UseModelResult {
25
+ const modelRef = useRef<LiteRTLM | null>(null);
26
+ const [isReady, setIsReady] = useState(false);
27
+ const [isGenerating, setIsGenerating] = useState(false);
28
+ const [downloadProgress, setDownloadProgress] = useState(0);
29
+ const [error, setError] = useState<string | null>(null);
30
+
31
+ // Extract autoLoad (default true)
32
+ const autoLoad = config?.autoLoad ?? true;
33
+
34
+ // Initialize the model instance
35
+ useEffect(() => {
36
+ modelRef.current = createLLM();
37
+ let isMounted = true;
38
+
39
+ // Cleanup on unmount
40
+ return () => {
41
+ isMounted = false;
42
+ try {
43
+ modelRef.current?.close();
44
+ } catch (e) {
45
+ console.warn("Failed to close model", e);
46
+ }
47
+ };
48
+ }, []);
49
+
50
+ const load = useCallback(async () => {
51
+ setIsReady(false);
52
+ setError(null);
53
+ setDownloadProgress(0);
54
+
55
+ try {
56
+ let modelPath = pathOrUrl;
57
+
58
+ // Handle URL download manually to capture progress
59
+ if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
60
+ const fileName = pathOrUrl.split("/").pop() || "model.bin";
61
+
62
+ if (modelRef.current) {
63
+ modelPath = await modelRef.current.downloadModel(
64
+ pathOrUrl,
65
+ fileName,
66
+ (progress) => {
67
+ setDownloadProgress(progress);
68
+ },
69
+ );
70
+ }
71
+ }
72
+
73
+ if (modelRef.current) {
74
+ // Create a clean config object for native loadModel (excluding autoLoad)
75
+ const nativeConfig: LLMConfig = { ...config };
76
+ delete (nativeConfig as any).autoLoad;
77
+
78
+ await modelRef.current.loadModel(modelPath, nativeConfig);
79
+ setIsReady(true);
80
+ }
81
+ } catch (e: any) {
82
+ setError(e.message || "Failed to load model");
83
+ console.error(e);
84
+ }
85
+ }, [pathOrUrl, config]);
86
+
87
+ useEffect(() => {
88
+ if (autoLoad) {
89
+ load();
90
+ }
91
+ }, [autoLoad, load]);
92
+
93
+ const generate = useCallback(
94
+ async (prompt: string): Promise<string> => {
95
+ if (!modelRef.current || !isReady) {
96
+ throw new Error("Model not ready");
97
+ }
98
+
99
+ setIsGenerating(true);
100
+ try {
101
+ return new Promise<string>((resolve, reject) => {
102
+ let fullResponse = "";
103
+ try {
104
+ modelRef.current?.sendMessageAsync(
105
+ prompt,
106
+ (token: string, done: boolean) => {
107
+ fullResponse += token;
108
+ if (done) {
109
+ resolve(fullResponse);
110
+ }
111
+ },
112
+ );
113
+ } catch (e: any) {
114
+ reject(e);
115
+ }
116
+ });
117
+ } catch (e: any) {
118
+ setError(e.message || "Generation failed");
119
+ throw e;
120
+ } finally {
121
+ setIsGenerating(false);
122
+ }
123
+ },
124
+ [isReady],
125
+ );
126
+
127
+ const reset = useCallback(() => {
128
+ if (modelRef.current) {
129
+ modelRef.current.resetConversation();
130
+ }
131
+ }, []);
132
+
133
+ const deleteModel = useCallback(async (fileName: string): Promise<void> => {
134
+ if (modelRef.current) {
135
+ await modelRef.current.deleteModel(fileName);
136
+ setIsReady(false);
137
+ setDownloadProgress(0);
138
+ }
139
+ }, []);
140
+
141
+ return {
142
+ model: modelRef.current,
143
+ isReady,
144
+ isGenerating,
145
+ downloadProgress,
146
+ error,
147
+ generate,
148
+ reset,
149
+ deleteModel,
150
+ load,
151
+ };
152
+ }
package/src/index.ts CHANGED
@@ -18,6 +18,16 @@ export type {
18
18
  GenerationStats,
19
19
  } from "./specs/LiteRTLM.nitro";
20
20
 
21
+ // Re-export template utilities
22
+ export type { ChatMessage } from "./templates";
23
+ export {
24
+ applyGemmaTemplate,
25
+ applyPhiTemplate,
26
+ applyLlamaTemplate,
27
+ } from "./templates";
28
+
29
+ export * from "./hooks";
30
+
21
31
  /**
22
32
  * Creates a new LiteRT-LM inference engine instance.
23
33
  *
@@ -51,9 +61,7 @@ export type {
51
61
  * llm.close();
52
62
  * ```
53
63
  */
54
- export function createLLM(): LiteRTLM {
55
- return NitroModules.createHybridObject<LiteRTLM>("LiteRTLM");
56
- }
64
+ export { createLLM } from "./modelFactory";
57
65
 
58
66
  /**
59
67
  * Pre-defined model identifiers for common models.
@@ -123,3 +131,33 @@ export function checkBackendSupport(backend: Backend): string | undefined {
123
131
 
124
132
  return undefined;
125
133
  }
134
+
135
+ /**
136
+ * Check if multimodal features (image/audio) are supported on the current platform.
137
+ * Returns an error message if not supported, undefined if OK.
138
+ *
139
+ * @returns Error message if multimodal is not supported, undefined if OK
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const error = checkMultimodalSupport();
144
+ * if (error) {
145
+ * console.warn(error);
146
+ * // Fall back to text-only
147
+ * } else {
148
+ * llm.sendMessageWithImage('Describe this', imagePath);
149
+ * }
150
+ * ```
151
+ */
152
+ export function checkMultimodalSupport(): string | undefined {
153
+ if (Platform.OS === "ios") {
154
+ return "Multimodal (image/audio) is not yet supported on iOS. LiteRT-LM iOS SDK is pending.";
155
+ }
156
+ return undefined;
157
+ }
158
+
159
+ /**
160
+ * Download URL for the Gemma 3n E2B IT INT4 model.
161
+ */
162
+ export const GEMMA_3N_E2B_IT_INT4 =
163
+ "https://litert.dev/gemma-3n-E2B-it-int4.litertlm";
@@ -0,0 +1,49 @@
1
+ import { NitroModules } from "react-native-nitro-modules";
2
+ import { LiteRTLM, LLMConfig } from "./specs/LiteRTLM.nitro";
3
+
4
+ /**
5
+ * Creates a new LiteRT-LM inference engine instance.
6
+ */
7
+ export function createLLM(): LiteRTLM {
8
+ const native = NitroModules.createHybridObject<LiteRTLM>("LiteRTLM");
9
+
10
+ return {
11
+ ...native,
12
+ loadModel: async (pathOrUrl: string, config?: LLMConfig) => {
13
+ let modelPath = pathOrUrl;
14
+
15
+ // Check if it's a URL
16
+ if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
17
+ // Extract filename from URL
18
+ const fileName = pathOrUrl.split("/").pop();
19
+ if (!fileName) {
20
+ throw new Error(`Invalid model URL: ${pathOrUrl}`);
21
+ }
22
+
23
+ console.log(`Checking model at ${pathOrUrl}...`);
24
+ modelPath = await native.downloadModel(
25
+ pathOrUrl,
26
+ fileName,
27
+ (progress) => {
28
+ console.log(`Download progress: ${progress}`);
29
+ },
30
+ );
31
+ console.log(`Model downloaded to: ${modelPath}`);
32
+ }
33
+
34
+ return native.loadModel(modelPath, config);
35
+ },
36
+ // Bind valid methods to native instance
37
+ sendMessage: native.sendMessage.bind(native),
38
+ sendMessageAsync: native.sendMessageAsync.bind(native),
39
+ sendMessageWithImage: native.sendMessageWithImage.bind(native),
40
+ sendMessageWithAudio: native.sendMessageWithAudio.bind(native),
41
+ getHistory: native.getHistory.bind(native),
42
+ resetConversation: native.resetConversation.bind(native),
43
+ isReady: native.isReady.bind(native),
44
+ getStats: native.getStats.bind(native),
45
+ close: native.close.bind(native),
46
+ downloadModel: native.downloadModel.bind(native),
47
+ deleteModel: native.deleteModel.bind(native),
48
+ };
49
+ }
@@ -21,6 +21,13 @@ export type Role = "user" | "model" | "system";
21
21
  * Configuration options for loading an LLM.
22
22
  */
23
23
  export interface LLMConfig {
24
+ /**
25
+ * System prompt to set the model's behavior.
26
+ * This is prepended to the conversation to guide model responses.
27
+ * @example "You are a helpful coding assistant."
28
+ */
29
+ systemPrompt?: string;
30
+
24
31
  /**
25
32
  * Primary compute backend for text generation.
26
33
  * - 'cpu': CPU inference (slower but always available)
@@ -138,6 +145,25 @@ export interface LiteRTLM extends HybridObject<{
138
145
  */
139
146
  sendMessageWithImage(message: string, imagePath: string): Promise<string>;
140
147
 
148
+ /**
149
+ * Download a model file from a URL.
150
+ * @param url URL to download from.
151
+ * @param fileName Filename to save as (in app's files directory).
152
+ * @param onProgress Callback for download progress (0.0 - 1.0).
153
+ * @returns Absolute path to the downloaded file.
154
+ */
155
+ downloadModel(
156
+ url: string,
157
+ fileName: string,
158
+ onProgress?: (progress: number) => void,
159
+ ): Promise<string>;
160
+
161
+ /**
162
+ * Delete a downloaded model file.
163
+ * @param fileName Filename to delete (in app's files directory).
164
+ */
165
+ deleteModel(fileName: string): Promise<void>;
166
+
141
167
  /**
142
168
  * Send a text message with audio (multimodal).
143
169
  * @param message User message text.
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Prompt template utilities for different LLM families.
3
+ *
4
+ * LiteRT-LM's Conversation API may handle templates internally for some models,
5
+ * but these utilities give developers explicit control for custom workflows
6
+ * or when using models with different template formats.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { applyGemmaTemplate, ChatMessage } from 'react-native-litert-lm';
11
+ *
12
+ * const history: ChatMessage[] = [
13
+ * { role: 'user', content: 'What is React Native?' },
14
+ * { role: 'model', content: 'React Native is a framework for building...' },
15
+ * { role: 'user', content: 'How do I use hooks?' }
16
+ * ];
17
+ *
18
+ * const prompt = applyGemmaTemplate(history, 'You are a helpful coding assistant.');
19
+ * ```
20
+ */
21
+
22
+ /**
23
+ * A message in a conversation.
24
+ */
25
+ export type ChatMessage = {
26
+ role: "user" | "model" | "system";
27
+ content: string;
28
+ };
29
+
30
+ /**
31
+ * Apply Gemma chat template (Gemma 2, Gemma 3, Gemma 3n).
32
+ *
33
+ * @param history Array of previous messages
34
+ * @param systemPrompt Optional system prompt
35
+ * @returns Formatted prompt string
36
+ */
37
+ export function applyGemmaTemplate(
38
+ history: ChatMessage[],
39
+ systemPrompt?: string,
40
+ ): string {
41
+ let result = "";
42
+
43
+ if (systemPrompt) {
44
+ result += `<start_of_turn>system\n${systemPrompt}<end_of_turn>\n`;
45
+ }
46
+
47
+ for (const m of history) {
48
+ result += `<start_of_turn>${m.role}\n${m.content}<end_of_turn>\n`;
49
+ }
50
+
51
+ result += "<start_of_turn>model\n";
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Apply Phi chat template (Phi-3, Phi-4).
57
+ *
58
+ * @param history Array of previous messages
59
+ * @param systemPrompt Optional system prompt
60
+ * @returns Formatted prompt string
61
+ */
62
+ export function applyPhiTemplate(
63
+ history: ChatMessage[],
64
+ systemPrompt?: string,
65
+ ): string {
66
+ let result = "";
67
+
68
+ if (systemPrompt) {
69
+ result += `<|system|>\n${systemPrompt}<|end|>\n`;
70
+ }
71
+
72
+ for (const m of history) {
73
+ const role = m.role === "model" ? "assistant" : m.role;
74
+ result += `<|${role}|>\n${m.content}<|end|>\n`;
75
+ }
76
+
77
+ result += "<|assistant|>\n";
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Apply Llama 3 chat template.
83
+ *
84
+ * @param history Array of previous messages
85
+ * @param systemPrompt Optional system prompt
86
+ * @returns Formatted prompt string
87
+ */
88
+ export function applyLlamaTemplate(
89
+ history: ChatMessage[],
90
+ systemPrompt?: string,
91
+ ): string {
92
+ let result = "<|begin_of_text|>";
93
+
94
+ if (systemPrompt) {
95
+ result += `<|start_header_id|>system<|end_header_id|>\n\n${systemPrompt}<|eot_id|>`;
96
+ }
97
+
98
+ for (const m of history) {
99
+ const role = m.role === "model" ? "assistant" : m.role;
100
+ result += `<|start_header_id|>${role}<|end_header_id|>\n\n${m.content}<|eot_id|>`;
101
+ }
102
+
103
+ result += "<|start_header_id|>assistant<|end_header_id|>\n\n";
104
+ return result;
105
+ }