cactus-react-native 1.0.2 → 1.2.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 (126) hide show
  1. package/README.md +378 -21
  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 +105 -0
  8. package/cpp/HybridCactus.hpp +13 -0
  9. package/cpp/cactus_ffi.h +27 -0
  10. package/ios/HybridCactusImage.swift +53 -0
  11. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +27 -0
  12. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/engine.h +37 -5
  13. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/ffi_utils.h +10 -9
  14. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/graph.h +49 -7
  15. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel.h +31 -0
  16. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  17. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_ffi.h +27 -0
  18. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/engine.h +37 -5
  19. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/ffi_utils.h +10 -9
  20. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/graph.h +49 -7
  21. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel.h +31 -0
  22. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/cactus +0 -0
  23. package/lib/module/api/Database.js +23 -0
  24. package/lib/module/api/Database.js.map +1 -1
  25. package/lib/module/api/RemoteLM.js +201 -0
  26. package/lib/module/api/RemoteLM.js.map +1 -0
  27. package/lib/module/classes/CactusLM.js +52 -26
  28. package/lib/module/classes/CactusLM.js.map +1 -1
  29. package/lib/module/classes/CactusSTT.js +139 -0
  30. package/lib/module/classes/CactusSTT.js.map +1 -0
  31. package/lib/module/config/CactusConfig.js +4 -0
  32. package/lib/module/config/CactusConfig.js.map +1 -1
  33. package/lib/module/constants/packageVersion.js +1 -1
  34. package/lib/module/hooks/useCactusLM.js +33 -10
  35. package/lib/module/hooks/useCactusLM.js.map +1 -1
  36. package/lib/module/hooks/useCactusSTT.js +234 -0
  37. package/lib/module/hooks/useCactusSTT.js.map +1 -0
  38. package/lib/module/index.js +2 -0
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/native/Cactus.js +50 -1
  41. package/lib/module/native/Cactus.js.map +1 -1
  42. package/lib/module/native/CactusFileSystem.js +2 -3
  43. package/lib/module/native/CactusFileSystem.js.map +1 -1
  44. package/lib/module/native/CactusImage.js +13 -0
  45. package/lib/module/native/CactusImage.js.map +1 -0
  46. package/lib/module/native/index.js +1 -0
  47. package/lib/module/native/index.js.map +1 -1
  48. package/lib/module/specs/CactusImage.nitro.js +4 -0
  49. package/lib/module/specs/CactusImage.nitro.js.map +1 -0
  50. package/lib/module/telemetry/Telemetry.js +53 -1
  51. package/lib/module/telemetry/Telemetry.js.map +1 -1
  52. package/lib/module/types/CactusSTT.js +2 -0
  53. package/lib/module/types/CactusSTT.js.map +1 -0
  54. package/lib/typescript/src/api/Database.d.ts +1 -0
  55. package/lib/typescript/src/api/Database.d.ts.map +1 -1
  56. package/lib/typescript/src/api/RemoteLM.d.ts +14 -0
  57. package/lib/typescript/src/api/RemoteLM.d.ts.map +1 -0
  58. package/lib/typescript/src/classes/CactusLM.d.ts +6 -4
  59. package/lib/typescript/src/classes/CactusLM.d.ts.map +1 -1
  60. package/lib/typescript/src/classes/CactusSTT.d.ts +26 -0
  61. package/lib/typescript/src/classes/CactusSTT.d.ts.map +1 -0
  62. package/lib/typescript/src/config/CactusConfig.d.ts +1 -0
  63. package/lib/typescript/src/config/CactusConfig.d.ts.map +1 -1
  64. package/lib/typescript/src/constants/packageVersion.d.ts +1 -1
  65. package/lib/typescript/src/hooks/useCactusLM.d.ts +4 -3
  66. package/lib/typescript/src/hooks/useCactusLM.d.ts.map +1 -1
  67. package/lib/typescript/src/hooks/useCactusSTT.d.ts +20 -0
  68. package/lib/typescript/src/hooks/useCactusSTT.d.ts.map +1 -0
  69. package/lib/typescript/src/index.d.ts +4 -1
  70. package/lib/typescript/src/index.d.ts.map +1 -1
  71. package/lib/typescript/src/native/Cactus.d.ts +9 -2
  72. package/lib/typescript/src/native/Cactus.d.ts.map +1 -1
  73. package/lib/typescript/src/native/CactusFileSystem.d.ts +1 -1
  74. package/lib/typescript/src/native/CactusFileSystem.d.ts.map +1 -1
  75. package/lib/typescript/src/native/CactusImage.d.ts +6 -0
  76. package/lib/typescript/src/native/CactusImage.d.ts.map +1 -0
  77. package/lib/typescript/src/native/index.d.ts +1 -0
  78. package/lib/typescript/src/native/index.d.ts.map +1 -1
  79. package/lib/typescript/src/specs/Cactus.nitro.d.ts +3 -0
  80. package/lib/typescript/src/specs/Cactus.nitro.d.ts.map +1 -1
  81. package/lib/typescript/src/specs/CactusImage.nitro.d.ts +9 -0
  82. package/lib/typescript/src/specs/CactusImage.nitro.d.ts.map +1 -0
  83. package/lib/typescript/src/telemetry/Telemetry.d.ts +5 -1
  84. package/lib/typescript/src/telemetry/Telemetry.d.ts.map +1 -1
  85. package/lib/typescript/src/types/CactusLM.d.ts +8 -5
  86. package/lib/typescript/src/types/CactusLM.d.ts.map +1 -1
  87. package/lib/typescript/src/types/CactusSTT.d.ts +37 -0
  88. package/lib/typescript/src/types/CactusSTT.d.ts.map +1 -0
  89. package/nitro.json +4 -0
  90. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.cpp +81 -0
  91. package/nitrogen/generated/android/c++/JHybridCactusImageSpec.hpp +66 -0
  92. package/nitrogen/generated/android/cactus+autolinking.cmake +2 -0
  93. package/nitrogen/generated/android/cactusOnLoad.cpp +10 -0
  94. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusImageSpec.kt +62 -0
  95. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.cpp +17 -0
  96. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.hpp +17 -0
  97. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Umbrella.hpp +5 -0
  98. package/nitrogen/generated/ios/CactusAutolinking.mm +8 -0
  99. package/nitrogen/generated/ios/CactusAutolinking.swift +15 -0
  100. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.cpp +11 -0
  101. package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.hpp +85 -0
  102. package/nitrogen/generated/ios/swift/HybridCactusImageSpec.swift +58 -0
  103. package/nitrogen/generated/ios/swift/HybridCactusImageSpec_cxx.swift +158 -0
  104. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.cpp +22 -0
  105. package/nitrogen/generated/shared/c++/HybridCactusImageSpec.hpp +64 -0
  106. package/nitrogen/generated/shared/c++/HybridCactusSpec.cpp +3 -0
  107. package/nitrogen/generated/shared/c++/HybridCactusSpec.hpp +3 -0
  108. package/package.json +1 -1
  109. package/src/api/Database.ts +27 -0
  110. package/src/api/RemoteLM.ts +273 -0
  111. package/src/classes/CactusLM.ts +72 -38
  112. package/src/classes/CactusSTT.ts +188 -0
  113. package/src/config/CactusConfig.ts +4 -0
  114. package/src/constants/packageVersion.ts +1 -1
  115. package/src/hooks/useCactusLM.ts +45 -17
  116. package/src/hooks/useCactusSTT.ts +285 -0
  117. package/src/index.tsx +14 -2
  118. package/src/native/Cactus.ts +94 -4
  119. package/src/native/CactusFileSystem.ts +2 -2
  120. package/src/native/CactusImage.ts +20 -0
  121. package/src/native/index.ts +1 -0
  122. package/src/specs/Cactus.nitro.ts +9 -0
  123. package/src/specs/CactusImage.nitro.ts +12 -0
  124. package/src/telemetry/Telemetry.ts +78 -1
  125. package/src/types/CactusLM.ts +9 -5
  126. package/src/types/CactusSTT.ts +42 -0
@@ -7,7 +7,8 @@ import type {
7
7
  CactusLMCompleteResult,
8
8
  CactusLMEmbedParams,
9
9
  CactusLMEmbedResult,
10
- CactusLMGetModelsParams,
10
+ CactusLMImageEmbedParams,
11
+ CactusLMImageEmbedResult,
11
12
  CactusLMCompleteParams,
12
13
  CactusLMDownloadParams,
13
14
  } from '../types/CactusLM';
@@ -84,10 +85,15 @@ export const useCactusLM = ({
84
85
  throw new Error(message);
85
86
  }
86
87
 
88
+ setError(null);
89
+
90
+ if (isDownloaded) {
91
+ return;
92
+ }
93
+
87
94
  const thisModel = currentModelRef.current;
88
95
  const thisDownloadId = ++currentDownloadIdRef.current;
89
96
 
90
- setError(null);
91
97
  setDownloadProgress(0);
92
98
  setIsDownloading(true);
93
99
  try {
@@ -135,7 +141,7 @@ export const useCactusLM = ({
135
141
  setDownloadProgress(0);
136
142
  }
137
143
  },
138
- [cactusLM, isDownloading]
144
+ [cactusLM, isDownloading, isDownloaded]
139
145
  );
140
146
 
141
147
  const init = useCallback(async () => {
@@ -163,6 +169,7 @@ export const useCactusLM = ({
163
169
  options,
164
170
  tools,
165
171
  onToken,
172
+ mode,
166
173
  }: CactusLMCompleteParams): Promise<CactusLMCompleteResult> => {
167
174
  if (isGenerating) {
168
175
  const message = 'CactusLM is already generating';
@@ -182,6 +189,7 @@ export const useCactusLM = ({
182
189
  setCompletion((prev) => prev + token);
183
190
  onToken?.(token);
184
191
  },
192
+ mode,
185
193
  });
186
194
  } catch (e) {
187
195
  setError(getErrorMessage(e));
@@ -215,6 +223,30 @@ export const useCactusLM = ({
215
223
  [cactusLM, isGenerating]
216
224
  );
217
225
 
226
+ const imageEmbed = useCallback(
227
+ async ({
228
+ imagePath,
229
+ }: CactusLMImageEmbedParams): Promise<CactusLMImageEmbedResult> => {
230
+ if (isGenerating) {
231
+ const message = 'CactusLM is already generating';
232
+ setError(message);
233
+ throw new Error(message);
234
+ }
235
+
236
+ setError(null);
237
+ setIsGenerating(true);
238
+ try {
239
+ return await cactusLM.imageEmbed({ imagePath });
240
+ } catch (e) {
241
+ setError(getErrorMessage(e));
242
+ throw e;
243
+ } finally {
244
+ setIsGenerating(false);
245
+ }
246
+ },
247
+ [cactusLM, isGenerating]
248
+ );
249
+
218
250
  const stop = useCallback(async () => {
219
251
  setError(null);
220
252
  try {
@@ -249,20 +281,15 @@ export const useCactusLM = ({
249
281
  }
250
282
  }, [cactusLM]);
251
283
 
252
- const getModels = useCallback(
253
- async ({ forceRefresh }: CactusLMGetModelsParams = {}): Promise<
254
- CactusModel[]
255
- > => {
256
- setError(null);
257
- try {
258
- return await cactusLM.getModels({ forceRefresh });
259
- } catch (e) {
260
- setError(getErrorMessage(e));
261
- throw e;
262
- }
263
- },
264
- [cactusLM]
265
- );
284
+ const getModels = useCallback(async (): Promise<CactusModel[]> => {
285
+ setError(null);
286
+ try {
287
+ return await cactusLM.getModels();
288
+ } catch (e) {
289
+ setError(getErrorMessage(e));
290
+ throw e;
291
+ }
292
+ }, [cactusLM]);
266
293
 
267
294
  return {
268
295
  completion,
@@ -277,6 +304,7 @@ export const useCactusLM = ({
277
304
  init,
278
305
  complete,
279
306
  embed,
307
+ imageEmbed,
280
308
  reset,
281
309
  stop,
282
310
  destroy,
@@ -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 [transcription, setTranscription] = 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
+ setTranscription('');
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
+ setTranscription('');
178
+ setIsGenerating(true);
179
+ try {
180
+ return await cactusSTT.transcribe({
181
+ audioFilePath,
182
+ prompt,
183
+ options,
184
+ onToken: (token) => {
185
+ setTranscription((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
+ setTranscription('');
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
+ setTranscription('');
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
+ transcription,
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,11 +1,16 @@
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 =
@@ -22,11 +27,30 @@ export class Cactus {
22
27
  public async complete(
23
28
  messages: Message[],
24
29
  responseBufferSize: number,
25
- options?: Options,
26
- tools?: Tool[],
30
+ options?: CompleteOptions,
31
+ tools?: { type: 'function'; function: Tool }[],
27
32
  callback?: (token: string, tokenId: number) => void
28
33
  ): Promise<CactusLMCompleteResult> {
29
- 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);
30
54
  const optionsJson = options
31
55
  ? JSON.stringify({
32
56
  temperature: options.temperature,
@@ -65,10 +89,76 @@ export class Cactus {
65
89
  }
66
90
  }
67
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
+
68
135
  public embed(text: string, embeddingBufferSize: number): Promise<number[]> {
69
136
  return this.hybridCactus.embed(text, embeddingBufferSize);
70
137
  }
71
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
+
72
162
  public reset(): Promise<void> {
73
163
  return this.hybridCactus.reset();
74
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';
@@ -13,7 +13,16 @@ export interface Cactus extends HybridObject<{ ios: 'c++'; android: 'c++' }> {
13
13
  toolsJson?: string,
14
14
  callback?: (token: string, tokenId: number) => void
15
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>;
16
23
  embed(text: string, embeddingBufferSize: number): Promise<number[]>;
24
+ imageEmbed(imagePath: string, embeddingBufferSize: number): Promise<number[]>;
25
+ audioEmbed(audioPath: string, embeddingBufferSize: number): Promise<number[]>;
17
26
  reset(): Promise<void>;
18
27
  stop(): Promise<void>;
19
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
+ }