docsPackagestivi — Voice Interface

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:

ExportWhat 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
VoiceSelectorTTS voice picker. Optional provider, backend and model props force a value and hide that control
getTiviSettings, updateTiviSettings, resetTiviSettings, TIVI_DEFAULTS, useTiviSettingsSettings
TypesTiviProps, 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().

OptionDefaultNotes
onTranscriptionFinal transcript callback
onInterrupt, onAudioLevel, onErrorCallbacks
language'en-US'Recognition language
mode'responsive'See Modes
positiveSpeechThreshold0.8VAD probability that starts speech
negativeSpeechThreshold0.6VAD probability below which speech ends
minSpeechStartMs150Sustained speech before speech-start fires
powerThreshold0.01Power level that starts recognition in responsive mode
prerollMs0Audio before the trigger fed to the recognizer (Chrome 135+)
redemptionMs500Silence before VAD speech-end
enableInterruptiontrueSpeech can stop TTS
ttsCallFn, ttsStreamCallFn, firebaseTtsCallableInjected TTS backends
voiceEngine'vie''vie' or 'legacy'
vieJudge'server''smart_turn', 'server' or 'none'
vieProfile, onVieEvent, onVieTelemetry, onVieTunerMovevie profile store and event hooks
semanticEndpointing'gate'Legacy engine only: 'off', 'shadow' or 'gate'
enableAcksPlay 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:

ModeRecognition starts on
guardedVAD speech detection. Filters noise, may miss the first word
responsiveAudio power above powerThreshold. Lower latency
continuousAlways 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:

SettingDefaultNotes
positiveSpeechThreshold0.5
negativeSpeechThreshold0.35
minSpeechStartMs150
powerThreshold0.01
prerollMs0
mode'responsive''guarded', 'responsive' or 'continuous'
enableInterruptiontrue
playbackRate1.0TTS 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
verdictCapMs800Longest the server waits for the verdict
enableAcksfalseBackchannel acknowledgements
redemptionMs500
semanticEndpointing'gate'Legacy engine
smartTurnThreshold0.15Legacy engine
maxAwaitCommitMs, proxyDebounceMs, incompleteSilenceMs, incompleteFallbackMinP1400, 100, 3000, 0.05Legacy 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