react-native-litert-lm 0.3.2 → 0.3.4

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.
@@ -109,6 +109,34 @@ std::string HybridLiteRTLM::buildAudioMessageJson(const std::string& text, const
109
109
  "]}";
110
110
  }
111
111
 
112
+ /**
113
+ * Strip Gemma / LiteRT-LM control tokens from model output.
114
+ * The iOS C API returns raw model text including stop/turn markers
115
+ * that the Android Kotlin SDK strips automatically.
116
+ */
117
+ static std::string stripControlTokens(const std::string& text) {
118
+ static const char* tokens[] = {
119
+ "<end_of_turn>",
120
+ "<start_of_turn>model",
121
+ "<start_of_turn>user",
122
+ "<start_of_turn>",
123
+ "<eos>",
124
+ };
125
+ std::string result = text;
126
+ for (auto* tok : tokens) {
127
+ std::string t(tok);
128
+ size_t pos;
129
+ while ((pos = result.find(t)) != std::string::npos) {
130
+ result.erase(pos, t.length());
131
+ }
132
+ }
133
+ // Trim leading/trailing whitespace
134
+ size_t start = result.find_first_not_of(" \t\n\r");
135
+ if (start == std::string::npos) return "";
136
+ size_t end = result.find_last_not_of(" \t\n\r");
137
+ return result.substr(start, end - start + 1);
138
+ }
139
+
112
140
  std::string HybridLiteRTLM::extractTextFromResponse(const std::string& jsonResponse) {
113
141
  // The C API response JSON is structured as:
114
142
  // {"role":"model","content":[{"type":"text","text":"..."}]}
@@ -141,7 +169,7 @@ std::string HybridLiteRTLM::extractTextFromResponse(const std::string& jsonRespo
141
169
  result += jsonResponse[i];
142
170
  }
143
171
  }
144
- return result;
172
+ return stripControlTokens(result);
145
173
  }
146
174
  }
147
175
 
@@ -164,11 +192,11 @@ std::string HybridLiteRTLM::extractTextFromResponse(const std::string& jsonRespo
164
192
  result += jsonResponse[i];
165
193
  }
166
194
  }
167
- return result;
195
+ return stripControlTokens(result);
168
196
  }
169
197
 
170
- // Fallback: return full response
171
- return jsonResponse;
198
+ // Fallback: return full response (still strip control tokens)
199
+ return stripControlTokens(jsonResponse);
172
200
  }
173
201
 
174
202
  // =============================================================================
@@ -284,7 +312,7 @@ void HybridLiteRTLM::loadModelInternal(
284
312
  modelPath.c_str(),
285
313
  backend,
286
314
  visionBackend,
287
- nullptr // audio executor not supported on iOS yet
315
+ "cpu" // audio executor: iOS XCFramework lacks compiled audio ops (INTERNAL ERROR at Invoke)
288
316
  );
289
317
  if (!settings) {
290
318
  return false;
@@ -476,9 +504,13 @@ void HybridLiteRTLM::streamCallbackFn(void* callback_data, const char* chunk,
476
504
 
477
505
  if (chunk) {
478
506
  std::string token(chunk);
479
- ctx->fullResponse += token;
507
+ // Filter out Gemma control tokens from streamed chunks
508
+ std::string cleaned = stripControlTokens(token);
509
+ ctx->fullResponse += cleaned;
480
510
  ctx->tokenCount++;
481
- ctx->onToken(token, false);
511
+ if (!cleaned.empty()) {
512
+ ctx->onToken(cleaned, false);
513
+ }
482
514
  }
483
515
  }
484
516
 
@@ -635,7 +667,12 @@ std::string HybridLiteRTLM::sendMessageWithAudioInternal(
635
667
  conversation_, msgJson.c_str(), nullptr);
636
668
 
637
669
  if (!response) {
638
- throw std::runtime_error("LiteRT-LM: sendMessageWithAudio failed");
670
+ std::string errMsg = "LiteRT-LM: sendMessageWithAudio failed";
671
+ const char* nativeErr = litert_lm_get_last_error();
672
+ if (nativeErr && nativeErr[0] != '\0') {
673
+ errMsg += ": " + std::string(nativeErr);
674
+ }
675
+ throw std::runtime_error(errMsg);
639
676
  }
640
677
 
641
678
  const char* responseStr = litert_lm_json_response_get_string(response);
package/lib/hooks.js CHANGED
@@ -51,6 +51,10 @@ function useModel(pathOrUrl, config) {
51
51
  enableMemoryTracking,
52
52
  maxMemorySnapshots,
53
53
  });
54
+ // Reset ready state — the new instance has no model loaded yet.
55
+ // This prevents stale isReady=true after Fast Refresh (which
56
+ // preserves useState but re-runs useEffect).
57
+ setIsReady(false);
54
58
  // Cleanup on unmount
55
59
  return () => {
56
60
  try {
package/lib/index.js CHANGED
@@ -145,7 +145,7 @@ function checkBackendSupport(backend) {
145
145
  */
146
146
  function checkMultimodalSupport() {
147
147
  if (react_native_1.Platform.OS === "ios") {
148
- return "Multimodal (image/audio) is experimental on iOS. Vision and audio executors may not be available in the current build.";
148
+ return "Multimodal (image/audio) is not available on iOS. The XCFramework lacks compiled vision and audio executor ops.";
149
149
  }
150
150
  return undefined;
151
151
  }
@@ -29,9 +29,9 @@ int initialize(JavaVM* vm) {
29
29
  }
30
30
 
31
31
  struct JHybridLiteRTLMSpecImpl: public jni::JavaClass<JHybridLiteRTLMSpecImpl, JHybridLiteRTLMSpec::JavaPart> {
32
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM;";
32
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM;";
33
33
  static std::shared_ptr<JHybridLiteRTLMSpec> create() {
34
- static auto constructorFn = javaClassStatic()->getConstructor<JHybridLiteRTLMSpecImpl::javaobject()>();
34
+ static const auto constructorFn = javaClassStatic()->getConstructor<JHybridLiteRTLMSpecImpl::javaobject()>();
35
35
  jni::local_ref<JHybridLiteRTLMSpec::JavaPart> javaPart = javaClassStatic()->newObject(constructorFn);
36
36
  return javaPart->getJHybridLiteRTLMSpec();
37
37
  }
@@ -19,7 +19,7 @@ namespace margelo::nitro::litertlm {
19
19
  */
20
20
  struct JBackend final: public jni::JavaClass<JBackend> {
21
21
  public:
22
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Backend;";
22
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Backend;";
23
23
 
24
24
  public:
25
25
  /**
@@ -23,7 +23,7 @@ namespace margelo::nitro::litertlm {
23
23
  */
24
24
  struct JFunc_void_double: public jni::JavaClass<JFunc_void_double> {
25
25
  public:
26
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_double;";
26
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_double;";
27
27
 
28
28
  public:
29
29
  /**
@@ -59,7 +59,7 @@ namespace margelo::nitro::litertlm {
59
59
  }
60
60
 
61
61
  public:
62
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_double_cxx;";
62
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_double_cxx;";
63
63
  static void registerNatives() {
64
64
  registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_double_cxx::invoke_cxx)});
65
65
  }
@@ -24,7 +24,7 @@ namespace margelo::nitro::litertlm {
24
24
  */
25
25
  struct JFunc_void_std__string_bool: public jni::JavaClass<JFunc_void_std__string_bool> {
26
26
  public:
27
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_std__string_bool;";
27
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_std__string_bool;";
28
28
 
29
29
  public:
30
30
  /**
@@ -60,7 +60,7 @@ namespace margelo::nitro::litertlm {
60
60
  }
61
61
 
62
62
  public:
63
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_std__string_bool_cxx;";
63
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Func_void_std__string_bool_cxx;";
64
64
  static void registerNatives() {
65
65
  registerHybrid({makeNativeMethod("invoke_cxx", JFunc_void_std__string_bool_cxx::invoke_cxx)});
66
66
  }
@@ -21,7 +21,7 @@ namespace margelo::nitro::litertlm {
21
21
  */
22
22
  struct JGenerationStats final: public jni::JavaClass<JGenerationStats> {
23
23
  public:
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/GenerationStats;";
24
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/GenerationStats;";
25
25
 
26
26
  public:
27
27
  /**
@@ -21,11 +21,11 @@ namespace margelo::nitro::litertlm {
21
21
  class JHybridLiteRTLMSpec: public virtual HybridLiteRTLMSpec, public virtual JHybridObject {
22
22
  public:
23
23
  struct JavaPart: public jni::JavaClass<JavaPart, JHybridObject::JavaPart> {
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec;";
24
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec;";
25
25
  std::shared_ptr<JHybridLiteRTLMSpec> getJHybridLiteRTLMSpec();
26
26
  };
27
27
  struct CxxPart: public jni::HybridClass<CxxPart, JHybridObject::CxxPart> {
28
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec$CxxPart;";
28
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec$CxxPart;";
29
29
  static jni::local_ref<jhybriddata> initHybrid(jni::alias_ref<jhybridobject> jThis);
30
30
  static void registerNatives();
31
31
  using HybridBase::HybridBase;
@@ -24,7 +24,7 @@ namespace margelo::nitro::litertlm {
24
24
  */
25
25
  struct JLLMConfig final: public jni::JavaClass<JLLMConfig> {
26
26
  public:
27
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/LLMConfig;";
27
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/LLMConfig;";
28
28
 
29
29
  public:
30
30
  /**
@@ -21,7 +21,7 @@ namespace margelo::nitro::litertlm {
21
21
  */
22
22
  struct JMemoryUsage final: public jni::JavaClass<JMemoryUsage> {
23
23
  public:
24
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/MemoryUsage;";
24
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/MemoryUsage;";
25
25
 
26
26
  public:
27
27
  /**
@@ -23,7 +23,7 @@ namespace margelo::nitro::litertlm {
23
23
  */
24
24
  struct JMessage final: public jni::JavaClass<JMessage> {
25
25
  public:
26
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Message;";
26
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Message;";
27
27
 
28
28
  public:
29
29
  /**
@@ -19,7 +19,7 @@ namespace margelo::nitro::litertlm {
19
19
  */
20
20
  struct JRole final: public jni::JavaClass<JRole> {
21
21
  public:
22
- static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Role;";
22
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/dev/litert/litertlm/Role;";
23
23
 
24
24
  public:
25
25
  /**
@@ -56,5 +56,7 @@ def add_nitrogen_files(spec)
56
56
  "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
57
57
  # Enables stricter modular headers
58
58
  "DEFINES_MODULE" => "YES",
59
+ # Disable auto-generated ObjC header for Swift (Static linkage on Xcode 26.4 breaks here)
60
+ "SWIFT_INSTALL_OBJC_HEADER" => "NO",
59
61
  })
60
62
  end
@@ -0,0 +1,35 @@
1
+ ///
2
+ /// LiteRTLMAutolinking.mm
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
+ #import <Foundation/Foundation.h>
9
+ #import <NitroModules/HybridObjectRegistry.hpp>
10
+
11
+ #import <type_traits>
12
+
13
+ #include "HybridLiteRTLM.hpp"
14
+
15
+ @interface LiteRTLMAutolinking : NSObject
16
+ @end
17
+
18
+ @implementation LiteRTLMAutolinking
19
+
20
+ + (void) load {
21
+ using namespace margelo::nitro;
22
+ using namespace margelo::nitro::litertlm;
23
+
24
+ HybridObjectRegistry::registerHybridObjectConstructor(
25
+ "LiteRTLM",
26
+ []() -> std::shared_ptr<HybridObject> {
27
+ static_assert(std::is_default_constructible_v<HybridLiteRTLM>,
28
+ "The HybridObject \"HybridLiteRTLM\" is not default-constructible! "
29
+ "Create a public constructor that takes zero arguments to be able to autolink this HybridObject.");
30
+ return std::make_shared<HybridLiteRTLM>();
31
+ }
32
+ );
33
+ }
34
+
35
+ @end
@@ -0,0 +1,16 @@
1
+ ///
2
+ /// LiteRTLMAutolinking.swift
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
+ import NitroModules
9
+
10
+ // TODO: Use empty enums once Swift supports exporting them as namespaces
11
+ // See: https://github.com/swiftlang/swift/pull/83616
12
+ public final class LiteRTLMAutolinking {
13
+ public typealias bridge = margelo.nitro.litertlm.bridge.swift
14
+
15
+
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-litert-lm",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "litertLm": {
5
5
  "version": "0.10.1",
6
6
  "androidMavenVersion": "0.10.0",
@@ -77,6 +77,7 @@
77
77
  "devDependencies": {
78
78
  "@expo/config-plugins": "~55.0.0",
79
79
  "@types/react": "~19.2.10",
80
+ "react-native-nitro-modules": "^0.35.4",
80
81
  "release-it": "^19.2.4",
81
82
  "typescript": "^5.0.0"
82
83
  },
@@ -84,7 +85,7 @@
84
85
  "expo": ">=55.0.0",
85
86
  "react": "*",
86
87
  "react-native": "*",
87
- "react-native-nitro-modules": "^0.35.0"
88
+ "react-native-nitro-modules": "^0.35.4"
88
89
  },
89
90
  "peerDependenciesMeta": {
90
91
  "expo": {
package/src/hooks.ts CHANGED
@@ -108,6 +108,11 @@ export function useModel(
108
108
  maxMemorySnapshots,
109
109
  });
110
110
 
111
+ // Reset ready state — the new instance has no model loaded yet.
112
+ // This prevents stale isReady=true after Fast Refresh (which
113
+ // preserves useState but re-runs useEffect).
114
+ setIsReady(false);
115
+
111
116
  // Cleanup on unmount
112
117
  return () => {
113
118
  try {
package/src/index.ts CHANGED
@@ -163,7 +163,7 @@ export function checkBackendSupport(backend: Backend): string | undefined {
163
163
  */
164
164
  export function checkMultimodalSupport(): string | undefined {
165
165
  if (Platform.OS === "ios") {
166
- return "Multimodal (image/audio) is experimental on iOS. Vision and audio executors may not be available in the current build.";
166
+ return "Multimodal (image/audio) is not available on iOS. The XCFramework lacks compiled vision and audio executor ops.";
167
167
  }
168
168
  return undefined;
169
169
  }