react-native-sherpa-onnx 0.3.8 → 0.3.9

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 (78) hide show
  1. package/README.md +4 -2
  2. package/SherpaOnnx.podspec +4 -1
  3. package/android/prebuilt-download.gradle +23 -23
  4. package/android/src/main/assets/model_licenses/asr-models-license-status.csv +1 -0
  5. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.cpp +23 -0
  6. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-helper.h +9 -0
  7. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect-stt.cpp +51 -8
  8. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-model-detect.h +10 -0
  9. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-stt-wrapper.cpp +5 -0
  10. package/android/src/main/cpp/jni/model_detect/sherpa-onnx-validate-stt.cpp +11 -0
  11. package/android/src/main/java/com/sherpaonnx/SherpaOnnxArchiveHelper.kt +110 -35
  12. package/android/src/main/java/com/sherpaonnx/SherpaOnnxExtractionNotificationHelper.kt +102 -0
  13. package/android/src/main/java/com/sherpaonnx/SherpaOnnxModule.kt +92 -18
  14. package/android/src/main/java/com/sherpaonnx/SherpaOnnxSttHelper.kt +22 -0
  15. package/ios/Resources/model_licenses/asr-models-license-status.csv +1 -0
  16. package/ios/SherpaOnnx+STT.mm +13 -1
  17. package/ios/SherpaOnnx.mm +87 -17
  18. package/ios/model_detect/sherpa-onnx-model-detect-helper.h +5 -0
  19. package/ios/model_detect/sherpa-onnx-model-detect-helper.mm +23 -0
  20. package/ios/model_detect/sherpa-onnx-model-detect-stt.mm +51 -7
  21. package/ios/model_detect/sherpa-onnx-model-detect.h +10 -0
  22. package/ios/model_detect/sherpa-onnx-validate-stt.mm +11 -0
  23. package/ios/stt/sherpa-onnx-stt-wrapper.h +11 -1
  24. package/ios/stt/sherpa-onnx-stt-wrapper.mm +30 -2
  25. package/ios/tts/sherpa-onnx-tts-wrapper.mm +16 -0
  26. package/lib/module/NativeSherpaOnnx.js.map +1 -1
  27. package/lib/module/download/postDownloadProcessing.js +17 -4
  28. package/lib/module/download/postDownloadProcessing.js.map +1 -1
  29. package/lib/module/extraction/extractTarBz2.js +2 -2
  30. package/lib/module/extraction/extractTarBz2.js.map +1 -1
  31. package/lib/module/extraction/extractTarZst.js +2 -2
  32. package/lib/module/extraction/extractTarZst.js.map +1 -1
  33. package/lib/module/extraction/index.js +10 -5
  34. package/lib/module/extraction/index.js.map +1 -1
  35. package/lib/module/stt/index.js +4 -2
  36. package/lib/module/stt/index.js.map +1 -1
  37. package/lib/module/stt/streaming.js +2 -1
  38. package/lib/module/stt/streaming.js.map +1 -1
  39. package/lib/module/stt/types.js +3 -1
  40. package/lib/module/stt/types.js.map +1 -1
  41. package/lib/module/tts/index.js +4 -2
  42. package/lib/module/tts/index.js.map +1 -1
  43. package/lib/module/tts/streaming.js +3 -1
  44. package/lib/module/tts/streaming.js.map +1 -1
  45. package/lib/typescript/src/NativeSherpaOnnx.d.ts +25 -9
  46. package/lib/typescript/src/NativeSherpaOnnx.d.ts.map +1 -1
  47. package/lib/typescript/src/download/postDownloadProcessing.d.ts +9 -0
  48. package/lib/typescript/src/download/postDownloadProcessing.d.ts.map +1 -1
  49. package/lib/typescript/src/extraction/extractTarBz2.d.ts +2 -1
  50. package/lib/typescript/src/extraction/extractTarBz2.d.ts.map +1 -1
  51. package/lib/typescript/src/extraction/extractTarZst.d.ts +2 -1
  52. package/lib/typescript/src/extraction/extractTarZst.d.ts.map +1 -1
  53. package/lib/typescript/src/extraction/index.d.ts +1 -1
  54. package/lib/typescript/src/extraction/index.d.ts.map +1 -1
  55. package/lib/typescript/src/extraction/types.d.ts +12 -0
  56. package/lib/typescript/src/extraction/types.d.ts.map +1 -1
  57. package/lib/typescript/src/stt/index.d.ts +1 -1
  58. package/lib/typescript/src/stt/index.d.ts.map +1 -1
  59. package/lib/typescript/src/stt/streaming.d.ts.map +1 -1
  60. package/lib/typescript/src/stt/types.d.ts +16 -1
  61. package/lib/typescript/src/stt/types.d.ts.map +1 -1
  62. package/lib/typescript/src/tts/index.d.ts.map +1 -1
  63. package/lib/typescript/src/tts/streaming.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/scripts/ci/update_model_license_csv.sh +16 -16
  66. package/src/NativeSherpaOnnx.ts +37 -10
  67. package/src/download/postDownloadProcessing.ts +24 -1
  68. package/src/extraction/extractTarBz2.ts +7 -2
  69. package/src/extraction/extractTarZst.ts +7 -2
  70. package/src/extraction/index.ts +29 -6
  71. package/src/extraction/types.ts +16 -0
  72. package/src/stt/index.ts +8 -7
  73. package/src/stt/streaming.ts +7 -1
  74. package/src/stt/types.ts +18 -0
  75. package/src/tts/index.ts +7 -7
  76. package/src/tts/streaming.ts +6 -3
  77. package/third_party/sherpa-onnx-prebuilt/ANDROID_RELEASE_TAG +1 -1
  78. package/third_party/sherpa-onnx-prebuilt/IOS_RELEASE_TAG +1 -1
@@ -54,7 +54,8 @@ class SherpaOnnxArchiveHelper {
54
54
  targetPath: String,
55
55
  force: Boolean,
56
56
  promise: Promise,
57
- onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
57
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit,
58
+ extractionNotification: SherpaOnnxExtractionNotificationHelper? = null,
58
59
  ) {
59
60
  val promiseSettled = AtomicBoolean(false)
60
61
  fun resolveOnce(success: Boolean, reason: String? = null) {
@@ -70,26 +71,28 @@ class SherpaOnnxArchiveHelper {
70
71
  val cancelFlag = AtomicBoolean(false)
71
72
  cancelFlags[sourcePath] = cancelFlag
72
73
 
73
- // Create a progress callback object that JNI can call
74
- val progressCallback = object : Any() {
75
- fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
76
- onProgress(bytesExtracted, totalBytes, percent)
77
- }
78
- }
79
-
80
74
  // Run extraction on a background thread so the React Native bridge thread is not blocked.
81
75
  // The thread pool allows multiple extractions in parallel.
82
76
  extractExecutor.execute {
77
+ val notif = extractionNotification
83
78
  try {
84
79
  // Check per-path cancel flag before starting the native extraction.
85
80
  if (cancelFlag.get()) {
86
81
  resolveOnce(false, "Cancelled")
87
82
  return@execute
88
83
  }
89
- nativeExtractTarBz2(sourcePath, targetPath, force, progressCallback, promise)
84
+ notif?.start()
85
+ val wrappedCallback = object : Any() {
86
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
87
+ onProgress(bytesExtracted, totalBytes, percent)
88
+ notif?.updateProgress(percent)
89
+ }
90
+ }
91
+ nativeExtractTarBz2(sourcePath, targetPath, force, wrappedCallback, promise)
90
92
  } catch (e: Exception) {
91
93
  resolveOnce(false, "Archive extraction error: ${e.message}")
92
94
  } finally {
95
+ notif?.finish()
93
96
  cancelFlags.remove(sourcePath)
94
97
  }
95
98
  }
@@ -104,7 +107,8 @@ class SherpaOnnxArchiveHelper {
104
107
  targetPath: String,
105
108
  force: Boolean,
106
109
  promise: Promise,
107
- onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
110
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit,
111
+ extractionNotification: SherpaOnnxExtractionNotificationHelper? = null,
108
112
  ) {
109
113
  val promiseSettled = AtomicBoolean(false)
110
114
  fun resolveOnce(success: Boolean, reason: String? = null) {
@@ -119,22 +123,26 @@ class SherpaOnnxArchiveHelper {
119
123
  val cancelFlag = AtomicBoolean(false)
120
124
  cancelFlags[sourcePath] = cancelFlag
121
125
 
122
- val progressCallback = object : Any() {
123
- fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
124
- onProgress(bytesExtracted, totalBytes, percent)
125
- }
126
- }
127
126
  extractExecutor.execute {
127
+ val notif = extractionNotification
128
128
  try {
129
129
  // Check per-path cancel flag before starting the native extraction.
130
130
  if (cancelFlag.get()) {
131
131
  resolveOnce(false, "Cancelled")
132
132
  return@execute
133
133
  }
134
- nativeExtractTarZst(sourcePath, targetPath, force, progressCallback, promise)
134
+ notif?.start()
135
+ val wrappedCallback = object : Any() {
136
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
137
+ onProgress(bytesExtracted, totalBytes, percent)
138
+ notif?.updateProgress(percent)
139
+ }
140
+ }
141
+ nativeExtractTarZst(sourcePath, targetPath, force, wrappedCallback, promise)
135
142
  } catch (e: Exception) {
136
143
  resolveOnce(false, "Archive extraction error: ${e.message}")
137
144
  } finally {
145
+ notif?.finish()
138
146
  cancelFlags.remove(sourcePath)
139
147
  }
140
148
  }
@@ -144,47 +152,106 @@ class SherpaOnnxArchiveHelper {
144
152
  }
145
153
  }
146
154
 
155
+ /**
156
+ * Which JNI stream entry to use for APK asset extraction.
157
+ *
158
+ * Both paths invoke libarchive’s `ExtractFromStream`, which **auto-detects** compression
159
+ * (`.tar.zst` vs `.tar.bz2`, etc.); `nativeExtractTarBz2FromStream` forwards to the same
160
+ * native implementation as zst. Keeping distinct JNI symbols preserves a clear API and avoids
161
+ * the impression that bz2 assets are mistakenly wired only to a “zst” method.
162
+ */
163
+ private enum class AssetTarStreamKind {
164
+ ZST,
165
+ BZ2,
166
+ }
167
+
147
168
  fun extractTarZstFromAsset(
148
169
  context: Context,
149
170
  assetPath: String,
150
171
  targetPath: String,
151
172
  force: Boolean,
152
173
  promise: Promise,
153
- onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
174
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit,
175
+ extractionNotification: SherpaOnnxExtractionNotificationHelper? = null,
176
+ ) {
177
+ extractTarArchiveFromAsset(
178
+ context,
179
+ assetPath,
180
+ targetPath,
181
+ force,
182
+ promise,
183
+ onProgress,
184
+ extractionNotification,
185
+ AssetTarStreamKind.ZST,
186
+ )
187
+ }
188
+
189
+ fun extractTarBz2FromAsset(
190
+ context: Context,
191
+ assetPath: String,
192
+ targetPath: String,
193
+ force: Boolean,
194
+ promise: Promise,
195
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit,
196
+ extractionNotification: SherpaOnnxExtractionNotificationHelper? = null,
197
+ ) {
198
+ extractTarArchiveFromAsset(
199
+ context,
200
+ assetPath,
201
+ targetPath,
202
+ force,
203
+ promise,
204
+ onProgress,
205
+ extractionNotification,
206
+ AssetTarStreamKind.BZ2,
207
+ )
208
+ }
209
+
210
+ private fun extractTarArchiveFromAsset(
211
+ context: Context,
212
+ assetPath: String,
213
+ targetPath: String,
214
+ force: Boolean,
215
+ promise: Promise,
216
+ onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit,
217
+ extractionNotification: SherpaOnnxExtractionNotificationHelper? = null,
218
+ kind: AssetTarStreamKind,
154
219
  ) {
155
220
  if (BuildConfig.DEBUG) {
156
- Log.i("SherpaOnnx", "extractTarZstFromAsset assetPath=$assetPath targetPath=$targetPath")
157
- }
158
- val progressCallback = object : Any() {
159
- fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
160
- onProgress(bytesExtracted, totalBytes, percent)
161
- }
221
+ Log.i(
222
+ "SherpaOnnx",
223
+ "extractTar${if (kind == AssetTarStreamKind.ZST) "Zst" else "Bz2"}FromAsset assetPath=$assetPath targetPath=$targetPath",
224
+ )
162
225
  }
163
226
  extractExecutor.execute {
227
+ val notif = extractionNotification
164
228
  try {
229
+ notif?.start()
230
+ val progressCallback = object : Any() {
231
+ fun invoke(bytesExtracted: Long, totalBytes: Long, percent: Double) {
232
+ onProgress(bytesExtracted, totalBytes, percent)
233
+ notif?.updateProgress(percent)
234
+ }
235
+ }
165
236
  context.assets.open(assetPath).use { stream ->
166
- nativeExtractTarZstFromStream(stream, targetPath, force, progressCallback, promise)
237
+ when (kind) {
238
+ AssetTarStreamKind.ZST ->
239
+ nativeExtractTarZstFromStream(stream, targetPath, force, progressCallback, promise)
240
+ AssetTarStreamKind.BZ2 ->
241
+ nativeExtractTarBz2FromStream(stream, targetPath, force, progressCallback, promise)
242
+ }
167
243
  }
168
244
  } catch (e: Exception) {
169
245
  val result = Arguments.createMap()
170
246
  result.putBoolean("success", false)
171
247
  result.putString("reason", e.message ?: "Failed to open asset")
172
248
  promise.resolve(result)
249
+ } finally {
250
+ notif?.finish()
173
251
  }
174
252
  }
175
253
  }
176
254
 
177
- fun extractTarBz2FromAsset(
178
- context: Context,
179
- assetPath: String,
180
- targetPath: String,
181
- force: Boolean,
182
- promise: Promise,
183
- onProgress: (bytes: Long, totalBytes: Long, percent: Double) -> Unit
184
- ) {
185
- extractTarZstFromAsset(context, assetPath, targetPath, force, promise, onProgress)
186
- }
187
-
188
255
  fun computeFileSha256(filePath: String, promise: Promise) {
189
256
  nativeComputeFileSha256(filePath, promise)
190
257
  }
@@ -214,6 +281,14 @@ class SherpaOnnxArchiveHelper {
214
281
  promise: Promise
215
282
  )
216
283
 
284
+ private external fun nativeExtractTarBz2FromStream(
285
+ inputStream: java.io.InputStream,
286
+ targetPath: String,
287
+ force: Boolean,
288
+ progressCallback: Any?,
289
+ promise: Promise
290
+ )
291
+
217
292
  private external fun nativeCancelExtract()
218
293
 
219
294
  private external fun nativeComputeFileSha256(
@@ -0,0 +1,102 @@
1
+ package com.sherpaonnx
2
+
3
+ import android.app.NotificationChannel
4
+ import android.app.NotificationManager
5
+ import android.content.Context
6
+ import android.os.Build
7
+ import android.util.Log
8
+ import androidx.core.app.NotificationCompat
9
+
10
+ /**
11
+ * Android-only progress notification for archive extraction (mirrors background download visibility).
12
+ * Safe no-ops if posting fails (e.g. POST_NOTIFICATIONS denied).
13
+ */
14
+ class SherpaOnnxExtractionNotificationHelper private constructor(
15
+ private val context: Context,
16
+ private val notificationId: Int,
17
+ private val title: String,
18
+ private val baseText: String,
19
+ ) {
20
+ private val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
21
+ @Volatile private var lastBucket: Int = -1
22
+
23
+ companion object {
24
+ private const val TAG = "SherpaOnnxExtractNotif"
25
+ const val CHANNEL_ID = "sherpa_onnx_extraction"
26
+ private val nextNotificationId = java.util.concurrent.atomic.AtomicInteger(9_200_000)
27
+
28
+ private const val DEFAULT_TITLE = "Model extraction"
29
+ private const val DEFAULT_TEXT = "Extracting archive…"
30
+
31
+ fun maybeCreate(
32
+ context: Context,
33
+ showNotificationsEnabled: Boolean?,
34
+ titleOverride: String?,
35
+ textOverride: String?,
36
+ ): SherpaOnnxExtractionNotificationHelper? {
37
+ if (showNotificationsEnabled == false) return null
38
+ val title = titleOverride?.trim()?.takeIf { it.isNotEmpty() } ?: DEFAULT_TITLE
39
+ val text = textOverride?.trim()?.takeIf { it.isNotEmpty() } ?: DEFAULT_TEXT
40
+ val id = nextNotificationId.getAndIncrement()
41
+ return SherpaOnnxExtractionNotificationHelper(context.applicationContext, id, title, text)
42
+ }
43
+
44
+ fun ensureChannel(ctx: Context) {
45
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
46
+ val nm = ctx.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
47
+ val existing = nm.getNotificationChannel(CHANNEL_ID)
48
+ if (existing != null) return
49
+ val ch = NotificationChannel(
50
+ CHANNEL_ID,
51
+ "Model extraction",
52
+ NotificationManager.IMPORTANCE_LOW,
53
+ ).apply {
54
+ setShowBadge(false)
55
+ }
56
+ nm.createNotificationChannel(ch)
57
+ }
58
+ }
59
+
60
+ private fun buildProgress(percentInt: Int): NotificationCompat.Builder {
61
+ val p = percentInt.coerceIn(0, 100)
62
+ val line = "$baseText $p%"
63
+ return NotificationCompat.Builder(context, CHANNEL_ID)
64
+ .setSmallIcon(android.R.drawable.stat_sys_download)
65
+ .setContentTitle(title)
66
+ .setContentText(line)
67
+ .setStyle(NotificationCompat.BigTextStyle().bigText(line))
68
+ .setOnlyAlertOnce(true)
69
+ .setPriority(NotificationCompat.PRIORITY_LOW)
70
+ .setOngoing(true)
71
+ .setProgress(100, p, false)
72
+ }
73
+
74
+ fun start() {
75
+ try {
76
+ ensureChannel(context)
77
+ nm.notify(notificationId, buildProgress(0).build())
78
+ } catch (e: Exception) {
79
+ Log.w(TAG, "start: ${e.message}")
80
+ }
81
+ }
82
+
83
+ fun updateProgress(percent: Double) {
84
+ val p = percent.toInt().coerceIn(0, 100)
85
+ val bucket = p / 4
86
+ if (bucket == lastBucket && p != 0 && p != 100) return
87
+ lastBucket = bucket
88
+ try {
89
+ nm.notify(notificationId, buildProgress(p).build())
90
+ } catch (e: Exception) {
91
+ Log.w(TAG, "updateProgress: ${e.message}")
92
+ }
93
+ }
94
+
95
+ fun finish() {
96
+ try {
97
+ nm.cancel(notificationId)
98
+ } catch (e: Exception) {
99
+ Log.w(TAG, "finish: ${e.message}")
100
+ }
101
+ }
102
+ }
@@ -293,10 +293,30 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
293
293
  assetHelper.resolveModelPath(config, promise)
294
294
  }
295
295
 
296
- override fun extractTarBz2(sourcePath: String, targetPath: String, force: Boolean, promise: Promise) {
297
- archiveHelper.extractTarBz2(sourcePath, targetPath, force, promise) { bytes, total, percent ->
298
- emitExtractProgress(sourcePath, bytes, total, percent)
299
- }
296
+ override fun extractTarBz2(
297
+ sourcePath: String,
298
+ targetPath: String,
299
+ force: Boolean,
300
+ showNotificationsEnabled: Boolean?,
301
+ notificationTitle: String?,
302
+ notificationText: String?,
303
+ promise: Promise,
304
+ ) {
305
+ val notif = extractionNotificationOrNull(
306
+ showNotificationsEnabled,
307
+ notificationTitle,
308
+ notificationText,
309
+ )
310
+ archiveHelper.extractTarBz2(
311
+ sourcePath,
312
+ targetPath,
313
+ force,
314
+ promise,
315
+ { bytes, total, percent ->
316
+ emitExtractProgress(sourcePath, bytes, total, percent)
317
+ },
318
+ notif,
319
+ )
300
320
  }
301
321
 
302
322
  override fun cancelExtractTarBz2(promise: Promise) {
@@ -304,10 +324,30 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
304
324
  promise.resolve(null)
305
325
  }
306
326
 
307
- override fun extractTarZst(sourcePath: String, targetPath: String, force: Boolean, promise: Promise) {
308
- archiveHelper.extractTarZst(sourcePath, targetPath, force, promise) { bytes, total, percent ->
309
- emitExtractTarZstProgress(sourcePath, bytes, total, percent)
310
- }
327
+ override fun extractTarZst(
328
+ sourcePath: String,
329
+ targetPath: String,
330
+ force: Boolean,
331
+ showNotificationsEnabled: Boolean?,
332
+ notificationTitle: String?,
333
+ notificationText: String?,
334
+ promise: Promise,
335
+ ) {
336
+ val notif = extractionNotificationOrNull(
337
+ showNotificationsEnabled,
338
+ notificationTitle,
339
+ notificationText,
340
+ )
341
+ archiveHelper.extractTarZst(
342
+ sourcePath,
343
+ targetPath,
344
+ force,
345
+ promise,
346
+ { bytes, total, percent ->
347
+ emitExtractTarZstProgress(sourcePath, bytes, total, percent)
348
+ },
349
+ notif,
350
+ )
311
351
  }
312
352
 
313
353
  override fun cancelExtractTarZst(promise: Promise) {
@@ -346,6 +386,20 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
346
386
  eventEmitter.emit("extractTarZstProgress", payload)
347
387
  }
348
388
 
389
+ /** Null when extraction notifications are disabled (`showNotificationsEnabled == false`). */
390
+ private fun extractionNotificationOrNull(
391
+ showNotificationsEnabled: Boolean?,
392
+ notificationTitle: String?,
393
+ notificationText: String?,
394
+ ): SherpaOnnxExtractionNotificationHelper? {
395
+ return SherpaOnnxExtractionNotificationHelper.maybeCreate(
396
+ reactApplicationContext,
397
+ showNotificationsEnabled,
398
+ notificationTitle,
399
+ notificationText,
400
+ )
401
+ }
402
+
349
403
  /**
350
404
  * Resolve asset path - copy from assets to internal storage if needed
351
405
  * Preserves the directory structure from assets (e.g., test_wavs/ stays as test_wavs/)
@@ -1101,34 +1155,54 @@ class SherpaOnnxModule(reactContext: ReactApplicationContext) :
1101
1155
  assetPath: String,
1102
1156
  targetPath: String,
1103
1157
  force: Boolean,
1104
- promise: Promise
1158
+ showNotificationsEnabled: Boolean?,
1159
+ notificationTitle: String?,
1160
+ notificationText: String?,
1161
+ promise: Promise,
1105
1162
  ) {
1163
+ val notif = extractionNotificationOrNull(
1164
+ showNotificationsEnabled,
1165
+ notificationTitle,
1166
+ notificationText,
1167
+ )
1106
1168
  archiveHelper.extractTarZstFromAsset(
1107
1169
  reactApplicationContext,
1108
1170
  assetPath,
1109
1171
  targetPath,
1110
1172
  force,
1111
- promise
1112
- ) { bytes, total, percent ->
1113
- emitExtractTarZstProgress(assetPath, bytes, total, percent)
1114
- }
1173
+ promise,
1174
+ { bytes, total, percent ->
1175
+ emitExtractTarZstProgress(assetPath, bytes, total, percent)
1176
+ },
1177
+ notif,
1178
+ )
1115
1179
  }
1116
1180
 
1117
1181
  override fun extractTarBz2FromAsset(
1118
1182
  assetPath: String,
1119
1183
  targetPath: String,
1120
1184
  force: Boolean,
1121
- promise: Promise
1185
+ showNotificationsEnabled: Boolean?,
1186
+ notificationTitle: String?,
1187
+ notificationText: String?,
1188
+ promise: Promise,
1122
1189
  ) {
1190
+ val notif = extractionNotificationOrNull(
1191
+ showNotificationsEnabled,
1192
+ notificationTitle,
1193
+ notificationText,
1194
+ )
1123
1195
  archiveHelper.extractTarBz2FromAsset(
1124
1196
  reactApplicationContext,
1125
1197
  assetPath,
1126
1198
  targetPath,
1127
1199
  force,
1128
- promise
1129
- ) { bytes, total, percent ->
1130
- emitExtractProgress(assetPath, bytes, total, percent)
1131
- }
1200
+ promise,
1201
+ { bytes, total, percent ->
1202
+ emitExtractProgress(assetPath, bytes, total, percent)
1203
+ },
1204
+ notif,
1205
+ )
1132
1206
  }
1133
1207
 
1134
1208
  override fun readAssetFileAsUtf8(assetPath: String, promise: Promise) {
@@ -22,6 +22,7 @@ import com.k2fsa.sherpa.onnx.OfflineSenseVoiceModelConfig
22
22
  import com.k2fsa.sherpa.onnx.OfflineZipformerCtcModelConfig
23
23
  import com.k2fsa.sherpa.onnx.OfflineWenetCtcModelConfig
24
24
  import com.k2fsa.sherpa.onnx.OfflineFunAsrNanoModelConfig
25
+ import com.k2fsa.sherpa.onnx.OfflineQwen3AsrModelConfig
25
26
  import com.k2fsa.sherpa.onnx.OfflineMoonshineModelConfig
26
27
  import com.k2fsa.sherpa.onnx.OfflineDolphinModelConfig
27
28
  import com.k2fsa.sherpa.onnx.OfflineFireRedAsrModelConfig
@@ -541,6 +542,10 @@ internal class SherpaOnnxSttHelper(
541
542
  val hasHotwords = fn.hasKey("hotwords") && fn.getString("hotwords")?.isNotBlank() == true
542
543
  parts.add("funasrNano:lang=$lang,hotwords=$hasHotwords")
543
544
  }
545
+ modelOptions.getMap("qwen3Asr")?.let { q ->
546
+ val mnt = if (q.hasKey("maxNewTokens")) q.getInt("maxNewTokens") else null
547
+ parts.add("qwen3Asr:maxNewTokens=$mnt")
548
+ }
544
549
  return parts.joinToString(";").take(200)
545
550
  }
546
551
 
@@ -699,6 +704,23 @@ internal class SherpaOnnxSttHelper(
699
704
  tokens = ""
700
705
  )
701
706
  }
707
+ "qwen3_asr" -> {
708
+ val q3 = modelOptions?.getMap("qwen3Asr")
709
+ OfflineModelConfig(
710
+ qwen3Asr = OfflineQwen3AsrModelConfig(
711
+ convFrontend = path(paths, "qwen3ConvFrontend"),
712
+ encoder = path(paths, "qwen3Encoder"),
713
+ decoder = path(paths, "qwen3Decoder"),
714
+ tokenizer = path(paths, "qwen3Tokenizer"),
715
+ maxTotalLen = if (q3?.hasKey("maxTotalLen") == true) q3.getInt("maxTotalLen") else 512,
716
+ maxNewTokens = if (q3?.hasKey("maxNewTokens") == true) q3.getInt("maxNewTokens") else 128,
717
+ temperature = if (q3?.hasKey("temperature") == true) q3.getDouble("temperature").toFloat() else 1e-6f,
718
+ topP = if (q3?.hasKey("topP") == true) q3.getDouble("topP").toFloat() else 0.8f,
719
+ seed = if (q3?.hasKey("seed") == true) q3.getInt("seed") else 42
720
+ ),
721
+ tokens = ""
722
+ )
723
+ }
702
724
  else -> {
703
725
  val tokens = path(paths, "tokens")
704
726
  when {
@@ -397,6 +397,7 @@ sherpa-onnx-rk3576-streaming-zipformer-en-2023-06-26.tar.bz2,apache-2.0,yes,high
397
397
  sherpa-onnx-rk3568-streaming-zipformer-en-2023-06-26.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/csukuangfj/sherpa-onnx-streaming-zipformer-en-2023-06-26
398
398
  sherpa-onnx-rk3566-streaming-zipformer-en-2023-06-26.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/csukuangfj/sherpa-onnx-streaming-zipformer-en-2023-06-26
399
399
  sherpa-onnx-rk3562-streaming-zipformer-en-2023-06-26.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/csukuangfj/sherpa-onnx-streaming-zipformer-en-2023-06-26
400
+ sherpa-onnx-qwen3-asr-0.6B-int8-2026-03-25.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/Qwen/Qwen3-ASR-0.6B
400
401
  sherpa-onnx-rk3588-streaming-zipformer-small-bilingual-zh-en-2023-02-16.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/csukuangfj/k2fsa-zipformer-bilingual-zh-en-t
401
402
  sherpa-onnx-rk3588-streaming-zipformer-bilingual-zh-en-2023-02-20.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/csukuangfj/k2fsa-zipformer-bilingual-zh-en-t
402
403
  sherpa-onnx-rk3576-streaming-zipformer-small-bilingual-zh-en-2023-02-16.tar.bz2,apache-2.0,yes,high,manual,https://huggingface.co/csukuangfj/k2fsa-zipformer-bilingual-zh-en-t
@@ -36,6 +36,7 @@ static NSString *sttModelKindToNSString(sherpaonnx::SttModelKind kind) {
36
36
  case K::kZipformerCtc: return @"zipformer_ctc";
37
37
  case K::kWhisper: return @"whisper";
38
38
  case K::kFunAsrNano: return @"funasr_nano";
39
+ case K::kQwen3Asr: return @"qwen3_asr";
39
40
  case K::kFireRedAsr: return @"fire_red_asr";
40
41
  case K::kMoonshine: return @"moonshine";
41
42
  case K::kMoonshineV2: return @"moonshine_v2";
@@ -164,10 +165,12 @@ static NSDictionary *sttResultToDict(const sherpaonnx::SttRecognitionResult& r)
164
165
  sherpaonnx::SttSenseVoiceOptions senseVoiceOpts;
165
166
  sherpaonnx::SttCanaryOptions canaryOpts;
166
167
  sherpaonnx::SttFunAsrNanoOptions funasrNanoOpts;
168
+ sherpaonnx::SttQwen3AsrOptions qwen3AsrOpts;
167
169
  const sherpaonnx::SttWhisperOptions *whisperOptsPtr = nullptr;
168
170
  const sherpaonnx::SttSenseVoiceOptions *senseVoiceOptsPtr = nullptr;
169
171
  const sherpaonnx::SttCanaryOptions *canaryOptsPtr = nullptr;
170
172
  const sherpaonnx::SttFunAsrNanoOptions *funasrNanoOptsPtr = nullptr;
173
+ const sherpaonnx::SttQwen3AsrOptions *qwen3AsrOptsPtr = nullptr;
171
174
  if (modelOptions != nil && [modelOptions isKindOfClass:[NSDictionary class]]) {
172
175
  NSDictionary *w = modelOptions[@"whisper"];
173
176
  if ([w isKindOfClass:[NSDictionary class]]) {
@@ -202,12 +205,21 @@ static NSDictionary *sttResultToDict(const sherpaonnx::SttRecognitionResult& r)
202
205
  if (fn[@"hotwords"] != nil) funasrNanoOpts.hotwords = std::string([(NSString *)fn[@"hotwords"] UTF8String]);
203
206
  funasrNanoOptsPtr = &funasrNanoOpts;
204
207
  }
208
+ NSDictionary *q3 = modelOptions[@"qwen3Asr"];
209
+ if ([q3 isKindOfClass:[NSDictionary class]]) {
210
+ if (q3[@"maxTotalLen"] != nil) qwen3AsrOpts.max_total_len = [(NSNumber *)q3[@"maxTotalLen"] intValue];
211
+ if (q3[@"maxNewTokens"] != nil) qwen3AsrOpts.max_new_tokens = [(NSNumber *)q3[@"maxNewTokens"] intValue];
212
+ if (q3[@"temperature"] != nil) qwen3AsrOpts.temperature = [(NSNumber *)q3[@"temperature"] floatValue];
213
+ if (q3[@"topP"] != nil) qwen3AsrOpts.top_p = [(NSNumber *)q3[@"topP"] floatValue];
214
+ if (q3[@"seed"] != nil) qwen3AsrOpts.seed = [(NSNumber *)q3[@"seed"] intValue];
215
+ qwen3AsrOptsPtr = &qwen3AsrOpts;
216
+ }
205
217
  }
206
218
 
207
219
  sherpaonnx::SttInitializeResult result = inst->wrapper->initialize(
208
220
  modelDirStr, preferInt8Opt, modelTypeOpt, debugVal, hotwordsFileOpt, hotwordsScoreOpt,
209
221
  numThreadsOpt, providerOpt, ruleFstsOpt, ruleFarsOpt, ditherOpt,
210
- whisperOptsPtr, senseVoiceOptsPtr, canaryOptsPtr, funasrNanoOptsPtr);
222
+ whisperOptsPtr, senseVoiceOptsPtr, canaryOptsPtr, funasrNanoOptsPtr, qwen3AsrOptsPtr);
211
223
 
212
224
  if (result.success) {
213
225
  RCTLogInfo(@"Sherpa-onnx initialized successfully");