A lightweight Vue 2 & Vue 3 compatible component + service bundle for adding a real-time speech-to-text transcription and voice command execution using WebSocket-based services like Amazon Transcribe.
You can:
- Use a drop-in
<VoiceButton />
for command-style or text-generation mic capture - Control everything via a lightweight API
This package offers a robust solution for speech-to-text functionality with two key benefits:
- No Data Storage: We don't collect, store, or process any user data. You're responsible for managing/generating your AWS Transcribe's presigned URLs and ensuring data security.
- Efficient Audio Processing: Our package utilizes Audio Worklet, offloading audio processing to a new thread and keeping your main thread free from blocking operations. This ensures a seamless and efficient experience for your users.
npm install vue-transcribe
This package requires material design icons to function properly. It is expected that you have this setup in the consuming project. If you don't have it setup yet, do the following:
npm install @mdi/font
import '@mdi/font/css/materialdesignicons.css';
// or
import '@mdi/font/css/materialdesignicons.min.css';
You must generate and provide a presigned WebSocket URL to start any transcription. Example (using AWS Transcribe):
import { TranscribeAPI } from 'vue-transcribe';
import axios from 'axios';
async function configure() {
const { data } = await axios.get('/api/transcribe-url'); // Replace with the presignedURL generation endpoint in your setup
TranscribeAPI.setURL(data.url);
TranscribeAPI.setConfigured(true); // ✅ Don't forget this
}
// AWS Transcribe presigned URLs expire every 5 minutes
configure();
setInterval(configure, 5 * 60 * 1000);
⚠️ setConfigured(true)
is required to indicate that a valid transcription URL is available. Without this, the component will not render. This can be used to filter envs as well. e.gTranscribeAPI.setConfigured(process.env.NODE_ENV !== 'development')
This button provides a quick way to interact with the API, and can be used in 2 modes - generate
and command
If there is an input/textrea HTMLElement you wish to populate with texts when you speak, the <VoiceButton />
component relies on an enclosing parent HTMLElement to be able to find the input element of interest. With that, you should have the input
or textarea
of interest, together with the <VoiceButton />
, enclosed within any parent HTMLElement. The order and styling is up to you:
<template>
<div class="input-voice-container">
<textarea class="input"></textarea>
<VoiceButton
:max-recording-time="10000"
:disabled="isDisabled"
mode="generate"
language-code="en-US"
theme="primary-transparent"
microphone="solid"
/>
</div>
</template>
<script>
import { VoiceButton } from 'vue-transcribe';
export default {
components: {
VoiceButton
},
data() {
return {
isDisabled: false
}
}
};
</script>
You can also grab the transcript and use it elsewhere without having to work with input
fields. For this, use @partial
for real time transcript and/or @final
for final transcript (emitted when the session ends):
<template>
<VoiceButton
:max-recording-time="10000"
mode="generate"
microphone="solid"
@partial="showTranscription($event)"
@final="sendMessage($event)"
/>
</template>
<script>
import { VoiceButton } from 'vue-transcribe';
export default {
components: {
VoiceButton
},
methods: {
showTranscription(transcript) {
console.log(transcript);
},
sendMessage(transcript) {
console.log(transcript);
}
}
};
</script>
In command
mode, your app can execute tasks based on matched phrases. You have to setup the command mapping first of all:
TranscribeAPI.setCommandMap([
{
command: 'clear input',
task: () => {
const el = document.querySelector('input');
if (el) el.value = '';
}
},
{
command: 'submit form',
task: () => {
document.querySelector('form')?.submit();
}
},
{
command: 'shut down app',
task: () => {
this.application.shutdown(); // assumption
}
}
]);
Once the command mapping is configured, use the component in your templates like so:
<template>
<VoiceButton
:disabled="isDisabled"
:max-recording-time="3000"
:threshold="0.7"
mode="command"
language-code="en-US"
theme="primary"
microphone="solid"
/>
</template>
<script>
import { VoiceButton } from 'vue-transcribe';
export default {
components: {
VoiceButton
},
data() {
return {
isDisabled: false
}
}
};
</script>
Prop | Type | Required | Description |
---|---|---|---|
mode |
string |
✅ | Transcription mode - text-generation or custom commands. generate / command . |
language-code |
string |
❌ | BCP-47 language code (default en-US ). |
max-recording-time |
number |
❌ | Max mic capture time in ms (default: 5000 ). |
microphone |
string |
❌ | Microphone's style (solid or outline ). default solid . |
disabled |
boolean |
❌ | If button should be disabled. default false . |
theme |
string |
❌ | Predefined themes that can be applied - primary (default), secondary , danger and bland . Append -transparent to any of the first three themes to get a button with transparent background. bland has a transparent button by design. |
expanded |
boolean |
❌ | To show or hide label. default is false . |
append |
boolean |
❌ | Whether to replace or append to existing text input. For generate mode. Will only work if <VoiceButton /> and input element are enclosed together. |
trim-punctuation |
boolean |
❌ | Whether to remove trailing punctuations from transcript. |
threshold |
number |
❌ | Command matching threshold using Fuse.js. Defaults to 0.6 . The lower the threshold, the stricter the matching. It accepts values between 0 and 1 inclusive. For command mode. |
Event Name | Payload | Required | Description |
---|---|---|---|
@partial |
string |
❌ | Emitted when a transcript is generating in real time |
@final |
string |
❌ | Emitted when a transcription session ends. |
Method | Description |
---|---|
setURL(url: string) |
Sets the active transcription WebSocket URL |
setConfigured(boolean) |
Enables/disables voice capture |
isConfigured() |
Returns current config state |
setCommandMap(array) |
Registers global voice commands |
on(event, callback) |
Subscribe to 'error' event |
off(event, callback) |
Unsubscribe from events |
Classes (top to bottom) | Location |
---|---|
vue-transcribe-command-container |
This is on the top-most div enclosing all other elements |
vue-transcribe-command-button |
This is on the button element |
vue-transcribe-loading-circle |
This is on the revolving div |
vue-transcribe-listening-animation |
This is on the listening animation |
TranscribeAPI.on('error', (error) => {
console.error('Transcribe error:', error);
});
// Error event structure
{
code: ErrorCodes,
message: String,
details?: ErrorEvent
}
// enum ErrorCodes {
// WS_CONNECTION_FAILED: 'WS_CONNECTION_FAILED',
// AUDIO_CAPTURE_ERROR: 'AUDIO_CAPTURE_ERROR',
// NO_MATCHING_COMMAND: 'NO_MATCHING_COMMAND'
// };
// To unsubscribe
TranscribeAPI.off('error', callback);
import Vue from 'vue';
import {
VoiceButton
TranscribeAPI
} from 'vue-transcribe';
import axios from 'axios';
TranscribeAPI.setCommandMap([
{
command: 'clear input',
task: () => {
const el = document.querySelector('input');
if (el) el.value = '';
}
},
]);
function setURL() {
axios.get('/api/transcribe-url') // You are to set up this endpoint
.then(res => TranscribeAPI.setURL(res.data.url));
}
setURL();
setInterval(setURL, 5 * 60 * 1000);
TranscribeAPI.on('error', err => {
console.error('Transcription error:', err);
// You can trigger a toast, log or do anything of choice here
});
TranscribeAPI.setConfigured(true)
Vue.component('VoiceButton', VoiceButton); // Global registration
- You must manually refresh AWS presigned URLs every 5 minutes
- VoiceButton only renders when:
TranscribeAPI.isConfigured()
istrue
- Command Map must be an array of
{ command, task }
- Use
TranscribeAPI.on('error', cb)
to listen for transcription/command errors
MIT
Merge Requests welcome! Please follow existing code style and include docs for any new features or events.
Charles Ugbana