From ee54654e7ac0fb98e4948aa6b5e9a82b6be11015 Mon Sep 17 00:00:00 2001 From: jakmro Date: Wed, 18 Mar 2026 11:13:27 +0100 Subject: [PATCH 1/5] fix: add slug and capabilities to CactusModel, resolve version per-model Signed-off-by: jakmro --- example/ios/Podfile.lock | 4 +- example/src/ModelBrowserScreen.tsx | 26 +++++++++ package.json | 2 +- src/modelRegistry.ts | 86 +++++++++++++++--------------- src/types/common.ts | 2 + 5 files changed, 75 insertions(+), 45 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a413b95..baab54c 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - boost (1.84.0) - - Cactus (1.10.2): + - Cactus (1.10.3): - boost - DoubleConversion - fast_float @@ -2643,7 +2643,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 - Cactus: 234297db6f48dd9652a137faa9121c5c4b27882e + Cactus: 4bca7bd8231819f4aadc2661ac393773352a14fd DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 FBLazyVector: b8f1312d48447cca7b4abc21ed155db14742bd03 diff --git a/example/src/ModelBrowserScreen.tsx b/example/src/ModelBrowserScreen.tsx index 9e64d9e..fe7cbaf 100644 --- a/example/src/ModelBrowserScreen.tsx +++ b/example/src/ModelBrowserScreen.tsx @@ -76,6 +76,15 @@ const ModelBrowserScreen = () => { return ( {key} + {model.capabilities.length > 0 && ( + + {model.capabilities.map((cap) => ( + + {cap} + + ))} + + )} @@ -168,6 +177,23 @@ const styles = StyleSheet.create({ color: '#000', marginBottom: 8, }, + capabilities: { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 4, + marginBottom: 8, + }, + capBadge: { + backgroundColor: '#e8f0fe', + borderRadius: 4, + paddingHorizontal: 6, + paddingVertical: 2, + }, + capLabel: { + fontSize: 11, + color: '#1a73e8', + fontWeight: '500', + }, variants: { gap: 6, }, diff --git a/package.json b/package.json index 79a908b..10a611f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cactus-react-native", - "version": "1.10.2", + "version": "1.10.3", "description": "Run AI models locally on mobile devices", "main": "./lib/module/index.js", "types": "./lib/typescript/src/index.d.ts", diff --git a/src/modelRegistry.ts b/src/modelRegistry.ts index 162c016..e9ef4d6 100644 --- a/src/modelRegistry.ts +++ b/src/modelRegistry.ts @@ -1,6 +1,6 @@ import type { CactusModel } from './types/common'; -const RUNTIME_VERSION = '1.10.2'; +const RUNTIME_VERSION = '1.10.3'; let registryPromise: Promise<{ [key: string]: CactusModel }> | null = null; @@ -56,50 +56,52 @@ async function fetchRegistry(): Promise<{ [key: string]: CactusModel }> { const models: any[] = await response.json(); if (!models.length) return {}; - const version = await resolveWeightVersion(models[0]!.id).catch((e) => { - registryPromise = null; - throw e; - }); - const registry: { [key: string]: CactusModel } = {}; - for (const { id, siblings = [] } of models) { - const weights: string[] = siblings - .map((s: any) => s.rfilename) - .filter((f: string) => f.startsWith('weights/') && f.endsWith('.zip')); - - if ( - !weights.some((f) => f.endsWith('-int4.zip')) || - !weights.some((f) => f.endsWith('-int8.zip')) - ) - continue; - - const key = weights - .find((f) => f.endsWith('-int4.zip'))! - .replace('weights/', '') - .replace('-int4.zip', ''); - - const base = `https://huggingface.co/${id}/resolve/${version}/weights/${key}`; - - registry[key] = { - quantization: { - int4: { - sizeMb: 0, - url: `${base}-int4.zip`, - ...(weights.some((f) => f.endsWith('-int4-apple.zip')) - ? { pro: { apple: `${base}-int4-apple.zip` } } - : {}), - }, - int8: { - sizeMb: 0, - url: `${base}-int8.zip`, - ...(weights.some((f) => f.endsWith('-int8-apple.zip')) - ? { pro: { apple: `${base}-int8-apple.zip` } } - : {}), + await Promise.all( + models.map(async ({ id, siblings = [], tags = [] }) => { + const weights: string[] = siblings + .map((s: any) => s.rfilename) + .filter((f: string) => f.startsWith('weights/') && f.endsWith('.zip')); + + if ( + !weights.some((f) => f.endsWith('-int4.zip')) || + !weights.some((f) => f.endsWith('-int8.zip')) + ) + return; + + const version = await resolveWeightVersion(id).catch(() => null); + if (!version) return; + + const key = weights + .find((f) => f.endsWith('-int4.zip'))! + .replace('weights/', '') + .replace('-int4.zip', ''); + + const base = `https://huggingface.co/${id}/resolve/${version}/weights/${key}`; + + registry[key] = { + slug: key, + capabilities: (tags as string[]).filter((t) => !t.includes(':')), + quantization: { + int4: { + sizeMb: 0, + url: `${base}-int4.zip`, + ...(weights.some((f) => f.endsWith('-int4-apple.zip')) + ? { pro: { apple: `${base}-int4-apple.zip` } } + : {}), + }, + int8: { + sizeMb: 0, + url: `${base}-int8.zip`, + ...(weights.some((f) => f.endsWith('-int8-apple.zip')) + ? { pro: { apple: `${base}-int8-apple.zip` } } + : {}), + }, }, - }, - }; - } + }; + }) + ); return registry; } diff --git a/src/types/common.ts b/src/types/common.ts index 03835d5..4b37d82 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -1,4 +1,6 @@ export interface CactusModel { + slug: string; + capabilities: string[]; quantization: { int4: { sizeMb: number; From bf51ce8993e82f5a05ba7aca46508681149c22f7 Mon Sep 17 00:00:00 2001 From: jakmro Date: Wed, 18 Mar 2026 11:27:17 +0100 Subject: [PATCH 2/5] fix: ci Signed-off-by: jakmro --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ca3938..7c059b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -122,7 +122,7 @@ jobs: runs-on: macos-latest env: - XCODE_VERSION: 16.3 + XCODE_VERSION: latest-stable TURBO_CACHE_DIR: .turbo/ios RCT_USE_RN_DEP: 1 RCT_USE_PREBUILT_RNCORE: 1 From eb69879c978a1b40e7c546fa9a8482e493a03283 Mon Sep 17 00:00:00 2001 From: jakmro Date: Wed, 18 Mar 2026 11:51:51 +0100 Subject: [PATCH 3/5] fix: exclude x86_64 simulator architecture from cactus.xcframework Signed-off-by: jakmro --- example/ios/Podfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/example/ios/Podfile b/example/ios/Podfile index 5f7c378..b319352 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -33,5 +33,12 @@ target 'CactusExample' do :mac_catalyst_enabled => false, # :ccache_enabled => true ) + + # cactus.xcframework only ships arm64 slices; exclude x86_64 simulator + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64' + end + end end end From 144a6da58c1d87baf42086c464be574cb3d754be Mon Sep 17 00:00:00 2001 From: jakmro Date: Wed, 18 Mar 2026 11:57:35 +0100 Subject: [PATCH 4/5] fix: refactor Podfile to improve exclusion of x86_64 simulator architecture Signed-off-by: jakmro --- example/ios/Podfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/example/ios/Podfile b/example/ios/Podfile index b319352..2790296 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -35,10 +35,13 @@ target 'CactusExample' do ) # cactus.xcframework only ships arm64 slices; exclude x86_64 simulator - installer.pods_project.targets.each do |target| - target.build_configurations.each do |config| - config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64' + [installer.pods_project, *installer.aggregate_targets.map(&:user_project)].uniq.compact.each do |project| + project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64' + end end + project.save end end end From a636e0bcf45222544595c27fe3db47476df6a4ea Mon Sep 17 00:00:00 2001 From: jakmro Date: Wed, 18 Mar 2026 12:26:37 +0100 Subject: [PATCH 5/5] docs: add language detection feature and update CactusModel with slug and capabilities Signed-off-by: jakmro --- README.md | 22 ++++++++++++++++++- .../CactusExample.xcodeproj/project.pbxproj | 2 ++ example/ios/Podfile.lock | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2cb01c..1a7dd08 100644 --- a/README.md +++ b/README.md @@ -631,6 +631,24 @@ const App = () => { }; ``` +### Language Detection + +Detect the spoken language in an audio file. Only available on the class, not the hook. + +```typescript +import { CactusSTT } from 'cactus-react-native'; + +const cactusSTT = new CactusSTT({ model: 'whisper-small' }); + +const result = await cactusSTT.detectLanguage({ + audio: 'path/to/audio.wav', + options: { useVad: true }, +}); + +console.log('Language:', result.language); // e.g. 'en' +console.log('Confidence:', result.confidence); +``` + ## Voice Activity Detection (VAD) The `CactusVAD` class detects speech segments in audio, returning timestamped intervals where speech is present. @@ -1346,7 +1364,7 @@ import { getRegistry } from 'cactus-react-native'; const registry = await getRegistry(); const model = registry['qwen3-0.6b']; -console.log(model.quantization.int4.url); +console.log(model); ``` ## Type Definitions @@ -1523,6 +1541,8 @@ interface CactusLMImageEmbedResult { ```typescript interface CactusModel { + slug: string; + capabilities: string[]; quantization: { int4: { sizeMb: number; diff --git a/example/ios/CactusExample.xcodeproj/project.pbxproj b/example/ios/CactusExample.xcodeproj/project.pbxproj index 9e027df..355d76e 100644 --- a/example/ios/CactusExample.xcodeproj/project.pbxproj +++ b/example/ios/CactusExample.xcodeproj/project.pbxproj @@ -261,6 +261,7 @@ CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_BITCODE = NO; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; INFOPLIST_FILE = CactusExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( @@ -288,6 +289,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = x86_64; INFOPLIST_FILE = CactusExample/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.1; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index baab54c..edd69f8 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2721,6 +2721,6 @@ SPEC CHECKSUMS: Yoga: fa23995c18b65978347b096d0836f4f5093df545 ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351 -PODFILE CHECKSUM: e920e5314af7e3deb524b3a80ec0380486972889 +PODFILE CHECKSUM: d868979afd4779f52776730343c7a1ecf2f41747 COCOAPODS: 1.15.2