tivi
tivi is the browser voice layer of SmartChats: the useTivi React hook,
a Tivi component, a VoiceSelector and device-local voice settings. It
runs Silero VAD v5 with onnxruntime-web in the browser, wraps the Web
Speech API for recognition, and plays TTS through an interruptible queue.
Turn-taking (deciding when the user has finished speaking) runs through
vie by default. The behaviour is described in
Voice engine.
Exports
The package entry (src/index.ts) exports:
| Export | What it is |
|---|---|
useTivi(options) | The hook. Returns UseTiviReturn |
Tivi (also the default export) | A component that wraps useTivi. Takes TiviProps, the same fields as UseTiviOptions |
VoiceSelector | TTS voice picker. Optional provider, backend and model props force a value and hide that control |
getTiviSettings, updateTiviSettings, resetTiviSettings, TIVI_DEFAULTS, useTiviSettings | Settings |
| Types | TiviProps, UseTiviOptions, UseTiviReturn, TiviSettings |
CalibrationPanel, useCalibration and the lower-level pieces in
src/lib/ (TTS queue, ack cache, Smart Turn predictor) are not exported from
the entry. The SmartChats app imports CalibrationPanel by source path.
Quick start
import { useTivi } from 'tivi';
function Mic() {
const tivi = useTivi({
onTranscription: (text) => console.log('user said:', text),
onInterrupt: () => console.log('user interrupted TTS'),
});
return (
<button onClick={tivi.isListening ? tivi.stopListening : tivi.startListening}>
{tivi.isListening ? 'Stop' : 'Start'} listening
</button>
);
}To speak:
await tivi.speak('Hello.'); // optional second argument: rate
tivi.cancelSpeech();With enableInterruption on, speech detected while TTS plays stops
playback and calls onInterrupt.
UseTiviOptions
The most used options. Defaults are the hook’s own. The SmartChats app passes
values from getTiviSettings().
| Option | Default | Notes |
|---|---|---|
onTranscription | Final transcript callback | |
onInterrupt, onAudioLevel, onError | Callbacks | |
language | 'en-US' | Recognition language |
mode | 'responsive' | See Modes |
positiveSpeechThreshold | 0.8 | VAD probability that starts speech |
negativeSpeechThreshold | 0.6 | VAD probability below which speech ends |
minSpeechStartMs | 150 | Sustained speech before speech-start fires |
powerThreshold | 0.01 | Power level that starts recognition in responsive mode |
prerollMs | 0 | Audio before the trigger fed to the recognizer (Chrome 135+) |
redemptionMs | 500 | Silence before VAD speech-end |
enableInterruption | true | Speech can stop TTS |
ttsCallFn, ttsStreamCallFn, firebaseTtsCallable | Injected TTS backends | |
voiceEngine | 'vie' | 'vie' or 'legacy' |
vieJudge | 'server' | 'smart_turn', 'server' or 'none' |
vieProfile, onVieEvent, onVieTelemetry, onVieTunerMove | vie profile store and event hooks | |
semanticEndpointing | 'gate' | Legacy engine only: 'off', 'shadow' or 'gate' |
enableAcks | Play a cached backchannel (“ok”, “mmhmm”) when a turn is held |
See types.ts
for the full list, including the queue, recognizer and legacy state machine
callbacks.
UseTiviReturn includes isListening, isSpeaking, transcription,
interimResult, audioLevelRef, speechProbRef, error, mode,
startListening, stopListening, speak, cancelSpeech,
clearTranscription, pauseVADProcessing, resumeVADProcessing,
warmupVAD, ttsQueue, getVie and voiceEngine.
Modes
TiviMode controls how speech recognition starts:
| Mode | Recognition starts on |
|---|---|
guarded | VAD speech detection. Filters noise, may miss the first word |
responsive | Audio power above powerThreshold. Lower latency |
continuous | Always running, restarted automatically. Uses more resources |
Interruption is a separate switch (enableInterruption).
Settings
Settings are device-local, stored in localStorage under tivi-settings.
TIVI_DEFAULTS holds the defaults:
| Setting | Default | Notes |
|---|---|---|
positiveSpeechThreshold | 0.5 | |
negativeSpeechThreshold | 0.35 | |
minSpeechStartMs | 150 | |
powerThreshold | 0.01 | |
prerollMs | 0 | |
mode | 'responsive' | 'guarded', 'responsive' or 'continuous' |
enableInterruption | true | |
playbackRate | 1.0 | TTS speed, 0.5 to 2.0 |
language | 'en-US' | |
ttsProvider | 'cloud' | 'browser' (Web Speech) or 'cloud' (backend TTS). 'openai' is a legacy value |
ttsBackend | 'firebase' | 'local' or 'firebase' |
openaiVoice | 'en-US-EmmaMultilingualNeural' | Voice id for the active cloud provider |
openaiModel | 'gpt-4o-mini-tts' | |
aiPreset | 'default' | A cortex preset id |
ttsCloudProvider | 'azure' | Derived from the preset |
voiceEngine | 'vie' | 'vie' or 'legacy' |
vieJudge | 'server' | Who judges the end of a turn under vie |
verdictModel | 'deepseek-v4.1-flash' | Server verdict model |
verdictEffort | 'none' | Effort for the verdict call |
verdictCapMs | 800 | Longest the server waits for the verdict |
enableAcks | false | Backchannel acknowledgements |
redemptionMs | 500 | |
semanticEndpointing | 'gate' | Legacy engine |
smartTurnThreshold | 0.15 | Legacy engine |
maxAwaitCommitMs, proxyDebounceMs, incompleteSilenceMs, incompleteFallbackMinP | 1400, 100, 3000, 0.05 | Legacy engine |
agentMessagePolicy | '' | How the app handles coding-agent messages. Empty means the default policy |
Fields ending in _applied are one-time migration markers.
import { getTiviSettings, updateTiviSettings, resetTiviSettings, useTiviSettings } from 'tivi';
const current = getTiviSettings(); // a copy
updateTiviSettings({ playbackRate: 1.2 }); // partial merge, saved and broadcast
resetTiviSettings();
// in a component:
const { settings, updateSettings, resetSettings } = useTiviSettings();Common pitfalls
Model files must be served from /onnx/. tivi loads
/onnx/silero_vad_v5.onnx, and Smart Turn weights from
/onnx/smart-turn-v3.2-gpu.onnx (or -cpu.onnx). The SmartChats prebuild
script (apps/smartchats/scripts/prebuild.mjs) copies them into public/.
Another project has to do the same.
iOS Safari needs a user gesture to start audio. Call
startListening from a click handler, not on mount.
speechProbRef is a ref, not state. It keeps its last value when VAD
pauses. Reset it yourself if a meter binds to it.
See also
- Voice engine for turn-taking behaviour
- vie for the turn-taking API
- llm-service for the server verdict
- Source:
packages/tivi/src/index.ts