UNPKG

@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,138 lines â€ĸ 50.4 kB
'use strict';

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
 */
var SpeechRecognitionErrorType;
(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";
})(SpeechRecognitionErrorType || (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(SpeechRecognitionErrorType.INVALID_CONFIG, 'Confidence threshold must be between 0 and 1');
        }
        if (config.maxAlternatives && config.maxAlternatives < 1) {
            throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Max alternatives must be at least 1');
        }
        if (config.sampleRate && config.sampleRate < 8000) {
            throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Sample rate must be at least 8000 Hz');
        }
    }
    /**
     * Validate audio input configuration
     */
    validateAudioConfig(audioConfig) {
        if (!audioConfig.source) {
            throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'Audio source must be specified');
        }
        if (audioConfig.source === 'file' && !audioConfig.filePath) {
            throw new SpeechRecognitionError(SpeechRecognitionErrorType.INVALID_CONFIG, 'File path must be specified for file source');
        }
        if (audioConfig.source === 'stream' && !audioConfig.audioStream) {
            throw new SpeechRecognitionError(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(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(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(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(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 = SpeechRecognitionErrorType.NO_SPEECH;
                    break;
                case 'aborted':
                    errorType = SpeechRecognitionErrorType.ABORTED;
                    break;
                case 'audio-capture':
                    errorType = SpeechRecognitionErrorType.AUDIO_CAPTURE;
                    break;
                case 'network':
                    errorType = SpeechRecognitionErrorType.NETWORK;
                    break;
                case 'not-allowed':
                    errorType = SpeechRecognitionErrorType.NOT_ALLOWED;
                    break;
                case 'service-not-allowed':
                    errorType = SpeechRecognitionErrorType.SERVICE_NOT_ALLOWED;
                    break;
                case 'bad-grammar':
                    errorType = SpeechRecognitionErrorType.BAD_GRAMMAR;
                    break;
                case 'language-not-supported':
                    errorType = SpeechRecognitionErrorType.LANGUAGE_NOT_SUPPORTED;
                    break;
                default:
                    errorType = 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(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 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 }))] })] }) }));
};

exports.FileUpload = FileUpload;
exports.LanguageSelector = LanguageSelector;
exports.RecordingControls = RecordingControls;
exports.ResultsDisplay = ResultsDisplay;
exports.VoiceRecorder = VoiceRecorder;
exports.VoiceToTextConverter = VoiceToTextConverter;
exports.VoiceToTextProvider = VoiceToTextProvider;
exports.useVoiceToTextContext = useVoiceToTextContext;
//# sourceMappingURL=components.js.map