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.
- package/README.md +378 -21
- package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusCrypto.kt +23 -15
- package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusDeviceInfo.kt +12 -9
- package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusFileSystem.kt +42 -41
- package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusImage.kt +81 -0
- package/android/src/main/jniLibs/arm64-v8a/libcactus.a +0 -0
- package/cpp/HybridCactus.cpp +105 -0
- package/cpp/HybridCactus.hpp +13 -0
- package/cpp/cactus_ffi.h +27 -0
- package/ios/HybridCactusImage.swift +53 -0
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +27 -0
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/engine.h +37 -5
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/ffi_utils.h +10 -9
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/graph.h +49 -7
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel.h +31 -0
- package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
- package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_ffi.h +27 -0
- package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/engine.h +37 -5
- package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/ffi_utils.h +10 -9
- package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/graph.h +49 -7
- package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel.h +31 -0
- package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/cactus +0 -0
- package/lib/module/api/Database.js +23 -0
- package/lib/module/api/Database.js.map +1 -1
- package/lib/module/api/RemoteLM.js +201 -0
- package/lib/module/api/RemoteLM.js.map +1 -0
- package/lib/module/classes/CactusLM.js +52 -26
- package/lib/module/classes/CactusLM.js.map +1 -1
- package/lib/module/classes/CactusSTT.js +139 -0
- package/lib/module/classes/CactusSTT.js.map +1 -0
- package/lib/module/config/CactusConfig.js +4 -0
- package/lib/module/config/CactusConfig.js.map +1 -1
- package/lib/module/constants/packageVersion.js +1 -1
- package/lib/module/hooks/useCactusLM.js +33 -10
- package/lib/module/hooks/useCactusLM.js.map +1 -1
- package/lib/module/hooks/useCactusSTT.js +234 -0
- package/lib/module/hooks/useCactusSTT.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/native/Cactus.js +50 -1
- package/lib/module/native/Cactus.js.map +1 -1
- package/lib/module/native/CactusFileSystem.js +2 -3
- package/lib/module/native/CactusFileSystem.js.map +1 -1
- package/lib/module/native/CactusImage.js +13 -0
- package/lib/module/native/CactusImage.js.map +1 -0
- package/lib/module/native/index.js +1 -0
- package/lib/module/native/index.js.map +1 -1
- package/lib/module/specs/CactusImage.nitro.js +4 -0
- package/lib/module/specs/CactusImage.nitro.js.map +1 -0
- package/lib/module/telemetry/Telemetry.js +53 -1
- package/lib/module/telemetry/Telemetry.js.map +1 -1
- package/lib/module/types/CactusSTT.js +2 -0
- package/lib/module/types/CactusSTT.js.map +1 -0
- package/lib/typescript/src/api/Database.d.ts +1 -0
- package/lib/typescript/src/api/Database.d.ts.map +1 -1
- package/lib/typescript/src/api/RemoteLM.d.ts +14 -0
- package/lib/typescript/src/api/RemoteLM.d.ts.map +1 -0
- package/lib/typescript/src/classes/CactusLM.d.ts +6 -4
- package/lib/typescript/src/classes/CactusLM.d.ts.map +1 -1
- package/lib/typescript/src/classes/CactusSTT.d.ts +26 -0
- package/lib/typescript/src/classes/CactusSTT.d.ts.map +1 -0
- package/lib/typescript/src/config/CactusConfig.d.ts +1 -0
- package/lib/typescript/src/config/CactusConfig.d.ts.map +1 -1
- package/lib/typescript/src/constants/packageVersion.d.ts +1 -1
- package/lib/typescript/src/hooks/useCactusLM.d.ts +4 -3
- package/lib/typescript/src/hooks/useCactusLM.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useCactusSTT.d.ts +20 -0
- package/lib/typescript/src/hooks/useCactusSTT.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native/Cactus.d.ts +9 -2
- package/lib/typescript/src/native/Cactus.d.ts.map +1 -1
- package/lib/typescript/src/native/CactusFileSystem.d.ts +1 -1
- package/lib/typescript/src/native/CactusFileSystem.d.ts.map +1 -1
- package/lib/typescript/src/native/CactusImage.d.ts +6 -0
- package/lib/typescript/src/native/CactusImage.d.ts.map +1 -0
- package/lib/typescript/src/native/index.d.ts +1 -0
- package/lib/typescript/src/native/index.d.ts.map +1 -1
- package/lib/typescript/src/specs/Cactus.nitro.d.ts +3 -0
- package/lib/typescript/src/specs/Cactus.nitro.d.ts.map +1 -1
- package/lib/typescript/src/specs/CactusImage.nitro.d.ts +9 -0
- package/lib/typescript/src/specs/CactusImage.nitro.d.ts.map +1 -0
- package/lib/typescript/src/telemetry/Telemetry.d.ts +5 -1
- package/lib/typescript/src/telemetry/Telemetry.d.ts.map +1 -1
- package/lib/typescript/src/types/CactusLM.d.ts +8 -5
- package/lib/typescript/src/types/CactusLM.d.ts.map +1 -1
- package/lib/typescript/src/types/CactusSTT.d.ts +37 -0
- package/lib/typescript/src/types/CactusSTT.d.ts.map +1 -0
- package/nitro.json +4 -0
- package/nitrogen/generated/android/c++/JHybridCactusImageSpec.cpp +81 -0
- package/nitrogen/generated/android/c++/JHybridCactusImageSpec.hpp +66 -0
- package/nitrogen/generated/android/cactus+autolinking.cmake +2 -0
- package/nitrogen/generated/android/cactusOnLoad.cpp +10 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusImageSpec.kt +62 -0
- package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.hpp +17 -0
- package/nitrogen/generated/ios/Cactus-Swift-Cxx-Umbrella.hpp +5 -0
- package/nitrogen/generated/ios/CactusAutolinking.mm +8 -0
- package/nitrogen/generated/ios/CactusAutolinking.swift +15 -0
- package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridCactusImageSpecSwift.hpp +85 -0
- package/nitrogen/generated/ios/swift/HybridCactusImageSpec.swift +58 -0
- package/nitrogen/generated/ios/swift/HybridCactusImageSpec_cxx.swift +158 -0
- package/nitrogen/generated/shared/c++/HybridCactusImageSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridCactusImageSpec.hpp +64 -0
- package/nitrogen/generated/shared/c++/HybridCactusSpec.cpp +3 -0
- package/nitrogen/generated/shared/c++/HybridCactusSpec.hpp +3 -0
- package/package.json +1 -1
- package/src/api/Database.ts +27 -0
- package/src/api/RemoteLM.ts +273 -0
- package/src/classes/CactusLM.ts +72 -38
- package/src/classes/CactusSTT.ts +188 -0
- package/src/config/CactusConfig.ts +4 -0
- package/src/constants/packageVersion.ts +1 -1
- package/src/hooks/useCactusLM.ts +45 -17
- package/src/hooks/useCactusSTT.ts +285 -0
- package/src/index.tsx +14 -2
- package/src/native/Cactus.ts +94 -4
- package/src/native/CactusFileSystem.ts +2 -2
- package/src/native/CactusImage.ts +20 -0
- package/src/native/index.ts +1 -0
- package/src/specs/Cactus.nitro.ts +9 -0
- package/src/specs/CactusImage.nitro.ts +12 -0
- package/src/telemetry/Telemetry.ts +78 -1
- package/src/types/CactusLM.ts +9 -5
- package/src/types/CactusSTT.ts +42 -0
package/src/hooks/useCactusLM.ts
CHANGED
|
@@ -7,7 +7,8 @@ import type {
|
|
|
7
7
|
CactusLMCompleteResult,
|
|
8
8
|
CactusLMEmbedParams,
|
|
9
9
|
CactusLMEmbedResult,
|
|
10
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
15
|
+
CompleteOptions,
|
|
14
16
|
Tool,
|
|
15
17
|
CactusLMCompleteParams,
|
|
16
18
|
CactusLMCompleteResult,
|
|
17
19
|
CactusLMEmbedParams,
|
|
18
20
|
CactusLMEmbedResult,
|
|
19
|
-
|
|
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';
|
package/src/native/Cactus.ts
CHANGED
|
@@ -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
|
-
|
|
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?:
|
|
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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/native/index.ts
CHANGED
|
@@ -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
|
+
}
|