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.
- package/cpp/HybridLiteRTLM.cpp +45 -8
- package/lib/hooks.js +4 -0
- package/lib/index.js +1 -1
- package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +2 -2
- package/nitrogen/generated/android/c++/JBackend.hpp +1 -1
- package/nitrogen/generated/android/c++/JFunc_void_double.hpp +2 -2
- package/nitrogen/generated/android/c++/JFunc_void_std__string_bool.hpp +2 -2
- package/nitrogen/generated/android/c++/JGenerationStats.hpp +1 -1
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +2 -2
- package/nitrogen/generated/android/c++/JLLMConfig.hpp +1 -1
- package/nitrogen/generated/android/c++/JMemoryUsage.hpp +1 -1
- package/nitrogen/generated/android/c++/JMessage.hpp +1 -1
- package/nitrogen/generated/android/c++/JRole.hpp +1 -1
- package/nitrogen/generated/ios/LiteRTLM+autolinking.rb +2 -0
- package/nitrogen/generated/ios/LiteRTLMAutolinking.mm +35 -0
- package/nitrogen/generated/ios/LiteRTLMAutolinking.swift +16 -0
- package/package.json +3 -2
- package/src/hooks.ts +5 -0
- package/src/index.ts +1 -1
package/cpp/HybridLiteRTLM.cpp
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
507
|
+
// Filter out Gemma control tokens from streamed chunks
|
|
508
|
+
std::string cleaned = stripControlTokens(token);
|
|
509
|
+
ctx->fullResponse += cleaned;
|
|
480
510
|
ctx->tokenCount++;
|
|
481
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
}
|