@actionkit/rn-voice-actions
v0.1.1
Published
Voice-driven action engine for React Native. Define actions, match voice commands, and execute handlers with confidence scoring.
Downloads
38
Maintainers
Readme
@actionkit/rn-voice-actions
Voice-driven action engine for React Native. Define actions with phrase patterns, match voice input with confidence scoring, and execute handlers — all with a minimal, composable API.
Features
- Action registry — declare actions with multiple trigger phrases
- Smart matching — exact, substring, and token-based Jaccard similarity scoring
- Confidence thresholds — filter matches by configurable minimum confidence
- React bindings — provider + hook for session state and capture lifecycle
- Pluggable input — swap voice providers or use a static stub for testing
- Event system — subscribe to
capturing,recognized,matched,no_match,executed, anderrorevents - TypeScript-first — full type safety with generics for action context
- Dual format — ships ESM and CommonJS
Installation
npm install @actionkit/rn-voice-actionsFor voice input on React Native, also install:
npm install @react-native-community/voice
reactand@react-native-community/voiceare optional peer dependencies. The core engine works standalone without either.
Quick start
import { createActions } from "@actionkit/rn-voice-actions";
import { ActionsProvider, useActions } from "@actionkit/rn-voice-actions/react";
import { createReactNativeVoiceProvider } from "@actionkit/rn-voice-actions/adapters";
import Voice from "@react-native-community/voice";
// 1. Create an input provider
const input = createReactNativeVoiceProvider({ Voice, locale: "en-US" });
// 2. Define actions and create the engine
const engine = createActions({
actions: [
{
id: "nav.home",
phrases: ["go home", "home", "back to home"],
handler: ({ router }) => router.navigate("Home"),
},
{
id: "nav.settings",
phrases: ["open settings", "settings"],
handler: ({ router }) => router.navigate("Settings"),
},
],
threshold: 0.6,
getContext: () => ({ router }),
input,
});
// 3. Wrap your app
export default function App() {
return (
<ActionsProvider engine={engine}>
<MicButton />
</ActionsProvider>
);
}
// 4. Use the hook
function MicButton() {
const { state, startCapture, stopCapture } = useActions();
return (
<Pressable onPressIn={startCapture} onPressOut={stopCapture}>
<Text>{state.status === "capturing" ? "Listening..." : "Hold to speak"}</Text>
</Pressable>
);
}Package exports
| Import path | Description |
|---|---|
| @actionkit/rn-voice-actions | Core engine, types, resolver, and utilities |
| @actionkit/rn-voice-actions/react | ActionsProvider and useActions hook |
| @actionkit/rn-voice-actions/adapters | Input providers (React Native Voice, static stub) |
API
createActions(config)
Creates an action engine instance.
const engine = createActions({
actions: Action[], // required — array of action definitions
threshold: number, // optional — minimum confidence to match (default: 0.55)
resolver: Resolver | "basic", // optional — custom resolver function (default: "basic")
getContext: () => Context, // optional — returns context passed to handlers
input: InputProvider, // optional — input source for voice capture
});Returns: Actions object with resolve(), runAction(), on(), off(), emit().
Action
type Action<C> = {
id: string; // unique action identifier
phrases: string[]; // trigger phrases to match against
handler: (ctx: C) => void | Promise<void>; // executed on match
enabled?: (ctx: C) => boolean; // optional gate — skip when false
meta?: {
description?: string;
category?: string;
};
};engine.resolve(input, context?)
Match an input string against registered actions. Returns a MatchResult:
// Successful match
{ type: "match", actionId: "nav.home", confidence: 1.0 }
// No match
{ type: "no_match", confidence: 0.3, candidates: [...] }engine.runAction(match, context?)
Execute the handler for a matched action.
Built-in resolver scoring
| Match type | Confidence |
|---|---|
| Exact match | 1.0 |
| Substring match | 0.8 |
| Token Jaccard similarity | up to 0.7 |
Actions below the threshold are filtered out. You can replace the resolver entirely with a custom function.
React integration
ActionsProvider
Wraps your component tree and manages capture lifecycle.
<ActionsProvider
engine={engine} // required — engine from createActions()
input={inputProvider} // optional — override engine's input provider
locale="en-US" // optional — locale for speech recognition
onEvent={(event, payload) => {}} // optional — callback for all events
>
{children}
</ActionsProvider>useActions()
Access the engine, session state, and capture controls.
const {
engine, // the action engine instance
state, // { status, input?, partial?, match?, error? }
last, // summary of last completed session
startCapture, // begin listening
stopCapture, // stop listening and process input
cancel, // cancel current session
} = useActions();state.status cycles through: idle → capturing → processing → executing → idle
Adapters
createReactNativeVoiceProvider(options)
Wraps @react-native-community/voice as an InputProvider.
import { createReactNativeVoiceProvider } from "@actionkit/rn-voice-actions/adapters";
import Voice from "@react-native-community/voice";
const input = createReactNativeVoiceProvider({
Voice, // required — the Voice module
locale: "en-US", // optional — default locale
onError: (err) => {}, // optional — error callback
});createStaticInputProvider(input)
Returns a fixed string on every capture. Useful for testing.
import { createStaticInputProvider } from "@actionkit/rn-voice-actions/adapters";
const input = createStaticInputProvider("open settings");Custom input provider
Implement the InputProvider interface to support any input source:
type InputProvider = {
start: (opts?: { locale?: string }) => Promise<void>;
stop: () => Promise<{ input: string }>;
cancel?: () => Promise<void>;
onPartial?: (cb: (partial: string) => void) => () => void;
};Events
Subscribe to engine events directly or via the onEvent prop on ActionsProvider.
const unsub = engine.on("matched", ({ input, match, context }) => {
console.log(`Matched "${input}" → ${match.actionId} (${match.confidence})`);
});
// Clean up
unsub();| Event | Payload |
|---|---|
| capturing | { locale? } |
| recognized | { input } |
| matched | { input, match, context } |
| no_match | { input, result, context } |
| executed | { input, actionId, context } |
| error | { error } |
Custom resolver
Replace the built-in resolver with your own matching logic:
const engine = createActions({
actions,
resolver: ({ input, actions, threshold, context }) => {
// Your matching logic here
// Return: { type: "match", actionId, confidence }
// or: { type: "no_match", confidence, candidates }
},
});Example project
A full Expo Router example with voice-controlled navigation is included in examples/voice-navigation.
# From repo root
npm install
npm run build
# Run the example
cd examples/voice-navigation
npm install
npx expo prebuild --platform ios
npx expo run:iosLicense
MIT
