cactus-react-native 1.4.0 → 1.7.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 (226) hide show
  1. package/Cactus.podspec +1 -1
  2. package/README.md +465 -174
  3. package/android/CMakeLists.txt +24 -5
  4. package/android/src/main/jniLibs/arm64-v8a/libcactus.a +0 -0
  5. package/android/src/main/jniLibs/arm64-v8a/libcurl.a +0 -0
  6. package/android/src/main/jniLibs/arm64-v8a/libmbedcrypto.a +0 -0
  7. package/android/src/main/jniLibs/arm64-v8a/libmbedtls.a +0 -0
  8. package/android/src/main/jniLibs/arm64-v8a/libmbedx509.a +0 -0
  9. package/cpp/HybridCactus.cpp +157 -6
  10. package/cpp/HybridCactus.hpp +20 -3
  11. package/cpp/cactus_ffi.h +65 -30
  12. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus.h +0 -1
  13. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_ffi.h +65 -30
  14. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_utils.h +357 -122
  15. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/engine.h +184 -63
  16. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/gemma_tools.h +549 -0
  17. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/graph.h +153 -27
  18. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel.h +90 -178
  19. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/kernel_utils.h +276 -151
  20. package/ios/cactus.xcframework/ios-arm64/cactus.framework/cactus +0 -0
  21. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus.h +0 -1
  22. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_ffi.h +65 -30
  23. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_utils.h +357 -122
  24. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/engine.h +184 -63
  25. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/gemma_tools.h +549 -0
  26. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/graph.h +153 -27
  27. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel.h +90 -178
  28. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/kernel_utils.h +276 -151
  29. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/cactus +0 -0
  30. package/lib/module/classes/CactusLM.js +43 -58
  31. package/lib/module/classes/CactusLM.js.map +1 -1
  32. package/lib/module/classes/CactusSTT.js +64 -38
  33. package/lib/module/classes/CactusSTT.js.map +1 -1
  34. package/lib/module/classes/CactusVAD.js +95 -0
  35. package/lib/module/classes/CactusVAD.js.map +1 -0
  36. package/lib/module/hooks/useCactusLM.js +23 -15
  37. package/lib/module/hooks/useCactusLM.js.map +1 -1
  38. package/lib/module/hooks/useCactusSTT.js +85 -28
  39. package/lib/module/hooks/useCactusSTT.js.map +1 -1
  40. package/lib/module/hooks/useCactusVAD.js +171 -0
  41. package/lib/module/hooks/useCactusVAD.js.map +1 -0
  42. package/lib/module/index.js +2 -3
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/modelRegistry.js +52 -0
  45. package/lib/module/modelRegistry.js.map +1 -0
  46. package/lib/module/native/Cactus.js +107 -8
  47. package/lib/module/native/Cactus.js.map +1 -1
  48. package/lib/module/native/CactusIndex.js.map +1 -1
  49. package/lib/module/native/index.js +0 -3
  50. package/lib/module/native/index.js.map +1 -1
  51. package/lib/module/types/CactusLM.js +2 -0
  52. package/lib/module/types/CactusSTT.js +2 -0
  53. package/lib/module/types/CactusVAD.js +4 -0
  54. package/lib/module/types/{CactusModel.js.map → CactusVAD.js.map} +1 -1
  55. package/lib/module/types/common.js +2 -0
  56. package/lib/module/types/{CactusSTTModel.js.map → common.js.map} +1 -1
  57. package/lib/typescript/src/classes/CactusLM.d.ts +8 -6
  58. package/lib/typescript/src/classes/CactusLM.d.ts.map +1 -1
  59. package/lib/typescript/src/classes/CactusSTT.d.ts +11 -6
  60. package/lib/typescript/src/classes/CactusSTT.d.ts.map +1 -1
  61. package/lib/typescript/src/classes/CactusVAD.d.ts +20 -0
  62. package/lib/typescript/src/classes/CactusVAD.d.ts.map +1 -0
  63. package/lib/typescript/src/hooks/useCactusLM.d.ts +3 -3
  64. package/lib/typescript/src/hooks/useCactusLM.d.ts.map +1 -1
  65. package/lib/typescript/src/hooks/useCactusSTT.d.ts +11 -5
  66. package/lib/typescript/src/hooks/useCactusSTT.d.ts.map +1 -1
  67. package/lib/typescript/src/hooks/useCactusVAD.d.ts +15 -0
  68. package/lib/typescript/src/hooks/useCactusVAD.d.ts.map +1 -0
  69. package/lib/typescript/src/index.d.ts +7 -6
  70. package/lib/typescript/src/index.d.ts.map +1 -1
  71. package/lib/typescript/src/modelRegistry.d.ts +5 -0
  72. package/lib/typescript/src/modelRegistry.d.ts.map +1 -0
  73. package/lib/typescript/src/native/Cactus.d.ts +12 -6
  74. package/lib/typescript/src/native/Cactus.d.ts.map +1 -1
  75. package/lib/typescript/src/native/CactusIndex.d.ts +2 -2
  76. package/lib/typescript/src/native/CactusIndex.d.ts.map +1 -1
  77. package/lib/typescript/src/native/index.d.ts +0 -3
  78. package/lib/typescript/src/native/index.d.ts.map +1 -1
  79. package/lib/typescript/src/specs/Cactus.nitro.d.ts +6 -1
  80. package/lib/typescript/src/specs/Cactus.nitro.d.ts.map +1 -1
  81. package/lib/typescript/src/types/CactusIndex.d.ts +2 -2
  82. package/lib/typescript/src/types/CactusIndex.d.ts.map +1 -1
  83. package/lib/typescript/src/types/CactusLM.d.ts +19 -9
  84. package/lib/typescript/src/types/CactusLM.d.ts.map +1 -1
  85. package/lib/typescript/src/types/CactusSTT.d.ts +45 -4
  86. package/lib/typescript/src/types/CactusSTT.d.ts.map +1 -1
  87. package/lib/typescript/src/types/CactusVAD.d.ts +34 -0
  88. package/lib/typescript/src/types/CactusVAD.d.ts.map +1 -0
  89. package/lib/typescript/src/types/common.d.ts +23 -0
  90. package/lib/typescript/src/types/common.d.ts.map +1 -0
  91. package/nitro.json +0 -11
  92. package/nitrogen/generated/android/cactus+autolinking.cmake +0 -5
  93. package/nitrogen/generated/android/cactusOnLoad.cpp +0 -30
  94. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.cpp +0 -50
  95. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Bridge.hpp +9 -147
  96. package/nitrogen/generated/ios/Cactus-Swift-Cxx-Umbrella.hpp +0 -13
  97. package/nitrogen/generated/ios/CactusAutolinking.mm +0 -26
  98. package/nitrogen/generated/ios/CactusAutolinking.swift +0 -30
  99. package/nitrogen/generated/shared/c++/HybridCactusSpec.cpp +5 -0
  100. package/nitrogen/generated/shared/c++/HybridCactusSpec.hpp +6 -1
  101. package/package.json +3 -3
  102. package/src/classes/CactusLM.ts +59 -74
  103. package/src/classes/CactusSTT.ts +92 -49
  104. package/src/classes/CactusVAD.ts +129 -0
  105. package/src/hooks/useCactusLM.ts +26 -9
  106. package/src/hooks/useCactusSTT.ts +105 -44
  107. package/src/hooks/useCactusVAD.ts +215 -0
  108. package/src/index.tsx +20 -10
  109. package/src/modelRegistry.ts +65 -0
  110. package/src/native/Cactus.ts +130 -14
  111. package/src/native/CactusIndex.ts +2 -2
  112. package/src/native/index.ts +0 -3
  113. package/src/specs/Cactus.nitro.ts +11 -2
  114. package/src/types/CactusIndex.ts +2 -2
  115. package/src/types/CactusLM.ts +20 -9
  116. package/src/types/CactusSTT.ts +50 -4
  117. package/src/types/CactusVAD.ts +39 -0
  118. package/src/types/common.ts +23 -0
  119. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusCrypto.kt +0 -46
  120. package/android/src/main/java/com/margelo/nitro/cactus/HybridCactusDeviceInfo.kt +0 -27
  121. package/android/src/main/jniLibs/arm64-v8a/libcactus_util.a +0 -0
  122. package/cpp/HybridCactusUtil.cpp +0 -47
  123. package/cpp/HybridCactusUtil.hpp +0 -27
  124. package/cpp/cactus_util.h +0 -25
  125. package/ios/HybridCactusCrypto.swift +0 -37
  126. package/ios/HybridCactusDeviceInfo.swift +0 -32
  127. package/ios/cactus.xcframework/ios-arm64/cactus.framework/Headers/cactus_telemetry.h +0 -656
  128. package/ios/cactus.xcframework/ios-arm64-simulator/cactus.framework/Headers/cactus_telemetry.h +0 -656
  129. package/ios/cactus_util.xcframework/Info.plist +0 -39
  130. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Headers/cactus_util.h +0 -25
  131. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Headers/database.h +0 -27
  132. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Headers/ios_utils.h +0 -10
  133. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Headers/logging.h +0 -25
  134. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/Info.plist +0 -0
  135. package/ios/cactus_util.xcframework/ios-arm64/cactus_util.framework/cactus_util +0 -0
  136. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Headers/cactus_util.h +0 -25
  137. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Headers/database.h +0 -27
  138. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Headers/ios_utils.h +0 -10
  139. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Headers/logging.h +0 -25
  140. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/Info.plist +0 -0
  141. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/_CodeSignature/CodeResources +0 -135
  142. package/ios/cactus_util.xcframework/ios-arm64-simulator/cactus_util.framework/cactus_util +0 -0
  143. package/lib/module/api/Database.js +0 -137
  144. package/lib/module/api/Database.js.map +0 -1
  145. package/lib/module/api/RemoteLM.js +0 -201
  146. package/lib/module/api/RemoteLM.js.map +0 -1
  147. package/lib/module/config/CactusConfig.js +0 -12
  148. package/lib/module/config/CactusConfig.js.map +0 -1
  149. package/lib/module/native/CactusCrypto.js +0 -10
  150. package/lib/module/native/CactusCrypto.js.map +0 -1
  151. package/lib/module/native/CactusDeviceInfo.js +0 -13
  152. package/lib/module/native/CactusDeviceInfo.js.map +0 -1
  153. package/lib/module/native/CactusUtil.js +0 -36
  154. package/lib/module/native/CactusUtil.js.map +0 -1
  155. package/lib/module/specs/CactusCrypto.nitro.js +0 -4
  156. package/lib/module/specs/CactusCrypto.nitro.js.map +0 -1
  157. package/lib/module/specs/CactusDeviceInfo.nitro.js +0 -4
  158. package/lib/module/specs/CactusDeviceInfo.nitro.js.map +0 -1
  159. package/lib/module/specs/CactusUtil.nitro.js +0 -4
  160. package/lib/module/specs/CactusUtil.nitro.js.map +0 -1
  161. package/lib/module/telemetry/Telemetry.js +0 -154
  162. package/lib/module/telemetry/Telemetry.js.map +0 -1
  163. package/lib/module/types/CactusModel.js +0 -2
  164. package/lib/module/types/CactusSTTModel.js +0 -2
  165. package/lib/typescript/src/api/Database.d.ts +0 -18
  166. package/lib/typescript/src/api/Database.d.ts.map +0 -1
  167. package/lib/typescript/src/api/RemoteLM.d.ts +0 -14
  168. package/lib/typescript/src/api/RemoteLM.d.ts.map +0 -1
  169. package/lib/typescript/src/config/CactusConfig.d.ts +0 -7
  170. package/lib/typescript/src/config/CactusConfig.d.ts.map +0 -1
  171. package/lib/typescript/src/native/CactusCrypto.d.ts +0 -5
  172. package/lib/typescript/src/native/CactusCrypto.d.ts.map +0 -1
  173. package/lib/typescript/src/native/CactusDeviceInfo.d.ts +0 -7
  174. package/lib/typescript/src/native/CactusDeviceInfo.d.ts.map +0 -1
  175. package/lib/typescript/src/native/CactusUtil.d.ts +0 -6
  176. package/lib/typescript/src/native/CactusUtil.d.ts.map +0 -1
  177. package/lib/typescript/src/specs/CactusCrypto.nitro.d.ts +0 -8
  178. package/lib/typescript/src/specs/CactusCrypto.nitro.d.ts.map +0 -1
  179. package/lib/typescript/src/specs/CactusDeviceInfo.nitro.d.ts +0 -16
  180. package/lib/typescript/src/specs/CactusDeviceInfo.nitro.d.ts.map +0 -1
  181. package/lib/typescript/src/specs/CactusUtil.nitro.d.ts +0 -10
  182. package/lib/typescript/src/specs/CactusUtil.nitro.d.ts.map +0 -1
  183. package/lib/typescript/src/telemetry/Telemetry.d.ts +0 -34
  184. package/lib/typescript/src/telemetry/Telemetry.d.ts.map +0 -1
  185. package/lib/typescript/src/types/CactusModel.d.ts +0 -13
  186. package/lib/typescript/src/types/CactusModel.d.ts.map +0 -1
  187. package/lib/typescript/src/types/CactusSTTModel.d.ts +0 -8
  188. package/lib/typescript/src/types/CactusSTTModel.d.ts.map +0 -1
  189. package/nitrogen/generated/android/c++/JDeviceInfo.hpp +0 -74
  190. package/nitrogen/generated/android/c++/JHybridCactusCryptoSpec.cpp +0 -65
  191. package/nitrogen/generated/android/c++/JHybridCactusCryptoSpec.hpp +0 -65
  192. package/nitrogen/generated/android/c++/JHybridCactusDeviceInfoSpec.cpp +0 -85
  193. package/nitrogen/generated/android/c++/JHybridCactusDeviceInfoSpec.hpp +0 -66
  194. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/DeviceInfo.kt +0 -50
  195. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusCryptoSpec.kt +0 -58
  196. package/nitrogen/generated/android/kotlin/com/margelo/nitro/cactus/HybridCactusDeviceInfoSpec.kt +0 -62
  197. package/nitrogen/generated/ios/c++/HybridCactusCryptoSpecSwift.cpp +0 -11
  198. package/nitrogen/generated/ios/c++/HybridCactusCryptoSpecSwift.hpp +0 -77
  199. package/nitrogen/generated/ios/c++/HybridCactusDeviceInfoSpecSwift.cpp +0 -11
  200. package/nitrogen/generated/ios/c++/HybridCactusDeviceInfoSpecSwift.hpp +0 -88
  201. package/nitrogen/generated/ios/swift/DeviceInfo.swift +0 -98
  202. package/nitrogen/generated/ios/swift/Func_void_DeviceInfo.swift +0 -47
  203. package/nitrogen/generated/ios/swift/Func_void_std__optional_std__string_.swift +0 -54
  204. package/nitrogen/generated/ios/swift/HybridCactusCryptoSpec.swift +0 -57
  205. package/nitrogen/generated/ios/swift/HybridCactusCryptoSpec_cxx.swift +0 -139
  206. package/nitrogen/generated/ios/swift/HybridCactusDeviceInfoSpec.swift +0 -58
  207. package/nitrogen/generated/ios/swift/HybridCactusDeviceInfoSpec_cxx.swift +0 -164
  208. package/nitrogen/generated/shared/c++/DeviceInfo.hpp +0 -92
  209. package/nitrogen/generated/shared/c++/HybridCactusCryptoSpec.cpp +0 -21
  210. package/nitrogen/generated/shared/c++/HybridCactusCryptoSpec.hpp +0 -63
  211. package/nitrogen/generated/shared/c++/HybridCactusDeviceInfoSpec.cpp +0 -22
  212. package/nitrogen/generated/shared/c++/HybridCactusDeviceInfoSpec.hpp +0 -67
  213. package/nitrogen/generated/shared/c++/HybridCactusUtilSpec.cpp +0 -23
  214. package/nitrogen/generated/shared/c++/HybridCactusUtilSpec.hpp +0 -66
  215. package/src/api/Database.ts +0 -188
  216. package/src/api/RemoteLM.ts +0 -273
  217. package/src/config/CactusConfig.ts +0 -11
  218. package/src/native/CactusCrypto.ts +0 -11
  219. package/src/native/CactusDeviceInfo.ts +0 -18
  220. package/src/native/CactusUtil.ts +0 -43
  221. package/src/specs/CactusCrypto.nitro.ts +0 -6
  222. package/src/specs/CactusDeviceInfo.nitro.ts +0 -15
  223. package/src/specs/CactusUtil.nitro.ts +0 -8
  224. package/src/telemetry/Telemetry.ts +0 -236
  225. package/src/types/CactusModel.ts +0 -15
  226. package/src/types/CactusSTTModel.ts +0 -10
@@ -6,37 +6,47 @@ import type {
6
6
  CactusSTTParams,
7
7
  CactusSTTAudioEmbedParams,
8
8
  CactusSTTAudioEmbedResult,
9
+ CactusSTTStreamTranscribeStartOptions,
10
+ CactusSTTStreamTranscribeProcessParams,
11
+ CactusSTTStreamTranscribeProcessResult,
12
+ CactusSTTStreamTranscribeStopResult,
9
13
  } from '../types/CactusSTT';
10
- import { Telemetry } from '../telemetry/Telemetry';
11
- import { CactusConfig } from '../config/CactusConfig';
12
- import { Database } from '../api/Database';
13
- import { getErrorMessage } from '../utils/error';
14
- import type { CactusSTTModel } from '../types/CactusSTTModel';
14
+ import { getRegistry } from '../modelRegistry';
15
+ import type { CactusModel } from '../types/common';
15
16
 
16
17
  export class CactusSTT {
17
18
  private readonly cactus = new Cactus();
18
19
 
19
20
  private readonly model: string;
20
- private readonly contextSize: number;
21
+ private readonly options: {
22
+ quantization: 'int4' | 'int8';
23
+ pro: boolean;
24
+ };
21
25
 
22
26
  private isDownloading = false;
23
27
  private isInitialized = false;
24
28
  private isGenerating = false;
29
+ private isStreamTranscribing = false;
25
30
 
26
31
  private static readonly defaultModel = 'whisper-small';
27
- private static readonly defaultContextSize = 2048;
32
+ private static readonly defaultOptions = {
33
+ quantization: 'int8' as const,
34
+ pro: false,
35
+ };
28
36
  private static readonly defaultPrompt =
29
37
  '<|startoftranscript|><|en|><|transcribe|><|notimestamps|>';
30
38
  private static readonly defaultTranscribeOptions = {
31
- maxTokens: 512,
39
+ maxTokens: 384,
32
40
  };
33
41
  private static readonly defaultEmbedBufferSize = 4096;
34
42
 
35
- constructor({ model, contextSize }: CactusSTTParams = {}) {
36
- Telemetry.init(CactusConfig.telemetryToken);
37
-
43
+ constructor({ model, options }: CactusSTTParams = {}) {
38
44
  this.model = model ?? CactusSTT.defaultModel;
39
- this.contextSize = contextSize ?? CactusSTT.defaultContextSize;
45
+ this.options = {
46
+ quantization:
47
+ options?.quantization ?? CactusSTT.defaultOptions.quantization,
48
+ pro: options?.pro ?? CactusSTT.defaultOptions.pro,
49
+ };
40
50
  }
41
51
 
42
52
  public async download({
@@ -51,17 +61,26 @@ export class CactusSTT {
51
61
  throw new Error('CactusSTT is already downloading');
52
62
  }
53
63
 
54
- if (await CactusFileSystem.modelExists(this.model)) {
64
+ if (await CactusFileSystem.modelExists(this.getModelName())) {
65
+ console.log('Model already exists', this.getModelName());
55
66
  onProgress?.(1.0);
56
67
  return;
57
68
  }
58
69
 
59
70
  this.isDownloading = true;
60
71
  try {
61
- const model = await Database.getSTTModel(this.model);
72
+ const registry = await getRegistry();
73
+ const modelConfig =
74
+ registry[this.model]?.quantization[this.options.quantization];
75
+ const url = this.options.pro ? modelConfig?.pro?.apple : modelConfig?.url;
76
+
77
+ if (!url) {
78
+ throw new Error(`Model ${this.model} with specified options not found`);
79
+ }
80
+
62
81
  await CactusFileSystem.downloadModel(
63
- this.model,
64
- model.downloadUrl,
82
+ this.getModelName(),
83
+ url,
65
84
  onProgress
66
85
  );
67
86
  } finally {
@@ -78,20 +97,19 @@ export class CactusSTT {
78
97
  if (this.isModelPath(this.model)) {
79
98
  modelPath = this.model.replace('file://', '');
80
99
  } else {
81
- if (!(await CactusFileSystem.modelExists(this.model))) {
82
- throw new Error(`Model "${this.model}" is not downloaded`);
100
+ if (!(await CactusFileSystem.modelExists(this.getModelName()))) {
101
+ console.log('Model does not exist', this.getModelName());
102
+ throw new Error(
103
+ `Model "${this.model}" with options ${JSON.stringify(this.options)} is not downloaded`
104
+ );
83
105
  }
84
- modelPath = await CactusFileSystem.getModelPath(this.model);
106
+ modelPath = await CactusFileSystem.getModelPath(this.getModelName());
85
107
  }
86
108
 
87
- try {
88
- await this.cactus.init(modelPath, this.contextSize);
89
- Telemetry.logInit(this.model, true);
90
- this.isInitialized = true;
91
- } catch (error) {
92
- Telemetry.logInit(this.model, false, getErrorMessage(error));
93
- throw error;
94
- }
109
+ const cacheDir = await CactusFileSystem.getCactusDirectory();
110
+ await this.cactus.setTelemetryEnvironment(cacheDir);
111
+ await this.cactus.init(modelPath);
112
+ this.isInitialized = true;
95
113
  }
96
114
 
97
115
  public async transcribe({
@@ -115,28 +133,52 @@ export class CactusSTT {
115
133
 
116
134
  this.isGenerating = true;
117
135
  try {
118
- const result = await this.cactus.transcribe(
136
+ return await this.cactus.transcribe(
119
137
  audio,
120
138
  prompt,
121
139
  responseBufferSize,
122
140
  options,
123
141
  onToken
124
142
  );
125
- Telemetry.logTranscribe(
126
- this.model,
127
- result.success,
128
- result.success ? undefined : result.response,
129
- result
130
- );
131
- return result;
132
- } catch (error) {
133
- Telemetry.logTranscribe(this.model, false, getErrorMessage(error));
134
- throw error;
135
143
  } finally {
136
144
  this.isGenerating = false;
137
145
  }
138
146
  }
139
147
 
148
+ public async streamTranscribeStart(
149
+ options?: CactusSTTStreamTranscribeStartOptions
150
+ ): Promise<void> {
151
+ if (this.isStreamTranscribing) {
152
+ return;
153
+ }
154
+
155
+ await this.init();
156
+ await this.cactus.streamTranscribeStart(options);
157
+ this.isStreamTranscribing = true;
158
+ }
159
+
160
+ public async streamTranscribeProcess({
161
+ audio,
162
+ }: CactusSTTStreamTranscribeProcessParams): Promise<CactusSTTStreamTranscribeProcessResult> {
163
+ if (!this.isStreamTranscribing) {
164
+ throw new Error('CactusSTT stream transcribe is not started');
165
+ }
166
+
167
+ return this.cactus.streamTranscribeProcess(audio);
168
+ }
169
+
170
+ public async streamTranscribeStop(): Promise<CactusSTTStreamTranscribeStopResult> {
171
+ if (!this.isStreamTranscribing) {
172
+ throw new Error('CactusSTT stream transcribe is not started');
173
+ }
174
+
175
+ try {
176
+ return await this.cactus.streamTranscribeStop();
177
+ } finally {
178
+ this.isStreamTranscribing = false;
179
+ }
180
+ }
181
+
140
182
  public async audioEmbed({
141
183
  audioPath,
142
184
  }: CactusSTTAudioEmbedParams): Promise<CactusSTTAudioEmbedResult> {
@@ -152,11 +194,7 @@ export class CactusSTT {
152
194
  audioPath,
153
195
  CactusSTT.defaultEmbedBufferSize
154
196
  );
155
- Telemetry.logAudioEmbedding(this.model, true);
156
197
  return { embedding };
157
- } catch (error) {
158
- Telemetry.logAudioEmbedding(this.model, false, getErrorMessage(error));
159
- throw error;
160
198
  } finally {
161
199
  this.isGenerating = false;
162
200
  }
@@ -177,20 +215,25 @@ export class CactusSTT {
177
215
  }
178
216
 
179
217
  await this.stop();
180
- await this.cactus.destroy();
181
218
 
219
+ if (this.isStreamTranscribing) {
220
+ await this.cactus.streamTranscribeStop().catch(() => {});
221
+ this.isStreamTranscribing = false;
222
+ }
223
+
224
+ await this.cactus.destroy();
182
225
  this.isInitialized = false;
183
226
  }
184
227
 
185
- public async getModels(): Promise<CactusSTTModel[]> {
186
- const models = await Database.getSTTModels();
187
- for (const model of models) {
188
- model.isDownloaded = await CactusFileSystem.modelExists(model.slug);
189
- }
190
- return models;
228
+ public async getModels(): Promise<CactusModel[]> {
229
+ return Object.values(await getRegistry());
191
230
  }
192
231
 
193
232
  private isModelPath(model: string): boolean {
194
233
  return model.startsWith('file://') || model.startsWith('/');
195
234
  }
235
+
236
+ public getModelName(): string {
237
+ return `${this.model}-${this.options.quantization}${this.options.pro ? '-pro' : ''}`;
238
+ }
196
239
  }
@@ -0,0 +1,129 @@
1
+ import { Cactus, CactusFileSystem } from '../native';
2
+ import type {
3
+ CactusVADParams,
4
+ CactusVADDownloadParams,
5
+ CactusVADVadParams,
6
+ CactusVADResult,
7
+ } from '../types/CactusVAD';
8
+ import { getRegistry } from '../modelRegistry';
9
+ import type { CactusModel } from '../types/common';
10
+
11
+ export class CactusVAD {
12
+ private readonly cactus = new Cactus();
13
+
14
+ private readonly model: string;
15
+ private readonly options: {
16
+ quantization: 'int4' | 'int8';
17
+ pro: boolean;
18
+ };
19
+
20
+ private isDownloading = false;
21
+ private isInitialized = false;
22
+
23
+ private static readonly defaultModel = 'silero-vad';
24
+ private static readonly defaultOptions = {
25
+ quantization: 'int8' as const,
26
+ pro: false,
27
+ };
28
+
29
+ constructor({ model, options }: CactusVADParams = {}) {
30
+ this.model = model ?? CactusVAD.defaultModel;
31
+ this.options = {
32
+ quantization:
33
+ options?.quantization ?? CactusVAD.defaultOptions.quantization,
34
+ pro: options?.pro ?? CactusVAD.defaultOptions.pro,
35
+ };
36
+ }
37
+
38
+ public async download({
39
+ onProgress,
40
+ }: CactusVADDownloadParams = {}): Promise<void> {
41
+ if (this.isModelPath(this.model)) {
42
+ onProgress?.(1.0);
43
+ return;
44
+ }
45
+
46
+ if (this.isDownloading) {
47
+ throw new Error('CactusVAD is already downloading');
48
+ }
49
+
50
+ if (await CactusFileSystem.modelExists(this.getModelName())) {
51
+ console.log('Model already exists', this.getModelName());
52
+ onProgress?.(1.0);
53
+ return;
54
+ }
55
+
56
+ this.isDownloading = true;
57
+ try {
58
+ const registry = await getRegistry();
59
+ const modelConfig =
60
+ registry[this.model]?.quantization[this.options.quantization];
61
+ const url = this.options.pro ? modelConfig?.pro?.apple : modelConfig?.url;
62
+
63
+ if (!url) {
64
+ throw new Error(`Model ${this.model} with specified options not found`);
65
+ }
66
+
67
+ await CactusFileSystem.downloadModel(
68
+ this.getModelName(),
69
+ url,
70
+ onProgress
71
+ );
72
+ } finally {
73
+ this.isDownloading = false;
74
+ }
75
+ }
76
+
77
+ public async init(): Promise<void> {
78
+ if (this.isInitialized) {
79
+ return;
80
+ }
81
+
82
+ let modelPath: string;
83
+ if (this.isModelPath(this.model)) {
84
+ modelPath = this.model.replace('file://', '');
85
+ } else {
86
+ if (!(await CactusFileSystem.modelExists(this.getModelName()))) {
87
+ console.log('Model does not exist', this.getModelName());
88
+ throw new Error(
89
+ `Model "${this.model}" with options ${JSON.stringify(this.options)} is not downloaded`
90
+ );
91
+ }
92
+ modelPath = await CactusFileSystem.getModelPath(this.getModelName());
93
+ }
94
+
95
+ const cacheDir = await CactusFileSystem.getCactusDirectory();
96
+ await this.cactus.setTelemetryEnvironment(cacheDir);
97
+ await this.cactus.init(modelPath);
98
+ this.isInitialized = true;
99
+ }
100
+
101
+ public async vad({
102
+ audio,
103
+ options,
104
+ }: CactusVADVadParams): Promise<CactusVADResult> {
105
+ await this.init();
106
+ return this.cactus.vad(audio, options);
107
+ }
108
+
109
+ public async destroy(): Promise<void> {
110
+ if (!this.isInitialized) {
111
+ return;
112
+ }
113
+
114
+ await this.cactus.destroy();
115
+ this.isInitialized = false;
116
+ }
117
+
118
+ public async getModels(): Promise<CactusModel[]> {
119
+ return Object.values(await getRegistry());
120
+ }
121
+
122
+ private isModelPath(model: string): boolean {
123
+ return model.startsWith('file://') || model.startsWith('/');
124
+ }
125
+
126
+ public getModelName(): string {
127
+ return `${this.model}-${this.options.quantization}${this.options.pro ? '-pro' : ''}`;
128
+ }
129
+ }
@@ -16,15 +16,19 @@ import type {
16
16
  CactusLMImageEmbedResult,
17
17
  CactusLMDownloadParams,
18
18
  } from '../types/CactusLM';
19
- import type { CactusModel } from '../types/CactusModel';
19
+ import type { CactusModel } from '../types/common';
20
20
 
21
21
  export const useCactusLM = ({
22
- model = 'qwen3-0.6',
23
- contextSize = 2048,
22
+ model = 'qwen3-0.6b',
24
23
  corpusDir = undefined,
24
+ cacheIndex = false,
25
+ options: modelOptions = {
26
+ quantization: undefined,
27
+ pro: false,
28
+ },
25
29
  }: CactusLMParams = {}) => {
26
30
  const [cactusLM, setCactusLM] = useState(
27
- () => new CactusLM({ model, contextSize, corpusDir })
31
+ () => new CactusLM({ model, corpusDir, cacheIndex, options: modelOptions })
28
32
  );
29
33
 
30
34
  // State
@@ -44,7 +48,16 @@ export const useCactusLM = ({
44
48
  }, [model]);
45
49
 
46
50
  useEffect(() => {
47
- setCactusLM(new CactusLM({ model, contextSize, corpusDir }));
51
+ const newInstance = new CactusLM({
52
+ model,
53
+ corpusDir,
54
+ cacheIndex,
55
+ options: {
56
+ quantization: modelOptions.quantization,
57
+ pro: modelOptions.pro,
58
+ },
59
+ });
60
+ setCactusLM(newInstance);
48
61
 
49
62
  setCompletion('');
50
63
  setIsGenerating(false);
@@ -55,7 +68,7 @@ export const useCactusLM = ({
55
68
  setError(null);
56
69
 
57
70
  let mounted = true;
58
- CactusFileSystem.modelExists(model)
71
+ CactusFileSystem.modelExists(newInstance.getModelName())
59
72
  .then((exists) => {
60
73
  if (!mounted) {
61
74
  return;
@@ -73,7 +86,13 @@ export const useCactusLM = ({
73
86
  return () => {
74
87
  mounted = false;
75
88
  };
76
- }, [model, contextSize, corpusDir]);
89
+ }, [
90
+ model,
91
+ corpusDir,
92
+ cacheIndex,
93
+ modelOptions.quantization,
94
+ modelOptions.pro,
95
+ ]);
77
96
 
78
97
  useEffect(() => {
79
98
  return () => {
@@ -173,7 +192,6 @@ export const useCactusLM = ({
173
192
  options,
174
193
  tools,
175
194
  onToken,
176
- mode,
177
195
  }: CactusLMCompleteParams): Promise<CactusLMCompleteResult> => {
178
196
  if (isGenerating) {
179
197
  const message = 'CactusLM is already generating';
@@ -193,7 +211,6 @@ export const useCactusLM = ({
193
211
  setCompletion((prev) => prev + token);
194
212
  onToken?.(token);
195
213
  },
196
- mode,
197
214
  });
198
215
  } catch (e) {
199
216
  setError(getErrorMessage(e));
@@ -9,20 +9,31 @@ import type {
9
9
  CactusSTTDownloadParams,
10
10
  CactusSTTAudioEmbedParams,
11
11
  CactusSTTAudioEmbedResult,
12
+ CactusSTTStreamTranscribeStartOptions,
13
+ CactusSTTStreamTranscribeProcessParams,
14
+ CactusSTTStreamTranscribeProcessResult,
15
+ CactusSTTStreamTranscribeStopResult,
12
16
  } from '../types/CactusSTT';
13
- import type { CactusSTTModel } from '../types/CactusSTTModel';
17
+ import type { CactusModel } from '../types/common';
14
18
 
15
19
  export const useCactusSTT = ({
16
20
  model = 'whisper-small',
17
- contextSize = 2048,
21
+ options: modelOptions = {
22
+ quantization: undefined,
23
+ pro: false,
24
+ },
18
25
  }: CactusSTTParams = {}) => {
19
26
  const [cactusSTT, setCactusSTT] = useState(
20
- () => new CactusSTT({ model, contextSize })
27
+ () => new CactusSTT({ model, options: modelOptions })
21
28
  );
22
29
 
23
30
  // State
24
31
  const [transcription, setTranscription] = useState('');
32
+ const [streamTranscribeConfirmed, setStreamTranscribeConfirmed] =
33
+ useState('');
34
+ const [streamTranscribePending, setStreamTranscribePending] = useState('');
25
35
  const [isGenerating, setIsGenerating] = useState(false);
36
+ const [isStreamTranscribing, setIsStreamTranscribing] = useState(false);
26
37
  const [isInitializing, setIsInitializing] = useState(false);
27
38
  const [isDownloaded, setIsDownloaded] = useState(false);
28
39
  const [isDownloading, setIsDownloading] = useState(false);
@@ -37,10 +48,20 @@ export const useCactusSTT = ({
37
48
  }, [model]);
38
49
 
39
50
  useEffect(() => {
40
- setCactusSTT(new CactusSTT({ model, contextSize }));
51
+ const newInstance = new CactusSTT({
52
+ model,
53
+ options: {
54
+ quantization: modelOptions.quantization,
55
+ pro: modelOptions.pro,
56
+ },
57
+ });
58
+ setCactusSTT(newInstance);
41
59
 
42
60
  setTranscription('');
61
+ setStreamTranscribeConfirmed('');
62
+ setStreamTranscribePending('');
43
63
  setIsGenerating(false);
64
+ setIsStreamTranscribing(false);
44
65
  setIsInitializing(false);
45
66
  setIsDownloaded(false);
46
67
  setIsDownloading(false);
@@ -48,25 +69,21 @@ export const useCactusSTT = ({
48
69
  setError(null);
49
70
 
50
71
  let mounted = true;
51
- CactusFileSystem.modelExists(model)
72
+ CactusFileSystem.modelExists(newInstance.getModelName())
52
73
  .then((exists) => {
53
- if (!mounted) {
54
- return;
55
- }
56
- setIsDownloaded(exists);
74
+ if (mounted) setIsDownloaded(exists);
57
75
  })
58
76
  .catch((e) => {
59
- if (!mounted) {
60
- return;
77
+ if (mounted) {
78
+ setIsDownloaded(false);
79
+ setError(getErrorMessage(e));
61
80
  }
62
- setIsDownloaded(false);
63
- setError(getErrorMessage(e));
64
81
  });
65
82
 
66
83
  return () => {
67
84
  mounted = false;
68
85
  };
69
- }, [model, contextSize]);
86
+ }, [model, modelOptions.quantization, modelOptions.pro]);
70
87
 
71
88
  useEffect(() => {
72
89
  return () => {
@@ -91,49 +108,29 @@ export const useCactusSTT = ({
91
108
  const thisModel = currentModelRef.current;
92
109
  const thisDownloadId = ++currentDownloadIdRef.current;
93
110
 
111
+ const isCurrent = () =>
112
+ currentModelRef.current === thisModel &&
113
+ currentDownloadIdRef.current === thisDownloadId;
114
+
94
115
  setDownloadProgress(0);
95
116
  setIsDownloading(true);
96
117
  try {
97
118
  await cactusSTT.download({
98
119
  onProgress: (progress) => {
99
- if (
100
- currentModelRef.current !== thisModel ||
101
- currentDownloadIdRef.current !== thisDownloadId
102
- ) {
103
- return;
104
- }
105
-
120
+ if (!isCurrent()) return;
106
121
  setDownloadProgress(progress);
107
122
  onProgress?.(progress);
108
123
  },
109
124
  });
110
125
 
111
- if (
112
- currentModelRef.current !== thisModel ||
113
- currentDownloadIdRef.current !== thisDownloadId
114
- ) {
115
- return;
116
- }
117
-
126
+ if (!isCurrent()) return;
118
127
  setIsDownloaded(true);
119
128
  } catch (e) {
120
- if (
121
- currentModelRef.current !== thisModel ||
122
- currentDownloadIdRef.current !== thisDownloadId
123
- ) {
124
- return;
125
- }
126
-
129
+ if (!isCurrent()) return;
127
130
  setError(getErrorMessage(e));
128
131
  throw e;
129
132
  } finally {
130
- if (
131
- currentModelRef.current !== thisModel ||
132
- currentDownloadIdRef.current !== thisDownloadId
133
- ) {
134
- return;
135
- }
136
-
133
+ if (!isCurrent()) return;
137
134
  setIsDownloading(false);
138
135
  setDownloadProgress(0);
139
136
  }
@@ -220,6 +217,61 @@ export const useCactusSTT = ({
220
217
  [cactusSTT, isGenerating]
221
218
  );
222
219
 
220
+ const streamTranscribeStart = useCallback(
221
+ async (options?: CactusSTTStreamTranscribeStartOptions) => {
222
+ if (isStreamTranscribing) {
223
+ return;
224
+ }
225
+
226
+ setError(null);
227
+ setStreamTranscribeConfirmed('');
228
+ setStreamTranscribePending('');
229
+ setIsStreamTranscribing(true);
230
+ try {
231
+ await cactusSTT.streamTranscribeStart(options);
232
+ } catch (e) {
233
+ setError(getErrorMessage(e));
234
+ setIsStreamTranscribing(false);
235
+ throw e;
236
+ }
237
+ },
238
+ [cactusSTT, isStreamTranscribing]
239
+ );
240
+
241
+ const streamTranscribeProcess = useCallback(
242
+ async ({
243
+ audio,
244
+ }: CactusSTTStreamTranscribeProcessParams): Promise<CactusSTTStreamTranscribeProcessResult> => {
245
+ setError(null);
246
+ try {
247
+ const result = await cactusSTT.streamTranscribeProcess({ audio });
248
+ setStreamTranscribeConfirmed((prev) => prev + result.confirmed);
249
+ setStreamTranscribePending(result.pending);
250
+ return result;
251
+ } catch (e) {
252
+ setError(getErrorMessage(e));
253
+ throw e;
254
+ }
255
+ },
256
+ [cactusSTT]
257
+ );
258
+
259
+ const streamTranscribeStop =
260
+ useCallback(async (): Promise<CactusSTTStreamTranscribeStopResult> => {
261
+ setError(null);
262
+ try {
263
+ const result = await cactusSTT.streamTranscribeStop();
264
+ setStreamTranscribeConfirmed((prev) => prev + result.confirmed);
265
+ setStreamTranscribePending('');
266
+ return result;
267
+ } catch (e) {
268
+ setError(getErrorMessage(e));
269
+ throw e;
270
+ } finally {
271
+ setIsStreamTranscribing(false);
272
+ }
273
+ }, [cactusSTT]);
274
+
223
275
  const stop = useCallback(async () => {
224
276
  setError(null);
225
277
  try {
@@ -251,10 +303,13 @@ export const useCactusSTT = ({
251
303
  throw e;
252
304
  } finally {
253
305
  setTranscription('');
306
+ setStreamTranscribeConfirmed('');
307
+ setStreamTranscribePending('');
308
+ setIsStreamTranscribing(false);
254
309
  }
255
310
  }, [cactusSTT]);
256
311
 
257
- const getModels = useCallback(async (): Promise<CactusSTTModel[]> => {
312
+ const getModels = useCallback(async (): Promise<CactusModel[]> => {
258
313
  setError(null);
259
314
  try {
260
315
  return await cactusSTT.getModels();
@@ -266,7 +321,10 @@ export const useCactusSTT = ({
266
321
 
267
322
  return {
268
323
  transcription,
324
+ streamTranscribeConfirmed,
325
+ streamTranscribePending,
269
326
  isGenerating,
327
+ isStreamTranscribing,
270
328
  isInitializing,
271
329
  isDownloaded,
272
330
  isDownloading,
@@ -277,8 +335,11 @@ export const useCactusSTT = ({
277
335
  init,
278
336
  transcribe,
279
337
  audioEmbed,
280
- reset,
338
+ streamTranscribeStart,
339
+ streamTranscribeProcess,
340
+ streamTranscribeStop,
281
341
  stop,
342
+ reset,
282
343
  destroy,
283
344
  getModels,
284
345
  };