@dr33m/react-native-litert-lm 0.5.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/LICENSE +21 -0
- package/README.md +627 -0
- package/android/CMakeLists.txt +32 -0
- package/android/build.gradle +104 -0
- package/android/src/main/AndroidManifest.xml +19 -0
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLM.kt +888 -0
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/HybridModelStore.kt +189 -0
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/LiteRTLMRegistry.kt +32 -0
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/MultimodalPartFactories.kt +15 -0
- package/android/src/main/java/com/margelo/nitro/dev/litert/litertlm/StreamingCallbackListener.kt +89 -0
- package/android/src/main/java/dev/litert/litertlm/LiteRTLMInitProvider.kt +57 -0
- package/android/src/main/java/dev/litert/litertlm/LiteRTLMPackage.kt +43 -0
- package/android/src/test/java/com/margelo/nitro/core/Promise.kt +46 -0
- package/android/src/test/java/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMTest.kt +122 -0
- package/app.plugin.js +58 -0
- package/cpp/cpp-adapter.cpp +21 -0
- package/cpp/include/README.md +32 -0
- package/cpp/include/litert_lm_engine.h +516 -0
- package/cpp/include/stb_image.h +7988 -0
- package/ios/HybridLiteRTLM+Execute.swift +287 -0
- package/ios/HybridLiteRTLM+Streaming.swift +153 -0
- package/ios/HybridLiteRTLM.swift +541 -0
- package/ios/HybridModelStore.swift +206 -0
- package/ios/MultimodalPart+Factories.swift +22 -0
- package/ios/Tests/HybridLiteRTLMTests.swift +187 -0
- package/lib/__mocks__/react-native-nitro-modules.d.ts +81 -0
- package/lib/__mocks__/react-native-nitro-modules.js +98 -0
- package/lib/__tests__/hooks.test.d.ts +1 -0
- package/lib/__tests__/hooks.test.js +175 -0
- package/lib/__tests__/inferenceRouting.test.d.ts +1 -0
- package/lib/__tests__/inferenceRouting.test.js +28 -0
- package/lib/__tests__/memoryTracker.test.d.ts +1 -0
- package/lib/__tests__/memoryTracker.test.js +74 -0
- package/lib/__tests__/modelFactory.test.d.ts +1 -0
- package/lib/__tests__/modelFactory.test.js +107 -0
- package/lib/__tests__/modelPath.test.d.ts +1 -0
- package/lib/__tests__/modelPath.test.js +12 -0
- package/lib/__tests__/modelRegistry.test.d.ts +1 -0
- package/lib/__tests__/modelRegistry.test.js +61 -0
- package/lib/hooks.d.ts +46 -0
- package/lib/hooks.js +159 -0
- package/lib/index.d.ts +142 -0
- package/lib/index.js +184 -0
- package/lib/inferenceRouting.d.ts +18 -0
- package/lib/inferenceRouting.js +63 -0
- package/lib/memoryTracker.d.ts +128 -0
- package/lib/memoryTracker.js +155 -0
- package/lib/modelFactory.d.ts +24 -0
- package/lib/modelFactory.js +128 -0
- package/lib/modelPath.d.ts +8 -0
- package/lib/modelPath.js +18 -0
- package/lib/modelRegistry.d.ts +55 -0
- package/lib/modelRegistry.js +82 -0
- package/lib/specs/LiteRTLM.nitro.d.ts +380 -0
- package/lib/specs/LiteRTLM.nitro.js +2 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/LiteRTLM+autolinking.cmake +83 -0
- package/nitrogen/generated/android/LiteRTLM+autolinking.gradle +27 -0
- package/nitrogen/generated/android/LiteRTLMOnLoad.cpp +74 -0
- package/nitrogen/generated/android/LiteRTLMOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JBackend.hpp +61 -0
- package/nitrogen/generated/android/c++/JExecuteResult.hpp +86 -0
- package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string_bool.hpp +76 -0
- package/nitrogen/generated/android/c++/JGenerationStats.hpp +77 -0
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.cpp +371 -0
- package/nitrogen/generated/android/c++/JHybridLiteRTLMSpec.hpp +83 -0
- package/nitrogen/generated/android/c++/JHybridModelStoreSpec.cpp +101 -0
- package/nitrogen/generated/android/c++/JHybridModelStoreSpec.hpp +67 -0
- package/nitrogen/generated/android/c++/JLLMConfig.hpp +125 -0
- package/nitrogen/generated/android/c++/JMemoryUsage.hpp +69 -0
- package/nitrogen/generated/android/c++/JMessage.hpp +63 -0
- package/nitrogen/generated/android/c++/JModelFile.hpp +69 -0
- package/nitrogen/generated/android/c++/JMultimodalPart.hpp +78 -0
- package/nitrogen/generated/android/c++/JPartType.hpp +61 -0
- package/nitrogen/generated/android/c++/JRole.hpp +61 -0
- package/nitrogen/generated/android/c++/JToolCall.hpp +61 -0
- package/nitrogen/generated/android/c++/JToolDefinition.hpp +65 -0
- package/nitrogen/generated/android/c++/JToolResponse.hpp +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Backend.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ExecuteResult.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Func_void_double.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Func_void_std__string_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/GenerationStats.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridLiteRTLMSpec.kt +165 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/HybridModelStoreSpec.kt +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LLMConfig.kt +106 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/LiteRTLMOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MemoryUsage.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Message.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ModelFile.kt +66 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/MultimodalPart.kt +71 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/PartType.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/Role.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolCall.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolDefinition.kt +61 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dev/litert/litertlm/ToolResponse.kt +56 -0
- package/nitrogen/generated/ios/LiteRTLM+autolinking.rb +62 -0
- package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.cpp +98 -0
- package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Bridge.hpp +585 -0
- package/nitrogen/generated/ios/LiteRTLM-Swift-Cxx-Umbrella.hpp +93 -0
- package/nitrogen/generated/ios/LiteRTLMAutolinking.mm +41 -0
- package/nitrogen/generated/ios/LiteRTLMAutolinking.swift +38 -0
- package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLiteRTLMSpecSwift.hpp +279 -0
- package/nitrogen/generated/ios/c++/HybridModelStoreSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridModelStoreSpecSwift.hpp +117 -0
- package/nitrogen/generated/ios/swift/Backend.swift +44 -0
- package/nitrogen/generated/ios/swift/ExecuteResult.swift +45 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_ExecuteResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/GenerationStats.swift +54 -0
- package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec.swift +75 -0
- package/nitrogen/generated/ios/swift/HybridLiteRTLMSpec_cxx.swift +516 -0
- package/nitrogen/generated/ios/swift/HybridModelStoreSpec.swift +59 -0
- package/nitrogen/generated/ios/swift/HybridModelStoreSpec_cxx.swift +203 -0
- package/nitrogen/generated/ios/swift/LLMConfig.swift +239 -0
- package/nitrogen/generated/ios/swift/MemoryUsage.swift +44 -0
- package/nitrogen/generated/ios/swift/Message.swift +34 -0
- package/nitrogen/generated/ios/swift/ModelFile.swift +44 -0
- package/nitrogen/generated/ios/swift/MultimodalPart.swift +101 -0
- package/nitrogen/generated/ios/swift/PartType.swift +44 -0
- package/nitrogen/generated/ios/swift/Role.swift +44 -0
- package/nitrogen/generated/ios/swift/ToolCall.swift +34 -0
- package/nitrogen/generated/ios/swift/ToolDefinition.swift +39 -0
- package/nitrogen/generated/ios/swift/ToolResponse.swift +34 -0
- package/nitrogen/generated/shared/c++/Backend.hpp +80 -0
- package/nitrogen/generated/shared/c++/ExecuteResult.hpp +94 -0
- package/nitrogen/generated/shared/c++/GenerationStats.hpp +103 -0
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.cpp +41 -0
- package/nitrogen/generated/shared/c++/HybridLiteRTLMSpec.hpp +109 -0
- package/nitrogen/generated/shared/c++/HybridModelStoreSpec.cpp +25 -0
- package/nitrogen/generated/shared/c++/HybridModelStoreSpec.hpp +71 -0
- package/nitrogen/generated/shared/c++/LLMConfig.hpp +134 -0
- package/nitrogen/generated/shared/c++/MemoryUsage.hpp +95 -0
- package/nitrogen/generated/shared/c++/Message.hpp +89 -0
- package/nitrogen/generated/shared/c++/ModelFile.hpp +95 -0
- package/nitrogen/generated/shared/c++/MultimodalPart.hpp +103 -0
- package/nitrogen/generated/shared/c++/PartType.hpp +80 -0
- package/nitrogen/generated/shared/c++/Role.hpp +80 -0
- package/nitrogen/generated/shared/c++/ToolCall.hpp +87 -0
- package/nitrogen/generated/shared/c++/ToolDefinition.hpp +91 -0
- package/nitrogen/generated/shared/c++/ToolResponse.hpp +87 -0
- package/package.json +115 -0
- package/react-native-litert-lm.podspec +62 -0
- package/react-native.config.js +16 -0
- package/scripts/download-ios-frameworks.sh +39 -0
- package/scripts/framework-source.js +46 -0
- package/scripts/postinstall.js +145 -0
- package/src/__mocks__/react-native-nitro-modules.ts +132 -0
- package/src/__tests__/hooks.test.ts +216 -0
- package/src/__tests__/inferenceRouting.test.ts +36 -0
- package/src/__tests__/memoryTracker.test.ts +87 -0
- package/src/__tests__/modelFactory.test.ts +153 -0
- package/src/__tests__/modelPath.test.ts +14 -0
- package/src/__tests__/modelRegistry.test.ts +73 -0
- package/src/hooks.ts +234 -0
- package/src/index.ts +214 -0
- package/src/inferenceRouting.ts +80 -0
- package/src/memoryTracker.ts +268 -0
- package/src/modelFactory.ts +163 -0
- package/src/modelPath.ts +16 -0
- package/src/modelRegistry.ts +104 -0
- package/src/specs/LiteRTLM.nitro.ts +451 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createLLM } from '../modelFactory';
|
|
2
|
+
import { mockLiteRTLM } from '../__mocks__/react-native-nitro-modules';
|
|
3
|
+
|
|
4
|
+
describe('modelFactory Security & Proxy Unit Tests', () => {
|
|
5
|
+
let llm: ReturnType<typeof createLLM>;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
jest.clearAllMocks();
|
|
9
|
+
llm = createLLM({ enableMemoryTracking: true });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should block insecure HTTP downloads', async () => {
|
|
13
|
+
await expect(llm.loadModel('http://example.com/model.litertlm'))
|
|
14
|
+
.rejects.toThrow('Insecure HTTP URLs are not allowed for model downloads');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should allow secure HTTPS downloads and strip query parameters', async () => {
|
|
18
|
+
await llm.loadModel('https://example.com/model.litertlm?token=123');
|
|
19
|
+
|
|
20
|
+
expect(mockLiteRTLM.downloadModel).toHaveBeenCalledWith(
|
|
21
|
+
'https://example.com/model.litertlm?token=123',
|
|
22
|
+
'model.litertlm',
|
|
23
|
+
expect.any(Function)
|
|
24
|
+
);
|
|
25
|
+
expect(mockLiteRTLM.loadModel).toHaveBeenCalledWith('/mock/path/model.litertlm', undefined);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should throw an error for invalid model URL', async () => {
|
|
29
|
+
await expect(llm.loadModel('https://example.com/'))
|
|
30
|
+
.rejects.toThrow('Invalid model URL: https://example.com/');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should successfully proxy sendMessage and record memory metrics', async () => {
|
|
34
|
+
const response = await llm.sendMessage("Test prompt");
|
|
35
|
+
|
|
36
|
+
expect(response).toBe("Mock token");
|
|
37
|
+
expect(mockLiteRTLM.execute).toHaveBeenCalledWith(
|
|
38
|
+
[{ type: "text", text: "Test prompt" }],
|
|
39
|
+
undefined,
|
|
40
|
+
);
|
|
41
|
+
expect(mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
|
|
42
|
+
expect(llm.memoryTracker?.getSnapshotCount()).toBe(1); // sendMessage records one
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should successfully proxy resetConversation and record memory metrics', async () => {
|
|
46
|
+
await llm.resetConversation();
|
|
47
|
+
|
|
48
|
+
expect(mockLiteRTLM.resetConversation).toHaveBeenCalled();
|
|
49
|
+
expect(mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should successfully proxy sendMessageAsync and record memory metrics when done', async () => {
|
|
53
|
+
const onToken = jest.fn();
|
|
54
|
+
await llm.sendMessageAsync("Async prompt", onToken);
|
|
55
|
+
|
|
56
|
+
expect(onToken).toHaveBeenCalledWith("Mock ", false);
|
|
57
|
+
expect(onToken).toHaveBeenCalledWith("token", true);
|
|
58
|
+
expect(mockLiteRTLM.execute).toHaveBeenCalledWith(
|
|
59
|
+
[{ type: "text", text: "Async prompt" }],
|
|
60
|
+
expect.any(Function),
|
|
61
|
+
);
|
|
62
|
+
expect(mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should successfully proxy sendMessageWithImageAsync and record memory metrics when done', async () => {
|
|
66
|
+
const onToken = jest.fn();
|
|
67
|
+
await llm.sendMessageWithImageAsync("Vision prompt", "/path/to/image.jpg", onToken);
|
|
68
|
+
|
|
69
|
+
expect(onToken).toHaveBeenCalledWith("Mock vision ", false);
|
|
70
|
+
expect(onToken).toHaveBeenCalledWith("token", true);
|
|
71
|
+
expect(mockLiteRTLM.execute).toHaveBeenCalledWith(
|
|
72
|
+
[
|
|
73
|
+
{ type: "text", text: "Vision prompt" },
|
|
74
|
+
{ type: "image", path: "/path/to/image.jpg" },
|
|
75
|
+
],
|
|
76
|
+
expect.any(Function),
|
|
77
|
+
);
|
|
78
|
+
expect(mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should successfully proxy sendMessageWithAudioAsync and record memory metrics when done', async () => {
|
|
82
|
+
const onToken = jest.fn();
|
|
83
|
+
await llm.sendMessageWithAudioAsync("Audio prompt", "/path/to/audio.wav", onToken);
|
|
84
|
+
|
|
85
|
+
expect(onToken).toHaveBeenCalledWith("Mock audio ", false);
|
|
86
|
+
expect(onToken).toHaveBeenCalledWith("token", true);
|
|
87
|
+
expect(mockLiteRTLM.execute).toHaveBeenCalledWith(
|
|
88
|
+
[
|
|
89
|
+
{ type: "text", text: "Audio prompt" },
|
|
90
|
+
{ type: "audio", path: "/path/to/audio.wav" },
|
|
91
|
+
],
|
|
92
|
+
expect.any(Function),
|
|
93
|
+
);
|
|
94
|
+
expect(mockLiteRTLM.getMemoryUsage).toHaveBeenCalled();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should successfully access memoryTracker and getSnapshots when memory tracking is enabled', () => {
|
|
98
|
+
expect(llm.memoryTracker).toBeDefined();
|
|
99
|
+
expect(llm.memoryTracker?.getCapacity()).toBe(256);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should not initialize memoryTracker when enableMemoryTracking option is false', () => {
|
|
103
|
+
const untrackedLLM = createLLM({ enableMemoryTracking: false });
|
|
104
|
+
expect(untrackedLLM.memoryTracker).toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should forward maxContextTokens and maxOutputTokens config to native loadModel', async () => {
|
|
108
|
+
const config = {
|
|
109
|
+
backend: 'cpu' as const,
|
|
110
|
+
maxContextTokens: 8192,
|
|
111
|
+
maxOutputTokens: 2048,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
await llm.loadModel('/local/path/model.litertlm', config);
|
|
115
|
+
|
|
116
|
+
expect(mockLiteRTLM.loadModel).toHaveBeenCalledWith(
|
|
117
|
+
'/local/path/model.litertlm',
|
|
118
|
+
config
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should forward legacy maxTokens config to native loadModel for backward compat', async () => {
|
|
123
|
+
const config = {
|
|
124
|
+
backend: 'cpu' as const,
|
|
125
|
+
maxTokens: 512,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await llm.loadModel('/local/path/model.litertlm', config);
|
|
129
|
+
|
|
130
|
+
expect(mockLiteRTLM.loadModel).toHaveBeenCalledWith(
|
|
131
|
+
'/local/path/model.litertlm',
|
|
132
|
+
config
|
|
133
|
+
);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should forward execute call exactly to native execute', async () => {
|
|
137
|
+
const parts = [
|
|
138
|
+
{ type: 'image' as const, path: '/path/to/image.jpg', imageBuffer: new ArrayBuffer(10) }
|
|
139
|
+
];
|
|
140
|
+
await llm.execute(parts);
|
|
141
|
+
expect(mockLiteRTLM.execute).toHaveBeenCalledWith(parts, undefined);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should strip file:// prefix from paths in execute parts before forwarding to native execute', async () => {
|
|
145
|
+
const parts = [
|
|
146
|
+
{ type: 'image' as const, path: 'file:///path/to/image.jpg', imageBuffer: new ArrayBuffer(10) }
|
|
147
|
+
];
|
|
148
|
+
await llm.execute(parts);
|
|
149
|
+
expect(mockLiteRTLM.execute).toHaveBeenCalledWith([
|
|
150
|
+
{ type: 'image' as const, path: '/path/to/image.jpg', imageBuffer: expect.any(ArrayBuffer) }
|
|
151
|
+
], undefined);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { extractFileName, resolveModelFileName } from "../modelPath";
|
|
2
|
+
|
|
3
|
+
describe("modelPath", () => {
|
|
4
|
+
it("strips query strings from URLs", () => {
|
|
5
|
+
expect(extractFileName("https://example.com/model.litertlm?token=abc")).toBe(
|
|
6
|
+
"model.litertlm",
|
|
7
|
+
);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("resolveModelFileName uses basename for paths and URLs", () => {
|
|
11
|
+
expect(resolveModelFileName("/data/models/foo.litertlm")).toBe("foo.litertlm");
|
|
12
|
+
expect(resolveModelFileName("bare-name.bin")).toBe("bare-name.bin");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ModelRegistry } from '../modelRegistry';
|
|
2
|
+
import { mockModelStore } from '../__mocks__/react-native-nitro-modules';
|
|
3
|
+
|
|
4
|
+
describe('ModelRegistry Unit Tests', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
jest.clearAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('isCached should query native store correctly', () => {
|
|
10
|
+
mockModelStore.isCached.mockReturnValueOnce(true);
|
|
11
|
+
const result = ModelRegistry.isCached('https://example.com/test-model.litertlm');
|
|
12
|
+
expect(mockModelStore.isCached).toHaveBeenCalledWith('test-model.litertlm');
|
|
13
|
+
expect(result).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('getFilePath should return cached path', () => {
|
|
17
|
+
mockModelStore.getFilePath.mockReturnValueOnce('/caches/test.bin');
|
|
18
|
+
const path = ModelRegistry.getFilePath('test.bin');
|
|
19
|
+
expect(mockModelStore.getFilePath).toHaveBeenCalledWith('test.bin');
|
|
20
|
+
expect(path).toBe('/caches/test.bin');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('listCachedFiles should delegate to native', () => {
|
|
24
|
+
const mockFiles = [
|
|
25
|
+
{
|
|
26
|
+
fileName: 'model.bin',
|
|
27
|
+
absolutePath: '/caches/model.bin',
|
|
28
|
+
sizeBytes: 1000,
|
|
29
|
+
lastModifiedMs: 12345,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
mockModelStore.listCachedFiles.mockReturnValueOnce(mockFiles as any);
|
|
33
|
+
const files = ModelRegistry.listCachedFiles();
|
|
34
|
+
expect(mockModelStore.listCachedFiles).toHaveBeenCalled();
|
|
35
|
+
expect(files).toEqual(mockFiles);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('deleteFile should delegate delete to native', () => {
|
|
39
|
+
ModelRegistry.deleteFile('https://example.com/model.bin?q=1');
|
|
40
|
+
expect(mockModelStore.deleteFile).toHaveBeenCalledWith('model.bin');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('resolveModel should throw error on HTTP urls', async () => {
|
|
44
|
+
await expect(ModelRegistry.resolveModel('http://example.com/model.bin'))
|
|
45
|
+
.rejects.toThrow('Insecure HTTP URLs are not allowed for model downloads');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('resolveModel should download HTTPS urls', async () => {
|
|
49
|
+
mockModelStore.downloadFile.mockResolvedValueOnce('/downloaded/model.bin');
|
|
50
|
+
const path = await ModelRegistry.resolveModel('https://example.com/model.bin', {
|
|
51
|
+
headers: { Authorization: 'Bearer test' },
|
|
52
|
+
});
|
|
53
|
+
expect(mockModelStore.downloadFile).toHaveBeenCalledWith(
|
|
54
|
+
'https://example.com/model.bin',
|
|
55
|
+
'model.bin',
|
|
56
|
+
JSON.stringify({ Authorization: 'Bearer test' }),
|
|
57
|
+
expect.any(Function)
|
|
58
|
+
);
|
|
59
|
+
expect(path).toBe('/downloaded/model.bin');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('resolveModel should return local paths directly', async () => {
|
|
63
|
+
const path = await ModelRegistry.resolveModel('/local/path/model.bin');
|
|
64
|
+
expect(mockModelStore.downloadFile).not.toHaveBeenCalled();
|
|
65
|
+
expect(path).toBe('/local/path/model.bin');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('resolveModel should strip file:// prefix from local paths', async () => {
|
|
69
|
+
const path = await ModelRegistry.resolveModel('file:///local/path/model.bin');
|
|
70
|
+
expect(mockModelStore.downloadFile).not.toHaveBeenCalled();
|
|
71
|
+
expect(path).toBe('/local/path/model.bin');
|
|
72
|
+
});
|
|
73
|
+
});
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { useState, useEffect, useRef, useCallback, useMemo } from "react";
|
|
2
|
+
import { LLMConfig } from "./index";
|
|
3
|
+
import { createLLM } from "./modelFactory";
|
|
4
|
+
import type { LiteRTLMInstance } from "./modelFactory";
|
|
5
|
+
import type { MemoryTracker, MemoryTrackerSummary } from "./memoryTracker";
|
|
6
|
+
import { ModelRegistry } from "./modelRegistry";
|
|
7
|
+
import { extractFileName } from "./modelPath";
|
|
8
|
+
|
|
9
|
+
export interface UseModelConfig extends LLMConfig {
|
|
10
|
+
autoLoad?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Enable memory tracking using native ArrayBuffers (v0.35+).
|
|
13
|
+
* When enabled, memory usage is tracked after each inference call
|
|
14
|
+
* using `NitroModules.createNativeArrayBuffer()` for zero-copy storage.
|
|
15
|
+
* @default false
|
|
16
|
+
*/
|
|
17
|
+
enableMemoryTracking?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of memory snapshots to store.
|
|
20
|
+
* Each snapshot uses 32 bytes of native memory.
|
|
21
|
+
* @default 256
|
|
22
|
+
*/
|
|
23
|
+
maxMemorySnapshots?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UseModelResult {
|
|
27
|
+
model: LiteRTLMInstance | null;
|
|
28
|
+
isReady: boolean;
|
|
29
|
+
isGenerating: boolean;
|
|
30
|
+
downloadProgress: number;
|
|
31
|
+
error: string | null;
|
|
32
|
+
generate: (prompt: string) => Promise<import("./specs/LiteRTLM.nitro").ExecuteResult>;
|
|
33
|
+
reset: () => void;
|
|
34
|
+
/**
|
|
35
|
+
* Delete the model file. If no fileName is provided, derives it from
|
|
36
|
+
* the URL/path passed to useModel.
|
|
37
|
+
*/
|
|
38
|
+
deleteModel: (fileName?: string) => Promise<void>;
|
|
39
|
+
load: () => Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Memory tracker instance (available when enableMemoryTracking is true).
|
|
42
|
+
* Uses native ArrayBuffers allocated via `NitroModules.createNativeArrayBuffer()`
|
|
43
|
+
* for efficient, zero-copy memory usage tracking.
|
|
44
|
+
*/
|
|
45
|
+
memoryTracker: MemoryTracker | null;
|
|
46
|
+
/**
|
|
47
|
+
* Current memory tracking summary (null if tracking is disabled).
|
|
48
|
+
* Updates automatically after each inference call.
|
|
49
|
+
*/
|
|
50
|
+
memorySummary: MemoryTrackerSummary | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function useModel(
|
|
54
|
+
pathOrUrl: string,
|
|
55
|
+
config?: UseModelConfig,
|
|
56
|
+
): UseModelResult {
|
|
57
|
+
const modelRef = useRef<LiteRTLMInstance | null>(null);
|
|
58
|
+
const [isReady, setIsReady] = useState(false);
|
|
59
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
60
|
+
const [downloadProgress, setDownloadProgress] = useState(0);
|
|
61
|
+
const [error, setError] = useState<string | null>(null);
|
|
62
|
+
const [memorySummary, setMemorySummary] =
|
|
63
|
+
useState<MemoryTrackerSummary | null>(null);
|
|
64
|
+
|
|
65
|
+
// Destructure config into primitive values for stable dependency arrays.
|
|
66
|
+
// This prevents infinite re-render loops when consumers pass inline config
|
|
67
|
+
// objects (e.g. useModel(url, { backend: 'cpu' })) without useMemo.
|
|
68
|
+
const autoLoad = config?.autoLoad ?? true;
|
|
69
|
+
const enableMemoryTracking = config?.enableMemoryTracking ?? false;
|
|
70
|
+
const maxMemorySnapshots = config?.maxMemorySnapshots ?? 256;
|
|
71
|
+
const backend = config?.backend;
|
|
72
|
+
const systemPrompt = config?.systemPrompt;
|
|
73
|
+
const maxTokens = config?.maxTokens;
|
|
74
|
+
const maxContextTokens = config?.maxContextTokens;
|
|
75
|
+
const maxOutputTokens = config?.maxOutputTokens;
|
|
76
|
+
const temperature = config?.temperature;
|
|
77
|
+
const topK = config?.topK;
|
|
78
|
+
const topP = config?.topP;
|
|
79
|
+
const multimodal = config?.multimodal;
|
|
80
|
+
const tools = config?.tools;
|
|
81
|
+
const enableSpeculativeDecoding = config?.enableSpeculativeDecoding;
|
|
82
|
+
const enableThinking = config?.enableThinking;
|
|
83
|
+
const toolsKey = tools ? JSON.stringify(tools) : undefined;
|
|
84
|
+
|
|
85
|
+
// Build a stable config object from the destructured primitives
|
|
86
|
+
const nativeConfig = useMemo<LLMConfig>(
|
|
87
|
+
() => ({
|
|
88
|
+
...(backend !== undefined && { backend }),
|
|
89
|
+
...(systemPrompt !== undefined && { systemPrompt }),
|
|
90
|
+
...(maxTokens !== undefined && { maxTokens }),
|
|
91
|
+
...(maxContextTokens !== undefined && { maxContextTokens }),
|
|
92
|
+
...(maxOutputTokens !== undefined && { maxOutputTokens }),
|
|
93
|
+
...(temperature !== undefined && { temperature }),
|
|
94
|
+
...(topK !== undefined && { topK }),
|
|
95
|
+
...(topP !== undefined && { topP }),
|
|
96
|
+
...(multimodal !== undefined && { multimodal }),
|
|
97
|
+
...(tools !== undefined && { tools }),
|
|
98
|
+
...(enableSpeculativeDecoding !== undefined && {
|
|
99
|
+
enableSpeculativeDecoding,
|
|
100
|
+
}),
|
|
101
|
+
...(enableThinking !== undefined && { enableThinking }),
|
|
102
|
+
}),
|
|
103
|
+
[
|
|
104
|
+
backend,
|
|
105
|
+
systemPrompt,
|
|
106
|
+
maxTokens,
|
|
107
|
+
maxContextTokens,
|
|
108
|
+
maxOutputTokens,
|
|
109
|
+
temperature,
|
|
110
|
+
topK,
|
|
111
|
+
topP,
|
|
112
|
+
multimodal,
|
|
113
|
+
toolsKey,
|
|
114
|
+
enableSpeculativeDecoding,
|
|
115
|
+
enableThinking,
|
|
116
|
+
],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Refresh memory summary from the tracker's native buffer.
|
|
121
|
+
*/
|
|
122
|
+
const refreshMemorySummary = useCallback(() => {
|
|
123
|
+
if (modelRef.current?.memoryTracker) {
|
|
124
|
+
setMemorySummary(modelRef.current.memoryTracker.getSummary());
|
|
125
|
+
}
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
// Initialize the model instance
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
modelRef.current = createLLM({
|
|
131
|
+
enableMemoryTracking,
|
|
132
|
+
maxMemorySnapshots,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Reset ready state — the new instance has no model loaded yet.
|
|
136
|
+
// This prevents stale isReady=true after Fast Refresh (which
|
|
137
|
+
// preserves useState but re-runs useEffect).
|
|
138
|
+
setIsReady(false);
|
|
139
|
+
|
|
140
|
+
// Cleanup on unmount
|
|
141
|
+
return () => {
|
|
142
|
+
try {
|
|
143
|
+
modelRef.current?.close();
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.warn("Failed to close model", e);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}, [enableMemoryTracking, maxMemorySnapshots]);
|
|
149
|
+
|
|
150
|
+
const load = useCallback(async () => {
|
|
151
|
+
setIsReady(false);
|
|
152
|
+
setError(null);
|
|
153
|
+
setDownloadProgress(0);
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (modelRef.current) {
|
|
157
|
+
// Delegate URL handling + download to the factory's loadModel,
|
|
158
|
+
// passing our progress setter as the callback (eliminates
|
|
159
|
+
// duplicate download logic that was previously in this hook).
|
|
160
|
+
await modelRef.current.loadModel(
|
|
161
|
+
pathOrUrl,
|
|
162
|
+
nativeConfig,
|
|
163
|
+
(progress) => {
|
|
164
|
+
setDownloadProgress(progress);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
setIsReady(true);
|
|
168
|
+
}
|
|
169
|
+
} catch (e: any) {
|
|
170
|
+
setError(e.message || "Failed to load model");
|
|
171
|
+
console.error(e);
|
|
172
|
+
}
|
|
173
|
+
}, [pathOrUrl, nativeConfig]);
|
|
174
|
+
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
if (autoLoad) {
|
|
177
|
+
load();
|
|
178
|
+
}
|
|
179
|
+
}, [autoLoad, load]);
|
|
180
|
+
|
|
181
|
+
const generate = useCallback(
|
|
182
|
+
async (prompt: string) => {
|
|
183
|
+
if (!modelRef.current || !isReady) {
|
|
184
|
+
throw new Error("Model not ready");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
setIsGenerating(true);
|
|
188
|
+
try {
|
|
189
|
+
const response = await modelRef.current.execute(
|
|
190
|
+
[{ type: "text", text: prompt }],
|
|
191
|
+
undefined
|
|
192
|
+
);
|
|
193
|
+
refreshMemorySummary();
|
|
194
|
+
return response;
|
|
195
|
+
} catch (e: any) {
|
|
196
|
+
setError(e.message || "Generation failed");
|
|
197
|
+
throw e;
|
|
198
|
+
} finally {
|
|
199
|
+
setIsGenerating(false);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
[isReady, refreshMemorySummary],
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const reset = useCallback(() => {
|
|
206
|
+
if (modelRef.current) {
|
|
207
|
+
modelRef.current.resetConversation();
|
|
208
|
+
}
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const deleteModel = useCallback(
|
|
212
|
+
async (fileName?: string): Promise<void> => {
|
|
213
|
+
const resolvedName = fileName ?? extractFileName(pathOrUrl);
|
|
214
|
+
ModelRegistry.deleteFile(resolvedName);
|
|
215
|
+
setIsReady(false);
|
|
216
|
+
setDownloadProgress(0);
|
|
217
|
+
},
|
|
218
|
+
[pathOrUrl],
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
model: modelRef.current,
|
|
223
|
+
isReady,
|
|
224
|
+
isGenerating,
|
|
225
|
+
downloadProgress,
|
|
226
|
+
error,
|
|
227
|
+
generate,
|
|
228
|
+
reset,
|
|
229
|
+
deleteModel,
|
|
230
|
+
load,
|
|
231
|
+
memoryTracker: modelRef.current?.memoryTracker ?? null,
|
|
232
|
+
memorySummary,
|
|
233
|
+
};
|
|
234
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { NitroModules } from "react-native-nitro-modules";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import type {
|
|
4
|
+
LiteRTLM,
|
|
5
|
+
LLMConfig,
|
|
6
|
+
Message,
|
|
7
|
+
Backend,
|
|
8
|
+
Role,
|
|
9
|
+
GenerationStats,
|
|
10
|
+
MemoryUsage,
|
|
11
|
+
} from "./specs/LiteRTLM.nitro";
|
|
12
|
+
|
|
13
|
+
export type {
|
|
14
|
+
LiteRTLM,
|
|
15
|
+
LLMConfig,
|
|
16
|
+
Message,
|
|
17
|
+
Backend,
|
|
18
|
+
Role,
|
|
19
|
+
GenerationStats,
|
|
20
|
+
MemoryUsage,
|
|
21
|
+
/** New in v0.5: pass to execute() instead of individual send methods */
|
|
22
|
+
MultimodalPart,
|
|
23
|
+
PartType,
|
|
24
|
+
/** Tool calling types */
|
|
25
|
+
ToolDefinition,
|
|
26
|
+
ToolCall,
|
|
27
|
+
ToolResponse,
|
|
28
|
+
ExecuteResult,
|
|
29
|
+
} from "./specs/LiteRTLM.nitro";
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Re-export memory tracking utilities (uses NitroModules.createNativeArrayBuffer v0.35+)
|
|
33
|
+
export type {
|
|
34
|
+
MemorySnapshot,
|
|
35
|
+
MemoryTracker,
|
|
36
|
+
MemoryTrackerSummary,
|
|
37
|
+
} from "./memoryTracker";
|
|
38
|
+
export { createMemoryTracker, createNativeBuffer } from "./memoryTracker";
|
|
39
|
+
|
|
40
|
+
export type { LiteRTLMInstance } from "./modelFactory";
|
|
41
|
+
export { ModelRegistry } from "./modelRegistry";
|
|
42
|
+
export type { ModelDownloadOptions } from "./modelRegistry";
|
|
43
|
+
export * from "./hooks";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new LiteRT-LM inference engine instance.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { createLLM } from 'react-native-litert-lm';
|
|
51
|
+
*
|
|
52
|
+
* const llm = createLLM();
|
|
53
|
+
* await llm.loadModel('/path/to/gemma-3n-e2b.litertlm', {
|
|
54
|
+
* backend: 'gpu',
|
|
55
|
+
* temperature: 0.7,
|
|
56
|
+
* maxContextTokens: 4096,
|
|
57
|
+
* maxOutputTokens: 512
|
|
58
|
+
* });
|
|
59
|
+
*
|
|
60
|
+
* // ── Unified entry point (recommended) ─────────────────────────────────────
|
|
61
|
+
* // Blocking text
|
|
62
|
+
* const response = await llm.execute([{ type: 'text', text: 'Hello!' }]);
|
|
63
|
+
*
|
|
64
|
+
* // Streaming text
|
|
65
|
+
* await llm.execute(
|
|
66
|
+
* [{ type: 'text', text: 'Write me a haiku' }],
|
|
67
|
+
* (token, done) => { process.stdout.write(token); }
|
|
68
|
+
* );
|
|
69
|
+
*
|
|
70
|
+
* // Multimodal (image path — auto-scaled to 1024px on both platforms)
|
|
71
|
+
* const desc = await llm.execute([
|
|
72
|
+
* { type: 'text', text: 'Describe this image' },
|
|
73
|
+
* { type: 'image', path: '/path/to/photo.jpg' },
|
|
74
|
+
* ]);
|
|
75
|
+
*
|
|
76
|
+
* // ── Legacy methods (still fully supported) ────────────────────────────────
|
|
77
|
+
* const r = await llm.sendMessage('Hello');
|
|
78
|
+
* llm.sendMessageAsync('Stream this', (t, done) => console.log(t));
|
|
79
|
+
*
|
|
80
|
+
* llm.close();
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export { createLLM } from "./modelFactory";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Pre-defined model identifiers for common models.
|
|
87
|
+
* Use with model download utilities or as reference.
|
|
88
|
+
*/
|
|
89
|
+
export const Models = {
|
|
90
|
+
/** Gemma 4 E2B Instruct (2B parameters, latest generation) */
|
|
91
|
+
GEMMA_4_E2B: "gemma-4-E2B-it-litert-lm",
|
|
92
|
+
/** Gemma 4 E4B Instruct (4B parameters, higher quality) */
|
|
93
|
+
GEMMA_4_E4B: "gemma-4-E4B-it-litert-lm",
|
|
94
|
+
/** Gemma 3n E2B (2B parameters, efficient) */
|
|
95
|
+
GEMMA_3N_E2B: "gemma-3n-E2B-it-litert-lm-preview",
|
|
96
|
+
/** Gemma 3n E4B (4B parameters, higher quality) */
|
|
97
|
+
GEMMA_3N_E4B: "gemma-3n-E4B-it-litert-lm-preview",
|
|
98
|
+
/** Gemma 3 1B (smallest Gemma) */
|
|
99
|
+
GEMMA_3_1B: "Gemma3-1B-IT_multi-prefill-seq_q4_ekv4096",
|
|
100
|
+
/** Phi-4 Mini Instruct */
|
|
101
|
+
PHI_4_MINI: "Phi-4-mini-instruct_multi-prefill-seq_q8_ekv4096",
|
|
102
|
+
/** Qwen 2.5 1.5B Instruct */
|
|
103
|
+
QWEN_2_5_1_5B: "Qwen2.5-1.5B-Instruct_multi-prefill-seq_q8_ekv4096",
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
export type ModelId = (typeof Models)[keyof typeof Models];
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the recommended backend for the current platform.
|
|
110
|
+
* Returns 'cpu' as the safe default. GPU (Metal on iOS, GPU delegate on Android)
|
|
111
|
+
* is faster but may not be available on all devices or model configurations.
|
|
112
|
+
*
|
|
113
|
+
* @returns The recommended backend ('cpu')
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const backend = getRecommendedBackend();
|
|
118
|
+
* llm.loadModel(path, { backend });
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export function getRecommendedBackend(): Backend {
|
|
122
|
+
// CPU is the safe default — always available, broadly compatible.
|
|
123
|
+
// GPU is faster but may fail on some models/devices.
|
|
124
|
+
return "cpu";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if a backend configuration is supported on the current platform.
|
|
129
|
+
* Returns a warning message if the configuration may have issues.
|
|
130
|
+
*
|
|
131
|
+
* @param backend The backend to check
|
|
132
|
+
* @returns Warning message if there may be issues, undefined if OK
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const warning = checkBackendSupport('npu');
|
|
137
|
+
* if (warning) {
|
|
138
|
+
* console.warn(warning);
|
|
139
|
+
* }
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
export function checkBackendSupport(backend: Backend): string | undefined {
|
|
143
|
+
if (backend === "gpu") {
|
|
144
|
+
if (Platform.OS === "android") {
|
|
145
|
+
// GPU uses OpenCL on Android. Supported on many devices (Mali, Adreno, etc.)
|
|
146
|
+
// but may not be available on all. The engine will automatically fall back
|
|
147
|
+
// to CPU if GPU initialization fails.
|
|
148
|
+
return "GPU acceleration uses OpenCL. If unavailable on this device, the engine will automatically fall back to CPU.";
|
|
149
|
+
}
|
|
150
|
+
// iOS always supports GPU via Metal
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (backend === "npu") {
|
|
155
|
+
if (Platform.OS === "android") {
|
|
156
|
+
return "NPU backend requires compatible hardware (Qualcomm Hexagon, MediaTek APU, etc.). Will automatically fall back to CPU if unavailable.";
|
|
157
|
+
}
|
|
158
|
+
if (Platform.OS === "ios") {
|
|
159
|
+
return "NPU (Neural Engine) is not yet supported on iOS. Mapped to GPU (Metal) internally.";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if multimodal features (image/audio) are supported on the current platform.
|
|
168
|
+
* Returns an error message if not supported, undefined if OK.
|
|
169
|
+
*
|
|
170
|
+
* Both iOS (v0.12.0 CLiteRTLM xcframework) and Android (LiteRT-LM SDK) ship the
|
|
171
|
+
* vision/audio executor ops, so there is no platform-level block. Whether a
|
|
172
|
+
* given call succeeds depends on the **loaded model**: only multimodal models
|
|
173
|
+
* (e.g. Gemma 3n) bundle the vision/audio executors. Pass `multimodal: true` to
|
|
174
|
+
* `loadModel` for such models, or rely on filename sniffing ("3n"/"gemma3").
|
|
175
|
+
*
|
|
176
|
+
* @returns Error message if multimodal is not supported, undefined if OK
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const error = checkMultimodalSupport();
|
|
181
|
+
* if (error) {
|
|
182
|
+
* console.warn(error);
|
|
183
|
+
* // Fall back to text-only
|
|
184
|
+
* } else {
|
|
185
|
+
* llm.sendMessageWithImage('Describe this', imagePath);
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
export function checkMultimodalSupport(): string | undefined {
|
|
190
|
+
// Supported on both platforms with a multimodal model loaded.
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Download URL for the Gemma 3n E2B IT INT4 model (~1.3 GB).
|
|
196
|
+
* Public — hosted on litert.dev, no authentication required.
|
|
197
|
+
*/
|
|
198
|
+
export const GEMMA_3N_E2B_IT_INT4 =
|
|
199
|
+
"https://litert.dev/gemma-3n-E2B-it-int4.litertlm";
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Download URL for the Gemma 4 E2B IT model (2.58 GB).
|
|
203
|
+
* Public — no HuggingFace account required.
|
|
204
|
+
*/
|
|
205
|
+
export const GEMMA_4_E2B_IT =
|
|
206
|
+
"https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm/resolve/main/gemma-4-E2B-it.litertlm";
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Download URL for the Gemma 4 E4B IT model (3.65 GB).
|
|
210
|
+
* Higher quality than E2B but requires more device memory.
|
|
211
|
+
* Public — no HuggingFace account required.
|
|
212
|
+
*/
|
|
213
|
+
export const GEMMA_4_E4B_IT =
|
|
214
|
+
"https://huggingface.co/litert-community/gemma-4-E4B-it-litert-lm/resolve/main/gemma-4-E4B-it.litertlm";
|