@lakshmiprasanth/react-voice-to-text
Version:
A modern React package for voice-to-text conversion with real-time speech recognition and file upload support
1,622 lines âĸ 67.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var react = require('react');
/**
* Simple EventEmitter implementation for browser compatibility
*/
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
return this;
}
once(event, listener) {
const onceWrapper = (...args) => {
this.off(event, onceWrapper);
listener.apply(this, args);
};
return this.on(event, onceWrapper);
}
off(event, listener) {
if (!this.events[event])
return this;
this.events[event] = this.events[event].filter(l => l !== listener);
return this;
}
emit(event, ...args) {
if (!this.events[event])
return false;
this.events[event].forEach(listener => {
try {
listener.apply(this, args);
}
catch (error) {
console.error('Error in event listener:', error);
}
});
return true;
}
removeAllListeners(event) {
if (event) {
delete this.events[event];
}
else {
this.events = {};
}
return this;
}
}
/**
* Error types that can occur during speech recognition
*/
exports.SpeechRecognitionErrorType = void 0;
(function (SpeechRecognitionErrorType) {
SpeechRecognitionErrorType["NO_SPEECH"] = "no-speech";
SpeechRecognitionErrorType["ABORTED"] = "aborted";
SpeechRecognitionErrorType["AUDIO_CAPTURE"] = "audio-capture";
SpeechRecognitionErrorType["NETWORK"] = "network";
SpeechRecognitionErrorType["NOT_ALLOWED"] = "not-allowed";
SpeechRecognitionErrorType["SERVICE_NOT_ALLOWED"] = "service-not-allowed";
SpeechRecognitionErrorType["BAD_GRAMMAR"] = "bad-grammar";
SpeechRecognitionErrorType["LANGUAGE_NOT_SUPPORTED"] = "language-not-supported";
SpeechRecognitionErrorType["ENGINE_ERROR"] = "engine-error";
SpeechRecognitionErrorType["INVALID_CONFIG"] = "invalid-config";
})(exports.SpeechRecognitionErrorType || (exports.SpeechRecognitionErrorType = {}));
/**
* Speech recognition error
*/
class SpeechRecognitionError extends Error {
constructor(type, message, originalError) {
super(message);
this.type = type;
this.originalError = originalError;
this.name = 'SpeechRecognitionError';
}
}
/**
* Base class for speech recognition engines
*/
class BaseSpeechRecognitionEngine extends EventEmitter {
constructor() {
super();
this.isRecording = false;
this.config = {};
}
/**
* Validate configuration
*/
validateConfig(config) {
if (config.confidenceThreshold && (config.confidenceThreshold < 0 || config.confidenceThreshold > 1)) {
throw new SpeechRecognitionError(exports.SpeechRecognitionErrorType.INVALID_CONFIG, 'Confidence threshold must be between 0 and 1');
}
if (config.maxAlternatives && config.maxAlternatives < 1) {
throw new SpeechRecognitionError(exports.SpeechRecognitionErrorType.INVALID_CONFIG, 'Max alternatives must be at least 1');
}
if (config.sampleRate && config.sampleRate < 8000) {
throw new SpeechRecognitionError(exports.SpeechRecognitionErrorType.INVALID_CONFIG, 'Sample rate must be at least 8000 Hz');
}
}
/**
* Validate audio input configuration
*/
validateAudioConfig(audioConfig) {
if (!audioConfig.source) {
throw new SpeechRecognitionError(exports.SpeechRecognitionErrorType.INVALID_CONFIG, 'Audio source must be specified');
}
if (audioConfig.source === 'file' && !audioConfig.filePath) {
throw new SpeechRecognitionError(exports.SpeechRecognitionErrorType.INVALID_CONFIG, 'File path must be specified for file source');
}
if (audioConfig.source === 'stream' && !audioConfig.audioStream) {
throw new SpeechRecognitionError(exports.SpeechRecognitionErrorType.INVALID_CONFIG, 'Audio stream must be specified for stream source');
}
}
/**
* Emit error event with proper error handling
*/
emitError(type, message, originalError) {
const error = new SpeechRecognitionError(type, message, originalError);
this.emit('error', error);
}
/**
* Emit result event with validation
*/
emitResult(result) {
// Validate result
if (!result.transcript) {
return; // Skip empty results
}
// Apply confidence threshold if configured
if (this.config.confidenceThreshold && result.confidence < this.config.confidenceThreshold) {
return; // Skip low-confidence results
}
this.emit('result', result);
}
/**
* Get default configuration merged with provided config
*/
getConfig(config) {
return {
language: 'en-US',
sampleRate: 16000,
continuous: false,
interimResults: false,
maxAlternatives: 1,
confidenceThreshold: 0.0,
encoding: 'LINEAR16',
...this.config,
...config
};
}
/**
* Check if currently recording
*/
get isActive() {
return this.isRecording;
}
/**
* Set recording state
*/
setRecordingState(recording) {
this.isRecording = recording;
}
/**
* Clean up resources
*/
cleanup() {
this.removeAllListeners();
this.isRecording = false;
}
/**
* Type-safe event emitter methods
*/
emit(event, ...args) {
return super.emit(event, ...args);
}
on(event, listener) {
return super.on(event, listener);
}
once(event, listener) {
return super.once(event, listener);
}
off(event, listener) {
return super.off(event, listener);
}
}
/**
* Web Speech API engine for browser environments
*/
class WebSpeechEngine extends BaseSpeechRecognitionEngine {
constructor() {
super();
this.recognition = null;
this.mediaRecorder = null;
this.audioChunks = [];
}
/**
* Check if Web Speech API is available
*/
isAvailable() {
if (typeof window === 'undefined') {
return false; // Not in browser environment
}
return !!(window.SpeechRecognition || window.webkitSpeechRecognition);
}
/**
* Get supported languages (common languages supported by most browsers)
*/
getSupportedLanguages() {
return [
'en-US', 'en-GB', 'en-AU', 'en-CA', 'en-IN', 'en-NZ', 'en-ZA',
'es-ES', 'es-MX', 'es-AR', 'es-CO', 'es-CL', 'es-PE', 'es-VE',
'fr-FR', 'fr-CA', 'fr-CH', 'fr-BE',
'de-DE', 'de-AT', 'de-CH',
'it-IT', 'it-CH',
'pt-BR', 'pt-PT',
'ru-RU',
'ja-JP',
'ko-KR',
'zh-CN', 'zh-TW', 'zh-HK',
'ar-SA', 'ar-EG',
'hi-IN',
'th-TH',
'tr-TR',
'pl-PL',
'nl-NL', 'nl-BE',
'sv-SE',
'da-DK',
'no-NO',
'fi-FI'
];
}
/**
* Initialize the speech recognition engine
*/
async initialize(_config) {
if (!this.isAvailable()) {
throw new Error('Web Speech API is not available in this environment');
}
this.config = this.getConfig(_config);
this.validateConfig(this.config);
try {
await this.initializeRecognition();
this.emit('initialized');
}
catch (error) {
this.emitError(exports.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to initialize Web Speech recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Start listening for speech
*/
async startListening(config) {
if (!this.recognition) {
throw new Error('Speech recognition not initialized. Call initialize() first.');
}
if (this.isRecording) {
await this.stopListening();
}
try {
this.recognition.start();
this.isRecording = true;
this.emit('started');
}
catch (error) {
this.emitError(exports.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to start listening: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Stop listening for speech
*/
async stopListening() {
if (!this.recognition) {
return;
}
try {
this.recognition.stop();
this.isRecording = false;
this.emit('stopped');
}
catch (error) {
this.emitError(exports.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to stop listening: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Start speech recognition
*/
async start(audioConfig, recognitionConfig) {
if (!this.isAvailable()) {
throw new Error('Web Speech API is not available in this environment');
}
this.validateAudioConfig(audioConfig);
this.config = this.getConfig(recognitionConfig);
this.validateConfig(this.config);
if (this.isRecording) {
await this.stop();
}
try {
await this.initializeRecognition();
if (audioConfig.source === 'microphone') {
await this.startMicrophoneRecognition(audioConfig);
}
else if (audioConfig.source === 'file') {
await this.processAudioFile(audioConfig.filePath);
}
else {
throw new Error('Web Speech API only supports microphone and file input');
}
}
catch (error) {
this.emitError(exports.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to start Web Speech recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Initialize speech recognition instance
*/
async initializeRecognition() {
const SpeechRecognitionClass = window.SpeechRecognition || window.webkitSpeechRecognition;
this.recognition = new SpeechRecognitionClass();
// Configure recognition
this.recognition.continuous = this.config.continuous || false;
this.recognition.interimResults = this.config.interimResults || false;
this.recognition.lang = this.config.language || 'en-US';
this.recognition.maxAlternatives = this.config.maxAlternatives || 1;
// Set up event handlers
this.recognition.onstart = () => {
this.setRecordingState(true);
this.emit('start');
this.emit('audiostart');
};
this.recognition.onend = () => {
this.setRecordingState(false);
this.emit('audioend');
this.emit('end');
};
this.recognition.onresult = (event) => {
for (let i = event.resultIndex; i < event.results.length; i++) {
const result = event.results[i];
const alternative = result[0];
const speechResult = {
transcript: alternative.transcript,
confidence: alternative.confidence,
isFinal: result.isFinal,
alternatives: Array.from(result).map((alt) => ({
transcript: alt.transcript,
confidence: alt.confidence
}))
};
this.emitResult(speechResult);
}
};
this.recognition.onerror = (event) => {
let errorType;
switch (event.error) {
case 'no-speech':
errorType = exports.SpeechRecognitionErrorType.NO_SPEECH;
break;
case 'aborted':
errorType = exports.SpeechRecognitionErrorType.ABORTED;
break;
case 'audio-capture':
errorType = exports.SpeechRecognitionErrorType.AUDIO_CAPTURE;
break;
case 'network':
errorType = exports.SpeechRecognitionErrorType.NETWORK;
break;
case 'not-allowed':
errorType = exports.SpeechRecognitionErrorType.NOT_ALLOWED;
break;
case 'service-not-allowed':
errorType = exports.SpeechRecognitionErrorType.SERVICE_NOT_ALLOWED;
break;
case 'bad-grammar':
errorType = exports.SpeechRecognitionErrorType.BAD_GRAMMAR;
break;
case 'language-not-supported':
errorType = exports.SpeechRecognitionErrorType.LANGUAGE_NOT_SUPPORTED;
break;
default:
errorType = exports.SpeechRecognitionErrorType.ENGINE_ERROR;
}
this.emitError(errorType, `Web Speech API error: ${event.error}`);
};
this.recognition.onspeechstart = () => this.emit('speechstart');
this.recognition.onspeechend = () => this.emit('speechend');
this.recognition.onsoundstart = () => this.emit('soundstart');
this.recognition.onsoundend = () => this.emit('soundend');
}
/**
* Start microphone recognition
*/
async startMicrophoneRecognition(audioConfig) {
if (!this.recognition) {
throw new Error('Recognition not initialized');
}
try {
// Request microphone permission and start recognition
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
deviceId: audioConfig.deviceId ? { exact: audioConfig.deviceId } : undefined,
sampleRate: this.config.sampleRate || 16000
}
});
// Start recognition
this.recognition.start();
// If duration is specified, stop after that time
if (audioConfig.duration) {
setTimeout(() => {
this.stop().catch(console.error);
}, audioConfig.duration);
}
// Clean up stream when recognition ends
this.recognition.onend = () => {
stream.getTracks().forEach(track => track.stop());
this.setRecordingState(false);
this.emit('audioend');
this.emit('end');
};
}
catch (error) {
this.emitError(exports.SpeechRecognitionErrorType.AUDIO_CAPTURE, `Failed to access microphone: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
}
}
/**
* Process audio file (requires MediaRecorder API)
*/
async processAudioFile(_filePath) {
// Note: Direct file processing with Web Speech API is limited
// This is a simplified implementation that would need additional audio processing
throw new Error('File processing with Web Speech API requires additional audio processing libraries');
}
/**
* Stop speech recognition
*/
async stop() {
if (this.recognition && this.isRecording) {
this.recognition.stop();
}
if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
this.mediaRecorder.stop();
}
this.setRecordingState(false);
}
/**
* Abort speech recognition
*/
async abort() {
if (this.recognition) {
this.recognition.abort();
}
if (this.mediaRecorder && this.mediaRecorder.state === 'recording') {
this.mediaRecorder.stop();
}
this.setRecordingState(false);
}
/**
* Process audio file directly (not supported by Web Speech API)
*/
async processFile(_file, _config) {
throw new Error('Direct file processing is not supported by Web Speech API. Use start() with file source instead.');
}
/**
* Process audio stream (not supported by Web Speech API)
*/
async processStream(_stream, _config) {
throw new Error('Direct stream processing is not supported by Web Speech API. Use start() with stream source instead.');
}
/**
* Get available audio input devices
*/
static async getAudioDevices() {
if (typeof navigator === 'undefined' || !navigator.mediaDevices) {
return [];
}
try {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === 'audioinput');
}
catch (error) {
console.error('Failed to enumerate audio devices:', error);
return [];
}
}
/**
* Check browser compatibility
*/
static getBrowserSupport() {
const features = [];
let supported = false;
if (typeof window !== 'undefined') {
if (window.SpeechRecognition || window.webkitSpeechRecognition) {
features.push('SpeechRecognition');
supported = true;
}
if (navigator.mediaDevices && typeof navigator.mediaDevices.getUserMedia === 'function') {
features.push('getUserMedia');
}
if (window.MediaRecorder) {
features.push('MediaRecorder');
}
}
return { supported, features };
}
}
/**
* Check browser support for speech recognition and related APIs
*/
function getBrowserSupport() {
if (typeof window === 'undefined') {
return {
webSpeechAPI: false,
mediaRecorder: false,
getUserMedia: false,
browser: {
name: 'unknown',
version: 'unknown'
}
};
}
const browserInfo = getBrowserInfo();
return {
webSpeechAPI: 'SpeechRecognition' in window || 'webkitSpeechRecognition' in window,
mediaRecorder: 'MediaRecorder' in window,
getUserMedia: 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices,
browser: browserInfo
};
}
/**
* Get available audio input devices
*/
async function getAudioDevices() {
if (typeof navigator === 'undefined' || !navigator.mediaDevices) {
return [];
}
try {
const devices = await navigator.mediaDevices.enumerateDevices();
return devices.filter(device => device.kind === 'audioinput');
}
catch (error) {
console.error('Failed to enumerate audio devices:', error);
return [];
}
}
/**
* Request microphone permission
*/
async function requestMicrophonePermission() {
if (typeof navigator === 'undefined' || !navigator.mediaDevices) {
return false;
}
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
// Stop the stream immediately as we only needed permission
stream.getTracks().forEach(track => track.stop());
return true;
}
catch (error) {
console.error('Microphone permission denied:', error);
return false;
}
}
/**
* Get browser name and version
*/
function getBrowserInfo() {
if (typeof navigator === 'undefined') {
return { name: 'unknown', version: 'unknown' };
}
const userAgent = navigator.userAgent;
let name = 'unknown';
let version = 'unknown';
// Chrome
if (userAgent.includes('Chrome') && !userAgent.includes('Edg')) {
name = 'Chrome';
const match = userAgent.match(/Chrome\/(\d+)/);
if (match)
version = match[1];
}
// Safari
else if (userAgent.includes('Safari') && !userAgent.includes('Chrome')) {
name = 'Safari';
const match = userAgent.match(/Version\/(\d+)/);
if (match)
version = match[1];
}
// Firefox
else if (userAgent.includes('Firefox')) {
name = 'Firefox';
const match = userAgent.match(/Firefox\/(\d+)/);
if (match)
version = match[1];
}
// Edge
else if (userAgent.includes('Edg')) {
name = 'Edge';
const match = userAgent.match(/Edg\/(\d+)/);
if (match)
version = match[1];
}
return { name, version };
}
const VoiceToTextContext = react.createContext(null);
const VoiceToTextProvider = ({ children, options = {}, onResult, onError, onStart, onStop }) => {
const [isInitialized, setIsInitialized] = react.useState(false);
const [isRecording, setIsRecording] = react.useState(false);
const [isProcessing, setIsProcessing] = react.useState(false);
const [results, setResults] = react.useState([]);
const [error, setError] = react.useState(null);
const [browserSupport, setBrowserSupport] = react.useState(null);
const [config, setConfig] = react.useState({
defaultRecognitionConfig: {
language: 'en-US',
continuous: true,
interimResults: true
},
debug: false,
...options
});
const [engine, setEngine] = react.useState(null);
react.useEffect(() => {
const initialize = async () => {
try {
// Check browser support
const support = getBrowserSupport();
setBrowserSupport(support);
if (!support.webSpeechAPI) {
throw new Error('Speech recognition is not supported in this browser');
}
// Initialize Web Speech engine
const speechEngine = new WebSpeechEngine();
await speechEngine.initialize(config.defaultRecognitionConfig);
// Set up event listeners
speechEngine.on('result', (result) => {
setResults(prev => [...prev, result]);
onResult?.(result);
});
speechEngine.on('error', (error) => {
const errorMessage = error.message || 'Speech recognition error';
setError(errorMessage);
onError?.(errorMessage);
});
speechEngine.on('start', () => {
setIsRecording(true);
onStart?.();
});
speechEngine.on('end', () => {
setIsRecording(false);
onStop?.();
});
setEngine(speechEngine);
setIsInitialized(true);
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize voice recognition';
setError(errorMessage);
onError?.(errorMessage);
}
};
initialize();
// Cleanup
return () => {
if (engine) {
engine.cleanup();
}
};
}, []);
const startRecording = async (recordingOptions) => {
if (!engine || !isInitialized) {
throw new Error('Voice recognition not initialized');
}
try {
setIsProcessing(true);
setError(null);
setResults([]);
const finalConfig = {
...config.defaultRecognitionConfig,
...recordingOptions
};
await engine.startListening(finalConfig);
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to start recording';
setError(errorMessage);
onError?.(errorMessage);
throw err;
}
finally {
setIsProcessing(false);
}
};
const stopRecording = async () => {
if (!engine) {
throw new Error('Voice recognition not initialized');
}
try {
setIsProcessing(true);
await engine.stopListening();
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to stop recording';
setError(errorMessage);
onError?.(errorMessage);
throw err;
}
finally {
setIsProcessing(false);
}
};
const clearResults = () => {
setResults([]);
};
const clearError = () => {
setError(null);
};
const updateConfig = (newConfig) => {
setConfig(prev => ({ ...prev, ...newConfig }));
};
const getConfig = () => config;
const contextValue = {
isInitialized,
isRecording,
isProcessing,
results,
error,
browserSupport,
startRecording,
stopRecording,
clearResults,
clearError,
updateConfig,
getConfig
};
return (jsxRuntime.jsx(VoiceToTextContext.Provider, { value: contextValue, children: children }));
};
const useVoiceToTextContext = () => {
const context = react.useContext(VoiceToTextContext);
if (!context) {
throw new Error('useVoiceToTextContext must be used within a VoiceToTextProvider');
}
return context;
};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var classnames = {exports: {}};
/*!
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
(function (module) {
/* global define */
(function () {
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = '';
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (arg) {
classes = appendClass(classes, parseValue(arg));
}
}
return classes;
}
function parseValue (arg) {
if (typeof arg === 'string' || typeof arg === 'number') {
return arg;
}
if (typeof arg !== 'object') {
return '';
}
if (Array.isArray(arg)) {
return classNames.apply(null, arg);
}
if (arg.toString !== Object.prototype.toString && !arg.toString.toString().includes('[native code]')) {
return arg.toString();
}
var classes = '';
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes = appendClass(classes, key);
}
}
return classes;
}
function appendClass (value, newClass) {
if (!newClass) {
return value;
}
if (value) {
return value + ' ' + newClass;
}
return value + newClass;
}
if (module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
} (classnames));
var classnamesExports = classnames.exports;
var classNames = /*@__PURE__*/getDefaultExportFromCjs(classnamesExports);
const SUPPORTED_LANGUAGES = [
{ code: 'en-US', name: 'English (US)' },
{ code: 'en-GB', name: 'English (UK)' },
{ code: 'es-ES', name: 'Spanish' },
{ code: 'fr-FR', name: 'French' },
{ code: 'de-DE', name: 'German' },
{ code: 'it-IT', name: 'Italian' },
{ code: 'pt-BR', name: 'Portuguese (Brazil)' },
{ code: 'ja-JP', name: 'Japanese' },
{ code: 'ko-KR', name: 'Korean' },
{ code: 'zh-CN', name: 'Chinese (Simplified)' },
{ code: 'zh-TW', name: 'Chinese (Traditional)' },
{ code: 'ru-RU', name: 'Russian' },
{ code: 'ar-SA', name: 'Arabic' },
{ code: 'hi-IN', name: 'Hindi' },
{ code: 'th-TH', name: 'Thai' },
{ code: 'vi-VN', name: 'Vietnamese' },
{ code: 'tr-TR', name: 'Turkish' },
{ code: 'pl-PL', name: 'Polish' },
{ code: 'nl-NL', name: 'Dutch' },
{ code: 'sv-SE', name: 'Swedish' },
{ code: 'da-DK', name: 'Danish' },
{ code: 'no-NO', name: 'Norwegian' },
{ code: 'fi-FI', name: 'Finnish' },
{ code: 'cs-CZ', name: 'Czech' },
{ code: 'hu-HU', name: 'Hungarian' },
{ code: 'ro-RO', name: 'Romanian' },
{ code: 'bg-BG', name: 'Bulgarian' },
{ code: 'hr-HR', name: 'Croatian' },
{ code: 'sk-SK', name: 'Slovak' },
{ code: 'sl-SI', name: 'Slovenian' },
{ code: 'et-EE', name: 'Estonian' },
{ code: 'lv-LV', name: 'Latvian' },
{ code: 'lt-LT', name: 'Lithuanian' },
{ code: 'mt-MT', name: 'Maltese' },
{ code: 'el-GR', name: 'Greek' },
{ code: 'he-IL', name: 'Hebrew' },
{ code: 'id-ID', name: 'Indonesian' },
{ code: 'ms-MY', name: 'Malay' },
{ code: 'fil-PH', name: 'Filipino' },
{ code: 'uk-UA', name: 'Ukrainian' },
{ code: 'be-BY', name: 'Belarusian' },
{ code: 'kk-KZ', name: 'Kazakh' },
{ code: 'ky-KG', name: 'Kyrgyz' },
{ code: 'uz-UZ', name: 'Uzbek' },
{ code: 'mn-MN', name: 'Mongolian' },
{ code: 'ka-GE', name: 'Georgian' },
{ code: 'hy-AM', name: 'Armenian' },
{ code: 'az-AZ', name: 'Azerbaijani' },
{ code: 'fa-IR', name: 'Persian' },
{ code: 'ur-PK', name: 'Urdu' },
{ code: 'bn-BD', name: 'Bengali' },
{ code: 'si-LK', name: 'Sinhala' },
{ code: 'my-MM', name: 'Burmese' },
{ code: 'km-KH', name: 'Khmer' },
{ code: 'lo-LA', name: 'Lao' },
{ code: 'ne-NP', name: 'Nepali' },
{ code: 'gu-IN', name: 'Gujarati' },
{ code: 'pa-IN', name: 'Punjabi' },
{ code: 'te-IN', name: 'Telugu' },
{ code: 'kn-IN', name: 'Kannada' },
{ code: 'ml-IN', name: 'Malayalam' },
{ code: 'ta-IN', name: 'Tamil' },
{ code: 'mr-IN', name: 'Marathi' },
{ code: 'or-IN', name: 'Odia' },
{ code: 'as-IN', name: 'Assamese' },
{ code: 'sa-IN', name: 'Sanskrit' },
{ code: 'am-ET', name: 'Amharic' },
{ code: 'sw-KE', name: 'Swahili' },
{ code: 'yo-NG', name: 'Yoruba' },
{ code: 'ig-NG', name: 'Igbo' },
{ code: 'ha-NG', name: 'Hausa' },
{ code: 'zu-ZA', name: 'Zulu' },
{ code: 'af-ZA', name: 'Afrikaans' },
{ code: 'xh-ZA', name: 'Xhosa' },
{ code: 'st-ZA', name: 'Southern Sotho' },
{ code: 'tn-ZA', name: 'Tswana' },
{ code: 'ss-ZA', name: 'Swati' },
{ code: 've-ZA', name: 'Venda' },
{ code: 'ts-ZA', name: 'Tsonga' },
{ code: 'nr-ZA', name: 'Southern Ndebele' },
{ code: 'nd-ZA', name: 'Northern Ndebele' }
];
const LanguageSelector = ({ value = 'en-US', onChange, disabled = false, className, showLabel = true, label = 'Language', placeholder = 'Select language...', ...props }) => {
const handleChange = (event) => {
onChange?.(event.target.value);
};
return (jsxRuntime.jsxs("div", { className: classNames('language-selector', className), ...props, children: [showLabel && (jsxRuntime.jsx("label", { className: "language-selector__label", children: label })), jsxRuntime.jsxs("select", { value: value, onChange: handleChange, disabled: disabled, className: "language-selector__select", children: [jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }), SUPPORTED_LANGUAGES.map((language) => (jsxRuntime.jsx("option", { value: language.code, children: language.name }, language.code)))] })] }));
};
const RecordingControls = ({ isRecording, isProcessing, onStart, onStop, disabled = false, className, showStatus = true, startText = 'đ¤ Start Recording', stopText = 'âšī¸ Stop Recording', processingText = 'đ Processing...', ...props }) => {
const handleStart = () => {
if (!disabled && !isRecording && !isProcessing) {
onStart?.();
}
};
const handleStop = () => {
if (!disabled && isRecording && !isProcessing) {
onStop?.();
}
};
const getStatusText = () => {
if (isProcessing)
return processingText;
if (isRecording)
return 'đ´ Recording...';
return 'Ready to record';
};
const getStatusColor = () => {
if (isProcessing)
return '#f39c12';
if (isRecording)
return '#e74c3c';
return '#27ae60';
};
return (jsxRuntime.jsxs("div", { className: classNames('recording-controls', className), ...props, children: [jsxRuntime.jsx("div", { className: "recording-controls__buttons", children: !isRecording ? (jsxRuntime.jsx("button", { onClick: handleStart, disabled: disabled || isProcessing, className: classNames('recording-controls__button', 'recording-controls__button--start', {
'recording-controls__button--disabled': disabled || isProcessing
}), children: startText })) : (jsxRuntime.jsx("button", { onClick: handleStop, disabled: disabled || isProcessing, className: classNames('recording-controls__button', 'recording-controls__button--stop', {
'recording-controls__button--disabled': disabled || isProcessing
}), children: stopText })) }), showStatus && (jsxRuntime.jsxs("div", { className: "recording-controls__status", children: [jsxRuntime.jsx("div", { className: "recording-controls__status-indicator", style: { backgroundColor: getStatusColor() } }), jsxRuntime.jsx("span", { className: "recording-controls__status-text", children: getStatusText() })] }))] }));
};
const ResultsDisplay = ({ results, error, onClear, showClearButton = true, showConfidence = true, showStatus = true, maxResults = 50, className, emptyText = 'No results yet. Start recording or convert a file to see results.', ...props }) => {
const handleClear = () => {
onClear?.();
};
const formatConfidence = (confidence) => {
return `${(confidence * 100).toFixed(1)}%`;
};
const getStatusIcon = (isFinal) => {
return isFinal ? 'â
' : 'âŗ';
};
const getStatusText = (isFinal) => {
return isFinal ? 'Final' : 'Interim';
};
const getStatusColor = (isFinal) => {
return isFinal ? '#27ae60' : '#f39c12';
};
const displayedResults = results.slice(-maxResults);
if (error) {
return (jsxRuntime.jsxs("div", { className: classNames('results-display', 'results-display--error', className), ...props, children: [jsxRuntime.jsxs("div", { className: "results-display__header", children: [jsxRuntime.jsx("h3", { children: "\u274C Error" }), showClearButton && (jsxRuntime.jsx("button", { onClick: handleClear, className: "results-display__clear-button", children: "Clear" }))] }), jsxRuntime.jsx("div", { className: "results-display__error", children: jsxRuntime.jsx("p", { children: error }) })] }));
}
if (results.length === 0) {
return (jsxRuntime.jsx("div", { className: classNames('results-display', 'results-display--empty', className), ...props, children: jsxRuntime.jsxs("div", { className: "results-display__empty", children: [jsxRuntime.jsx("div", { className: "results-display__empty-icon", children: "\uD83D\uDCDD" }), jsxRuntime.jsx("h3", { children: "Results" }), jsxRuntime.jsx("p", { children: emptyText })] }) }));
}
return (jsxRuntime.jsxs("div", { className: classNames('results-display', className), ...props, children: [jsxRuntime.jsxs("div", { className: "results-display__header", children: [jsxRuntime.jsxs("h3", { children: ["\uD83D\uDCDD Results (", results.length, ")"] }), showClearButton && (jsxRuntime.jsx("button", { onClick: handleClear, className: "results-display__clear-button", children: "Clear All" }))] }), jsxRuntime.jsxs("div", { className: "results-display__content", children: [jsxRuntime.jsx("div", { className: "results-display__list", children: displayedResults.map((result, index) => (jsxRuntime.jsx("div", { className: classNames('results-display__item', {
'results-display__item--final': result.isFinal,
'results-display__item--interim': !result.isFinal
}), children: jsxRuntime.jsxs("div", { className: "results-display__item-content", children: [jsxRuntime.jsx("div", { className: "results-display__text", children: result.transcript }), jsxRuntime.jsxs("div", { className: "results-display__meta", children: [showStatus && (jsxRuntime.jsxs("div", { className: "results-display__status", style: { color: getStatusColor(result.isFinal) }, children: [jsxRuntime.jsx("span", { className: "results-display__status-icon", children: getStatusIcon(result.isFinal) }), jsxRuntime.jsx("span", { className: "results-display__status-text", children: getStatusText(result.isFinal) })] })), showConfidence && (jsxRuntime.jsxs("div", { className: "results-display__confidence", children: ["Confidence: ", formatConfidence(result.confidence)] })), jsxRuntime.jsxs("div", { className: "results-display__index", children: ["#", results.length - displayedResults.length + index + 1] })] })] }) }, `${result.transcript}-${index}`))) }), results.length > maxResults && (jsxRuntime.jsx("div", { className: "results-display__overflow", children: jsxRuntime.jsxs("p", { children: ["Showing last ", maxResults, " results of ", results.length, " total"] }) }))] })] }));
};
const VoiceRecorder = ({ className, showLanguageSelector = true, showResults = true, showControls = true, language = 'en-US', continuous = true, interimResults = true, onResult: _onResult, onError, onStart, onStop, children, ...props }) => {
const { isInitialized, isRecording, isProcessing, results, error, startRecording, stopRecording, clearResults, clearError } = useVoiceToTextContext();
const [selectedLanguage, setSelectedLanguage] = react.useState(language);
const handleStartRecording = async () => {
try {
await startRecording({
language: selectedLanguage,
continuous,
interimResults
});
onStart?.();
}
catch (err) {
onError?.(err instanceof Error ? err.message : 'Failed to start recording');
}
};
const handleStopRecording = async () => {
try {
await stopRecording();
onStop?.();
}
catch (err) {
onError?.(err instanceof Error ? err.message : 'Failed to stop recording');
}
};
const handleLanguageChange = (newLanguage) => {
setSelectedLanguage(newLanguage);
};
const handleClearResults = () => {
clearResults();
};
const handleClearError = () => {
clearError();
};
if (!isInitialized) {
return (jsxRuntime.jsx("div", { className: classNames('voice-recorder', 'voice-recorder--loading', className), ...props, children: jsxRuntime.jsxs("div", { className: "voice-recorder__status", children: [jsxRuntime.jsx("div", { className: "voice-recorder__loading-spinner" }), jsxRuntime.jsx("p", { children: "Initializing voice recognition..." })] }) }));
}
if (error) {
return (jsxRuntime.jsx("div", { className: classNames('voice-recorder', 'voice-recorder--error', className), ...props, children: jsxRuntime.jsxs("div", { className: "voice-recorder__error", children: [jsxRuntime.jsx("h3", { children: "\u274C Error" }), jsxRuntime.jsx("p", { children: error }), jsxRuntime.jsx("button", { onClick: handleClearError, className: "voice-recorder__error-button", children: "Try Again" })] }) }));
}
return (jsxRuntime.jsxs("div", { className: classNames('voice-recorder', className), ...props, children: [jsxRuntime.jsxs("div", { className: "voice-recorder__header", children: [jsxRuntime.jsx("h2", { children: "\uD83C\uDFA4 Voice Recorder" }), jsxRuntime.jsx("p", { children: "Record your voice and see real-time transcription" })] }), jsxRuntime.jsxs("div", { className: "voice-recorder__content", children: [showLanguageSelector && (jsxRuntime.jsx("div", { className: "voice-recorder__language", children: jsxRuntime.jsx(LanguageSelector, { value: selectedLanguage, onChange: handleLanguageChange, disabled: isRecording || isProcessing }) })), showControls && (jsxRuntime.jsx("div", { className: "voice-recorder__controls", children: jsxRuntime.jsx(RecordingControls, { isRecording: isRecording, isProcessing: isProcessing, onStart: handleStartRecording, onStop: handleStopRecording, disabled: !isInitialized }) })), showResults && (jsxRuntime.jsx("div", { className: "voice-recorder__results", children: jsxRuntime.jsx(ResultsDisplay, { results: results, error: error, onClear: handleClearResults, showClearButton: results.length > 0 }) })), children && (jsxRuntime.jsx("div", { className: "voice-recorder__children", children: children }))] })] }));
};
const FileUpload = ({ className, onFileSelect, onConvert, onError, acceptedFormats = ['audio/*'], maxFileSize = 50 * 1024 * 1024, // 50MB
showLanguageSelector = true, language = 'en-US', dragAndDrop = true, multiple = false, disabled = false, children, ...props }) => {
const [selectedFile, setSelectedFile] = react.useState(null);
const [isConverting, setIsConverting] = react.useState(false);
const [isDragOver, setIsDragOver] = react.useState(false);
const [selectedLanguage, setSelectedLanguage] = react.useState(language);
const fileInputRef = react.useRef(null);
const handleFileSelect = (file) => {
// Validate file size
if (file.size > maxFileSize) {
const error = `File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds maximum allowed size (${(maxFileSize / 1024 / 1024).toFixed(2)}MB)`;
onError?.(error);
return;
}
// Validate file type
const isValidType = acceptedFormats.some(format => {
if (format === 'audio/*') {
return file.type.startsWith('audio/');
}
return file.type === format;
});
if (!isValidType) {
const error = `File type ${file.type} is not supported. Please select an audio file.`;
onError?.(error);
return;
}
setSelectedFile(file);
onFileSelect?.(file);
};
const handleFileInputChange = (event) => {
const files = event.target.files;
if (files && files.length > 0) {
handleFileSelect(files[0]);
}
};
const handleDragOver = (event) => {
event.preventDefault();
setIsDragOver(true);
};
const handleDragLeave = (event) => {
event.preventDefault();
setIsDragOver(false);
};
const handleDrop = (event) => {
event.preventDefault();
setIsDragOver(false);
const files = event.dataTransfer.files;
if (files && files.length > 0) {
handleFileSelect(files[0]);
}
};
const handleConvert = async () => {
if (!selectedFile)
return;
setIsConverting(true);
try {
await onConvert?.(selectedFile, selectedLanguage);
}
catch (error) {
onError?.(error instanceof Error ? error.message : 'Failed to convert file');
}
finally {
setIsConverting(false);
}
};
const handleClearFile = () => {
setSelectedFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleLanguageChange = (newLanguage) => {
setSelectedLanguage(newLanguage);
};
const openFileDialog = () => {
fileInputRef.current?.click();
};
const formatFileSize = (bytes) => {
if (bytes === 0)
return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
return (jsxRuntime.jsxs("div", { className: classNames('file-upload', className), ...props, children: [jsxRuntime.jsxs("div", { className: "file-upload__header", children: [jsxRuntime.jsx("h3", { children: "\uD83D\uDCC1 File Upload & Convert" }), jsxRuntime.jsx("p", { children: "Upload an audio file to convert speech to text" })] }), jsxRuntime.jsxs("div", { className: "file-upload__content", children: [showLanguageSelector && (jsxRuntime.jsx("div", { className: "file-upload__language", children: jsxRuntime.jsx(LanguageSelector, { value: selectedLanguage, onChange: handleLanguageChange, disabled: disabled || isConverting }) })), jsxRuntime.jsxs("div", { className: "file-upload__upload-area", children: [dragAndDrop ? (jsxRuntime.jsx("div", { className: classNames('file-upload__drop-zone', {
'file-upload__drop-zone--drag-over': isDragOver,
'file-upload__drop-zone--disabled': disabled
}), onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: !disabled ? openFileDialog : undefined, children: jsxRuntime.jsxs("div", { className: "file-upload__drop-zone-content", children: [jsxRuntime.jsx("div", { className: "file-upload__icon", children: "\uD83D\uDCC1" }), jsxRuntime.jsx("p", { className: "file-upload__drop-text", children: isDragOver ? 'Drop your audio file here' : 'Click to select or drag & drop audio file' }), jsxRuntime.jsx("p", { className: "file-upload__drop-hint", children: "Supported formats: MP3, WAV, OGG, FLAC, M4A" }), jsxRuntime.jsxs("p", { className: "file-upload__drop-hint", children: ["Max size: ", formatFileSize(maxFileSize)] })] }) })) : (jsxRuntime.jsxs("div", { className: "file-upload__input-wrapper", children: [jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: acceptedFormats.join(','), onChange: handleFileInputChange, disabled: disabled, multiple: multiple, className: "file-upload__input" }), jsxRuntime.jsx("button", { onClick: openFileDialog, disabled: disabled, className: "file-upload__select-button", children: "Select Audio File" })] })), jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: acceptedFormats.join(','), onChange: handleFileInputChange, disabled: disabled, multiple: multiple, className: "file-upload__hidden-input" })] }), selectedFile && (jsxRuntime.jsxs("div", { className: "file-upload__file-info", children: [jsxRuntime.jsxs("div", { className: "file-upload__file-details", children: [jsxRuntime.jsx("div", { className: "file-upload__file-name", children: selectedFile.name }), jsxRuntime.jsx("div", { className: "file-upload__file-size", children: formatFileSize(selectedFile.size) })] }), jsxRuntime.jsx("button", { onClick: handleClearFile, disabled: isConverting, className: "file-upload__clear-button", children: "\u2715" })] })), jsxRuntime.jsx("div", { className: "file-upload__controls", children: jsxRuntime.jsx("button", { onClick: handleConvert, disabled: !selectedFile || disabled || isConverting, className: "file-upload__convert-button", children: isConverting ? 'đ Converting...' : 'đ Convert File' }) }), children && (jsxRuntime.jsx("div", { className: "file-upload__children", children: children }))] })] }));
};
const VoiceToTextConverter = ({ className, showFileUpload = true, showVoiceRecorder = true, showResults = true, defaultLanguage = 'en-US', onResult, onError, onStart, onStop, children }) => {
const [activeTab, setActiveTab] = react.useState('recorder');
const [fileResults, setFileResults] = react.useState([]);
const handleFileConvert = async (file, _language) => {
try {
// For file conversion, we'll use a simple approach with Web Speech API
// In a real implementation, you might want to use a different service
// const audio = new Audio(URL.createObjectURL(file));
// Note: This is a simplified implementation
// Real file conversion would require a backend service or different approach
console.log('File conversion not fully implemented in this demo');
// For demo purposes, we'll create a mock result
const mockResult = {
transcript: `Converted audio file: ${file.name}`,
confidence: 0.95,
isFinal: true,
timestamp: { start: Date.now(), end: Date.now() }
};
setFileResults([mockResult]);
onResult?.(mockResult);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to convert file';
onError?.(errorMessage);
}
};
const handleFileError = (error) => {
onError?.(error);
};
return (jsxRuntime.jsx(VoiceToTextProvider, { options: {
defaultRecognitionConfig: {
language: defaultLanguage,
continuous: true,
interimResults: true
}
}, onResult: onResult, onError: onError, onStart: onStart, onStop: onStop, children: jsxRuntime.jsxs("div", { className: classNames('voice-to-text-converter', className), children: [jsxRuntime.jsxs("div", { className: "voice-to-text-converter__header", children: [jsxRuntime.jsx("h1", { children: "\uD83C\uDFA4 Voice-to-Text Converter" }), jsxRuntime.jsx("p", { children: "Convert speech to text in real-time or from audio files" })] }), jsxRuntime.jsxs("div", { className: "voice-to-text-converter__content", children: [showFileUpload && showVoiceRecorder && (jsxRuntime.jsxs("div", { className: "voice-to-text-converter__tabs", children: [jsxRuntime.jsx("button", { onClick: () => setActiveTab('recorder'), className: classNames('voice-to-text-converter__tab', {
'voice-to-text-converter__tab--active': activeTab === 'recorder'
}), children: "\uD83C\uDF99\uFE0F Voice Recorder" }), jsxRuntime.jsx("button", { onClick: () => setActiveTab('upload'), className: classNames('voice-to-text-converter__tab', {
'voice-to-text-converter__tab--active': activeTab === 'upload'
}), children: "\uD83D\uDCC1 File Upload" })] })), jsxRuntime.jsxs("div", { className: "voice-to-text-converter__main", children: [showVoiceRecorder && (activeTab === 'recorder' || !showFileUpload) && (jsxRuntime.jsx(VoiceRecorder, { showResults: false, onResult: onResult, onError: onError, onStart: onStart, onStop: onStop })), showFileUpload && (activeTab === 'upload' || !showVoiceRecorder) && (jsxRuntime.jsx(FileUpload, { onConvert: handleFileConvert, onError: handleFileError, language: defaultLanguage }))] }), showResults && (jsxRuntime.jsx("div", { className: "voice-to-text-converter__results", children: jsxRuntime.jsx(ResultsDisplay, { results: fileResults, showClearButton: true, onClear: () => setFileResults([]) }) })), children && (jsxRuntime.jsx("div", { className: "voice-to-text-converter__children", children: children }))] })] }) }));
};
const useVoiceToText = (options = {}) => {
const [isInitialized, setIsInitialized] = react.useState(false);
const [isRecording, setIsRecording] = react.useState(false);
const [isProcessing, setIsProcessing] = react.useState(false);
const [results, setResults] = react.useState([]);
const [error, setError] = react.useState(null);
const [browserSupport, setBrowserSupport] = react.useState(null);
const [config, setConfig] = react.useState({
defaultRecognitionConfig: {
language: 'en-US',
continuous: true,
interimResults: true
},
debug: false,
...options
});
const [engine, setEngine] = react.useState(null);
// Initialize the engine
react.useEffect(() => {
const initialize = async () => {
try {
// Check browser support
const support = getBrowserSupport();
setBrowserSupport(support);
if (!support.webSpeechAPI) {
throw new Error('Speech recognition is not supported in this browser');
}
// Initialize Web Speech engine
const speechEngine = new WebSpeechEngine();
await speechEngine.initialize(config.defaultRecognitionConfig);
// Set up event listeners
speechEngine.on('result', (result) => {
setResults(prev => [...prev, result]);
options.onResult?.(result);
});
speechEngine.on('error', (error) => {
const errorMessage = error.message || 'Speech recognition error';
setError(errorMessage);
options.onError?.(errorMessage);
});
speechEngine.on('start', () => {
setIsRecording(true);
options.onStart?.();
});
speechEngine.on('end', () => {
setIsRecording(false);
options.onStop?.();
});
setEngine(speechEngine);
setIsInitialized(true);
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to initialize voice recognition';
setError(errorMessage);
options.onError?.(errorMessage);
}
};
initialize();
// Cleanup
return () => {
if (engine) {
engine.cleanup();
}
};
}, []);
const startRecording = react.useCallback(async (recordingOptions) => {
if (!engine || !isInitialized) {
throw new Error('Voice recognition not initialized');
}
try {
setIsProcessing(true);
setError(null);
setResults([]);
const finalConfig = {
...config.defaultRecognitionConfig,
...recordingOptions
};
await engine.startListening(finalConfig);
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to start recording';
setError(errorMessage);
options.onError?.(errorMessage);
throw err;
}
finally {
setIsProcessing(false);
}
}, [engine, isInitialized, config.defaultRecognitionConfig, options]);
const stopRecording = react.useCallback(async () => {
if (!engine) {
throw new Error('Voice recognition not initialized');
}
try {
setIsProcessing(true);
await engine.stopListening();
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to stop recording';
setError(errorMessage);
options.onError?.(errorMessage);
throw err;
}
finally {
setIsProcessing(false);
}
}, [engine, options]);
const clearResults = react.useCallback(() => {
setResults([]);
}, []);
const clearError = react.useCallback(() => {
setError(null);
}, []);
const updateConfig = react.useCallback((newConfig) => {
setConfig(prev => ({ ...prev, ...newConfig }));
}, []);
const getConfig = react.useCallback(() => config, [config]);
return {
isInitialized,
isRecording,
isProcessing,
results,
error,
browserSupport,
startRecording,
stopRecording,
clearResults,
clearError,
updateConfig,
getConfig
};
};
const useVoiceRecorder = (options = {}) => {
const { language = 'en-US', continuous = true, interimResults = true, onResult, onError, onStart, onStop } = options;
const voiceToText = useVoiceToText({
defaultRecognitionConfig: {
language,
continuous,
interimResults
},
onResult,
onError,
onStart,
onStop
});
const startRecording = react.useCallback(async () => {
await voiceToText.startRecording({
language,
continuous,
interimResults
});
}, [voiceToText, language, continuous, interimResults]);
const stopRecording = react.useCallback(async () => {
await voiceToText.stopRecording();
}, [voiceToText]);
return {
isInitialized: voiceToText.isInitialized,
isRecording: voiceToText.isRecording,
isProcessing: voiceToText.isProcessing,
results: voiceToText.results,
error: voiceToText.error,
startRecording,
stopRecording,
clearResults: voiceToText.clearResults,
clearError: voiceToText.clearError
};
};
const useFileUpload = (options = {}) => {
const { acceptedFormats = ['audio/*'], maxFileSize = 50 * 1024 * 1024, // 50MB
onFileSelect, onConvert, onError } = options;
const [selectedFile, setSelectedFile] = react.useState(null);
const [isConverting, setIsConverting] = react.useState(false);
const [results, setResults] = react.useState([]);
const [error, setError] = react.useState(null);
const validateFile = react.useCallback((file) => {
// Validate file size
if (file.size > maxFileSize) {
return {
isValid: false,
error: `File size (${(file.size / 1024 / 1024).toFixed(2)}MB) exceeds maximum allowed size (${(maxFileSize / 1024 / 1024).toFixed(2)}MB)`
};
}
// Validate file type
const isValidType = acceptedFormats.some(format => {
if (format === 'audio/*') {
return file.type.startsWith('audio/');
}
return file.type === format;
});
if (!isValidType) {
return {
isValid: false,
error: `File type ${file.type} is not supported. Please select an audio file.`
};
}
return { isValid: true };
}, [acceptedFormats, maxFileSize]);
const selectFile = react.useCallback((file) => {
const validation = validateFile(file);
if (!validation.isValid) {
setError(validation.error || 'Unknown validation error');
onError?.(validation.error);
return;
}
setSelectedFile(file);
setError(null);
onFileSelect?.(file);
}, [validateFile, onFileSelect, onError]);
const convertFile = react.useCallback(async (language = 'en-US') => {
if (!selectedFile) {
const error = 'No file selected';
setError(error);
onError?.(error);
return;
}
setIsConverting(true);
setError(null);
try {
const result = await onConvert?.(selectedFile, language);
if (result) {
setResults(prev => [...prev, result]);
}
}
catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Failed to convert file';
setError(errorMessage);
onError?.(errorMessage);
}
finally {
setIsConverting(false);
}
}, [selectedFile, onConvert, onError]);
const clearFile = react.useCallback(() => {
setSelectedFile(null);
}, []);
const clearResults = react.useCallback(() => {
setResults([]);
}, []);
const clearError = react.useCallback(() => {
setError(null);
}, []);
return {
selectedFile,
isConverting,
results,
error,
selectFile,
convertFile,
clearFile,
clearResults,
clearError,
validateFile
};
};
const useSpeechRecognition = (options = {}) => {
const { language = 'en-US', continuous = true, interimResults = true, maxAlternatives = 1, onResult, onError, onStart, onEnd, onAudioStart, onAudioEnd, onSoundStart, onSoundEnd, onSpeechStart, onSpeechEnd, onNoMatch, onNomatch } = options;
const [isSupported, setIsSupported] = react.useState(false);
const [isListening, setIsListening] = react.useState(false);
const [transcript, setTranscript] = react.useState('');
const [finalTranscript, setFinalTranscript] = react.useState('');
const [interimTranscript, setInterimTranscript] = react.useState('');
const [error, setError] = react.useState(null);
const recognitionRef = react.useRef(null);
const finalTranscriptRef = react.useRef('');
// Check browser support and initialize
react.useEffect(() => {
const support = getBrowserSupport();
setIsSupported(support.webSpeechAPI);
if (support.webSpeechAPI) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognitionRef.current = new SpeechRecognition();
const recognition = recognitionRef.current;
// Configure recognition
recognition.continuous = continuous;
recognition.interimResults = interimResults;
recognition.lang = language;
recognition.maxAlternatives = maxAlternatives;
// Set up event listeners
recognition.onresult = (event) => {
let interimTranscript = '';
let finalTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript += transcript;
}
else {
interimTranscript += transcript;
}
}
finalTranscriptRef.current = finalTranscript;
setFinalTranscript(finalTranscript);
setInterimTranscript(interimTranscript);
setTranscript(finalTranscript + interimTranscript);
onResult?.(event);
};
recognition.onerror = (event) => {
setError(event.error);
setIsListening(false);
onError?.(event);
};
recognition.onstart = () => {
setIsListening(true);
setError(null);
onStart?.();
};
recognition.onend = () => {
setIsListening(false);
onEnd?.();
};
recognition.onaudiostart = onAudioStart;
recognition.onaudioend = onAudioEnd;
recognition.onsoundstart = onSoundStart;
recognition.onsoundend = onSoundEnd;
recognition.onspeechstart = onSpeechStart;
recognition.onspeechend = onSpeechEnd;
recognition.onnomatch = onNomatch || onNoMatch;
}
// Cleanup
return () => {
if (recognitionRef.current) {
recognitionRef.current.abort();
}
};
}, [continuous, interimResults, language, maxAlternatives, onResult, onError, onStart, onEnd, onAudioStart, onAudioEnd, onSoundStart, onSoundEnd, onSpeechStart, onSpeechEnd, onNoMatch, onNomatch]);
const start = react.useCallback(() => {
if (!isSupported || !recognitionRef.current) {
setError('Speech recognition is not supported');
return;
}
try {
recognitionRef.current.start();
}
catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start recognition');
}
}, [isSupported]);
const stop = react.useCallback(() => {
if (recognitionRef.current) {
recognitionRef.current.stop();
}
}, []);
const abort = react.useCallback(() => {
if (recognitionRef.current) {
recognitionRef.current.abort();
}
}, []);
const reset = react.useCallback(() => {
setTranscript('');
setFinalTranscript('');
setInterimTranscript('');
setError(null);
finalTranscriptRef.current = '';
}, []);
const updateConfig = react.useCallback((config) => {
if (recognitionRef.current) {
if (config.language !== undefined) {
recognitionRef.current.lang = config.language;
}
if (config.continuous !== undefined) {
recognitionRef.current.continuous = config.continuous;
}
if (config.interimResults !== undefined) {
recognitionRef.current.interimResults = config.interimResults;
}
if (config.maxAlternatives !== undefined) {
recognitionRef.current.maxAlternatives = config.maxAlternatives;
}
}
}, []);
return {
isSupported,
isListening,
transcript,
finalTranscript,
interimTranscript,
error,
start,
stop,
abort,
reset,
updateConfig
};
};
/**
* Supported audio file formats
*/
const SUPPORTED_AUDIO_FORMATS = ['.wav', '.mp3', '.ogg', '.flac', '.m4a'];
/**
* Check if a file is a supported audio format
*/
function isSupportedAudioFormat(filePath) {
const ext = getFileExtension(filePath).toLowerCase();
return SUPPORTED_AUDIO_FORMATS.includes(ext);
}
/**
* Get file extension from file path or name
*/
function getFileExtension(filePath) {
const lastDotIndex = filePath.lastIndexOf('.');
return lastDotIndex !== -1 ? filePath.substring(lastDotIndex) : '';
}
// Browser-specific entry point for React components
// This file exports React components and hooks optimized for browser use
// React Components
// Browser-specific utility functions
/**
* Check browser capabilities for speech recognition
* @returns Browser support information
*/
function checkBrowserSupport() {
return getBrowserSupport();
}
// Version information
const VERSION = '1.0.0';
// Auto-attach to window in browser environment
if (typeof window !== 'undefined') {
window.ReactVoiceToText = {
VoiceToTextConverter,
VoiceRecorder,
FileUpload,
useVoiceToText,
useVoiceRecorder,
checkBrowserSupport,
getAudioDevices,
requestMicrophonePermission
};
}
exports.EventEmitter = EventEmitter;
exports.FileUpload = FileUpload;
exports.LanguageSelector = LanguageSelector;
exports.RecordingControls = RecordingControls;
exports.ResultsDisplay = ResultsDisplay;
exports.SUPPORTED_AUDIO_FORMATS = SUPPORTED_AUDIO_FORMATS;
exports.SpeechRecognitionError = SpeechRecognitionError;
exports.VERSION = VERSION;
exports.VoiceRecorder = VoiceRecorder;
exports.VoiceToTextConverter = VoiceToTextConverter;
exports.VoiceToTextProvider = VoiceToTextProvider;
exports.checkBrowserSupport = checkBrowserSupport;
exports.default = VoiceToTextConverter;
exports.getAudioDevices = getAudioDevices;
exports.getBrowserSupport = getBrowserSupport;
exports.isSupportedAudioFormat = isSupportedAudioFormat;
exports.requestMicrophonePermission = requestMicrophonePermission;
exports.useFileUpload = useFileUpload;
exports.useSpeechRecognition = useSpeechRecognition;
exports.useVoiceRecorder = useVoiceRecorder;
exports.useVoiceToText = useVoiceToText;
exports.useVoiceToTextContext = useVoiceToTextContext;
//# sourceMappingURL=browser.js.map