help@rskworld.in +91 93305 39277
RSK World
  • Home
  • Development
    • Web Development
    • Mobile Apps
    • Software
    • Games
    • Project
  • Technologies
    • Data Science
    • AI Development
    • Cloud Development
    • Blockchain
    • Cyber Security
    • Dev Tools
    • Testing Tools
  • Blog
  • About
  • Contact

Theme Settings

Color Scheme
Display Options
Font Size
100%
Back to Project
RSK World
voice-assistant-chatbot
RSK World
voice-assistant-chatbot
Voice Assistant Chatbot - JavaScript + Web Speech API + Speech Recognition + Text-to-Speech + Voice Commands + AI
voice-assistant-chatbot
  • .gitignore470 B
  • DATA_STORAGE.md6.2 KB
  • ERROR_FIXES.md4.1 KB
  • GITHUB_RELEASE_INSTRUCTIONS.md5.9 KB
  • LICENSE1.2 KB
  • README.md8.1 KB
  • RELEASE_NOTES.md6.5 KB
  • config.js2.3 KB
  • index.html7 KB
  • script.js41.6 KB
  • styles.css9.7 KB
script.js
script.js
Raw Download
Find: Go to:
/*
    Voice Assistant Chatbot - Main JavaScript
    Developed by: RSK World
    Website: https://rskworld.in
    Email: help@rskworld.in
    Phone: +91 93305 39277
    Year: 2026
*/

class VoiceAssistant {
    constructor() {
        this.recognition = null;
        this.synthesis = window.speechSynthesis;
        this.isListening = false;
        this.isSpeaking = false;
        this.currentUtterance = null;
        this.audioContext = null;
        this.analyser = null;
        this.microphone = null;
        this.dataArray = null;
        this.animationFrame = null;
        this.conversationHistory = [];
        this.stats = {
            totalMessages: 0,
            userMessages: 0,
            botMessages: 0,
            voiceInteractions: 0,
            sessionStartTime: Date.now()
        };
        this.settings = {
            voice: null,
            rate: 1.0,
            pitch: 1.0,
            volume: 1.0,
            language: 'en-US',
            continuousMode: false,
            autoSpeak: true,
            showVisualizer: false,
            darkMode: false
        };
        
        this.init();
    }

    init() {
        this.setupSpeechRecognition();
        this.setupEventListeners();
        this.loadSettings();
        this.populateVoices();
        this.setupAudioVisualizer();
        this.updateStats();
        this.startSessionTimer();
        
        // Listen for voice changes
        this.synthesis.onvoiceschanged = () => this.populateVoices();
    }

    setupSpeechRecognition() {
        if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
            const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
            this.recognition = new SpeechRecognition();
            this.recognition.continuous = this.settings.continuousMode;
            this.recognition.interimResults = true;
            this.recognition.lang = this.settings.language;

            this.recognition.onstart = () => {
                this.isListening = true;
                this.updateVoiceStatus('Listening...', 'listening');
                this.updateVoiceButton(true);
                this.stats.voiceInteractions++;
                this.saveStatistics();
                this.startAudioVisualization();
            };

            this.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;
                    }
                }

                if (finalTranscript) {
                    this.handleUserInput(finalTranscript.trim());
                }
            };

            this.recognition.onerror = (event) => {
                console.error('Speech recognition error:', event.error);
                if (event.error !== 'no-speech') {
                    this.updateVoiceStatus('Error: ' + event.error, 'error');
                }
                this.stopListening();
            };

            this.recognition.onend = () => {
                this.stopListening();
                if (this.settings.continuousMode && !this.isSpeaking) {
                    setTimeout(() => {
                        if (!this.isListening) {
                            this.startListening();
                        }
                    }, 500);
                }
            };
        } else {
            console.warn('Speech recognition not supported in this browser');
            document.getElementById('voiceBtn').style.display = 'none';
        }
    }

    setupEventListeners() {
        // Send button
        const sendBtn = document.getElementById('sendBtn');
        if (sendBtn) {
            sendBtn.addEventListener('click', () => {
                const input = document.getElementById('textInput');
                if (input && input.value.trim()) {
                    this.handleUserInput(input.value);
                    input.value = '';
                }
            });
        }

        // Enter key in input
        const textInput = document.getElementById('textInput');
        if (textInput) {
            textInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    const sendBtn = document.getElementById('sendBtn');
                    if (sendBtn) sendBtn.click();
                }
            });
        }

        // Voice button
        const voiceBtn = document.getElementById('voiceBtn');
        if (voiceBtn) {
            voiceBtn.addEventListener('click', () => {
                if (this.isListening) {
                    this.stopListening();
                } else {
                    this.startListening();
                }
            });
        }

        // Clear button
        const clearBtn = document.getElementById('clearBtn');
        if (clearBtn) {
            clearBtn.addEventListener('click', () => {
                this.clearChat();
            });
        }

        // Export button
        const exportBtn = document.getElementById('exportBtn');
        if (exportBtn) {
            exportBtn.addEventListener('click', () => {
                this.exportChatHistory();
            });
        }

        // Stats button
        const statsBtn = document.getElementById('statsBtn');
        if (statsBtn) {
            statsBtn.addEventListener('click', () => {
                this.toggleStats();
            });
        }

        // Dark mode button
        const darkModeBtn = document.getElementById('darkModeBtn');
        if (darkModeBtn) {
            darkModeBtn.addEventListener('click', () => {
                this.toggleDarkMode();
            });
        }

        // Settings button
        const settingsBtn = document.getElementById('settingsBtn');
        if (settingsBtn) {
            settingsBtn.addEventListener('click', () => {
                this.toggleSettings();
            });
        }

        // Close settings
        const closeSettings = document.getElementById('closeSettings');
        if (closeSettings) {
            closeSettings.addEventListener('click', () => {
                this.toggleSettings();
            });
        }

        // Close stats
        const closeStats = document.getElementById('closeStats');
        if (closeStats) {
            closeStats.addEventListener('click', () => {
                this.toggleStats();
            });
        }

        // Settings controls (with null checks)
        const voiceSelect = document.getElementById('voiceSelect');
        if (voiceSelect) {
            voiceSelect.addEventListener('change', (e) => {
                this.settings.voice = e.target.value;
                this.saveSettings();
            });
        }

        const rateSelect = document.getElementById('rateSelect');
        if (rateSelect) {
            rateSelect.addEventListener('input', (e) => {
                this.settings.rate = parseFloat(e.target.value);
                const rateValue = document.getElementById('rateValue');
                if (rateValue) rateValue.textContent = this.settings.rate.toFixed(1);
                this.saveSettings();
            });
        }

        const pitchSelect = document.getElementById('pitchSelect');
        if (pitchSelect) {
            pitchSelect.addEventListener('input', (e) => {
                this.settings.pitch = parseFloat(e.target.value);
                const pitchValue = document.getElementById('pitchValue');
                if (pitchValue) pitchValue.textContent = this.settings.pitch.toFixed(1);
                this.saveSettings();
            });
        }

        const volumeSelect = document.getElementById('volumeSelect');
        if (volumeSelect) {
            volumeSelect.addEventListener('input', (e) => {
                this.settings.volume = parseFloat(e.target.value);
                const volumeValue = document.getElementById('volumeValue');
                if (volumeValue) volumeValue.textContent = this.settings.volume.toFixed(1);
                this.saveSettings();
            });
        }

        const languageSelect = document.getElementById('languageSelect');
        if (languageSelect) {
            languageSelect.addEventListener('change', (e) => {
                this.settings.language = e.target.value;
                if (this.recognition) {
                    this.recognition.lang = this.settings.language;
                }
                this.saveSettings();
            });
        }

        const continuousMode = document.getElementById('continuousMode');
        if (continuousMode) {
            continuousMode.addEventListener('change', (e) => {
                this.settings.continuousMode = e.target.checked;
                if (this.recognition) {
                    this.recognition.continuous = this.settings.continuousMode;
                }
                this.saveSettings();
            });
        }

        const autoSpeak = document.getElementById('autoSpeak');
        if (autoSpeak) {
            autoSpeak.addEventListener('change', (e) => {
                this.settings.autoSpeak = e.target.checked;
                this.saveSettings();
            });
        }

        const showVisualizer = document.getElementById('showVisualizer');
        if (showVisualizer) {
            showVisualizer.addEventListener('change', (e) => {
                this.settings.showVisualizer = e.target.checked;
                const visualizer = document.getElementById('audioVisualizer');
                if (visualizer) {
                    if (this.settings.showVisualizer) {
                        visualizer.classList.add('active');
                    } else {
                        visualizer.classList.remove('active');
                        this.stopAudioVisualization();
                    }
                }
                this.saveSettings();
            });
        }
    }

    setupAudioVisualizer() {
        try {
            const canvas = document.getElementById('visualizerCanvas');
            if (!canvas) return;
            
            const ctx = canvas.getContext('2d');
            if (!ctx) return;
            
            canvas.width = 200;
            canvas.height = 60;

            // Initialize audio context for visualization
            if (window.AudioContext || window.webkitAudioContext) {
                this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
                this.analyser = this.audioContext.createAnalyser();
                this.analyser.fftSize = 256;
                const bufferLength = this.analyser.frequencyBinCount;
                this.dataArray = new Uint8Array(bufferLength);
            }
        } catch (error) {
            console.warn('Audio visualization not supported:', error);
            this.audioContext = null;
        }
    }

    async startAudioVisualization() {
        if (!this.settings.showVisualizer || !this.audioContext || !this.analyser) return;

        try {
            if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                console.warn('getUserMedia not supported');
                return;
            }
            
            const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
            if (this.microphone) {
                this.microphone.disconnect();
            }
            this.microphone = this.audioContext.createMediaStreamSource(stream);
            this.microphone.connect(this.analyser);
            this.visualize();
        } catch (error) {
            console.warn('Could not access microphone for visualization:', error);
            // Silently fail - visualization is optional
        }
    }

    visualize() {
        if (!this.analyser || !this.isListening || !this.dataArray) {
            this.stopAudioVisualization();
            return;
        }

        try {
            const canvas = document.getElementById('visualizerCanvas');
            if (!canvas) {
                this.stopAudioVisualization();
                return;
            }
            
            const ctx = canvas.getContext('2d');
            if (!ctx) {
                this.stopAudioVisualization();
                return;
            }
            
            this.analyser.getByteFrequencyData(this.dataArray);

            ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            const barWidth = (canvas.width / this.dataArray.length) * 2.5;
            let barHeight;
            let x = 0;

            for (let i = 0; i < this.dataArray.length; i++) {
                barHeight = (this.dataArray[i] / 255) * canvas.height;

                const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
                gradient.addColorStop(0, '#667eea');
                gradient.addColorStop(1, '#764ba2');
                ctx.fillStyle = gradient;

                ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
                x += barWidth + 1;
            }

            this.animationFrame = requestAnimationFrame(() => this.visualize());
        } catch (error) {
            console.error('Error in visualization:', error);
            this.stopAudioVisualization();
        }
    }

    stopAudioVisualization() {
        if (this.animationFrame) {
            cancelAnimationFrame(this.animationFrame);
            this.animationFrame = null;
        }
        if (this.microphone) {
            this.microphone.disconnect();
            this.microphone = null;
        }
        const canvas = document.getElementById('visualizerCanvas');
        if (canvas) {
            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
        }
    }

    startListening() {
        if (this.recognition && !this.isListening) {
            try {
                this.recognition.start();
            } catch (error) {
                console.error('Error starting recognition:', error);
            }
        }
    }

    stopListening() {
        if (this.recognition && this.isListening) {
            this.recognition.stop();
            this.isListening = false;
            this.updateVoiceStatus('', '');
            this.updateVoiceButton(false);
            this.stopAudioVisualization();
        }
    }

    updateVoiceButton(recording) {
        const btn = document.getElementById('voiceBtn');
        if (recording) {
            btn.classList.add('recording');
        } else {
            btn.classList.remove('recording');
        }
    }

    updateVoiceStatus(message, className) {
        const status = document.getElementById('voiceStatus');
        status.textContent = message;
        status.className = 'voice-status ' + className;
    }

    handleUserInput(input) {
        if (!input.trim()) return;

        // Add user message to chat
        this.addMessage(input, 'user');
        this.stats.userMessages++;
        this.stats.totalMessages++;
        this.updateStats();
        this.saveStatistics();

            // Save to conversation history
            this.conversationHistory.push({
                type: 'user',
                message: input,
                timestamp: new Date()
            });
            this.saveConversationHistory();

        // Update status
        this.updateVoiceStatus('Processing...', 'processing');

        // Process the input and generate response
        setTimeout(() => {
            const response = this.generateResponse(input);
            this.addMessage(response, 'bot');
            this.stats.botMessages++;
            this.stats.totalMessages++;
            this.updateStats();
            this.saveStatistics();

            // Save to conversation history
            this.conversationHistory.push({
                type: 'bot',
                message: response,
                timestamp: new Date()
            });
            this.saveConversationHistory();

            if (this.settings.autoSpeak) {
                this.speak(response);
            }
            this.updateVoiceStatus('', '');
        }, 500);
    }

    generateResponse(input) {
        const lowerInput = input.toLowerCase().trim();
        const context = this.getConversationContext();

        // Enhanced voice commands with context awareness
        if (lowerInput.includes('hello') || lowerInput.includes('hi') || lowerInput.includes('hey')) {
            const greetings = [
                "Hello! How can I assist you today? I can help you with various tasks using voice commands.",
                "Hi there! What can I do for you?",
                "Hey! I'm here to help. What would you like to know?",
                "Hello! I'm your voice assistant. How may I assist you?"
            ];
            return greetings[Math.floor(Math.random() * greetings.length)];
        }

        if (lowerInput.includes('time') || lowerInput.includes('what time')) {
            const now = new Date();
            return `The current time is ${now.toLocaleTimeString()}.`;
        }

        if (lowerInput.includes('date') || lowerInput.includes('what date') || lowerInput.includes('today')) {
            const now = new Date();
            return `Today's date is ${now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}.`;
        }

        if (lowerInput.includes('weather')) {
            return "I'm sorry, I don't have access to weather data at the moment. You can check weather on your preferred weather app or website.";
        }

        if (lowerInput.includes('name') || lowerInput.includes('who are you')) {
            return "I'm your Voice Assistant Chatbot, developed by RSK World. You can call me Assistant or Chatbot. I'm here to help you with various tasks!";
        }

        if (lowerInput.includes('help') || lowerInput.includes('commands') || lowerInput.includes('what can you do')) {
            return `I can help you with many things! Here are some commands you can try:
- Ask about time and date
- Tell me a joke
- Perform calculations (e.g., "calculate 5 plus 3")
- Have a conversation
- Ask for help
- Change settings using voice commands
You can also use continuous listening mode for hands-free interaction!`;
        }

        if (lowerInput.includes('thank')) {
            return "You're welcome! Is there anything else I can help you with?";
        }

        if (lowerInput.includes('bye') || lowerInput.includes('goodbye') || lowerInput.includes('see you')) {
            return "Goodbye! Have a great day! Feel free to come back anytime.";
        }

        if (lowerInput.includes('joke') || lowerInput.includes('funny')) {
            const jokes = [
                "Why don't scientists trust atoms? Because they make up everything!",
                "Why did the scarecrow win an award? He was outstanding in his field!",
                "What do you call a fake noodle? An impasta!",
                "Why don't eggs tell jokes? They'd crack each other up!",
                "What do you call a bear with no teeth? A gummy bear!",
                "Why did the math book look so sad? Because it had too many problems!",
                "What's the best thing about Switzerland? I don't know, but the flag is a big plus!",
                "Why don't skeletons fight each other? They don't have the guts!"
            ];
            return jokes[Math.floor(Math.random() * jokes.length)];
        }

        // Enhanced math calculations
        if (lowerInput.includes('calculate') || lowerInput.includes('math') || lowerInput.includes('compute')) {
            // Support for various formats
            const patterns = [
                /(\d+)\s*([+\-*/]|plus|minus|times|divided by)\s*(\d+)/i,
                /what is (\d+)\s*([+\-*/]|plus|minus|times|divided by)\s*(\d+)/i,
                /(\d+)\s*([+\-*/]|plus|minus|times|divided by)\s*(\d+)/i
            ];

            for (const pattern of patterns) {
                const match = input.match(pattern);
                if (match) {
                    const num1 = parseFloat(match[1]);
                    let operator = match[2].toLowerCase();
                    const num2 = parseFloat(match[3]);

                    // Convert word operators
                    if (operator === 'plus' || operator === '+') operator = '+';
                    else if (operator === 'minus' || operator === '-') operator = '-';
                    else if (operator === 'times' || operator === '*') operator = '*';
                    else if (operator === 'divided by' || operator === '/') operator = '/';

                    let result;
                    switch(operator) {
                        case '+': result = num1 + num2; break;
                        case '-': result = num1 - num2; break;
                        case '*': result = num1 * num2; break;
                        case '/': result = num2 !== 0 ? num1 / num2 : 'undefined';
                        default: continue;
                    }
                    return `The answer is ${result}.`;
                }
            }
            return "I can help with basic calculations. Try saying something like 'calculate 5 plus 3' or 'what is 10 times 4'.";
        }

        // Voice commands for settings
        if (lowerInput.includes('enable') || lowerInput.includes('turn on')) {
            if (lowerInput.includes('continuous') || lowerInput.includes('continuous mode')) {
                this.settings.continuousMode = true;
                if (this.recognition) this.recognition.continuous = true;
                this.saveSettings();
                document.getElementById('continuousMode').checked = true;
                return "Continuous listening mode enabled. I'll keep listening for your commands!";
            }
            if (lowerInput.includes('visualizer')) {
                this.settings.showVisualizer = true;
                document.getElementById('showVisualizer').checked = true;
                document.getElementById('audioVisualizer').classList.add('active');
                this.saveSettings();
                return "Audio visualizer enabled!";
            }
        }

        if (lowerInput.includes('disable') || lowerInput.includes('turn off')) {
            if (lowerInput.includes('continuous') || lowerInput.includes('continuous mode')) {
                this.settings.continuousMode = false;
                if (this.recognition) this.recognition.continuous = false;
                this.saveSettings();
                document.getElementById('continuousMode').checked = false;
                return "Continuous listening mode disabled.";
            }
            if (lowerInput.includes('visualizer')) {
                this.settings.showVisualizer = false;
                document.getElementById('showVisualizer').checked = false;
                document.getElementById('audioVisualizer').classList.remove('active');
                this.stopAudioVisualization();
                this.saveSettings();
                return "Audio visualizer disabled.";
            }
        }

        if (lowerInput.includes('dark mode') || lowerInput.includes('dark theme')) {
            this.toggleDarkMode();
            return "Dark mode toggled!";
        }

        if (lowerInput.includes('statistics') || lowerInput.includes('stats')) {
            this.toggleStats();
            return "Opening statistics panel.";
        }

        // Context-aware responses
        if (context.length > 0) {
            const lastUserMessage = context[context.length - 1];
            if (lastUserMessage && lastUserMessage.type === 'user') {
                if (lowerInput.includes('yes') || lowerInput.includes('yeah') || lowerInput.includes('sure')) {
                    return "Great! What would you like to do next?";
                }
                if (lowerInput.includes('no') || lowerInput.includes('nope')) {
                    return "No problem! Is there anything else I can help you with?";
                }
            }
        }

        // Default response with more variety
        const responses = [
            "That's interesting! Tell me more.",
            "I understand. How can I help you further?",
            "I see. Is there anything specific you'd like to know?",
            "Thanks for sharing that! What else can I assist you with?",
            "I'm here to help! Feel free to ask me anything.",
            "That's a good point. What would you like to explore next?",
            "I'm listening. How can I assist you?",
            "Interesting! Can you tell me more about that?"
        ];
        return responses[Math.floor(Math.random() * responses.length)];
    }

    getConversationContext() {
        // Return last 5 messages for context
        return this.conversationHistory.slice(-5);
    }

    addMessage(text, type) {
        const messagesContainer = document.getElementById('chatMessages');
        const messageDiv = document.createElement('div');
        messageDiv.className = `message ${type}-message`;

        const icon = type === 'user' ? 'fa-user' : 'fa-robot';
        const time = new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
        
        messageDiv.innerHTML = `
            <div class="message-content">
                <i class="fas ${icon}"></i>
                <div>
                    <p>${this.escapeHtml(text)}</p>
                    <div class="message-time">${time}</div>
                </div>
            </div>
        `;

        messagesContainer.appendChild(messageDiv);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    speak(text) {
        if (this.isSpeaking) {
            this.synthesis.cancel();
        }

        const utterance = new SpeechSynthesisUtterance(text);
        
        // Apply settings
        if (this.settings.voice) {
            const voices = this.synthesis.getVoices();
            utterance.voice = voices.find(v => v.name === this.settings.voice) || voices[0];
        }
        
        utterance.rate = this.settings.rate;
        utterance.pitch = this.settings.pitch;
        utterance.volume = this.settings.volume;
        utterance.lang = this.settings.language;

        utterance.onstart = () => {
            this.isSpeaking = true;
        };

        utterance.onend = () => {
            this.isSpeaking = false;
        };

        utterance.onerror = (error) => {
            console.error('Speech synthesis error:', error);
            this.isSpeaking = false;
        };

        this.currentUtterance = utterance;
        this.synthesis.speak(utterance);
    }

    populateVoices() {
        const voiceSelect = document.getElementById('voiceSelect');
        if (!voiceSelect) return;
        
        const voices = this.synthesis.getVoices();
        if (!voices || voices.length === 0) {
            // Retry if voices not loaded yet
            setTimeout(() => this.populateVoices(), 100);
            return;
        }
        
        voiceSelect.innerHTML = '';
        
        voices.forEach((voice, index) => {
            const option = document.createElement('option');
            option.value = voice.name;
            option.textContent = `${voice.name} (${voice.lang})`;
            if (voice.default) {
                option.selected = true;
                if (!this.settings.voice) {
                    this.settings.voice = voice.name;
                }
            }
            voiceSelect.appendChild(option);
        });

        // Restore saved voice if available
        if (this.settings.voice) {
            voiceSelect.value = this.settings.voice;
        }
    }

    toggleSettings() {
        const panel = document.getElementById('settingsPanel');
        if (panel) {
            panel.classList.toggle('active');
        }
    }

    toggleStats() {
        const panel = document.getElementById('statsPanel');
        if (panel) {
            this.updateStats();
            panel.classList.toggle('active');
        }
    }

    toggleDarkMode() {
        this.settings.darkMode = !this.settings.darkMode;
        document.body.classList.toggle('dark-mode', this.settings.darkMode);
        const darkBtn = document.getElementById('darkModeBtn');
        if (darkBtn) {
            if (this.settings.darkMode) {
                darkBtn.classList.add('active');
                darkBtn.innerHTML = '<i class="fas fa-sun"></i>';
            } else {
                darkBtn.classList.remove('active');
                darkBtn.innerHTML = '<i class="fas fa-moon"></i>';
            }
        }
        this.saveSettings();
    }

    updateStats() {
        const totalMessagesEl = document.getElementById('totalMessages');
        const userMessagesEl = document.getElementById('userMessages');
        const botMessagesEl = document.getElementById('botMessages');
        const voiceInteractionsEl = document.getElementById('voiceInteractions');
        const sessionDurationEl = document.getElementById('sessionDuration');
        
        if (totalMessagesEl) totalMessagesEl.textContent = this.stats.totalMessages || 0;
        if (userMessagesEl) userMessagesEl.textContent = this.stats.userMessages || 0;
        if (botMessagesEl) botMessagesEl.textContent = this.stats.botMessages || 0;
        if (voiceInteractionsEl) voiceInteractionsEl.textContent = this.stats.voiceInteractions || 0;
        
        if (sessionDurationEl) {
            const sessionStart = this.stats.sessionStartTime || Date.now();
            const duration = Math.floor((Date.now() - sessionStart) / 1000);
            const minutes = Math.floor(duration / 60);
            const seconds = duration % 60;
            sessionDurationEl.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
        }
        
        // Auto-save statistics every 10 seconds
        const sessionStart = this.stats.sessionStartTime || Date.now();
        const duration = Math.floor((Date.now() - sessionStart) / 1000);
        if (duration % 10 === 0 && duration > 0) {
            this.saveStatistics();
        }
    }

    startSessionTimer() {
        setInterval(() => {
            this.updateStats();
        }, 1000);
    }

    clearChat() {
        if (confirm('Are you sure you want to clear the chat? This will delete all messages and reset statistics.')) {
            const messagesContainer = document.getElementById('chatMessages');
            if (messagesContainer) {
                messagesContainer.innerHTML = `
                    <div class="message bot-message">
                        <div class="message-content">
                            <i class="fas fa-robot"></i>
                            <div>
                                <p>Chat cleared! How can I help you today?</p>
                                <div class="message-time">${new Date().toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })}</div>
                            </div>
                        </div>
                    </div>
                `;
            }
            this.conversationHistory = [];
            this.stats.totalMessages = 0;
            this.stats.userMessages = 0;
            this.stats.botMessages = 0;
            this.stats.voiceInteractions = 0;
            this.stats.sessionStartTime = Date.now();
            this.updateStats();
            this.saveConversationHistory();
            this.saveStatistics();
        }
    }

    exportChatHistory() {
        if (!this.conversationHistory || this.conversationHistory.length === 0) {
            alert('No chat history to export!');
            return;
        }

        try {
            let exportText = 'Voice Assistant Chatbot - Chat History\n';
            exportText += 'Exported on: ' + new Date().toLocaleString() + '\n';
            exportText += 'Developed by: RSK World (https://rskworld.in)\n';
            exportText += '='.repeat(50) + '\n\n';

            this.conversationHistory.forEach((entry, index) => {
                if (entry && entry.message) {
                    const timestamp = entry.timestamp ? new Date(entry.timestamp) : new Date();
                    const time = timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
                    const type = entry.type === 'user' ? 'You' : 'Assistant';
                    exportText += `[${time}] ${type}: ${entry.message}\n\n`;
                }
            });

            exportText += '\n' + '='.repeat(50) + '\n';
            exportText += 'Statistics:\n';
            exportText += `Total Messages: ${this.stats.totalMessages || 0}\n`;
            exportText += `Your Messages: ${this.stats.userMessages || 0}\n`;
            exportText += `Bot Responses: ${this.stats.botMessages || 0}\n`;
            exportText += `Voice Interactions: ${this.stats.voiceInteractions || 0}\n`;

            const blob = new Blob([exportText], { type: 'text/plain' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `chat-history-${new Date().toISOString().split('T')[0]}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        } catch (e) {
            console.error('Error exporting chat history:', e);
            alert('Error exporting chat history. Please try again.');
        }
    }

    loadSettings() {
        try {
            const saved = localStorage.getItem('voiceAssistantSettings');
            if (saved) {
                this.settings = { ...this.settings, ...JSON.parse(saved) };
            }
        } catch (e) {
            console.error('Error loading settings:', e);
        }

        // Load conversation history
        const savedHistory = localStorage.getItem('voiceAssistantHistory');
        if (savedHistory) {
            try {
                const parsed = JSON.parse(savedHistory);
                if (Array.isArray(parsed)) {
                    this.conversationHistory = parsed.map(item => ({
                        ...item,
                        timestamp: new Date(item.timestamp)
                    }));
                    // Restore messages to UI (only if DOM is ready)
                    if (document.getElementById('chatMessages')) {
                        this.restoreChatHistory();
                    }
                }
            } catch (e) {
                console.error('Error loading conversation history:', e);
                // Clear corrupted data
                localStorage.removeItem('voiceAssistantHistory');
            }
        }

        // Load statistics
        const savedStats = localStorage.getItem('voiceAssistantStats');
        if (savedStats) {
            try {
                const parsed = JSON.parse(savedStats);
                this.stats = { ...this.stats, ...parsed };
                // Preserve session start time if it's a new session
                if (!parsed.sessionStartTime) {
                    this.stats.sessionStartTime = Date.now();
                }
            } catch (e) {
                console.error('Error loading statistics:', e);
                // Clear corrupted data
                localStorage.removeItem('voiceAssistantStats');
            }
        }

        // Apply to UI (with null checks)
        const rateSelect = document.getElementById('rateSelect');
        const rateValue = document.getElementById('rateValue');
        const pitchSelect = document.getElementById('pitchSelect');
        const pitchValue = document.getElementById('pitchValue');
        const volumeSelect = document.getElementById('volumeSelect');
        const volumeValue = document.getElementById('volumeValue');
        const languageSelect = document.getElementById('languageSelect');
        const continuousMode = document.getElementById('continuousMode');
        const autoSpeak = document.getElementById('autoSpeak');
        const showVisualizer = document.getElementById('showVisualizer');

        if (rateSelect) rateSelect.value = this.settings.rate;
        if (rateValue) rateValue.textContent = this.settings.rate.toFixed(1);
        if (pitchSelect) pitchSelect.value = this.settings.pitch;
        if (pitchValue) pitchValue.textContent = this.settings.pitch.toFixed(1);
        if (volumeSelect) volumeSelect.value = this.settings.volume;
        if (volumeValue) volumeValue.textContent = this.settings.volume.toFixed(1);
        if (languageSelect) languageSelect.value = this.settings.language;
        if (continuousMode) continuousMode.checked = this.settings.continuousMode;
        if (autoSpeak) autoSpeak.checked = this.settings.autoSpeak;
        if (showVisualizer) showVisualizer.checked = this.settings.showVisualizer;

        // Apply dark mode
        if (this.settings.darkMode) {
            document.body.classList.add('dark-mode');
            const darkBtn = document.getElementById('darkModeBtn');
            if (darkBtn) {
                darkBtn.classList.add('active');
                darkBtn.innerHTML = '<i class="fas fa-sun"></i>';
            }
        }

        // Apply visualizer
        if (this.settings.showVisualizer) {
            const visualizer = document.getElementById('audioVisualizer');
            if (visualizer) {
                visualizer.classList.add('active');
            }
        }

        // Apply continuous mode
        if (this.recognition) {
            this.recognition.continuous = this.settings.continuousMode;
        }
    }

    saveSettings() {
        localStorage.setItem('voiceAssistantSettings', JSON.stringify(this.settings));
    }

    saveConversationHistory() {
        try {
            localStorage.setItem('voiceAssistantHistory', JSON.stringify(this.conversationHistory));
        } catch (e) {
            console.error('Error saving conversation history:', e);
            // If storage is full, clear old history
            if (e.name === 'QuotaExceededError') {
                this.conversationHistory = this.conversationHistory.slice(-50); // Keep last 50 messages
                localStorage.setItem('voiceAssistantHistory', JSON.stringify(this.conversationHistory));
            }
        }
    }

    saveStatistics() {
        try {
            localStorage.setItem('voiceAssistantStats', JSON.stringify(this.stats));
        } catch (e) {
            console.error('Error saving statistics:', e);
        }
    }

    restoreChatHistory() {
        if (!this.conversationHistory || this.conversationHistory.length === 0) return;
        
        const messagesContainer = document.getElementById('chatMessages');
        if (!messagesContainer) return;
        
        messagesContainer.innerHTML = '';
        
        this.conversationHistory.forEach(entry => {
            if (!entry || !entry.message) return;
            
            try {
                const messageDiv = document.createElement('div');
                messageDiv.className = `message ${entry.type}-message`;

                const icon = entry.type === 'user' ? 'fa-user' : 'fa-robot';
                const timestamp = entry.timestamp ? new Date(entry.timestamp) : new Date();
                const time = timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
                
                messageDiv.innerHTML = `
                    <div class="message-content">
                        <i class="fas ${icon}"></i>
                        <div>
                            <p>${this.escapeHtml(entry.message)}</p>
                            <div class="message-time">${time}</div>
                        </div>
                    </div>
                `;

                messagesContainer.appendChild(messageDiv);
            } catch (e) {
                console.error('Error restoring message:', e, entry);
            }
        });
        
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    }

    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
}

// Initialize the voice assistant when the page loads
document.addEventListener('DOMContentLoaded', () => {
    try {
        new VoiceAssistant();
    } catch (error) {
        console.error('Error initializing Voice Assistant:', error);
        // Show user-friendly error message
        const messagesContainer = document.getElementById('chatMessages');
        if (messagesContainer) {
            messagesContainer.innerHTML = `
                <div class="message bot-message">
                    <div class="message-content">
                        <i class="fas fa-robot"></i>
                        <p>Sorry, there was an error initializing the voice assistant. Please refresh the page or check the browser console for details.</p>
                    </div>
                </div>
            `;
        }
    }
});
1,077 lines•41.6 KB
javascript
🚀 Support RSK World

Subscribe to our YouTube channel for latest tutorials & updates!



Click subscribe & support our work ❤️

About RSK World

Founded by Molla Samser, with Designer & Tester Rima Khatun, RSK World is your one-stop destination for free programming resources, source code, and development tools.

Founder: Molla Samser
Designer & Tester: Rima Khatun

Development

  • Game Development
  • Web Development
  • Mobile Development
  • AI Development
  • Development Tools

Legal

  • Terms & Conditions
  • Privacy Policy
  • Disclaimer

Contact Info

Nutanhat, Mongolkote
Purba Burdwan, West Bengal
India, 713147

+91 93305 39277

hello@rskworld.in
support@rskworld.in

© 2026 RSK World. All rights reserved.

Content used for educational purposes only. View Disclaimer