{"id":46387791,"url":"https://github.com/cactus-compute/cactus-react-native","last_synced_at":"2026-05-05T19:32:12.072Z","repository":{"id":312822223,"uuid":"1048652045","full_name":"cactus-compute/cactus-react-native","owner":"cactus-compute","description":"Cactus React Native package: Run AI locally in your React Native apps","archived":false,"fork":false,"pushed_at":"2026-04-19T03:05:37.000Z","size":107221,"stargazers_count":156,"open_issues_count":6,"forks_count":21,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-04-19T04:34:02.672Z","etag":null,"topics":["ai","apps","cactus","llamacpp","llm","llm-inference","llms","react","react-native"],"latest_commit_sha":null,"homepage":"https://cactuscompute.com","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cactus-compute.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-01T19:51:34.000Z","updated_at":"2026-04-19T03:05:41.000Z","dependencies_parsed_at":"2025-09-02T08:28:02.102Z","dependency_job_id":"bd94b4ad-254b-4b10-93af-b3923cb26b10","html_url":"https://github.com/cactus-compute/cactus-react-native","commit_stats":null,"previous_names":["cactus-compute/cactus-react","cactus-compute/cactus-react-native"],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/cactus-compute/cactus-react-native","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cactus-compute%2Fcactus-react-native","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cactus-compute%2Fcactus-react-native/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cactus-compute%2Fcactus-react-native/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cactus-compute%2Fcactus-react-native/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cactus-compute","download_url":"https://codeload.github.com/cactus-compute/cactus-react-native/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cactus-compute%2Fcactus-react-native/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32664851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","apps","cactus","llamacpp","llm","llm-inference","llms","react","react-native"],"created_at":"2026-03-05T08:07:28.093Z","updated_at":"2026-05-05T19:32:12.066Z","avatar_url":"https://github.com/cactus-compute.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Cactus Logo](assets/logo.png)\n\n## Resources\n\n[![cactus](https://img.shields.io/badge/cactus-000000?logo=github\u0026logoColor=white)](https://github.com/cactus-compute/cactus) [![HuggingFace](https://img.shields.io/badge/HuggingFace-FFD21E?logo=huggingface\u0026logoColor=black)](https://huggingface.co/Cactus-Compute/models?sort=downloads) [![Discord](https://img.shields.io/badge/Discord-5865F2?logo=discord\u0026logoColor=white)](https://discord.gg/bNurx3AXTJ) [![Documentation](https://img.shields.io/badge/Documentation-4285F4?logo=googledocs\u0026logoColor=white)](https://cactuscompute.com/docs/react-native)\n\n## Installation\n\n```bash\nnpm install cactus-react-native react-native-nitro-modules\n```\n\n## Quick Start\n\nGet started with Cactus in just a few lines of code:\n\n```typescript\nimport { CactusLM, type CactusLMMessage } from 'cactus-react-native';\n\n// Create a new instance\nconst cactusLM = new CactusLM();\n\n// Download the model\nawait cactusLM.download({\n  onProgress: (progress) =\u003e console.log(`Download: ${Math.round(progress * 100)}%`)\n});\n\n// Generate a completion\nconst messages: CactusLMMessage[] = [\n  { role: 'user', content: 'What is the capital of France?' }\n];\n\nconst result = await cactusLM.complete({ messages });\nconsole.log(result.response); // \"The capital of France is Paris.\"\n\n// Clean up resources\nawait cactusLM.destroy();\n```\n\n**Using the React Hook:**\n\n```tsx\nimport { useCactusLM } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM();\n\n  useEffect(() =\u003e {\n    // Download the model if not already available\n    if (!cactusLM.isDownloaded) {\n      cactusLM.download();\n    }\n  }, []);\n\n  const handleGenerate = () =\u003e {\n    // Generate a completion\n    cactusLM.complete({\n      messages: [{ role: 'user', content: 'Hello!' }],\n    });\n  };\n\n  if (cactusLM.isDownloading) {\n    return (\n      \u003cText\u003e\n        Downloading model: {Math.round(cactusLM.downloadProgress * 100)}%\n      \u003c/Text\u003e\n    );\n  }\n\n  return (\n    \u003c\u003e\n      \u003cButton onPress={handleGenerate} title=\"Generate\" /\u003e\n      \u003cText\u003e{cactusLM.completion}\u003c/Text\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n## Language Model\n\n### Model Options\n\nChoose model quantization and NPU acceleration with Pro models.\n\n```typescript\nimport { CactusLM } from 'cactus-react-native';\n\n// Use int8 for better accuracy (default)\nconst cactusLM = new CactusLM({\n  model: 'lfm2-vl-450m',\n  options: {\n    quantization: 'int8', // 'int4' or 'int8'\n    pro: false\n  }\n});\n\n// Use pro models for NPU acceleration\nconst cactusPro = new CactusLM({\n  model: 'lfm2-vl-450m',\n  options: {\n    quantization: 'int8',\n    pro: true\n  }\n});\n```\n\n### Completion\n\nGenerate text responses from the model by providing a conversation history.\n\n#### Class\n\n```typescript\nimport { CactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM();\n\nconst messages: CactusLMMessage[] = [{ role: 'user', content: 'Hello, World!' }];\nconst onToken = (token: string) =\u003e { console.log('Token:', token) };\n\nconst result = await cactusLM.complete({ messages, onToken });\nconsole.log('Completion result:', result);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM();\n\n  const handleComplete = async () =\u003e {\n    const messages: CactusLMMessage[] = [{ role: 'user', content: 'Hello, World!' }];\n\n    const result = await cactusLM.complete({ messages });\n    console.log('Completion result:', result);\n  };\n\n  return (\n    \u003c\u003e\n      \u003cButton title=\"Complete\" onPress={handleComplete} /\u003e\n      \u003cText\u003e{cactusLM.completion}\u003c/Text\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n### Vision\n\nVision allows you to pass images along with text prompts, enabling the model to analyze and understand visual content.\n\n#### Class\n\n```typescript\nimport { CactusLM, type CactusLMMessage } from 'cactus-react-native';\n\n// Vision-capable model\nconst cactusLM = new CactusLM({ model: 'lfm2-vl-450m' });\n\nconst messages: CactusLMMessage[] = [\n  {\n    role: 'user',\n    content: \"What's in the image?\",\n    images: ['path/to/your/image'],\n  },\n];\n\nconst result = await cactusLM.complete({ messages });\nconsole.log('Response:', result.response);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  // Vision-capable model\n  const cactusLM = useCactusLM({ model: 'lfm2-vl-450m' });\n\n  const handleAnalyze = async () =\u003e {\n    const messages: CactusLMMessage[] = [\n      {\n        role: 'user',\n        content: \"What's in the image?\",\n        images: ['path/to/your/image'],\n      },\n    ];\n\n    await cactusLM.complete({ messages });\n  };\n\n  return (\n    \u003c\u003e\n      \u003cButton title=\"Analyze Image\" onPress={handleAnalyze} /\u003e\n      \u003cText\u003e{cactusLM.completion}\u003c/Text\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n### Tool Calling\n\nEnable the model to generate function calls by defining available tools and their parameters.\n\n#### Class\n\n```typescript\nimport { CactusLM, type CactusLMMessage, type CactusLMTool } from 'cactus-react-native';\n\nconst tools: CactusLMTool[] = [\n  {\n    name: 'get_weather',\n    description: 'Get current weather for a location',\n    parameters: {\n      type: 'object',\n      properties: {\n        location: {\n          type: 'string',\n          description: 'City name',\n        },\n      },\n      required: ['location'],\n    },\n  },\n];\n\nconst cactusLM = new CactusLM();\n\nconst messages: CactusLMMessage[] = [\n  { role: 'user', content: \"What's the weather in San Francisco?\" },\n];\n\nconst result = await cactusLM.complete({ messages, tools });\nconsole.log('Response:', result.response);\nconsole.log('Function calls:', result.functionCalls);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM, type CactusLMMessage, type CactusLMTool } from 'cactus-react-native';\n\nconst tools: CactusLMTool[] = [\n  {\n    name: 'get_weather',\n    description: 'Get current weather for a location',\n    parameters: {\n      type: 'object',\n      properties: {\n        location: {\n          type: 'string',\n          description: 'City name',\n        },\n      },\n      required: ['location'],\n    },\n  },\n];\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM();\n\n  const handleComplete = async () =\u003e {\n    const messages: CactusLMMessage[] = [\n      { role: 'user', content: \"What's the weather in San Francisco?\" },\n    ];\n\n    const result = await cactusLM.complete({ messages, tools });\n    console.log('Response:', result.response);\n    console.log('Function calls:', result.functionCalls);\n  };\n\n  return \u003cButton title=\"Complete\" onPress={handleComplete} /\u003e;\n};\n```\n\n### Audio Completion\n\nPass raw PCM audio alongside text prompts for multimodal completion with audio-capable models like Gemma 4.\n\n#### Class\n\n```typescript\nimport { CactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM({ model: 'gemma-4-e2b-it' });\n\nconst messages: CactusLMMessage[] = [\n  { role: 'user', content: 'What do you hear in this audio?' },\n];\n\n// Pass raw 16-bit PCM samples as a byte array\nconst pcmAudio: number[] = [/* raw PCM bytes */];\nconst result = await cactusLM.complete({ messages, audio: pcmAudio });\nconsole.log(result.response);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM({ model: 'gemma-4-e2b-it' });\n\n  const handleAudioComplete = async (pcmAudio: number[]) =\u003e {\n    const messages: CactusLMMessage[] = [\n      { role: 'user', content: 'Describe this audio.' },\n    ];\n\n    await cactusLM.complete({ messages, audio: pcmAudio });\n  };\n\n  return \u003cText\u003e{cactusLM.completion}\u003c/Text\u003e;\n};\n```\n\n### RAG (Retrieval Augmented Generation)\n\nRAG allows you to provide a corpus of documents that the model can reference during generation, enabling it to answer questions based on your data.\n\n#### Class\n\n```typescript\nimport { CactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM({\n  corpusDir: 'path/to/your/corpus', // Directory containing .txt files\n});\n\nconst messages: CactusLMMessage[] = [\n  { role: 'user', content: 'What information is in the documents?' },\n];\n\nconst result = await cactusLM.complete({ messages });\nconsole.log(result.response);\n\n// Or query the RAG index directly\nconst ragResult = await cactusLM.ragQuery({ query: 'search terms', topK: 5 });\nconsole.log('Chunks:', ragResult.chunks);\n// [{ score: 0.85, source: 'doc.txt', content: '...' }, ...]\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM, type CactusLMMessage } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM({\n    corpusDir: 'path/to/your/corpus', // Directory containing .txt files\n  });\n\n  const handleAsk = async () =\u003e {\n    const messages: CactusLMMessage[] = [\n      { role: 'user', content: 'What information is in the documents?' },\n    ];\n\n    await cactusLM.complete({ messages });\n  };\n\n  const handleRagQuery = async () =\u003e {\n    const result = await cactusLM.ragQuery({ query: 'search terms', topK: 3 });\n    console.log('Chunks:', result.chunks);\n  };\n\n  return (\n    \u003c\u003e\n      \u003cButton title=\"Ask Question\" onPress={handleAsk} /\u003e\n      \u003cButton title=\"RAG Query\" onPress={handleRagQuery} /\u003e\n      \u003cText\u003e{cactusLM.completion}\u003c/Text\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n### Tokenization\n\nConvert text into tokens using the model's tokenizer.\n\n#### Class\n\n```typescript\nimport { CactusLM } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM();\n\nconst result = await cactusLM.tokenize({ text: 'Hello, World!' });\nconsole.log('Token IDs:', result.tokens);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM();\n\n  const handleTokenize = async () =\u003e {\n    const result = await cactusLM.tokenize({ text: 'Hello, World!' });\n    console.log('Token IDs:', result.tokens);\n  };\n\n  return \u003cButton title=\"Tokenize\" onPress={handleTokenize} /\u003e;\n};\n```\n\n### Score Window\n\nCalculate perplexity scores for a window of tokens within a sequence.\n\n#### Class\n\n```typescript\nimport { CactusLM } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM();\n\nconst tokens = [123, 456, 789, 101, 112];\nconst result = await cactusLM.scoreWindow({\n  tokens,\n  start: 1,\n  end: 3,\n  context: 2\n});\nconsole.log('Score:', result.score);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusLM } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM();\n\n  const handleScoreWindow = async () =\u003e {\n    const tokens = [123, 456, 789, 101, 112];\n    const result = await cactusLM.scoreWindow({\n      tokens,\n      start: 1,\n      end: 3,\n      context: 2\n    });\n    console.log('Score:', result.score);\n  };\n\n  return \u003cButton title=\"Score Window\" onPress={handleScoreWindow} /\u003e;\n};\n```\n\n### Embedding\n\nConvert text and images into numerical vector representations that capture semantic meaning, useful for similarity search and semantic understanding.\n\n#### Text Embedding\n\n##### Class\n\n```typescript\nimport { CactusLM } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM();\n\nconst result = await cactusLM.embed({ text: 'Hello, World!' });\nconsole.log('Embedding vector:', result.embedding);\nconsole.log('Embedding vector length:', result.embedding.length);\n```\n\n##### Hook\n\n```tsx\nimport { useCactusLM } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM();\n\n  const handleEmbed = async () =\u003e {\n    const result = await cactusLM.embed({ text: 'Hello, World!' });\n    console.log('Embedding vector:', result.embedding);\n    console.log('Embedding vector length:', result.embedding.length);\n  };\n\n  return \u003cButton title=\"Embed\" onPress={handleEmbed} /\u003e;\n};\n```\n\n#### Image Embedding\n\n##### Class\n\n```typescript\nimport { CactusLM } from 'cactus-react-native';\n\nconst cactusLM = new CactusLM({ model: 'lfm2-vl-450m' });\n\nconst result = await cactusLM.imageEmbed({ imagePath: 'path/to/your/image.jpg' });\nconsole.log('Image embedding vector:', result.embedding);\nconsole.log('Embedding vector length:', result.embedding.length);\n```\n\n##### Hook\n\n```tsx\nimport { useCactusLM } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusLM = useCactusLM({ model: 'lfm2-vl-450m' });\n\n  const handleImageEmbed = async () =\u003e {\n    const result = await cactusLM.imageEmbed({ imagePath: 'path/to/your/image.jpg' });\n    console.log('Image embedding vector:', result.embedding);\n    console.log('Embedding vector length:', result.embedding.length);\n  };\n\n  return \u003cButton title=\"Embed Image\" onPress={handleImageEmbed} /\u003e;\n};\n```\n\n## Speech-to-Text (STT)\n\nThe `CactusSTT` class provides audio transcription and audio embedding capabilities using speech-to-text models such as Whisper and Moonshine.\n\n### Transcription\n\nTranscribe audio to text with streaming support. Accepts either a file path or raw PCM audio samples.\n\n#### Class\n\n```typescript\nimport { CactusSTT } from 'cactus-react-native';\n\nconst cactusSTT = new CactusSTT({ model: 'whisper-small' });\n\n// Transcribe from file path\nconst result = await cactusSTT.transcribe({\n  audio: 'path/to/audio.wav',\n  onToken: (token) =\u003e console.log('Token:', token)\n});\n\nconsole.log('Transcription:', result.response);\n\n// Or transcribe from raw PCM samples\nconst pcmSamples: number[] = [/* ... */];\nconst result2 = await cactusSTT.transcribe({\n  audio: pcmSamples,\n  onToken: (token) =\u003e console.log('Token:', token)\n});\n\nconsole.log('Transcription:', result2.response);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusSTT } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusSTT = useCactusSTT({ model: 'whisper-small' });\n\n  const handleTranscribe = async () =\u003e {\n    // Transcribe from file path\n    const result = await cactusSTT.transcribe({\n      audio: 'path/to/audio.wav',\n    });\n    console.log('Transcription:', result.response);\n\n    const pcmSamples: number[] = [/* ... */];\n    const result2 = await cactusSTT.transcribe({\n      audio: pcmSamples,\n    });\n    console.log('Transcription:', result2.response);\n  };\n\n  return (\n    \u003c\u003e\n      \u003cButton onPress={handleTranscribe} title=\"Transcribe\" /\u003e\n      \u003cText\u003e{cactusSTT.transcription}\u003c/Text\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n### Streaming Transcription\n\nTranscribe audio in real-time with incremental results. Each call to `streamTranscribeProcess` feeds an audio chunk and returns the currently confirmed and pending text.\n\n#### Class\n\n```typescript\nimport { CactusSTT } from 'cactus-react-native';\n\nconst cactusSTT = new CactusSTT({ model: 'whisper-small' });\n\nawait cactusSTT.streamTranscribeStart({\n  confirmationThreshold: 0.99,  // confidence required to confirm text\n  minChunkSize: 32000,          // minimum samples before processing\n});\n\nconst audioChunk: number[] = [/* PCM samples as bytes */];\nconst result = await cactusSTT.streamTranscribeProcess({ audio: audioChunk });\n\nconsole.log('Confirmed:', result.confirmed);\nconsole.log('Pending:', result.pending);\n\nconst final = await cactusSTT.streamTranscribeStop();\nconsole.log('Final confirmed:', final.confirmed);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusSTT } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusSTT = useCactusSTT({ model: 'whisper-small' });\n\n  const handleStart = async () =\u003e {\n    await cactusSTT.streamTranscribeStart({ confirmationThreshold: 0.99 });\n  };\n\n  const handleChunk = async (audioChunk: number[]) =\u003e {\n    const result = await cactusSTT.streamTranscribeProcess({ audio: audioChunk });\n    console.log('Confirmed:', result.confirmed);\n    console.log('Pending:', result.pending);\n  };\n\n  const handleStop = async () =\u003e {\n    const final = await cactusSTT.streamTranscribeStop();\n    console.log('Final:', final.confirmed);\n  };\n\n  return (\n    \u003c\u003e\n      \u003cButton onPress={handleStart} title=\"Start\" /\u003e\n      \u003cButton onPress={handleStop} title=\"Stop\" /\u003e\n      \u003cText\u003e{cactusSTT.streamTranscribeConfirmed}\u003c/Text\u003e\n      \u003cText\u003e{cactusSTT.streamTranscribePending}\u003c/Text\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n### Audio Embedding\n\nGenerate embeddings from audio files for audio understanding.\n\n#### Class\n\n```typescript\nimport { CactusSTT } from 'cactus-react-native';\n\nconst cactusSTT = new CactusSTT();\n\nconst result = await cactusSTT.audioEmbed({\n  audioPath: 'path/to/audio.wav'\n});\n\nconsole.log('Audio embedding vector:', result.embedding);\nconsole.log('Embedding vector length:', result.embedding.length);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusSTT } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusSTT = useCactusSTT();\n\n  const handleAudioEmbed = async () =\u003e {\n    const result = await cactusSTT.audioEmbed({\n      audioPath: 'path/to/audio.wav'\n    });\n    console.log('Audio embedding vector:', result.embedding);\n    console.log('Embedding vector length:', result.embedding.length);\n  };\n\n  return \u003cButton title=\"Embed Audio\" onPress={handleAudioEmbed} /\u003e;\n};\n```\n\n### Language Detection\n\nDetect the spoken language in an audio file. Only available on the class, not the hook.\n\n```typescript\nimport { CactusSTT } from 'cactus-react-native';\n\nconst cactusSTT = new CactusSTT({ model: 'whisper-small' });\n\nconst result = await cactusSTT.detectLanguage({\n  audio: 'path/to/audio.wav',\n  options: { useVad: true },\n});\n\nconsole.log('Language:', result.language);  // e.g. 'en'\nconsole.log('Confidence:', result.confidence);\n```\n\n## Audio Processing\n\nThe `CactusAudio` class provides voice activity detection (VAD), speaker diarization, and speaker embedding extraction.\n\n### Voice Activity Detection\n\n```typescript\nimport { CactusAudio } from 'cactus-react-native';\n\nconst cactusAudio = new CactusAudio({ model: 'silero-vad' });\n\nconst result = await cactusAudio.vad({\n  audio: 'path/to/audio.wav',\n  options: {\n    threshold: 0.5,\n    minSpeechDurationMs: 250,\n    minSilenceDurationMs: 100,\n  }\n});\n\nconsole.log('Speech segments:', result.segments);\n// [{ start: 0, end: 16000 }, { start: 32000, end: 48000 }, ...]\nconsole.log('Total time (ms):', result.totalTime);\n```\n\n### Speaker Diarization\n\n```typescript\nimport { CactusAudio } from 'cactus-react-native';\n\nconst cactusAudio = new CactusAudio({ model: 'silero-vad' });\n\nconst result = await cactusAudio.diarize({\n  audio: 'path/to/audio.wav',\n  options: {\n    numSpeakers: 2,\n    minSpeakers: 1,\n    maxSpeakers: 4,\n  }\n});\n\nconsole.log('Number of speakers:', result.numSpeakers);\nconsole.log('Scores:', result.scores);\n```\n\n### Speaker Embedding\n\nExtract a speaker embedding vector from audio, optionally with mask weights for speaker-specific segments from diarization.\n\n```typescript\nimport { CactusAudio } from 'cactus-react-native';\n\nconst cactusAudio = new CactusAudio({ model: 'silero-vad' });\n\nconst result = await cactusAudio.embedSpeaker({\n  audio: 'path/to/audio.wav',\n});\n\nconsole.log('Speaker embedding:', result.embedding);\n\n// With mask weights from diarization\nconst maskedResult = await cactusAudio.embedSpeaker({\n  audio: 'path/to/audio.wav',\n  options: {\n    maskWeights: [1.0, 1.0, 0.0, 0.0, 1.0], // per-frame weights\n    maskNumFrames: 5,\n  },\n});\n```\n\n### Hook\n\n```tsx\nimport { useCactusAudio } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusAudio = useCactusAudio({ model: 'silero-vad' });\n\n  const handleVAD = async () =\u003e {\n    const result = await cactusAudio.vad({\n      audio: 'path/to/audio.wav',\n    });\n    console.log('Speech segments:', result.segments);\n  };\n\n  const handleDiarize = async () =\u003e {\n    const result = await cactusAudio.diarize({\n      audio: 'path/to/audio.wav',\n    });\n    console.log('Speakers:', result.numSpeakers);\n  };\n\n  return (\n    \u003c\u003e\n      \u003cButton title=\"Detect Speech\" onPress={handleVAD} /\u003e\n      \u003cButton title=\"Diarize\" onPress={handleDiarize} /\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n## Vector Index\n\nThe `CactusIndex` class provides a vector database for storing and querying embeddings with metadata. Enabling similarity search and retrieval.\n\n### Creating and Initializing an Index\n\n#### Class\n\n```typescript\nimport { CactusIndex } from 'cactus-react-native';\n\nconst cactusIndex = new CactusIndex('my-index', 1024);\nawait cactusIndex.init();\n```\n\n#### Hook\n\n```tsx\nimport { useCactusIndex } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusIndex = useCactusIndex({\n    name: 'my-index',\n    embeddingDim: 1024\n  });\n\n  const handleInit = async () =\u003e {\n    await cactusIndex.init();\n  };\n\n  return \u003cButton title=\"Initialize Index\" onPress={handleInit} /\u003e\n};\n```\n\n### Adding Documents\n\nAdd documents with their embeddings and metadata to the index.\n\n#### Class\n\n```typescript\nimport { CactusIndex } from 'cactus-react-native';\n\nconst cactusIndex = new CactusIndex('my-index', 1024);\nawait cactusIndex.init();\n\nawait cactusIndex.add({\n  ids: [1, 2, 3],\n  documents: ['First document', 'Second document', 'Third document'],\n  embeddings: [\n    [0.1, 0.2, ...],\n    [0.3, 0.4, ...],\n    [0.5, 0.6, ...]\n  ],\n  metadatas: ['metadata1', 'metadata2', 'metadata3']\n});\n```\n\n#### Hook\n\n```tsx\nimport { useCactusIndex } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusIndex = useCactusIndex({\n    name: 'my-index',\n    embeddingDim: 1024\n  });\n\n  const handleAdd = async () =\u003e {\n    await cactusIndex.add({\n      ids: [1, 2, 3],\n      documents: ['First document', 'Second document', 'Third document'],\n      embeddings: [\n        [0.1, 0.2, ...],\n        [0.3, 0.4, ...],\n        [0.5, 0.6, ...]\n      ],\n      metadatas: ['metadata1', 'metadata2', 'metadata3']\n    });\n  };\n\n  return \u003cButton title=\"Add Documents\" onPress={handleAdd} /\u003e;\n};\n```\n\n### Querying the Index\n\nSearch for similar documents using embedding vectors.\n\n#### Class\n\n```typescript\nimport { CactusIndex } from 'cactus-react-native';\n\nconst cactusIndex = new CactusIndex('my-index', 1024);\nawait cactusIndex.init();\n\nconst result = await cactusIndex.query({\n  embeddings: [[0.1, 0.2, ...]],\n  options: {\n    topK: 5,\n    scoreThreshold: 0.7\n  }\n});\n\nconsole.log('IDs:', result.ids);\nconsole.log('Scores:', result.scores);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusIndex } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusIndex = useCactusIndex({\n    name: 'my-index',\n    embeddingDim: 1024\n  });\n\n  const handleQuery = async () =\u003e {\n    const result = await cactusIndex.query({\n      embeddings: [[0.1, 0.2, ...]],\n      options: {\n        topK: 5,\n        scoreThreshold: 0.7\n      }\n    });\n    console.log('IDs:', result.ids);\n    console.log('Scores:', result.scores);\n  };\n\n  return \u003cButton title=\"Query Index\" onPress={handleQuery} /\u003e;\n};\n```\n\n### Retrieving Documents\n\nGet documents by their IDs.\n\n#### Class\n\n```typescript\nimport { CactusIndex } from 'cactus-react-native';\n\nconst cactusIndex = new CactusIndex('my-index', 1024);\nawait cactusIndex.init();\n\nconst result = await cactusIndex.get({ ids: [1, 2, 3] });\nconsole.log('Documents:', result.documents);\nconsole.log('Metadatas:', result.metadatas);\nconsole.log('Embeddings:', result.embeddings);\n```\n\n#### Hook\n\n```tsx\nimport { useCactusIndex } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusIndex = useCactusIndex({\n    name: 'my-index',\n    embeddingDim: 1024\n  });\n\n  const handleGet = async () =\u003e {\n    const result = await cactusIndex.get({ ids: [1, 2, 3] });\n    console.log('Documents:', result.documents);\n    console.log('Metadatas:', result.metadatas);\n    console.log('Embeddings:', result.embeddings);\n  };\n\n  return \u003cButton title=\"Get Documents\" onPress={handleGet} /\u003e;\n};\n```\n\n### Deleting Documents\n\nMark documents as deleted by their IDs.\n\n#### Class\n\n```typescript\nimport { CactusIndex } from 'cactus-react-native';\n\nconst cactusIndex = new CactusIndex('my-index', 1024);\nawait cactusIndex.init();\n\nawait cactusIndex.delete({ ids: [1, 2, 3] });\n```\n\n#### Hook\n\n```tsx\nimport { useCactusIndex } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusIndex = useCactusIndex({\n    name: 'my-index',\n    embeddingDim: 1024\n  });\n\n  const handleDelete = async () =\u003e {\n    await cactusIndex.delete({ ids: [1, 2, 3] });\n  };\n\n  return \u003cButton title=\"Delete Documents\" onPress={handleDelete} /\u003e;\n};\n```\n\n### Compacting the Index\n\nOptimize the index by removing deleted documents and reorganizing data.\n\n#### Class\n\n```typescript\nimport { CactusIndex } from 'cactus-react-native';\n\nconst cactusIndex = new CactusIndex('my-index', 1024);\nawait cactusIndex.init();\n\nawait cactusIndex.compact();\n```\n\n#### Hook\n\n```tsx\nimport { useCactusIndex } from 'cactus-react-native';\n\nconst App = () =\u003e {\n  const cactusIndex = useCactusIndex({\n    name: 'my-index',\n    embeddingDim: 1024\n  });\n\n  const handleCompact = async () =\u003e {\n    await cactusIndex.compact();\n  };\n\n  return \u003cButton title=\"Compact Index\" onPress={handleCompact} /\u003e;\n};\n```\n\n## API Reference\n\n### CactusLM Class\n\n#### Constructor\n\n**`new CactusLM(params?: CactusLMParams)`**\n\n**Parameters:**\n- `model` - Model slug or absolute path to a model file (default: `'qwen3-0.6b'`).\n- `corpusDir` - Directory containing text files for RAG (default: `undefined`).\n- `cacheIndex` - Whether to cache the RAG corpus index on disk (default: `false`).\n- `options` - Model options for quantization and NPU acceleration:\n  - `quantization` - Quantization type: `'int4'` | `'int8'` (default: `'int8'`).\n  - `pro` - Enable NPU-accelerated models (default: `false`).\n\n#### Methods\n\n**`download(params?: CactusLMDownloadParams): Promise\u003cvoid\u003e`**\n\nDownloads the model. If the model is already downloaded, returns immediately with progress `1`. Throws an error if a download is already in progress.\n\n**Parameters:**\n- `onProgress` - Callback for download progress (0-1).\n\n**`init(): Promise\u003cvoid\u003e`**\n\nInitializes the model and prepares it for inference. Safe to call multiple times (idempotent). Throws an error if the model is not downloaded yet.\n\n**`complete(params: CactusLMCompleteParams): Promise\u003cCactusLMCompleteResult\u003e`**\n\nPerforms text completion with optional streaming and tool support. Automatically calls `init()` if not already initialized. Throws an error if a generation (completion or embedding) is already in progress.\n\n**Parameters:**\n- `messages` - Array of `CactusLMMessage` objects.\n- `options` - Generation options:\n  - `temperature` - Sampling temperature.\n  - `topP` - Nucleus sampling threshold.\n  - `topK` - Top-K sampling limit.\n  - `maxTokens` - Maximum number of tokens to generate (default: `512`).\n  - `stopSequences` - Array of strings to stop generation.\n  - `forceTools` - Force the model to call one of the provided tools (default: `false`).\n  - `telemetryEnabled` - Enable telemetry for this request (default: `true`).\n  - `confidenceThreshold` - Confidence threshold below which cloud handoff is triggered (default: `0.7`).\n  - `toolRagTopK` - Number of tools to select via RAG when tool list is large (default: `2`).\n  - `includeStopSequences` - Whether to include stop sequences in the response (default: `false`).\n  - `useVad` - Whether to use VAD preprocessing (default: `true`).\n  - `enableThinking` - Whether to enable thinking/reasoning output if supported by the model (default: unset).\n- `tools` - Array of `CactusLMTool` objects for function calling.\n- `onToken` - Callback for streaming tokens.\n- `audio` - Optional raw 16-bit PCM audio samples as a byte array for multimodal audio completion (e.g., Gemma 4).\n\n**`prefill(params: CactusLMPrefillParams): Promise\u003cCactusLMPrefillResult\u003e`**\n\nRuns prompt prefill without generating any output tokens. Useful for measuring prefill performance or warming up the model's KV cache. Automatically calls `init()` if not already initialized. Throws an error if a generation is already in progress.\n\n**Parameters:**\n- `messages` - Array of `CactusLMMessage` objects.\n- `options` - Same options as `complete`.\n- `tools` - Array of `CactusLMTool` objects.\n- `audio` - Optional raw 16-bit PCM audio samples as a byte array for multimodal audio prefill.\n\n**`tokenize(params: CactusLMTokenizeParams): Promise\u003cCactusLMTokenizeResult\u003e`**\n\nConverts text into tokens using the model's tokenizer.\n\n**Parameters:**\n- `text` - Text to tokenize.\n\n**`scoreWindow(params: CactusLMScoreWindowParams): Promise\u003cCactusLMScoreWindowResult\u003e`**\n\nCalculates the log-probability score for a window of tokens within a sequence.\n\n**Parameters:**\n- `tokens` - Array of token IDs.\n- `start` - Start index of the window.\n- `end` - End index of the window.\n- `context` - Number of context tokens before the window.\n\n**`embed(params: CactusLMEmbedParams): Promise\u003cCactusLMEmbedResult\u003e`**\n\nGenerates embeddings for the given text. Automatically calls `init()` if not already initialized. Throws an error if a generation (completion or embedding) is already in progress.\n\n**Parameters:**\n- `text` - Text to embed.\n- `normalize` - Whether to normalize the embedding vector (default: `false`).\n\n**`imageEmbed(params: CactusLMImageEmbedParams): Promise\u003cCactusLMImageEmbedResult\u003e`**\n\nGenerates embeddings for the given image. Requires a vision-capable model. Automatically calls `init()` if not already initialized. Throws an error if a generation (completion or embedding) is already in progress.\n\n**Parameters:**\n- `imagePath` - Path to the image file.\n\n**`ragQuery(params: CactusLMRagQueryParams): Promise\u003cCactusLMRagQueryResult\u003e`**\n\nQueries the RAG corpus index directly, returning the top matching document chunks with scores. Requires the model to be initialized with a `corpusDir`. Automatically calls `init()` if not already initialized.\n\n**Parameters:**\n- `query` - Search query string.\n- `topK` - Number of top results to return (default: `5`).\n\n**`stop(): Promise\u003cvoid\u003e`**\n\nStops ongoing generation.\n\n**`reset(): Promise\u003cvoid\u003e`**\n\nResets the model's internal state, clearing any cached context. Automatically calls `stop()` first.\n\n**`destroy(): Promise\u003cvoid\u003e`**\n\nReleases all resources associated with the model. Automatically calls `stop()` first. Safe to call even if the model is not initialized.\n\n**`getModels(): Promise\u003cCactusModel[]\u003e`**\n\nReturns available models.\n\n**`getModelName(): string`**\n\nReturns the computed model identifier including quantization and pro suffix (e.g., `'qwen3-0.6b-int8'`, `'lfm2-vl-450m-int4-pro'`).\n\n### useCactusLM Hook\n\nThe `useCactusLM` hook manages a `CactusLM` instance with reactive state. When model parameters (`model`, `corpusDir`, `cacheIndex`, `options`) change, the hook creates a new instance and resets all state. The hook automatically cleans up resources when the component unmounts.\n\n#### State\n\n- `completion: string` - Current generated text. Automatically accumulated during streaming. Cleared before each new completion and when calling `reset()` or `destroy()`.\n- `isGenerating: boolean` - Whether the model is currently running an operation. Shared by `complete`, `tokenize`, `scoreWindow`, `embed`, and `imageEmbed`.\n- `isInitializing: boolean` - Whether the model is initializing.\n- `isDownloaded: boolean` - Whether the model is downloaded locally. Automatically checked when the hook mounts or model changes.\n- `isDownloading: boolean` - Whether the model is being downloaded.\n- `downloadProgress: number` - Download progress (0-1). Reset to `0` after download completes.\n- `error: string | null` - Last error message from any operation, or `null` if there is no error. Cleared before starting new operations.\n\n#### Methods\n\n- `download(params?: CactusLMDownloadParams): Promise\u003cvoid\u003e` - Downloads the model. Updates `isDownloading` and `downloadProgress` state during download. Sets `isDownloaded` to `true` on success.\n- `init(): Promise\u003cvoid\u003e` - Initializes the model for inference. Sets `isInitializing` to `true` during initialization.\n- `complete(params: CactusLMCompleteParams): Promise\u003cCactusLMCompleteResult\u003e` - Generates text completions. Automatically accumulates tokens in the `completion` state during streaming. Sets `isGenerating` to `true` while generating. Clears `completion` before starting.\n- `tokenize(params: CactusLMTokenizeParams): Promise\u003cCactusLMTokenizeResult\u003e` - Converts text into tokens. Sets `isGenerating` to `true` during operation.\n- `scoreWindow(params: CactusLMScoreWindowParams): Promise\u003cCactusLMScoreWindowResult\u003e` - Calculates log-probability scores for a window of tokens. Sets `isGenerating` to `true` during operation.\n- `embed(params: CactusLMEmbedParams): Promise\u003cCactusLMEmbedResult\u003e` - Generates embeddings for the given text. Sets `isGenerating` to `true` during operation.\n- `imageEmbed(params: CactusLMImageEmbedParams): Promise\u003cCactusLMImageEmbedResult\u003e` - Generates embeddings for the given image. Sets `isGenerating` to `true` while generating.\n- `ragQuery(params: CactusLMRagQueryParams): Promise\u003cCactusLMRagQueryResult\u003e` - Queries the RAG corpus index directly. Sets `isGenerating` to `true` during operation.\n- `stop(): Promise\u003cvoid\u003e` - Stops ongoing generation. Clears any errors.\n- `reset(): Promise\u003cvoid\u003e` - Resets the model's internal state, clearing cached context. Also clears the `completion` state.\n- `destroy(): Promise\u003cvoid\u003e` - Releases all resources associated with the model. Clears the `completion` state. Automatically called when the component unmounts.\n- `getModels(): Promise\u003cCactusModel[]\u003e` - Returns available models.\n\n### CactusSTT Class\n\n#### Constructor\n\n**`new CactusSTT(params?: CactusSTTParams)`**\n\n**Parameters:**\n- `model` - Model slug or absolute path to a model file (default: `'whisper-small'`).\n- `options` - Model options for quantization and NPU acceleration:\n  - `quantization` - Quantization type: `'int4'` | `'int8'` (default: `'int8'`).\n  - `pro` - Enable NPU-accelerated models (default: `false`).\n\n#### Methods\n\n**`download(params?: CactusSTTDownloadParams): Promise\u003cvoid\u003e`**\n\nDownloads the model. If the model is already downloaded, returns immediately with progress `1`. Throws an error if a download is already in progress.\n\n**Parameters:**\n- `onProgress` - Callback for download progress (0-1).\n\n**`init(): Promise\u003cvoid\u003e`**\n\nInitializes the model and prepares it for inference. Safe to call multiple times (idempotent). Throws an error if the model is not downloaded yet.\n\n**`transcribe(params: CactusSTTTranscribeParams): Promise\u003cCactusSTTTranscribeResult\u003e`**\n\nTranscribes audio to text with optional streaming support. Accepts either a file path or raw PCM audio samples. Automatically calls `init()` if not already initialized. Throws an error if a generation is already in progress.\n\n**Parameters:**\n- `audio` - Path to the audio file or raw PCM samples as a byte array.\n- `prompt` - Optional prompt to guide transcription (default: `'\u003c|startoftranscript|\u003e\u003c|en|\u003e\u003c|transcribe|\u003e\u003c|notimestamps|\u003e'`).\n- `options` - Transcription options:\n  - `temperature` - Sampling temperature.\n  - `topP` - Nucleus sampling threshold.\n  - `topK` - Top-K sampling limit.\n  - `maxTokens` - Maximum number of tokens to generate (default: `384`).\n  - `stopSequences` - Array of strings to stop generation.\n  - `useVad` - Whether to apply VAD to strip silence before transcription (default: `true`).\n  - `telemetryEnabled` - Enable telemetry for this request (default: `true`).\n  - `confidenceThreshold` - Confidence threshold for quality assessment (default: `0.7`).\n  - `cloudHandoffThreshold` - Max entropy threshold above which cloud handoff is triggered.\n  - `includeStopSequences` - Whether to include stop sequences in the response (default: `false`).\n- `onToken` - Callback for streaming tokens.\n\n**`streamTranscribeStart(options?: CactusSTTStreamTranscribeStartOptions): Promise\u003cvoid\u003e`**\n\nStarts a streaming transcription session. Automatically calls `init()` if not already initialized. If a session is already active, returns immediately.\n\n**Parameters:**\n- `confirmationThreshold` - Fuzzy match ratio required to confirm a transcription segment (default: `0.99`).\n- `minChunkSize` - Minimum number of audio samples before processing (default: `32000`).\n- `telemetryEnabled` - Enable telemetry for this session (default: `true`).\n- `language` - Language code for transcription (e.g., `'en'`, `'es'`, `'fr'`). If not set, language is auto-detected.\n\n**`streamTranscribeProcess(params: CactusSTTStreamTranscribeProcessParams): Promise\u003cCactusSTTStreamTranscribeProcessResult\u003e`**\n\nFeeds audio samples into the streaming session and returns the current transcription state. Throws an error if no session is active.\n\n**Parameters:**\n- `audio` - PCM audio samples as a byte array.\n\n**`streamTranscribeStop(): Promise\u003cCactusSTTStreamTranscribeStopResult\u003e`**\n\nStops the streaming session and returns the final confirmed transcription text. Throws an error if no session is active.\n\n**`detectLanguage(params: CactusSTTDetectLanguageParams): Promise\u003cCactusSTTDetectLanguageResult\u003e`**\n\nDetects the spoken language in the given audio. Automatically calls `init()` if not already initialized. Throws an error if a generation is already in progress.\n\n**Parameters:**\n- `audio` - Path to the audio file or raw PCM samples as a byte array.\n- `options`:\n  - `useVad` - Whether to apply VAD before detection (default: `true`).\n\n**`audioEmbed(params: CactusSTTAudioEmbedParams): Promise\u003cCactusSTTAudioEmbedResult\u003e`**\n\nGenerates embeddings for the given audio file. Automatically calls `init()` if not already initialized. Throws an error if a generation is already in progress.\n\n**Parameters:**\n- `audioPath` - Path to the audio file.\n\n**`stop(): Promise\u003cvoid\u003e`**\n\nStops ongoing transcription or embedding generation.\n\n**`reset(): Promise\u003cvoid\u003e`**\n\nResets the model's internal state. Automatically calls `stop()` first.\n\n**`destroy(): Promise\u003cvoid\u003e`**\n\nReleases all resources associated with the model. Stops any active streaming session. Automatically calls `stop()` first. Safe to call even if the model is not initialized.\n\n**`getModels(): Promise\u003cCactusModel[]\u003e`**\n\nReturns available speech-to-text models.\n\n**`getModelName(): string`**\n\nReturns the computed model identifier including quantization and pro suffix (e.g., `'whisper-small-int8'`).\n\n### useCactusSTT Hook\n\nThe `useCactusSTT` hook manages a `CactusSTT` instance with reactive state. When model parameters (`model`, `options`) change, the hook creates a new instance and resets all state. The hook automatically cleans up resources when the component unmounts.\n\n#### State\n\n- `transcription: string` - Current transcription text. Automatically accumulated during streaming. Cleared before each new transcription and when calling `reset()` or `destroy()`.\n- `streamTranscribeConfirmed: string` - Accumulated confirmed text from the active streaming session. Updated after each successful `streamTranscribeProcess` call and finalized by `streamTranscribeStop`.\n- `streamTranscribePending: string` - Uncommitted (in-progress) text from the current audio chunk. Cleared when the session stops.\n- `isGenerating: boolean` - Whether the model is currently transcribing or embedding. Both operations share this flag.\n- `isStreamTranscribing: boolean` - Whether a streaming transcription session is currently active.\n- `isInitializing: boolean` - Whether the model is initializing.\n- `isDownloaded: boolean` - Whether the model is downloaded locally. Automatically checked when the hook mounts or model changes.\n- `isDownloading: boolean` - Whether the model is being downloaded.\n- `downloadProgress: number` - Download progress (0-1). Reset to `0` after download completes.\n- `error: string | null` - Last error message from any operation, or `null` if there is no error. Cleared before starting new operations.\n\n#### Methods\n\n- `download(params?: CactusSTTDownloadParams): Promise\u003cvoid\u003e` - Downloads the model. Updates `isDownloading` and `downloadProgress` state during download. Sets `isDownloaded` to `true` on success.\n- `init(): Promise\u003cvoid\u003e` - Initializes the model for inference. Sets `isInitializing` to `true` during initialization.\n- `transcribe(params: CactusSTTTranscribeParams): Promise\u003cCactusSTTTranscribeResult\u003e` - Transcribes audio to text. Automatically accumulates tokens in the `transcription` state during streaming. Sets `isGenerating` to `true` while generating. Clears `transcription` before starting.\n- `audioEmbed(params: CactusSTTAudioEmbedParams): Promise\u003cCactusSTTAudioEmbedResult\u003e` - Generates embeddings for the given audio. Sets `isGenerating` to `true` during operation.\n- `streamTranscribeStart(options?: CactusSTTStreamTranscribeStartOptions): Promise\u003cvoid\u003e` - Starts a streaming transcription session. If a session is already active, returns immediately. Clears `streamTranscribeConfirmed` and `streamTranscribePending` before starting. Sets `isStreamTranscribing` to `true`.\n- `streamTranscribeProcess(params: CactusSTTStreamTranscribeProcessParams): Promise\u003cCactusSTTStreamTranscribeProcessResult\u003e` - Feeds audio and returns incremental results. Appends confirmed text to `streamTranscribeConfirmed` and updates `streamTranscribePending`.\n- `streamTranscribeStop(): Promise\u003cCactusSTTStreamTranscribeStopResult\u003e` - Stops the session and returns the final result. Sets `isStreamTranscribing` to `false`. Appends final confirmed text to `streamTranscribeConfirmed` and clears `streamTranscribePending`.\n- `stop(): Promise\u003cvoid\u003e` - Stops ongoing generation. Clears any errors.\n- `reset(): Promise\u003cvoid\u003e` - Resets the model's internal state. Also clears the `transcription` state.\n- `destroy(): Promise\u003cvoid\u003e` - Releases all resources associated with the model. Clears the `transcription`, `streamTranscribeConfirmed`, and `streamTranscribePending` state. Automatically called when the component unmounts.\n- `getModels(): Promise\u003cCactusModel[]\u003e` - Returns available speech-to-text models.\n\n### CactusAudio Class\n\n#### Constructor\n\n**`new CactusAudio(params?: CactusAudioParams)`**\n\n**Parameters:**\n- `model` - Model slug or absolute path to an audio model file (default: `'silero-vad'`).\n- `options` - Model options:\n  - `quantization` - Quantization type: `'int4'` | `'int8'` (default: `'int8'`).\n  - `pro` - Enable NPU-accelerated models (default: `false`).\n\n#### Methods\n\n**`download(params?: CactusAudioDownloadParams): Promise\u003cvoid\u003e`**\n\nDownloads the audio model. If the model is already downloaded, returns immediately with progress `1`. Throws an error if a download is already in progress.\n\n**Parameters:**\n- `onProgress` - Callback for download progress (0-1).\n\n**`init(): Promise\u003cvoid\u003e`**\n\nInitializes the audio model. Safe to call multiple times (idempotent). Throws an error if the model is not downloaded yet.\n\n**`vad(params: CactusAudioVADParams): Promise\u003cCactusAudioVADResult\u003e`**\n\nRuns voice activity detection on the given audio. Automatically calls `init()` if not already initialized.\n\n**Parameters:**\n- `audio` - Path to the audio file or raw PCM samples as a byte array.\n- `options` - VAD options:\n  - `threshold` - Speech probability threshold (default: model default).\n  - `negThreshold` - Silence probability threshold.\n  - `minSpeechDurationMs` - Minimum speech segment duration in ms.\n  - `maxSpeechDurationS` - Maximum speech segment duration in seconds.\n  - `minSilenceDurationMs` - Minimum silence duration before ending a segment.\n  - `speechPadMs` - Padding added to each speech segment in ms.\n  - `windowSizeSamples` - Processing window size in samples.\n  - `samplingRate` - Audio sampling rate.\n  - `minSilenceAtMaxSpeech` - Minimum silence at max speech duration.\n  - `useMaxPossSilAtMaxSpeech` - Whether to use maximum possible silence at max speech.\n\n**`diarize(params: CactusAudioDiarizeParams): Promise\u003cCactusAudioDiarizeResult\u003e`**\n\nRuns speaker diarization on the given audio. Automatically calls `init()` if not already initialized.\n\n**Parameters:**\n- `audio` - Path to the audio file or raw PCM samples as a byte array.\n- `options` - Diarize options:\n  - `stepMs` - Step size in milliseconds.\n  - `threshold` - Diarization threshold.\n  - `numSpeakers` - Expected number of speakers.\n  - `minSpeakers` - Minimum number of speakers.\n  - `maxSpeakers` - Maximum number of speakers.\n\n**`embedSpeaker(params: CactusAudioEmbedSpeakerParams): Promise\u003cCactusAudioEmbedSpeakerResult\u003e`**\n\nExtracts a speaker embedding vector from the given audio. Automatically calls `init()` if not already initialized.\n\n**Parameters:**\n- `audio` - Path to the audio file or raw PCM samples as a byte array.\n- `options` - Optional speaker embedding options:\n  - `stepMs` - Step size in milliseconds.\n  - `threshold` - Embedding threshold.\n  - `maskWeights` - Per-frame mask weights for speaker-specific embedding extraction (from diarization).\n  - `maskNumFrames` - Number of frames for the mask weights.\n\n**`destroy(): Promise\u003cvoid\u003e`**\n\nReleases all resources associated with the model. Safe to call even if the model is not initialized.\n\n**`getModels(): Promise\u003cCactusModel[]\u003e`**\n\nReturns available audio models.\n\n**`getModelName(): string`**\n\nReturns the computed model identifier including quantization and pro suffix (e.g., `'silero-vad-int8'`).\n\n### useCactusAudio Hook\n\nThe `useCactusAudio` hook manages a `CactusAudio` instance with reactive state. When model parameters (`model`, `options`) change, the hook creates a new instance and resets all state. The hook automatically cleans up resources when the component unmounts.\n\n#### State\n\n- `isInitializing: boolean` - Whether the model is initializing.\n- `isDownloaded: boolean` - Whether the model is downloaded locally. Automatically checked when the hook mounts or model changes.\n- `isDownloading: boolean` - Whether the model is being downloaded.\n- `downloadProgress: number` - Download progress (0-1). Reset to `0` after download completes.\n- `error: string | null` - Last error message, or `null`.\n\n#### Methods\n\n- `download(params?: CactusAudioDownloadParams): Promise\u003cvoid\u003e` - Downloads the model. Updates `isDownloading` and `downloadProgress` state during download. Sets `isDownloaded` to `true` on success.\n- `init(): Promise\u003cvoid\u003e` - Initializes the model.\n- `vad(params: CactusAudioVADParams): Promise\u003cCactusAudioVADResult\u003e` - Runs voice activity detection.\n- `diarize(params: CactusAudioDiarizeParams): Promise\u003cCactusAudioDiarizeResult\u003e` - Runs speaker diarization.\n- `embedSpeaker(params: CactusAudioEmbedSpeakerParams): Promise\u003cCactusAudioEmbedSpeakerResult\u003e` - Extracts a speaker embedding.\n- `destroy(): Promise\u003cvoid\u003e` - Releases all resources. Automatically called when the component unmounts.\n- `getModels(): Promise\u003cCactusModel[]\u003e` - Returns available audio models.\n\n### CactusIndex Class\n\n#### Constructor\n\n**`new CactusIndex(name: string, embeddingDim: number)`**\n\n**Parameters:**\n- `name` - Name of the index.\n- `embeddingDim` - Dimension of the embedding vectors.\n\n#### Methods\n\n**`init(): Promise\u003cvoid\u003e`**\n\nInitializes the index and prepares it for operations. Must be called before using any other methods.\n\n**`add(params: CactusIndexAddParams): Promise\u003cvoid\u003e`**\n\nAdds documents with their embeddings and metadata to the index.\n\n**Parameters:**\n- `ids` - Array of document IDs.\n- `documents` - Array of document texts.\n- `embeddings` - Array of embedding vectors (each vector must match `embeddingDim`).\n- `metadatas` - Optional array of metadata strings.\n\n**`query(params: CactusIndexQueryParams): Promise\u003cCactusIndexQueryResult\u003e`**\n\nSearches for similar documents using embedding vectors.\n\n**Parameters:**\n- `embeddings` - Array of query embedding vectors.\n- `options` - Query options:\n  - `topK` - Number of top results to return (default: 10).\n  - `scoreThreshold` - Minimum similarity score threshold (default: -1.0).\n\n**`get(params: CactusIndexGetParams): Promise\u003cCactusIndexGetResult\u003e`**\n\nRetrieves documents by their IDs.\n\n**Parameters:**\n- `ids` - Array of document IDs to retrieve.\n\n**`delete(params: CactusIndexDeleteParams): Promise\u003cvoid\u003e`**\n\nDeletes documents from the index by their IDs.\n\n**Parameters:**\n- `ids` - Array of document IDs to delete.\n\n**`compact(): Promise\u003cvoid\u003e`**\n\nOptimizes the index by removing deleted documents and reorganizing data for better performance. Call after a series of deletions.\n\n**`destroy(): Promise\u003cvoid\u003e`**\n\nReleases all resources associated with the index from memory.\n\n### useCactusIndex Hook\n\nThe `useCactusIndex` hook manages a `CactusIndex` instance with reactive state. When index parameters (`name` or `embeddingDim`) change, the hook creates a new instance and resets all state. The hook automatically cleans up resources when the component unmounts.\n\n#### State\n\n- `isInitializing: boolean` - Whether the index is initializing.\n- `isProcessing: boolean` - Whether the index is processing an operation (add, query, get, delete, or compact).\n- `error: string | null` - Last error message from any operation, or `null` if there is no error. Cleared before starting new operations.\n\n#### Methods\n\n- `init(): Promise\u003cvoid\u003e` - Initializes the index. Sets `isInitializing` to `true` during initialization.\n- `add(params: CactusIndexAddParams): Promise\u003cvoid\u003e` - Adds documents to the index. Sets `isProcessing` to `true` during operation.\n- `query(params: CactusIndexQueryParams): Promise\u003cCactusIndexQueryResult\u003e` - Searches for similar documents. Sets `isProcessing` to `true` during operation.\n- `get(params: CactusIndexGetParams): Promise\u003cCactusIndexGetResult\u003e` - Retrieves documents by IDs. Sets `isProcessing` to `true` during operation.\n- `delete(params: CactusIndexDeleteParams): Promise\u003cvoid\u003e` - Deletes documents. Sets `isProcessing` to `true` during operation.\n- `compact(): Promise\u003cvoid\u003e` - Optimizes the index. Sets `isProcessing` to `true` during operation.\n- `destroy(): Promise\u003cvoid\u003e` - Releases all resources. Automatically called when the component unmounts.\n\n### getRegistry\n\n**`getRegistry(): Promise\u003c{ [key: string]: CactusModel }\u003e`**\n\nReturns all available models from HuggingFace, keyed by model slug. Result is cached across calls.\n\n```typescript\nimport { getRegistry } from 'cactus-react-native';\n\nconst registry = await getRegistry();\nconst model = registry['qwen3-0.6b'];\nconsole.log(model);\n```\n\n## Type Definitions\n\n### CactusLMParams\n\n```typescript\ninterface CactusLMParams {\n  model?: string;\n  corpusDir?: string;\n  cacheIndex?: boolean;\n  options?: CactusModelOptions;\n}\n```\n\n### CactusLMDownloadParams\n\n```typescript\ninterface CactusLMDownloadParams {\n  onProgress?: (progress: number) =\u003e void;\n}\n```\n\n### CactusLMMessage\n\n```typescript\ninterface CactusLMMessage {\n  role: 'user' | 'assistant' | 'system';\n  content?: string;\n  images?: string[];\n}\n```\n\n### CactusLMCompleteOptions\n\n```typescript\ninterface CactusLMCompleteOptions {\n  temperature?: number;\n  topP?: number;\n  topK?: number;\n  maxTokens?: number;\n  stopSequences?: string[];\n  forceTools?: boolean;\n  telemetryEnabled?: boolean;\n  confidenceThreshold?: number;\n  toolRagTopK?: number;\n  includeStopSequences?: boolean;\n  useVad?: boolean;\n  enableThinking?: boolean;\n}\n```\n\n### CactusLMTool\n\n```typescript\ninterface CactusLMTool {\n  name: string;\n  description: string;\n  parameters: {\n    type: 'object';\n    properties: {\n      [key: string]: {\n        type: string;\n        description: string;\n      };\n    };\n    required: string[];\n  };\n}\n```\n\n### CactusLMCompleteParams\n\n```typescript\ninterface CactusLMCompleteParams {\n  messages: CactusLMMessage[];\n  options?: CactusLMCompleteOptions;\n  tools?: CactusLMTool[];\n  onToken?: (token: string) =\u003e void;\n  audio?: number[];\n}\n```\n\n### CactusLMPrefillParams\n\n```typescript\ninterface CactusLMPrefillParams {\n  messages: CactusLMMessage[];\n  options?: CactusLMCompleteOptions;\n  tools?: CactusLMTool[];\n  audio?: number[];\n}\n```\n\n### CactusLMRagQueryParams\n\n```typescript\ninterface CactusLMRagQueryParams {\n  query: string;\n  topK?: number;\n}\n```\n\n### CactusLMRagQueryChunk\n\n```typescript\ninterface CactusLMRagQueryChunk {\n  score: number;\n  source: string;\n  content: string;\n}\n```\n\n### CactusLMRagQueryResult\n\n```typescript\ninterface CactusLMRagQueryResult {\n  chunks: CactusLMRagQueryChunk[];\n  error?: string;\n}\n```\n\n### CactusLMPrefillResult\n\n```typescript\ninterface CactusLMPrefillResult {\n  success: boolean;\n  error: string | null;\n  prefillTokens: number;\n  prefillTps: number;\n  totalTimeMs: number;\n  ramUsageMb: number;\n}\n```\n\n### CactusLMCompleteResult\n\n```typescript\ninterface CactusLMCompleteResult {\n  success: boolean;\n  response: string;\n  thinking?: string;\n  functionCalls?: {\n    name: string;\n    arguments: { [key: string]: any };\n  }[];\n  cloudHandoff?: boolean;\n  confidence?: number;\n  timeToFirstTokenMs: number;\n  totalTimeMs: number;\n  prefillTokens: number;\n  prefillTps: number;\n  decodeTokens: number;\n  decodeTps: number;\n  totalTokens: number;\n  ramUsageMb?: number;\n}\n```\n\n### CactusLMTokenizeParams\n\n```typescript\ninterface CactusLMTokenizeParams {\n  text: string;\n}\n```\n\n### CactusLMTokenizeResult\n\n```typescript\ninterface CactusLMTokenizeResult {\n  tokens: number[];\n}\n```\n\n### CactusLMScoreWindowParams\n\n```typescript\ninterface CactusLMScoreWindowParams {\n  tokens: number[];\n  start: number;\n  end: number;\n  context: number;\n}\n```\n\n### CactusLMScoreWindowResult\n\n```typescript\ninterface CactusLMScoreWindowResult {\n  score: number;\n}\n```\n\n### CactusLMEmbedParams\n\n```typescript\ninterface CactusLMEmbedParams {\n  text: string;\n  normalize?: boolean;\n}\n```\n\n### CactusLMEmbedResult\n\n```typescript\ninterface CactusLMEmbedResult {\n  embedding: number[];\n}\n```\n\n### CactusLMImageEmbedParams\n\n```typescript\ninterface CactusLMImageEmbedParams {\n  imagePath: string;\n}\n```\n\n### CactusLMImageEmbedResult\n\n```typescript\ninterface CactusLMImageEmbedResult {\n  embedding: number[];\n}\n```\n\n### CactusModel\n\n```typescript\ninterface CactusModel {\n  slug: string;\n  capabilities: string[];\n  quantization: {\n    int4: {\n      sizeMb: number;\n      url: string;\n      pro?: {\n        apple: string;\n      };\n    };\n    int8: {\n      sizeMb: number;\n      url: string;\n      pro?: {\n        apple: string;\n      };\n    };\n  };\n}\n```\n\n### CactusModelOptions\n\n```typescript\ninterface CactusModelOptions {\n  quantization?: 'int4' | 'int8';\n  pro?: boolean;\n}\n```\n\n### CactusSTTParams\n\n```typescript\ninterface CactusSTTParams {\n  model?: string;\n  options?: CactusModelOptions;\n}\n```\n\n### CactusSTTDownloadParams\n\n```typescript\ninterface CactusSTTDownloadParams {\n  onProgress?: (progress: number) =\u003e void;\n}\n```\n\n### CactusSTTTranscribeOptions\n\n```typescript\ninterface CactusSTTTranscribeOptions {\n  temperature?: number;\n  topP?: number;\n  topK?: number;\n  maxTokens?: number;\n  stopSequences?: string[];\n  useVad?: boolean;\n  telemetryEnabled?: boolean;\n  confidenceThreshold?: number;\n  cloudHandoffThreshold?: number;\n  includeStopSequences?: boolean;\n}\n```\n\n### CactusSTTTranscribeParams\n\n```typescript\ninterface CactusSTTTranscribeParams {\n  audio: string | number[];\n  prompt?: string;\n  options?: CactusSTTTranscribeOptions;\n  onToken?: (token: string) =\u003e void;\n}\n```\n\n### CactusSTTTranscribeResult\n\n```typescript\ninterface CactusSTTTranscribeResult {\n  success: boolean;\n  response: string;\n  cloudHandoff?: boolean;\n  confidence?: number;\n  timeToFirstTokenMs: number;\n  totalTimeMs: number;\n  prefillTokens: number;\n  prefillTps: number;\n  decodeTokens: number;\n  decodeTps: number;\n  totalTokens: number;\n  ramUsageMb?: number;\n}\n```\n\n### CactusSTTAudioEmbedParams\n\n```typescript\ninterface CactusSTTAudioEmbedParams {\n  audioPath: string;\n}\n```\n\n### CactusSTTAudioEmbedResult\n\n```typescript\ninterface CactusSTTAudioEmbedResult {\n  embedding: number[];\n}\n```\n\n### CactusSTTStreamTranscribeStartOptions\n\n```typescript\ninterface CactusSTTStreamTranscribeStartOptions {\n  confirmationThreshold?: number;\n  minChunkSize?: number;\n  telemetryEnabled?: boolean;\n  language?: string;\n}\n```\n\n### CactusSTTStreamTranscribeProcessParams\n\n```typescript\ninterface CactusSTTStreamTranscribeProcessParams {\n  audio: number[];\n}\n```\n\n### CactusSTTStreamTranscribeProcessResult\n\n```typescript\ninterface CactusSTTStreamTranscribeProcessResult {\n  success: boolean;\n  confirmed: string;\n  pending: string;\n  bufferDurationMs?: number;\n  confidence?: number;\n  cloudHandoff?: boolean;\n  cloudResult?: string;\n  cloudJobId?: number;\n  cloudResultJobId?: number;\n  timeToFirstTokenMs?: number;\n  totalTimeMs?: number;\n  prefillTokens?: number;\n  prefillTps?: number;\n  decodeTokens?: number;\n  decodeTps?: number;\n  totalTokens?: number;\n  ramUsageMb?: number;\n}\n```\n\n### CactusSTTStreamTranscribeStopResult\n\n```typescript\ninterface CactusSTTStreamTranscribeStopResult {\n  success: boolean;\n  confirmed: string;\n}\n```\n\n### CactusSTTDetectLanguageOptions\n\n```typescript\ninterface CactusSTTDetectLanguageOptions {\n  useVad?: boolean;\n}\n```\n\n### CactusSTTDetectLanguageParams\n\n```typescript\ninterface CactusSTTDetectLanguageParams {\n  audio: string | number[];\n  options?: CactusSTTDetectLanguageOptions;\n}\n```\n\n### CactusSTTDetectLanguageResult\n\n```typescript\ninterface CactusSTTDetectLanguageResult {\n  language: string;\n  confidence?: number;\n}\n```\n\n### CactusAudioParams\n\n```typescript\ninterface CactusAudioParams {\n  model?: string;\n  options?: CactusModelOptions;\n}\n```\n\n### CactusAudioDownloadParams\n\n```typescript\ninterface CactusAudioDownloadParams {\n  onProgress?: (progress: number) =\u003e void;\n}\n```\n\n### CactusAudioVADOptions\n\n```typescript\ninterface CactusAudioVADOptions {\n  threshold?: number;\n  negThreshold?: number;\n  minSpeechDurationMs?: number;\n  maxSpeechDurationS?: number;\n  minSilenceDurationMs?: number;\n  speechPadMs?: number;\n  windowSizeSamples?: number;\n  samplingRate?: number;\n  minSilenceAtMaxSpeech?: number;\n  useMaxPossSilAtMaxSpeech?: boolean;\n}\n```\n\n### CactusAudioVADSegment\n\n```typescript\ninterface CactusAudioVADSegment {\n  start: number;\n  end: number;\n}\n```\n\n### CactusAudioVADResult\n\n```typescript\ninterface CactusAudioVADResult {\n  segments: CactusAudioVADSegment[];\n  totalTime: number;\n  ramUsage: number;\n}\n```\n\n### CactusAudioVADParams\n\n```typescript\ninterface CactusAudioVADParams {\n  audio: string | number[];\n  options?: CactusAudioVADOptions;\n}\n```\n\n### CactusAudioDiarizeOptions\n\n```typescript\ninterface CactusAudioDiarizeOptions {\n  stepMs?: number;\n  threshold?: number;\n  numSpeakers?: number;\n  minSpeakers?: number;\n  maxSpeakers?: number;\n}\n```\n\n### CactusAudioDiarizeParams\n\n```typescript\ninterface CactusAudioDiarizeParams {\n  audio: string | number[];\n  options?: CactusAudioDiarizeOptions;\n}\n```\n\n### CactusAudioDiarizeResult\n\n```typescript\ninterface CactusAudioDiarizeResult {\n  success: boolean;\n  error: string | null;\n  numSpeakers: number;\n  scores: number[];\n  totalTimeMs: number;\n  ramUsageMb: number;\n}\n```\n\n### CactusAudioEmbedSpeakerOptions\n\n```typescript\ninterface CactusAudioEmbedSpeakerOptions {\n  stepMs?: number;\n  threshold?: number;\n  maskWeights?: number[];\n  maskNumFrames?: number;\n}\n```\n\n### CactusAudioEmbedSpeakerParams\n\n```typescript\ninterface CactusAudioEmbedSpeakerParams {\n  audio: string | number[];\n  options?: CactusAudioEmbedSpeakerOptions;\n}\n```\n\n### CactusAudioEmbedSpeakerResult\n\n```typescript\ninterface CactusAudioEmbedSpeakerResult {\n  success: boolean;\n  error: string | null;\n  embedding: number[];\n  totalTimeMs: number;\n  ramUsageMb: number;\n}\n```\n\n### CactusIndexParams\n\n```typescript\ninterface CactusIndexParams {\n  name: string;\n  embeddingDim: number;\n}\n```\n\n### CactusIndexAddParams\n\n```typescript\ninterface CactusIndexAddParams {\n  ids: number[];\n  documents: string[];\n  embeddings: number[][];\n  metadatas?: string[];\n}\n```\n\n### CactusIndexGetParams\n\n```typescript\ninterface CactusIndexGetParams {\n  ids: number[];\n}\n```\n\n### CactusIndexGetResult\n\n```typescript\ninterface CactusIndexGetResult {\n  documents: string[];\n  metadatas: string[];\n  embeddings: number[][];\n}\n```\n\n### CactusIndexQueryOptions\n\n```typescript\ninterface CactusIndexQueryOptions {\n  topK?: number;\n  scoreThreshold?: number;\n}\n```\n\n### CactusIndexQueryParams\n\n```typescript\ninterface CactusIndexQueryParams {\n  embeddings: number[][];\n  options?: CactusIndexQueryOptions;\n}\n```\n\n### CactusIndexQueryResult\n\n```typescript\ninterface CactusIndexQueryResult {\n  ids: number[][];\n  scores: number[][];\n}\n```\n\n### CactusIndexDeleteParams\n\n```typescript\ninterface CactusIndexDeleteParams {\n  ids: number[];\n}\n```\n\n## Performance Tips\n\n- **Model Selection** - Choose smaller models for faster inference on mobile devices.\n- **Memory Management** - Always call `destroy()` when you're done with models to free up resources.\n- **VAD** - Use `useVad: true` (the default) when transcribing audio with silence, to strip non-speech regions and speed up transcription.\n\n## Example App\n\nCheck out [our example app](/example) for a complete React Native implementation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcactus-compute%2Fcactus-react-native","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcactus-compute%2Fcactus-react-native","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcactus-compute%2Fcactus-react-native/lists"}