Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1523,6 +1541,8 @@ interface CactusLMImageEmbedResult {

```typescript
interface CactusModel {
slug: string;
capabilities: string[];
quantization: {
int4: {
sizeMb: number;
Expand Down
2 changes: 2 additions & 0 deletions example/ios/CactusExample.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down Expand Up @@ -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 = (
Expand Down
10 changes: 10 additions & 0 deletions example/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,15 @@ target 'CactusExample' do
:mac_catalyst_enabled => false,
# :ccache_enabled => true
)

# cactus.xcframework only ships arm64 slices; exclude x86_64 simulator
[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
6 changes: 3 additions & 3 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PODS:
- boost (1.84.0)
- Cactus (1.10.2):
- Cactus (1.10.3):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -2643,7 +2643,7 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
Cactus: 234297db6f48dd9652a137faa9121c5c4b27882e
Cactus: 4bca7bd8231819f4aadc2661ac393773352a14fd
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
FBLazyVector: b8f1312d48447cca7b4abc21ed155db14742bd03
Expand Down Expand Up @@ -2721,6 +2721,6 @@ SPEC CHECKSUMS:
Yoga: fa23995c18b65978347b096d0836f4f5093df545
ZIPFoundation: dfd3d681c4053ff7e2f7350bc4e53b5dba3f5351

PODFILE CHECKSUM: e920e5314af7e3deb524b3a80ec0380486972889
PODFILE CHECKSUM: d868979afd4779f52776730343c7a1ecf2f41747

COCOAPODS: 1.15.2
26 changes: 26 additions & 0 deletions example/src/ModelBrowserScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ const ModelBrowserScreen = () => {
return (
<View key={key} style={styles.card}>
<Text style={styles.modelName}>{key}</Text>
{model.capabilities.length > 0 && (
<View style={styles.capabilities}>
{model.capabilities.map((cap) => (
<View key={cap} style={styles.capBadge}>
<Text style={styles.capLabel}>{cap}</Text>
</View>
))}
</View>
)}
<View style={styles.variants}>
<VariantRow label="int4" url={int4.url} pro={int4.pro?.apple} />
<VariantRow label="int8" url={int8.url} pro={int8.pro?.apple} />
Expand Down Expand Up @@ -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,
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
86 changes: 44 additions & 42 deletions src/modelRegistry.ts
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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)
Comment thread
jakmro marked this conversation as resolved.
.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;

Comment thread
jakmro marked this conversation as resolved.
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;
}
2 changes: 2 additions & 0 deletions src/types/common.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface CactusModel {
slug: string;
capabilities: string[];
Comment thread
jakmro marked this conversation as resolved.
quantization: {
int4: {
sizeMb: number;
Expand Down
Loading