Skip to content

Quickstart (Vue + WebSocket)

Use the Vue composables to capture audio, connect a WebSocket provider, and render partial/final transcripts.

Terminal window
# Required
pnpm add @saraudio/vue @saraudio/deepgram
# Optional stages (VAD + Meter)
pnpm add @saraudio/vad-energy @saraudio/meter

This brings the browser runtime transitively via @saraudio/vue.

provider/deepgram.ts
import { deepgram } from '@saraudio/deepgram';
export const deepgramProvider = deepgram({
model: 'nova-3',
auth: { apiKey: '<DEEPGRAM_API_KEY>' },
});

Note: In production browsers prefer short‑lived tokens from your backend. We’ll cover this in the Auth guide.

Issue a short‑lived token on your server and provide it via auth.getToken in your provider module:

provider/deepgram.ts
import { deepgram } from '@saraudio/deepgram';
type EphemeralTokenResponse = {
access_token: string;
expires_in: number; // seconds
};
let tokenCache: { value: string; expiresAt: number } | null = null;
const nowMs = () => Date.now();
async function getToken(): Promise<string> {
if (tokenCache && tokenCache.expiresAt - nowMs() > 2000) {
return tokenCache.value;
}
const response = await fetch('/api/deepgram/token', { method: 'POST' });
if (!response.ok) {
throw new Error(`Failed to obtain Deepgram token (status ${response.status})`);
}
const body: EphemeralTokenResponse = await response.json();
const token = body.access_token;
const ttlSeconds = body.expires_in;
const safeTtlMs = Math.max(1, ttlSeconds - 2) * 1000;
tokenCache = { value: token, expiresAt: nowMs() + safeTtlMs };
return token;
}
export const deepgramProvider = deepgram({
model: 'nova-3',
auth: { getToken },
});
<script setup lang="ts">
import { computed } from 'vue'
import { useTranscription } from '@saraudio/vue'
import { deepgramProvider } from '@/provider/deepgram'
import { vadEnergy } from '@saraudio/vad-energy'
import { meter } from '@saraudio/meter'
const {
start,
stop,
connect,
disconnect,
partial,
transcript,
status,
isConnected,
} = useTranscription({
provider: deepgramProvider,
transport: 'websocket',
autoConnect: true, // connect on mount and start internal recorder
// Internal recorder will include VAD + Meter stages
stages: [vadEnergy({ thresholdDb: -50, attackMs: 80, releaseMs: 200 }), meter()],
connection: { ws: { silencePolicy: 'keep' } }, // 'keep' | 'drop' | 'mute'
onError: (e) => console.error(e),
})
const connectedText = computed(() => (isConnected.value ? 'Connected' : 'Disconnected'))
</script>
<template>
<section>
<p>Status: {{ status }} ({{ connectedText }})</p>
<div v-if="partial">Partial: {{ partial }}</div>
<div v-if="transcript">Final: {{ transcript }}</div>
<div class="controls">
<button @click="start?.()">Start mic</button>
<button @click="stop?.()">Stop mic</button>
<button @click="connect">Connect</button>
<button @click="disconnect">Disconnect</button>
</div>
</section>
<!-- Optional: drop silence to save bandwidth -->
<!-- change to connection: { ws: { silencePolicy: 'drop' } } in the hook options -->
</template>
<style scoped>
.controls { display: flex; gap: .5rem; margin-top: .5rem; }
</style>
  • The hook creates an internal recorder when you don’t pass one; it exposes start/stop helpers.
  • autoConnect: true connects on mount and starts the internal recorder.
  • Use silencePolicy: 'drop' to send only speech frames; mute keeps cadence with zeroed frames.
  • Concepts → Controller & Transport (policies, retries)
  • Providers → Deepgram / Soniox (WS‑specific options)