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
@@ -7,23 +7,32 @@ import java.util.Locale
7
7
  import java.util.UUID
8
8
 
9
9
  class HybridCactusCrypto : HybridCactusCryptoSpec() {
10
- override fun uuidv5(namespaceUuid: String, name: String): Promise<String> {
11
- return Promise.async {
12
- val nsUuid = try {
13
- UUID.fromString(namespaceUuid)
14
- } catch (e: IllegalArgumentException) {
15
- throw IllegalArgumentException("Invalid namespace UUID")
16
- }
10
+ override fun uuidv5(
11
+ namespaceUuid: String,
12
+ name: String,
13
+ ): Promise<String> =
14
+ Promise.async {
15
+ val nsUuid =
16
+ try {
17
+ UUID.fromString(namespaceUuid)
18
+ } catch (e: IllegalArgumentException) {
19
+ throw IllegalArgumentException("Invalid namespace UUID")
20
+ }
17
21
 
18
- val nsBytes = ByteBuffer.allocate(16).apply {
19
- putLong(nsUuid.mostSignificantBits)
20
- putLong(nsUuid.leastSignificantBits)
21
- }.array()
22
+ val nsBytes =
23
+ ByteBuffer
24
+ .allocate(16)
25
+ .apply {
26
+ putLong(nsUuid.mostSignificantBits)
27
+ putLong(nsUuid.leastSignificantBits)
28
+ }.array()
22
29
  val nameBytes = name.toByteArray(Charsets.UTF_8)
23
30
 
24
- val sha1 = MessageDigest.getInstance("SHA-1")
25
- .apply { update(nsBytes) }
26
- .digest(nameBytes)
31
+ val sha1 =
32
+ MessageDigest
33
+ .getInstance("SHA-1")
34
+ .apply { update(nsBytes) }
35
+ .digest(nameBytes)
27
36
  val uuidBytes = sha1.copyOfRange(0, 16)
28
37
  uuidBytes[6] = (uuidBytes[6].toInt() and 0x0F or 0x50).toByte()
29
38
  uuidBytes[8] = (uuidBytes[8].toInt() and 0x3F or 0x80).toByte()
@@ -34,5 +43,4 @@ class HybridCactusCrypto : HybridCactusCryptoSpec() {
34
43
 
35
44
  uuid.toString().lowercase(Locale.ROOT)
36
45
  }
37
- }
38
46
  }
@@ -8,17 +8,20 @@ import com.margelo.nitro.core.Promise
8
8
  class HybridCactusDeviceInfo : HybridCactusDeviceInfoSpec() {
9
9
  private val context = NitroModules.applicationContext ?: error("Android context not found")
10
10
 
11
- override fun getAppIdentifier(): Promise<String?> {
12
- return Promise.async { context.packageName }
13
- }
11
+ override fun getAppIdentifier(): Promise<String?> = Promise.async { context.packageName }
14
12
 
15
- override fun getDeviceInfo(): Promise<DeviceInfo> {
16
- return Promise.async {
13
+ override fun getDeviceInfo(): Promise<DeviceInfo> =
14
+ Promise.async {
17
15
  DeviceInfo(
18
- brand = Build.MANUFACTURER, model = Build.MODEL, device_id = Settings.Secure.getString(
19
- context.contentResolver, Settings.Secure.ANDROID_ID
20
- ), os = "Android", os_version = Build.VERSION.RELEASE
16
+ brand = Build.MANUFACTURER,
17
+ model = Build.MODEL,
18
+ device_id =
19
+ Settings.Secure.getString(
20
+ context.contentResolver,
21
+ Settings.Secure.ANDROID_ID,
22
+ ),
23
+ os = "Android",
24
+ os_version = Build.VERSION.RELEASE,
21
25
  )
22
26
  }
23
- }
24
27
  }
@@ -16,29 +16,28 @@ import kotlin.math.floor
16
16
  class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
17
17
  private val context = NitroModules.applicationContext ?: error("Android context not found")
18
18
 
19
- override fun getCactusDirectory(): Promise<String> {
20
- return Promise.async { cactusFile().absolutePath }
21
- }
19
+ override fun getCactusDirectory(): Promise<String> = Promise.async { cactusFile().absolutePath }
22
20
 
23
- override fun fileExists(path: String): Promise<Boolean> {
24
- return Promise.async {
21
+ override fun fileExists(path: String): Promise<Boolean> =
22
+ Promise.async {
25
23
  val cactusDir = cactusFile()
26
24
  val file = File(cactusDir, path)
27
25
  file.exists()
28
26
  }
29
- }
30
27
 
31
- override fun writeFile(path: String, content: String): Promise<Unit> {
32
- return Promise.async {
28
+ override fun writeFile(
29
+ path: String,
30
+ content: String,
31
+ ): Promise<Unit> =
32
+ Promise.async {
33
33
  val cactusDir = cactusFile()
34
34
  val file = File(cactusDir, path)
35
35
  file.parentFile?.mkdirs()
36
36
  file.writeText(content)
37
37
  }
38
- }
39
38
 
40
- override fun readFile(path: String): Promise<String> {
41
- return Promise.async {
39
+ override fun readFile(path: String): Promise<String> =
40
+ Promise.async {
42
41
  val cactusDir = cactusFile()
43
42
  val file = File(cactusDir, path)
44
43
 
@@ -48,10 +47,9 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
48
47
 
49
48
  file.readText()
50
49
  }
51
- }
52
50
 
53
- override fun deleteFile(path: String): Promise<Unit> {
54
- return Promise.async {
51
+ override fun deleteFile(path: String): Promise<Unit> =
52
+ Promise.async {
55
53
  val cactusDir = cactusFile()
56
54
  val file = File(cactusDir, path)
57
55
 
@@ -61,18 +59,15 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
61
59
 
62
60
  file.deleteRecursively()
63
61
  }
64
- }
65
62
 
66
- override fun modelExists(model: String): Promise<Boolean> {
67
- return Promise.async { modelFile(model).exists() }
68
- }
63
+ override fun modelExists(model: String): Promise<Boolean> = Promise.async { modelFile(model).exists() }
69
64
 
70
- override fun getModelPath(model: String): Promise<String> {
71
- return Promise.async { modelFile(model).absolutePath }
72
- }
65
+ override fun getModelPath(model: String): Promise<String> = Promise.async { modelFile(model).absolutePath }
73
66
 
74
67
  override fun downloadModel(
75
- model: String, from: String, callback: ((progress: Double) -> Unit)?
68
+ model: String,
69
+ from: String,
70
+ callback: ((progress: Double) -> Unit)?,
76
71
  ): Promise<Unit> {
77
72
  return Promise.async {
78
73
  val modelFile = modelFile(model)
@@ -82,21 +77,23 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
82
77
  return@async
83
78
  }
84
79
 
85
- val url = try {
86
- URL(from)
87
- } catch (_: Throwable) {
88
- throw Error("Invalid URL")
89
- }
80
+ val url =
81
+ try {
82
+ URL(from)
83
+ } catch (_: Throwable) {
84
+ throw Error("Invalid URL")
85
+ }
90
86
 
91
87
  val tmpZip = File.createTempFile("dl_", ".zip", context.cacheDir)
92
88
  var connection: HttpURLConnection? = null
93
89
 
94
90
  try {
95
- connection = (url.openConnection() as HttpURLConnection).apply {
96
- connectTimeout = 30_000
97
- readTimeout = 5 * 60_000
98
- instanceFollowRedirects = true
99
- }
91
+ connection =
92
+ (url.openConnection() as HttpURLConnection).apply {
93
+ connectTimeout = 30_000
94
+ readTimeout = 5 * 60_000
95
+ instanceFollowRedirects = true
96
+ }
100
97
  connection.connect()
101
98
  val code = connection.responseCode
102
99
 
@@ -129,11 +126,13 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
129
126
 
130
127
  if (contentLength > 0) {
131
128
  // cap at 0.99; 1.0 will be sent after unzip
132
- val pct = floor(
133
- (downloaded.toDouble() / contentLength.toDouble()).coerceIn(
134
- 0.0, 1.0
135
- ) * 99
136
- ) / 100.0
129
+ val pct =
130
+ floor(
131
+ (downloaded.toDouble() / contentLength.toDouble()).coerceIn(
132
+ 0.0,
133
+ 1.0,
134
+ ) * 99,
135
+ ) / 100.0
137
136
 
138
137
  if (pct - lastPct >= 0.01) {
139
138
  callback?.invoke(pct)
@@ -160,7 +159,10 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
160
159
  }
161
160
  }
162
161
 
163
- private fun unzipItem(zipFile: File, outDir: File) {
162
+ private fun unzipItem(
163
+ zipFile: File,
164
+ outDir: File,
165
+ ) {
164
166
  val outRoot = outDir.canonicalFile
165
167
  ZipInputStream(BufferedInputStream(FileInputStream(zipFile))).use { zis ->
166
168
  var entry: ZipEntry? = zis.nextEntry
@@ -196,8 +198,8 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
196
198
  }
197
199
  }
198
200
 
199
- override fun deleteModel(model: String): Promise<Unit> {
200
- return Promise.async {
201
+ override fun deleteModel(model: String): Promise<Unit> =
202
+ Promise.async {
201
203
  val modelFile = modelFile(model)
202
204
 
203
205
  if (!modelFile.exists()) {
@@ -206,7 +208,6 @@ class HybridCactusFileSystem : HybridCactusFileSystemSpec() {
206
208
 
207
209
  modelFile.deleteRecursively()
208
210
  }
209
- }
210
211
 
211
212
  private fun cactusFile(): File {
212
213
  val documentsDir =
@@ -0,0 +1,81 @@
1
+ package com.margelo.nitro.cactus
2
+
3
+ import android.graphics.Bitmap
4
+ import android.graphics.BitmapFactory
5
+ import androidx.core.graphics.createBitmap
6
+ import com.margelo.nitro.NitroModules
7
+ import com.margelo.nitro.core.Promise
8
+ import java.io.File
9
+ import java.io.FileOutputStream
10
+ import java.util.UUID
11
+
12
+ class HybridCactusImage : HybridCactusImageSpec() {
13
+ private val context = NitroModules.applicationContext ?: error("Android context not found")
14
+
15
+ override fun base64(path: String): Promise<String> =
16
+ Promise.async {
17
+ val file = File(path)
18
+
19
+ if (!file.exists()) {
20
+ throw Error("No such file: $path")
21
+ }
22
+
23
+ val imageData = file.readBytes()
24
+ android.util.Base64.encodeToString(imageData, android.util.Base64.NO_WRAP)
25
+ }
26
+
27
+ override fun resize(
28
+ path: String,
29
+ height: Double,
30
+ width: Double,
31
+ quality: Double,
32
+ ): Promise<String> =
33
+ Promise.async {
34
+ val file = File(path)
35
+
36
+ if (!file.exists()) {
37
+ throw Error("No such file: $path")
38
+ }
39
+
40
+ val bitmap = BitmapFactory.decodeFile(path)
41
+ if (bitmap == null) {
42
+ throw Error("Failed to load image from: $path")
43
+ }
44
+ val resizedBitmap = createBitmap(width.toInt(), height.toInt(), Bitmap.Config.RGB_565)
45
+ val canvas = android.graphics.Canvas(resizedBitmap)
46
+ val paint =
47
+ android.graphics.Paint().apply {
48
+ isFilterBitmap = true
49
+ isAntiAlias = true
50
+ }
51
+
52
+ val scaleX = width.toFloat() / bitmap.width
53
+ val scaleY = height.toFloat() / bitmap.height
54
+ val matrix =
55
+ android.graphics.Matrix().apply {
56
+ setScale(scaleX, scaleY)
57
+ }
58
+
59
+ canvas.drawBitmap(bitmap, matrix, paint)
60
+ bitmap.recycle()
61
+
62
+ val cacheDir = context.cacheDir
63
+ val outputDir = File(cacheDir, "cactus/images")
64
+ outputDir.mkdirs()
65
+
66
+ val fileName = "${UUID.randomUUID()}.jpg"
67
+ val outputFile = File(outputDir, fileName)
68
+
69
+ try {
70
+ FileOutputStream(outputFile).use { outputStream ->
71
+ val qualityInt = (quality * 100).toInt().coerceIn(0, 100)
72
+ resizedBitmap.compress(Bitmap.CompressFormat.JPEG, qualityInt, outputStream)
73
+ }
74
+ resizedBitmap.recycle()
75
+ outputFile.absolutePath
76
+ } catch (e: Exception) {
77
+ resizedBitmap.recycle()
78
+ throw Error("Failed to save resized image: ${e.message}")
79
+ }
80
+ }
81
+ }
@@ -1,30 +1,41 @@
1
1
  #include "HybridCactus.hpp"
2
2
 
3
- namespace margelo::nitro::cactus
4
- {
3
+ namespace margelo::nitro::cactus {
5
4
  HybridCactus::HybridCactus() : HybridObject(TAG) {}
6
5
 
7
- std::shared_ptr<Promise<void>> HybridCactus::init(const std::string &modelPath, double contextSize) {
8
- return Promise<void>::async([this, modelPath, contextSize]() -> void {
9
- std::lock_guard<std::mutex> lock(this->_modelMutex);
6
+ std::shared_ptr<Promise<void>>
7
+ HybridCactus::init(const std::string &modelPath, double contextSize,
8
+ const std::optional<std::string> &corpusDir) {
9
+ return Promise<void>::async(
10
+ [this, modelPath, contextSize, corpusDir]() -> void {
11
+ std::lock_guard<std::mutex> lock(this->_modelMutex);
10
12
 
11
- if (this->_model) {
12
- throw std::runtime_error("Cactus model is already initialized");
13
- }
13
+ if (this->_model) {
14
+ throw std::runtime_error("Cactus model is already initialized");
15
+ }
14
16
 
15
- const cactus_model_t model = cactus_init(modelPath.c_str(), contextSize);
17
+ const cactus_model_t model =
18
+ cactus_init(modelPath.c_str(), contextSize,
19
+ corpusDir ? corpusDir->c_str() : nullptr);
16
20
 
17
- if (!model) {
18
- throw std::runtime_error("Failed to initialize Cactus model");
19
- }
21
+ if (!model) {
22
+ throw std::runtime_error("Failed to initialize Cactus model");
23
+ }
20
24
 
21
- this->_model = model;
22
- this->_contextSize = contextSize;
23
- });
25
+ this->_model = model;
26
+ this->_contextSize = contextSize;
27
+ });
24
28
  }
25
29
 
26
- std::shared_ptr<Promise<std::string>> HybridCactus::complete(const std::string &messagesJson, double responseBufferSize, const std::optional<std::string> &optionsJson, const std::optional<std::string> &toolsJson, const std::optional<std::function<void(const std::string & /* token */, double /* tokenId */)>> &callback) {
27
- return Promise<std::string>::async([this, messagesJson, optionsJson, toolsJson, callback, responseBufferSize]() -> std::string {
30
+ std::shared_ptr<Promise<std::string>> HybridCactus::complete(
31
+ const std::string &messagesJson, double responseBufferSize,
32
+ const std::optional<std::string> &optionsJson,
33
+ const std::optional<std::string> &toolsJson,
34
+ const std::optional<std::function<void(const std::string & /* token */,
35
+ double /* tokenId */)>> &callback) {
36
+ return Promise<std::string>::async([this, messagesJson, optionsJson,
37
+ toolsJson, callback,
38
+ responseBufferSize]() -> std::string {
28
39
  std::lock_guard<std::mutex> lock(this->_modelMutex);
29
40
 
30
41
  if (!this->_model) {
@@ -32,28 +43,26 @@ std::shared_ptr<Promise<std::string>> HybridCactus::complete(const std::string &
32
43
  }
33
44
 
34
45
  struct CallbackCtx {
35
- const std::function<void(const std::string & /* token */, double /* tokenId */)> *callback;
36
- } callbackCtx{ callback.has_value() ? &callback.value() : nullptr };
37
-
38
- auto cactusTokenCallback = [](const char* token, uint32_t tokenId, void* userData) {
39
- auto* callbackCtx = static_cast<CallbackCtx*>(userData);
40
- if (!callbackCtx || !callbackCtx->callback || !(*callbackCtx->callback)) return;
46
+ const std::function<void(const std::string & /* token */,
47
+ double /* tokenId */)> *callback;
48
+ } callbackCtx{callback.has_value() ? &callback.value() : nullptr};
49
+
50
+ auto cactusTokenCallback = [](const char *token, uint32_t tokenId,
51
+ void *userData) {
52
+ auto *callbackCtx = static_cast<CallbackCtx *>(userData);
53
+ if (!callbackCtx || !callbackCtx->callback || !(*callbackCtx->callback))
54
+ return;
41
55
  (*callbackCtx->callback)(token, tokenId);
42
56
  };
43
57
 
44
58
  std::string responseBuffer;
45
59
  responseBuffer.resize(responseBufferSize);
46
60
 
47
- int result = cactus_complete(
48
- this->_model,
49
- messagesJson.c_str(),
50
- responseBuffer.data(),
51
- responseBufferSize,
52
- optionsJson ? optionsJson->c_str() : nullptr,
53
- toolsJson ? toolsJson->c_str() : nullptr,
54
- cactusTokenCallback,
55
- &callbackCtx
56
- );
61
+ int result = cactus_complete(this->_model, messagesJson.c_str(),
62
+ responseBuffer.data(), responseBufferSize,
63
+ optionsJson ? optionsJson->c_str() : nullptr,
64
+ toolsJson ? toolsJson->c_str() : nullptr,
65
+ cactusTokenCallback, &callbackCtx);
57
66
 
58
67
  if (result < 0) {
59
68
  throw std::runtime_error("Cactus completion failed");
@@ -66,29 +75,139 @@ std::shared_ptr<Promise<std::string>> HybridCactus::complete(const std::string &
66
75
  });
67
76
  }
68
77
 
69
- std::shared_ptr<Promise<std::vector<double>>> HybridCactus::embed(const std::string &text, double embeddingBufferSize) {
70
- return Promise<std::vector<double>>::async([this, text, embeddingBufferSize]() -> std::vector<double> {
78
+ std::shared_ptr<Promise<std::string>> HybridCactus::transcribe(
79
+ const std::string &audioFilePath, const std::string &prompt,
80
+ double responseBufferSize, const std::optional<std::string> &optionsJson,
81
+ const std::optional<std::function<void(const std::string & /* token */,
82
+ double /* tokenId */)>> &callback) {
83
+ return Promise<std::string>::async([this, audioFilePath, prompt, optionsJson,
84
+ callback,
85
+ responseBufferSize]() -> std::string {
71
86
  std::lock_guard<std::mutex> lock(this->_modelMutex);
72
87
 
73
88
  if (!this->_model) {
74
89
  throw std::runtime_error("Cactus model is not initialized");
75
90
  }
76
91
 
77
- std::vector<float> embeddingBuffer(embeddingBufferSize);
78
- size_t embeddingDim;
92
+ struct CallbackCtx {
93
+ const std::function<void(const std::string & /* token */,
94
+ double /* tokenId */)> *callback;
95
+ } callbackCtx{callback.has_value() ? &callback.value() : nullptr};
96
+
97
+ auto cactusTokenCallback = [](const char *token, uint32_t tokenId,
98
+ void *userData) {
99
+ auto *callbackCtx = static_cast<CallbackCtx *>(userData);
100
+ if (!callbackCtx || !callbackCtx->callback || !(*callbackCtx->callback))
101
+ return;
102
+ (*callbackCtx->callback)(token, tokenId);
103
+ };
104
+
105
+ std::string responseBuffer;
106
+ responseBuffer.resize(responseBufferSize);
79
107
 
80
- int result = cactus_embed(this->_model, text.c_str(), embeddingBuffer.data(), embeddingBufferSize * sizeof(float), &embeddingDim);
108
+ int result =
109
+ cactus_transcribe(this->_model, audioFilePath.c_str(), prompt.c_str(),
110
+ responseBuffer.data(), responseBufferSize,
111
+ optionsJson ? optionsJson->c_str() : nullptr,
112
+ cactusTokenCallback, &callbackCtx);
81
113
 
82
114
  if (result < 0) {
83
- throw std::runtime_error("Cactus embedding failed");
115
+ throw std::runtime_error("Cactus transcription failed");
84
116
  }
85
117
 
86
- embeddingBuffer.resize(embeddingDim);
118
+ // Remove null terminator
119
+ responseBuffer.resize(strlen(responseBuffer.c_str()));
87
120
 
88
- return std::vector<double>(embeddingBuffer.begin(), embeddingBuffer.end());
121
+ return responseBuffer;
89
122
  });
90
123
  }
91
124
 
125
+ std::shared_ptr<Promise<std::vector<double>>>
126
+ HybridCactus::embed(const std::string &text, double embeddingBufferSize) {
127
+ return Promise<std::vector<double>>::async(
128
+ [this, text, embeddingBufferSize]() -> std::vector<double> {
129
+ std::lock_guard<std::mutex> lock(this->_modelMutex);
130
+
131
+ if (!this->_model) {
132
+ throw std::runtime_error("Cactus model is not initialized");
133
+ }
134
+
135
+ std::vector<float> embeddingBuffer(embeddingBufferSize);
136
+ size_t embeddingDim;
137
+
138
+ int result =
139
+ cactus_embed(this->_model, text.c_str(), embeddingBuffer.data(),
140
+ embeddingBufferSize * sizeof(float), &embeddingDim);
141
+
142
+ if (result < 0) {
143
+ throw std::runtime_error("Cactus embedding failed");
144
+ }
145
+
146
+ embeddingBuffer.resize(embeddingDim);
147
+
148
+ return std::vector<double>(embeddingBuffer.begin(),
149
+ embeddingBuffer.end());
150
+ });
151
+ }
152
+
153
+ std::shared_ptr<Promise<std::vector<double>>>
154
+ HybridCactus::imageEmbed(const std::string &imagePath,
155
+ double embeddingBufferSize) {
156
+ return Promise<std::vector<double>>::async(
157
+ [this, imagePath, embeddingBufferSize]() -> std::vector<double> {
158
+ std::lock_guard<std::mutex> lock(this->_modelMutex);
159
+
160
+ if (!this->_model) {
161
+ throw std::runtime_error("Cactus model is not initialized");
162
+ }
163
+
164
+ std::vector<float> embeddingBuffer(embeddingBufferSize);
165
+ size_t embeddingDim;
166
+
167
+ int result = cactus_image_embed(
168
+ this->_model, imagePath.c_str(), embeddingBuffer.data(),
169
+ embeddingBufferSize * sizeof(float), &embeddingDim);
170
+
171
+ if (result < 0) {
172
+ throw std::runtime_error("Cactus image embedding failed");
173
+ }
174
+
175
+ embeddingBuffer.resize(embeddingDim);
176
+
177
+ return std::vector<double>(embeddingBuffer.begin(),
178
+ embeddingBuffer.end());
179
+ });
180
+ }
181
+
182
+ std::shared_ptr<Promise<std::vector<double>>>
183
+ HybridCactus::audioEmbed(const std::string &audioPath,
184
+ double embeddingBufferSize) {
185
+ return Promise<std::vector<double>>::async(
186
+ [this, audioPath, embeddingBufferSize]() -> std::vector<double> {
187
+ std::lock_guard<std::mutex> lock(this->_modelMutex);
188
+
189
+ if (!this->_model) {
190
+ throw std::runtime_error("Cactus model is not initialized");
191
+ }
192
+
193
+ std::vector<float> embeddingBuffer(embeddingBufferSize);
194
+ size_t embeddingDim;
195
+
196
+ int result = cactus_audio_embed(
197
+ this->_model, audioPath.c_str(), embeddingBuffer.data(),
198
+ embeddingBufferSize * sizeof(float), &embeddingDim);
199
+
200
+ if (result < 0) {
201
+ throw std::runtime_error("Cactus audio embedding failed");
202
+ }
203
+
204
+ embeddingBuffer.resize(embeddingDim);
205
+
206
+ return std::vector<double>(embeddingBuffer.begin(),
207
+ embeddingBuffer.end());
208
+ });
209
+ }
210
+
92
211
  std::shared_ptr<Promise<void>> HybridCactus::reset() {
93
212
  return Promise<void>::async([this]() -> void {
94
213
  std::lock_guard<std::mutex> lock(this->_modelMutex);
@@ -102,9 +221,7 @@ std::shared_ptr<Promise<void>> HybridCactus::reset() {
102
221
  }
103
222
 
104
223
  std::shared_ptr<Promise<void>> HybridCactus::stop() {
105
- return Promise<void>::async([this]() -> void {
106
- cactus_stop(this->_model);
107
- });
224
+ return Promise<void>::async([this]() -> void { cactus_stop(this->_model); });
108
225
  }
109
226
 
110
227
  std::shared_ptr<Promise<void>> HybridCactus::destroy() {
@@ -5,26 +5,46 @@
5
5
 
6
6
  #include <mutex>
7
7
 
8
- namespace margelo::nitro::cactus
9
- {
8
+ namespace margelo::nitro::cactus {
10
9
 
11
- class HybridCactus : public HybridCactusSpec
12
- {
10
+ class HybridCactus : public HybridCactusSpec {
13
11
  public:
14
12
  HybridCactus();
15
-
16
- std::shared_ptr<Promise<void>> init(const std::string &modelPath, double contextSize) override;
17
-
18
- std::shared_ptr<Promise<std::string>> complete(const std::string &messagesJson, double responseBufferSize, const std::optional<std::string> &optionsJson, const std::optional<std::string> &toolsJson, const std::optional<std::function<void(const std::string & /* token */, double /* tokenId */)>> &callback) override;
19
-
20
- std::shared_ptr<Promise<std::vector<double>>> embed(const std::string &text, double embeddingBufferSize) override;
21
-
13
+
14
+ std::shared_ptr<Promise<void>>
15
+ init(const std::string &modelPath, double contextSize,
16
+ const std::optional<std::string> &corpusDir) override;
17
+
18
+ std::shared_ptr<Promise<std::string>> complete(
19
+ const std::string &messagesJson, double responseBufferSize,
20
+ const std::optional<std::string> &optionsJson,
21
+ const std::optional<std::string> &toolsJson,
22
+ const std::optional<std::function<void(const std::string & /* token */,
23
+ double /* tokenId */)>> &callback)
24
+ override;
25
+
26
+ std::shared_ptr<Promise<std::string>> transcribe(
27
+ const std::string &audioFilePath, const std::string &prompt,
28
+ double responseBufferSize, const std::optional<std::string> &optionsJson,
29
+ const std::optional<std::function<void(const std::string & /* token */,
30
+ double /* tokenId */)>> &callback)
31
+ override;
32
+
33
+ std::shared_ptr<Promise<std::vector<double>>>
34
+ embed(const std::string &text, double embeddingBufferSize) override;
35
+
36
+ std::shared_ptr<Promise<std::vector<double>>>
37
+ imageEmbed(const std::string &imagePath, double embeddingBufferSize) override;
38
+
39
+ std::shared_ptr<Promise<std::vector<double>>>
40
+ audioEmbed(const std::string &audioPath, double embeddingBufferSize) override;
41
+
22
42
  std::shared_ptr<Promise<void>> reset() override;
23
-
43
+
24
44
  std::shared_ptr<Promise<void>> stop() override;
25
-
45
+
26
46
  std::shared_ptr<Promise<void>> destroy() override;
27
-
47
+
28
48
  private:
29
49
  cactus_model_t _model = nullptr;
30
50
  size_t _contextSize;