cactus-react-native 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +609 -56
  2. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusCrypto.kt +23 -15
  3. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusDeviceInfo.kt +12 -9
  4. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusFileSystem.kt +42 -41
  5. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusImage.kt +81 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libcactus.a +0 -0
  7. package/cpp/HybridCactus.cpp +161 -44
  8. package/cpp/HybridCactus.hpp +34 -14
  9. package/cpp/HybridCactusUtil.cpp +13 -11
  10. package/cpp/HybridCactusUtil.hpp +9 -9
  11. package/cpp/cactus_ffi.h +28 -1
  12. package/ios/HybridCactusImage.swift +53 -0
  13. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +28 -1
  14. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/engine.h +237 -7
  15. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/ffi_utils.h +158 -43
  16. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/graph.h +23 -2
  17. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel.h +52 -0
  18. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  19. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_ffi.h +28 -1
  20. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/engine.h +237 -7
  21. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/ffi_utils.h +158 -43
  22. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/graph.h +23 -2
  23. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel.h +52 -0
  24. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/cactus +0 -0
  25. package/lib/module/api/Database.js +23 -0
  26. package/lib/module/api/Database.js.map +1 -1
  27. package/lib/module/api/RemoteLM.js +201 -0
  28. package/lib/module/api/RemoteLM.js.map +1 -0
  29. package/lib/module/classes/CactusLM.js +56 -28
  30. package/lib/module/classes/CactusLM.js.map +1 -1
  31. package/lib/module/classes/CactusSTT.js +137 -0
  32. package/lib/module/classes/CactusSTT.js.map +1 -0
  33. package/lib/module/config/CactusConfig.js +4 -0
  34. package/lib/module/config/CactusConfig.js.map +1 -1
  35. package/lib/module/constants/packageVersion.js +1 -1
  36. package/lib/module/hooks/useCactusLM.js +44 -16
  37. package/lib/module/hooks/useCactusLM.js.map +1 -1
  38. package/lib/module/hooks/useCactusSTT.js +234 -0
  39. package/lib/module/hooks/useCactusSTT.js.map +1 -0
  40. package/lib/module/index.js +2 -0
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/native/Cactus.js +52 -3
  43. package/lib/module/native/Cactus.js.map +1 -1
  44. package/lib/module/native/CactusFileSystem.js +2 -3
  45. package/lib/module/native/CactusFileSystem.js.map +1 -1
  46. package/lib/module/native/CactusImage.js +13 -0
  47. package/lib/module/native/CactusImage.js.map +1 -0
  48. package/lib/module/native/index.js +1 -0
  49. package/lib/module/native/index.js.map +1 -1
  50. package/lib/module/specs/CactusImage.nitro.js +4 -0
  51. package/lib/module/specs/CactusImage.nitro.js.map +1 -0
  52. package/lib/module/telemetry/Telemetry.js +53 -1
  53. package/lib/module/telemetry/Telemetry.js.map +1 -1
  54. package/lib/module/types/CactusSTT.js +2 -0
  55. package/lib/module/types/CactusSTT.js.map +1 -0
  56. package/lib/typescript/src/api/Database.d.ts +1 -0
  57. package/lib/typescript/src/api/Database.d.ts.map +1 -1
  58. package/lib/typescript/src/api/RemoteLM.d.ts +14 -0
  59. package/lib/typescript/src/api/RemoteLM.d.ts.map +1 -0
  60. package/lib/typescript/src/classes/CactusLM.d.ts +8 -5
  61. package/lib/typescript/src/classes/CactusLM.d.ts.map +1 -1
  62. package/lib/typescript/src/classes/CactusSTT.d.ts +25 -0
  63. package/lib/typescript/src/classes/CactusSTT.d.ts.map +1 -0
  64. package/lib/typescript/src/config/CactusConfig.d.ts +1 -0
  65. package/lib/typescript/src/config/CactusConfig.d.ts.map +1 -1
  66. package/lib/typescript/src/constants/packageVersion.d.ts +1 -1
  67. package/lib/typescript/src/hooks/useCactusLM.d.ts +5 -4
  68. package/lib/typescript/src/hooks/useCactusLM.d.ts.map +1 -1
  69. package/lib/typescript/src/hooks/useCactusSTT.d.ts +20 -0
  70. package/lib/typescript/src/hooks/useCactusSTT.d.ts.map +1 -0
  71. package/lib/typescript/src/index.d.ts +4 -1
  72. package/lib/typescript/src/index.d.ts.map +1 -1
  73. package/lib/typescript/src/native/Cactus.d.ts +10 -3
  74. package/lib/typescript/src/native/Cactus.d.ts.map +1 -1
  75. package/lib/typescript/src/native/CactusFileSystem.d.ts +1 -1
  76. package/lib/typescript/src/native/CactusFileSystem.d.ts.map +1 -1
  77. package/lib/typescript/src/native/CactusImage.d.ts +6 -0
  78. package/lib/typescript/src/native/CactusImage.d.ts.map +1 -0
  79. package/lib/typescript/src/native/index.d.ts +1 -0
  80. package/lib/typescript/src/native/index.d.ts.map +1 -1
  81. package/lib/typescript/src/specs/Cactus.nitro.d.ts +4 -1
  82. package/lib/typescript/src/specs/Cactus.nitro.d.ts.map +1 -1
  83. package/lib/typescript/src/specs/CactusImage.nitro.d.ts +9 -0
  84. package/lib/typescript/src/specs/CactusImage.nitro.d.ts.map +1 -0
  85. package/lib/typescript/src/telemetry/Telemetry.d.ts +5 -1
  86. package/lib/typescript/src/telemetry/Telemetry.d.ts.map +1 -1
  87. package/lib/typescript/src/types/CactusLM.d.ts +11 -6
  88. package/lib/typescript/src/types/CactusLM.d.ts.map +1 -1
  89. package/lib/typescript/src/types/CactusSTT.d.ts +37 -0
  90. package/lib/typescript/src/types/CactusSTT.d.ts.map +1 -0
  91. package/nitro.json +4 -0
  92. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.cpp +81 -0
  93. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.hpp +66 -0
  94. package/nitrogen/generated/android/cactus+autolinking.cmake +2 -0
  95. package/nitrogen/generated/android/cactusOnLoad.cpp +10 -0
  96. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusImageSpec.kt +62 -0
  97. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.cpp +17 -0
  98. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.hpp +17 -0
  99. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Umbrella.hpp +5 -0
  100. package/nitrogen/generated/ios/CactusAutolinking.mm +8 -0
  101. package/nitrogen/generated/ios/CactusAutolinking.swift +15 -0
  102. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.cpp +11 -0
  103. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.hpp +85 -0
  104. package/nitrogen/generated/ios/swift/HybridCactusImageSpec.swift +58 -0
  105. package/nitrogen/generated/ios/swift/HybridCactusImageSpec_cxx.swift +158 -0
  106. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.cpp +22 -0
  107. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.hpp +64 -0
  108. package/nitrogen/generated/shared/c++/HybridCactusSpec.cpp +3 -0
  109. package/nitrogen/generated/shared/c++/HybridCactusSpec.hpp +4 -1
  110. package/package.json +1 -1
  111. package/src/api/Database.ts +27 -0
  112. package/src/api/RemoteLM.ts +273 -0
  113. package/src/classes/CactusLM.ts +76 -40
  114. package/src/classes/CactusSTT.ts +182 -0
  115. package/src/config/CactusConfig.ts +4 -0
  116. package/src/constants/packageVersion.ts +1 -1
  117. package/src/hooks/useCactusLM.ts +53 -22
  118. package/src/hooks/useCactusSTT.ts +285 -0
  119. package/src/index.tsx +14 -2
  120. package/src/native/Cactus.ts +100 -6
  121. package/src/native/CactusFileSystem.ts +2 -2
  122. package/src/native/CactusImage.ts +20 -0
  123. package/src/native/index.ts +1 -0
  124. package/src/specs/Cactus.nitro.ts +14 -1
  125. package/src/specs/CactusImage.nitro.ts +12 -0
  126. package/src/telemetry/Telemetry.ts +78 -1
  127. package/src/types/CactusLM.ts +12 -6
  128. package/src/types/CactusSTT.ts +42 -0
@@ -0,0 +1,285 @@
1
+ import { useCallback, useEffect, useState, useRef } from 'react';
2
+ import { CactusSTT } from '../classes/CactusSTT';
3
+ import { CactusFileSystem } from '../native';
4
+ import { getErrorMessage } from '../utils/error';
5
+ import type {
6
+ CactusSTTParams,
7
+ CactusSTTTranscribeResult,
8
+ CactusSTTTranscribeParams,
9
+ CactusSTTDownloadParams,
10
+ CactusSTTAudioEmbedParams,
11
+ CactusSTTAudioEmbedResult,
12
+ } from '../types/CactusSTT';
13
+ import type { CactusModel } from '../types/CactusModel';
14
+
15
+ export const useCactusSTT = ({
16
+ model = 'whisper-small',
17
+ contextSize = 2048,
18
+ }: CactusSTTParams = {}) => {
19
+ const [cactusSTT, setCactusSTT] = useState(
20
+ () => new CactusSTT({ model, contextSize })
21
+ );
22
+
23
+ // State
24
+ const [response, setResponse] = useState('');
25
+ const [isGenerating, setIsGenerating] = useState(false);
26
+ const [isInitializing, setIsInitializing] = useState(false);
27
+ const [isDownloaded, setIsDownloaded] = useState(false);
28
+ const [isDownloading, setIsDownloading] = useState(false);
29
+ const [downloadProgress, setDownloadProgress] = useState(0);
30
+ const [error, setError] = useState<string | null>(null);
31
+
32
+ const currentModelRef = useRef(model);
33
+ const currentDownloadIdRef = useRef(0);
34
+
35
+ useEffect(() => {
36
+ currentModelRef.current = model;
37
+ }, [model]);
38
+
39
+ useEffect(() => {
40
+ setCactusSTT(new CactusSTT({ model, contextSize }));
41
+
42
+ setResponse('');
43
+ setIsGenerating(false);
44
+ setIsInitializing(false);
45
+ setIsDownloaded(false);
46
+ setIsDownloading(false);
47
+ setDownloadProgress(0);
48
+ setError(null);
49
+
50
+ let mounted = true;
51
+ CactusFileSystem.modelExists(model)
52
+ .then((exists) => {
53
+ if (!mounted) {
54
+ return;
55
+ }
56
+ setIsDownloaded(exists);
57
+ })
58
+ .catch((e) => {
59
+ if (!mounted) {
60
+ return;
61
+ }
62
+ setIsDownloaded(false);
63
+ setError(getErrorMessage(e));
64
+ });
65
+
66
+ return () => {
67
+ mounted = false;
68
+ };
69
+ }, [model, contextSize]);
70
+
71
+ useEffect(() => {
72
+ return () => {
73
+ cactusSTT.destroy().catch(() => {});
74
+ };
75
+ }, [cactusSTT]);
76
+
77
+ const download = useCallback(
78
+ async ({ onProgress }: CactusSTTDownloadParams = {}) => {
79
+ if (isDownloading) {
80
+ const message = 'CactusSTT is already downloading';
81
+ setError(message);
82
+ throw new Error(message);
83
+ }
84
+
85
+ setError(null);
86
+
87
+ if (isDownloaded) {
88
+ return;
89
+ }
90
+
91
+ const thisModel = currentModelRef.current;
92
+ const thisDownloadId = ++currentDownloadIdRef.current;
93
+
94
+ setDownloadProgress(0);
95
+ setIsDownloading(true);
96
+ try {
97
+ await cactusSTT.download({
98
+ onProgress: (progress) => {
99
+ if (
100
+ currentModelRef.current !== thisModel ||
101
+ currentDownloadIdRef.current !== thisDownloadId
102
+ ) {
103
+ return;
104
+ }
105
+
106
+ setDownloadProgress(progress);
107
+ onProgress?.(progress);
108
+ },
109
+ });
110
+
111
+ if (
112
+ currentModelRef.current !== thisModel ||
113
+ currentDownloadIdRef.current !== thisDownloadId
114
+ ) {
115
+ return;
116
+ }
117
+
118
+ setIsDownloaded(true);
119
+ } catch (e) {
120
+ if (
121
+ currentModelRef.current !== thisModel ||
122
+ currentDownloadIdRef.current !== thisDownloadId
123
+ ) {
124
+ return;
125
+ }
126
+
127
+ setError(getErrorMessage(e));
128
+ throw e;
129
+ } finally {
130
+ if (
131
+ currentModelRef.current !== thisModel ||
132
+ currentDownloadIdRef.current !== thisDownloadId
133
+ ) {
134
+ return;
135
+ }
136
+
137
+ setIsDownloading(false);
138
+ setDownloadProgress(0);
139
+ }
140
+ },
141
+ [cactusSTT, isDownloading, isDownloaded]
142
+ );
143
+
144
+ const init = useCallback(async () => {
145
+ if (isInitializing) {
146
+ const message = 'CactusSTT is already initializing';
147
+ setError(message);
148
+ throw new Error(message);
149
+ }
150
+
151
+ setError(null);
152
+ setIsInitializing(true);
153
+ try {
154
+ await cactusSTT.init();
155
+ } catch (e) {
156
+ setError(getErrorMessage(e));
157
+ throw e;
158
+ } finally {
159
+ setIsInitializing(false);
160
+ }
161
+ }, [cactusSTT, isInitializing]);
162
+
163
+ const transcribe = useCallback(
164
+ async ({
165
+ audioFilePath,
166
+ prompt,
167
+ options,
168
+ onToken,
169
+ }: CactusSTTTranscribeParams): Promise<CactusSTTTranscribeResult> => {
170
+ if (isGenerating) {
171
+ const message = 'CactusSTT is already generating';
172
+ setError(message);
173
+ throw new Error(message);
174
+ }
175
+
176
+ setError(null);
177
+ setResponse('');
178
+ setIsGenerating(true);
179
+ try {
180
+ return await cactusSTT.transcribe({
181
+ audioFilePath,
182
+ prompt,
183
+ options,
184
+ onToken: (token) => {
185
+ setResponse((prev) => prev + token);
186
+ onToken?.(token);
187
+ },
188
+ });
189
+ } catch (e) {
190
+ setError(getErrorMessage(e));
191
+ throw e;
192
+ } finally {
193
+ setIsGenerating(false);
194
+ }
195
+ },
196
+ [cactusSTT, isGenerating]
197
+ );
198
+
199
+ const audioEmbed = useCallback(
200
+ async ({
201
+ audioPath,
202
+ }: CactusSTTAudioEmbedParams): Promise<CactusSTTAudioEmbedResult> => {
203
+ if (isGenerating) {
204
+ const message = 'CactusSTT is already generating';
205
+ setError(message);
206
+ throw new Error(message);
207
+ }
208
+
209
+ setError(null);
210
+ setIsGenerating(true);
211
+ try {
212
+ return await cactusSTT.audioEmbed({ audioPath });
213
+ } catch (e) {
214
+ setError(getErrorMessage(e));
215
+ throw e;
216
+ } finally {
217
+ setIsGenerating(false);
218
+ }
219
+ },
220
+ [cactusSTT, isGenerating]
221
+ );
222
+
223
+ const stop = useCallback(async () => {
224
+ setError(null);
225
+ try {
226
+ await cactusSTT.stop();
227
+ } catch (e) {
228
+ setError(getErrorMessage(e));
229
+ throw e;
230
+ }
231
+ }, [cactusSTT]);
232
+
233
+ const reset = useCallback(async () => {
234
+ setError(null);
235
+ try {
236
+ await cactusSTT.reset();
237
+ } catch (e) {
238
+ setError(getErrorMessage(e));
239
+ throw e;
240
+ } finally {
241
+ setResponse('');
242
+ }
243
+ }, [cactusSTT]);
244
+
245
+ const destroy = useCallback(async () => {
246
+ setError(null);
247
+ try {
248
+ await cactusSTT.destroy();
249
+ } catch (e) {
250
+ setError(getErrorMessage(e));
251
+ throw e;
252
+ } finally {
253
+ setResponse('');
254
+ }
255
+ }, [cactusSTT]);
256
+
257
+ const getModels = useCallback(async (): Promise<CactusModel[]> => {
258
+ setError(null);
259
+ try {
260
+ return await cactusSTT.getModels();
261
+ } catch (e) {
262
+ setError(getErrorMessage(e));
263
+ throw e;
264
+ }
265
+ }, [cactusSTT]);
266
+
267
+ return {
268
+ response,
269
+ isGenerating,
270
+ isInitializing,
271
+ isDownloaded,
272
+ isDownloading,
273
+ downloadProgress,
274
+ error,
275
+
276
+ download,
277
+ init,
278
+ transcribe,
279
+ audioEmbed,
280
+ reset,
281
+ stop,
282
+ destroy,
283
+ getModels,
284
+ };
285
+ };
package/src/index.tsx CHANGED
@@ -1,8 +1,10 @@
1
1
  // Classes
2
2
  export { CactusLM } from './classes/CactusLM';
3
+ export { CactusSTT } from './classes/CactusSTT';
3
4
 
4
5
  // Hooks
5
6
  export { useCactusLM } from './hooks/useCactusLM';
7
+ export { useCactusSTT } from './hooks/useCactusSTT';
6
8
 
7
9
  // Types
8
10
  export type { CactusModel } from './types/CactusModel';
@@ -10,14 +12,24 @@ export type {
10
12
  CactusLMParams,
11
13
  CactusLMDownloadParams,
12
14
  Message,
13
- Options,
15
+ CompleteOptions,
14
16
  Tool,
15
17
  CactusLMCompleteParams,
16
18
  CactusLMCompleteResult,
17
19
  CactusLMEmbedParams,
18
20
  CactusLMEmbedResult,
19
- CactusLMGetModelsParams,
21
+ CactusLMImageEmbedParams,
22
+ CactusLMImageEmbedResult,
20
23
  } from './types/CactusLM';
24
+ export type {
25
+ CactusSTTParams,
26
+ CactusSTTDownloadParams,
27
+ TranscribeOptions,
28
+ CactusSTTTranscribeParams,
29
+ CactusSTTTranscribeResult,
30
+ CactusSTTAudioEmbedParams,
31
+ CactusSTTAudioEmbedResult,
32
+ } from './types/CactusSTT';
21
33
 
22
34
  // Config
23
35
  export { CactusConfig } from './config/CactusConfig';
@@ -1,28 +1,56 @@
1
1
  import { NitroModules } from 'react-native-nitro-modules';
2
2
  import type { Cactus as CactusSpec } from '../specs/Cactus.nitro';
3
+ import { CactusImage } from './CactusImage';
3
4
  import type {
4
5
  CactusLMCompleteResult,
5
6
  Message,
6
- Options,
7
+ CompleteOptions,
7
8
  Tool,
8
9
  } from '../types/CactusLM';
10
+ import type {
11
+ CactusSTTTranscribeResult,
12
+ TranscribeOptions,
13
+ } from '../types/CactusSTT';
9
14
 
10
15
  export class Cactus {
11
16
  private readonly hybridCactus =
12
17
  NitroModules.createHybridObject<CactusSpec>('Cactus');
13
18
 
14
- public init(modelPath: string, contextSize: number): Promise<void> {
15
- return this.hybridCactus.init(modelPath, contextSize);
19
+ public init(
20
+ modelPath: string,
21
+ contextSize: number,
22
+ corpusDir?: string
23
+ ): Promise<void> {
24
+ return this.hybridCactus.init(modelPath, contextSize, corpusDir);
16
25
  }
17
26
 
18
27
  public async complete(
19
28
  messages: Message[],
20
29
  responseBufferSize: number,
21
- options?: Options,
22
- tools?: Tool[],
30
+ options?: CompleteOptions,
31
+ tools?: { type: 'function'; function: Tool }[],
23
32
  callback?: (token: string, tokenId: number) => void
24
33
  ): Promise<CactusLMCompleteResult> {
25
- const messagesJson = JSON.stringify(messages);
34
+ const messagesInternal: Message[] = [];
35
+ for (const message of messages) {
36
+ if (!message.images) {
37
+ messagesInternal.push(message);
38
+ continue;
39
+ }
40
+ const resizedImages: string[] = [];
41
+ for (const imagePath of message.images) {
42
+ const resizedImage = await CactusImage.resize(
43
+ imagePath.replace('file://', ''),
44
+ 128,
45
+ 128,
46
+ 1
47
+ );
48
+ resizedImages.push(resizedImage);
49
+ }
50
+ messagesInternal.push({ ...message, images: resizedImages });
51
+ }
52
+
53
+ const messagesJson = JSON.stringify(messagesInternal);
26
54
  const optionsJson = options
27
55
  ? JSON.stringify({
28
56
  temperature: options.temperature,
@@ -61,10 +89,76 @@ export class Cactus {
61
89
  }
62
90
  }
63
91
 
92
+ public async transcribe(
93
+ audioFilePath: string,
94
+ prompt: string,
95
+ responseBufferSize: number,
96
+ options?: TranscribeOptions,
97
+ callback?: (token: string, tokenId: number) => void
98
+ ): Promise<CactusSTTTranscribeResult> {
99
+ const optionsJson = options
100
+ ? JSON.stringify({
101
+ temperature: options.temperature,
102
+ top_p: options.topP,
103
+ top_k: options.topK,
104
+ max_tokens: options.maxTokens,
105
+ stop_sequences: options.stopSequences,
106
+ })
107
+ : undefined;
108
+
109
+ const response = await this.hybridCactus.transcribe(
110
+ audioFilePath.replace('file://', ''),
111
+ prompt,
112
+ responseBufferSize,
113
+ optionsJson,
114
+ callback
115
+ );
116
+
117
+ try {
118
+ const parsed = JSON.parse(response);
119
+
120
+ return {
121
+ success: parsed.success,
122
+ response: parsed.response,
123
+ timeToFirstTokenMs: parsed.time_to_first_token_ms,
124
+ totalTimeMs: parsed.total_time_ms,
125
+ tokensPerSecond: parsed.tokens_per_second,
126
+ prefillTokens: parsed.prefill_tokens,
127
+ decodeTokens: parsed.decode_tokens,
128
+ totalTokens: parsed.total_tokens,
129
+ };
130
+ } catch {
131
+ throw new Error('Unable to parse transcription response');
132
+ }
133
+ }
134
+
64
135
  public embed(text: string, embeddingBufferSize: number): Promise<number[]> {
65
136
  return this.hybridCactus.embed(text, embeddingBufferSize);
66
137
  }
67
138
 
139
+ public async imageEmbed(
140
+ imagePath: string,
141
+ embeddingBufferSize: number
142
+ ): Promise<number[]> {
143
+ const resizedImage = await CactusImage.resize(
144
+ imagePath.replace('file://', ''),
145
+ 128,
146
+ 128,
147
+ 1
148
+ );
149
+ return this.hybridCactus.imageEmbed(resizedImage, embeddingBufferSize);
150
+ }
151
+
152
+ public audioEmbed(
153
+ audioPath: string,
154
+ embeddingBufferSize: number
155
+ ): Promise<number[]> {
156
+ return this.hybridCactus.audioEmbed(
157
+ audioPath.replace('file://', ''),
158
+ embeddingBufferSize
159
+ );
160
+ }
161
+
68
162
  public reset(): Promise<void> {
69
163
  return this.hybridCactus.reset();
70
164
  }
@@ -35,10 +35,10 @@ export class CactusFileSystem {
35
35
 
36
36
  public static downloadModel(
37
37
  model: string,
38
+ url: string,
38
39
  onProgress?: (progress: number) => void
39
40
  ): Promise<void> {
40
- const from = `https://vlqqczxwyaodtcdmdmlw.supabase.co/storage/v1/object/public/cactus-models/${model}.zip`;
41
- return this.hybridCactusFileSystem.downloadModel(model, from, onProgress);
41
+ return this.hybridCactusFileSystem.downloadModel(model, url, onProgress);
42
42
  }
43
43
 
44
44
  public static deleteModel(model: string): Promise<void> {
@@ -0,0 +1,20 @@
1
+ import { NitroModules } from 'react-native-nitro-modules';
2
+ import type { CactusImage as CactusImageSpec } from '../specs/CactusImage.nitro';
3
+
4
+ export class CactusImage {
5
+ private static readonly hybridCactusImage =
6
+ NitroModules.createHybridObject<CactusImageSpec>('CactusImage');
7
+
8
+ public static base64(path: string): Promise<string> {
9
+ return this.hybridCactusImage.base64(path);
10
+ }
11
+
12
+ public static resize(
13
+ path: string,
14
+ height: number,
15
+ width: number,
16
+ quality: number
17
+ ): Promise<string> {
18
+ return this.hybridCactusImage.resize(path, height, width, quality);
19
+ }
20
+ }
@@ -2,4 +2,5 @@ export { Cactus } from './Cactus';
2
2
  export { CactusCrypto } from './CactusCrypto';
3
3
  export { CactusDeviceInfo } from './CactusDeviceInfo';
4
4
  export { CactusFileSystem } from './CactusFileSystem';
5
+ export { CactusImage } from './CactusImage';
5
6
  export { CactusUtil } from './CactusUtil';
@@ -1,7 +1,11 @@
1
1
  import type { HybridObject } from 'react-native-nitro-modules';
2
2
 
3
3
  export interface Cactus extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
4
- init(modelPath: string, contextSize: number): Promise<void>;
4
+ init(
5
+ modelPath: string,
6
+ contextSize: number,
7
+ corpusDir?: string
8
+ ): Promise<void>;
5
9
  complete(
6
10
  messagesJson: string,
7
11
  responseBufferSize: number,
@@ -9,7 +13,16 @@ export interface Cactus extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
9
13
  toolsJson?: string,
10
14
  callback?: (token: string, tokenId: number) => void
11
15
  ): Promise<string>;
16
+ transcribe(
17
+ audioFilePath: string,
18
+ prompt: string,
19
+ responseBufferSize: number,
20
+ optionsJson?: string,
21
+ callback?: (token: string, tokenId: number) => void
22
+ ): Promise<string>;
12
23
  embed(text: string, embeddingBufferSize: number): Promise<number[]>;
24
+ imageEmbed(imagePath: string, embeddingBufferSize: number): Promise<number[]>;
25
+ audioEmbed(audioPath: string, embeddingBufferSize: number): Promise<number[]>;
13
26
  reset(): Promise<void>;
14
27
  stop(): Promise<void>;
15
28
  destroy(): Promise<void>;
@@ -0,0 +1,12 @@
1
+ import type { HybridObject } from 'react-native-nitro-modules';
2
+
3
+ export interface CactusImage
4
+ extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
5
+ base64(path: string): Promise<string>;
6
+ resize(
7
+ path: string,
8
+ height: number,
9
+ width: number,
10
+ quality: number
11
+ ): Promise<string>;
12
+ }
@@ -8,6 +8,7 @@ import {
8
8
  import { CactusConfig } from '../config/CactusConfig';
9
9
  import { packageVersion } from '../constants/packageVersion';
10
10
  import type { CactusLMCompleteResult } from '../types/CactusLM';
11
+ import type { CactusSTTTranscribeResult } from '../types/CactusSTT';
11
12
 
12
13
  export interface LogRecord {
13
14
  // Framework
@@ -15,7 +16,13 @@ export interface LogRecord {
15
16
  framework_version: string;
16
17
 
17
18
  // Event
18
- event_type: 'init' | 'completion' | 'embedding';
19
+ event_type:
20
+ | 'init'
21
+ | 'completion'
22
+ | 'transcription'
23
+ | 'embedding'
24
+ | 'image_embedding'
25
+ | 'audio_embedding';
19
26
  model: string;
20
27
  success: boolean;
21
28
  message?: string;
@@ -41,10 +48,17 @@ export class Telemetry {
41
48
  private static readonly logBufferPaths = {
42
49
  init: 'logs/init.json',
43
50
  completion: 'logs/completion.json',
51
+ transcription: 'logs/transcription.json',
44
52
  embedding: 'logs/embedding.json',
53
+ image_embedding: 'logs/image_embedding.json',
54
+ audio_embedding: 'logs/audio_embedding.json',
45
55
  };
46
56
 
47
57
  private static async handleLog(logRecord: LogRecord) {
58
+ if (!this.isInitialized()) {
59
+ return;
60
+ }
61
+
48
62
  if (!CactusConfig.isTelemetryEnabled) {
49
63
  return;
50
64
  }
@@ -78,6 +92,10 @@ export class Telemetry {
78
92
  }
79
93
 
80
94
  public static async init(cactusTelemetryToken?: string): Promise<void> {
95
+ if (this.isInitialized()) {
96
+ return;
97
+ }
98
+
81
99
  if (!CactusConfig.isTelemetryEnabled) {
82
100
  return;
83
101
  }
@@ -139,6 +157,29 @@ export class Telemetry {
139
157
  });
140
158
  }
141
159
 
160
+ public static logTranscribe(
161
+ model: string,
162
+ success: boolean,
163
+ message?: string,
164
+ result?: CactusSTTTranscribeResult
165
+ ): Promise<void> {
166
+ return this.handleLog({
167
+ framework: 'react-native',
168
+ framework_version: packageVersion,
169
+ event_type: 'transcription',
170
+ model,
171
+ success,
172
+ message,
173
+ telemetry_token: this.cactusTelemetryToken,
174
+ project_id: this.projectId,
175
+ device_id: this.deviceId,
176
+ tokens: result?.totalTokens,
177
+ response_time: result?.totalTimeMs,
178
+ ttft: result?.timeToFirstTokenMs,
179
+ tps: result?.tokensPerSecond,
180
+ });
181
+ }
182
+
142
183
  public static logEmbedding(
143
184
  model: string,
144
185
  success: boolean,
@@ -156,4 +197,40 @@ export class Telemetry {
156
197
  device_id: this.deviceId,
157
198
  });
158
199
  }
200
+
201
+ public static logImageEmbedding(
202
+ model: string,
203
+ success: boolean,
204
+ message?: string
205
+ ): Promise<void> {
206
+ return this.handleLog({
207
+ framework: 'react-native',
208
+ framework_version: packageVersion,
209
+ event_type: 'image_embedding',
210
+ model,
211
+ success,
212
+ message,
213
+ telemetry_token: this.cactusTelemetryToken,
214
+ project_id: this.projectId,
215
+ device_id: this.deviceId,
216
+ });
217
+ }
218
+
219
+ public static logAudioEmbedding(
220
+ model: string,
221
+ success: boolean,
222
+ message?: string
223
+ ): Promise<void> {
224
+ return this.handleLog({
225
+ framework: 'react-native',
226
+ framework_version: packageVersion,
227
+ event_type: 'audio_embedding',
228
+ model,
229
+ success,
230
+ message,
231
+ telemetry_token: this.cactusTelemetryToken,
232
+ project_id: this.projectId,
233
+ device_id: this.deviceId,
234
+ });
235
+ }
159
236
  }