ras / bot_script.js
bhagvat's picture
Update bot_script.js
55c57f2 verified
Raw
History Blame
26 kB
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js')
.then(registration => {
console.log('ServiceWorker registered: ', registration);
})
.catch(error => {
console.log('ServiceWorker registration failed: ', error);
});
});
}
// Check if the browser supports the Web Speech API
if (!('webkitSpeechRecognition' in window)) {
alert("Your browser doesn't support speech recognition. Please use Google Chrome.");
} else {
// Initialize speech recognition
const recognition = new webkitSpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-IN'; // Default language (can be 'hi-IN' for Hindi)
const micButton = document.getElementById('micButton'); // Microphone icon
const userInput = document.getElementById('userInput'); // Text input field
const chatBody = document.getElementById('chatBody'); // Chat history container
// Voice Input Button Click Event
micButton.addEventListener('click', () => {
if (micButton.classList.contains('active')) {
recognition.stop(); // Stop if already active
} else {
recognition.start(); // Start listening
}
});
// When speech recognition starts
recognition.onstart = () => {
micButton.classList.add('active'); // Change mic color to green
};
// When speech recognition ends
recognition.onend = () => {
micButton.classList.remove('active'); // Reset mic color to red
};
// When speech recognition result is detected
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
userInput.value = transcript; // Fill the input with transcribed text
sendMessage(); // Call sendMessage after getting the input
};
// Handle errors during speech recognition
recognition.onerror = (event) => {
alert('Speech recognition error: ' + event.error);
micButton.classList.remove('active'); // Reset mic color to red if error occurs
};
}
// Add an event listener to the input field to detect the Enter key
const userInput = document.getElementById('userInput');
userInput.addEventListener('keydown', function(event) {
// Check if the Enter key (key code 13) is pressed
if (event.key === 'Enter') {
event.preventDefault(); // Prevent the default behavior (new line)
sendMessage(); // Call your send message function
}
});
let isSpeakEnabled = true;
function toggleHistory() {
const historyPanel = document.querySelector('.history-panel');
const chatHistoryText = document.querySelector('.chat-history-text');
// Toggle the width of the history panel
if (historyPanel.style.width === '0px') {
historyPanel.style.width = '250px'; // Open the panel
chatHistoryText.style.display = 'block'; // Show "Chat History" text
} else {
historyPanel.style.width = '0px'; // Close the panel
chatHistoryText.style.display = 'none'; // Hide "Chat History" text
}
}
function toggleMenu() {
const menu = document.getElementById("menuOptions");
menu.style.display = menu.style.display === "block" ? "none" : "block";
}
function openSettings() {
document.getElementById("settingsModal").style.display = "block";
document.getElementById("menuOptions").style.display = "none"; // Close menu when settings open
}
function closeSettings() {
document.getElementById("settingsModal").style.display = "none";
}
function toggleSpeak() {
const speakToggle = document.getElementById("speakToggle");
isSpeakEnabled = speakToggle.checked;
console.log(isSpeakEnabled ? "Speech enabled" : "Speech disabled");
}
// Close the modal when clicking outside of it
window.onclick = function(event) {
const modal = document.getElementById("settingsModal");
if (event.target === modal) {
closeSettings();
}
}
function startNewChat() {
// Select the chat body element
const chatBody = document.getElementById('chatBody');
// Remove all chat messages
chatBody.innerHTML = ''; // This clears all existing messages
// Optionally, add the default welcome message again
const welcomeMessage = document.createElement('div');
welcomeMessage.classList.add('bot-message');
welcomeMessage.textContent = "Hello! How can I assist you today?";
chatBody.appendChild(welcomeMessage);
// Scroll to the top after resetting chat
chatBody.scrollTop = chatBody.scrollHeight;
}
function remove_special_chars(text) {
// Allow only specified characters
const allow = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 :,.?!";
// Replace <br> with a space for better readability
text = text.replace(/<br>/g, " ");
let newtxt = "";
// Iterate through the text and include only allowed characters
for (let char of text) {
if (allow.includes(char)) {
newtxt += char;
}
}
return newtxt;
}
// Helper function to speak text and execute a callback when done
function speakTextSequential(text, language, callback) {
if (!isSpeakEnabled) {
console.log('Speech is disabled.');
if (callback) callback(); // Call callback even if speech is disabled
return; // Exit if speech is disabled
}
// Clean the text of HTML tags
text = text.replace(/<\/?[^>]+(>|$)/g, "");
if ('speechSynthesis' in window) {
// Cancel any ongoing speech synthesis to avoid overlap
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(text);
// Set the language based on the input
switch (language) {
case 'hi':
utterance.lang = 'hi-IN'; // Hindi
break;
case 'en':
default:
utterance.lang = 'en-IN'; // English (India)
break;
}
// Set voice preferences to find a male voice
const voices = window.speechSynthesis.getVoices();
const maleVoice = voices.find(voice => {
return (voice.name.includes('Google') || voice.name.includes('Indian')) && voice.name.toLowerCase().includes('male');
});
if (maleVoice) {
utterance.voice = maleVoice;
} else {
// If no specific male voice found, fallback to any voice
const defaultVoice = voices.find(voice => voice.name.includes('Google') || voice.name.includes('Indian'));
if (defaultVoice) {
utterance.voice = defaultVoice;
}
}
// Set pitch, rate, and volume for a more natural sound
utterance.pitch = 1; // Adjust pitch for a male voice
utterance.rate = 1; // Normal speed
utterance.volume = 1; // Maximum volume
// Insert pauses for a more teaching-like effect
const pauseDuration = 500; // Pause duration in milliseconds
const splitText = text.split(/(?<=[.!?])\s+/); // Split text by punctuation and following spaces
const speakingOrder = splitText.map(part => part.trim()).filter(part => part); // Clean up split results and filter out empty strings
// Function to speak the segments sequentially
const speakSegment = (index) => {
if (index < speakingOrder.length) {
const segment = speakingOrder[index];
if (segment) {
utterance.text = segment; // Set the text to speak
speechSynthesis.speak(utterance);
utterance.onend = () => speakSegment(index + 1); // Speak next segment after the current one ends
} else {
speakSegment(index + 1); // Move to the next index if the segment is empty
}
} else {
if (callback) {
callback(); // Call the callback when done
}
}
};
// Start speaking the first segment
speakSegment(0);
// Handle potential speech synthesis errors
utterance.onerror = function(event) {
console.error('SpeechSynthesisUtterance error:', event.error);
if (callback) {
callback(); // Call callback even if there's an error
}
};
} else {
alert('Text-to-Speech not supported in your browser.');
if (callback) {
callback(); // Call callback if TTS is not supported
}
}
}
// Format bot response and handle categories
function formatBotResponse(rawResponse) {
// Replace newlines with actual <br> for line breaks
let rawResponseList = rawResponse.split('\n');
let category = rawResponseList.length > 0 ? rawResponseList[0] : "General"; // Use ternary for category assignment
let onlyResponse = rawResponseList.slice(1).join('\n'); // Use slice to skip the first element
let formattedResponse = onlyResponse
.replace(/\n/g, '<br>') // Replace newline with line breaks
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // Bold text between ** **
.replace(/\*(.*?)\*/g, '<em>$1</em>') // Italic text between * *
.replace(/(\d+\.\s.*?)(?=\n|$)/g, '<li>$1</li>'); // Convert numbered lists
// Wrap list items in <ul> tags
formattedResponse = formattedResponse.replace(/(<li>.*?<\/li>)+/g, '<ul>$&</ul>');
return { formattedResponse, category }; // Return an object for clarity
}
// function showProcessingOverlay() {
// document.getElementById("processingOverlay").style.display = "flex";
// }
// function hideProcessingOverlay() {
// document.getElementById("processingOverlay").style.display = "none";
// }
function showLoadingIndicator() {
const chatBody = document.getElementById('chatBody');
const loadingIndicator = document.createElement('div');
loadingIndicator.classList.add('loading-indicator');
loadingIndicator.id = 'loadingIndicator'; // Unique ID for easy removal
const spinner = document.createElement('div');
spinner.classList.add('spinner');
loadingIndicator.appendChild(spinner);
chatBody.appendChild(loadingIndicator);
}
function hideLoadingIndicator() {
const loadingIndicator = document.getElementById('loadingIndicator');
if (loadingIndicator) {
loadingIndicator.remove();
}
}
function sendMessage() {
const message = userInput.value.trim();
if (message !== "") {
// Show the processing overlay
// showProcessingOverlay();
appendMessage(message, 'user-message');
userInput.value = '';
// Show the loading indicator just below the user's message
showLoadingIndicator();
// Disable the mic button to prevent multiple submissions during processing
micButton.disabled = true;
let username = null;
let isRegisteredUser = false;
// Try to get the user info from the token stored in local storage
try {
const token = localStorage.getItem('authToken');
if (token) {
const userObject = jwt_decode(token); // Decode the JWT token to get user info
username = userObject.email; // Extract username (email) from the decoded token
isRegisteredUser = !!username; // Check if user is registered (truthy if email exists)
}
} catch (error) {
console.error("Error decoding token or no token found:", error);
}
// Prepare the request data to send to the backend
const requestData = {
user_data: message,
bot_data: null // Placeholder for the bot's response
};
// Send the user message to the backend for processing
fetch('https://thevera-backendvera.hf.space/generate_result', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message })
})
.then(response => {
if (!response.ok) {
throw new Error("Failed to generate result");
}
return response.json();
})
.then(data => {
hideLoadingIndicator();
let botResponses = data.response;
const language = data.language || 'en';
const { formattedResponse: botResponse, category } = formatBotResponse(botResponses); // Destructure correctly
appendMessage(category, 'bot-message'); // Show the category
// Regular expression to match all mantras or sloks within [[ ]]
const mantraRegex = /\[\[(.*?)\]\]/g;
let lastIndex = 0; // Keep track of the last index processed
let blocks = []; // Array to hold the sections of text and mantras
let match;
// Loop over all mantra matches and build blocks array
while ((match = mantraRegex.exec(botResponse)) !== null) {
const mantraText = match[1].trim(); // Extract the mantra/slok content
const mantraStartIndex = match.index; // Start of mantra/slok
// Extract the text before this mantra and add it to blocks
const beforeMantraText = botResponse.substring(lastIndex, mantraStartIndex).trim();
if (beforeMantraText) {
blocks.push({ type: 'text', content: beforeMantraText });
}
// Add the mantra/slok to the blocks
blocks.push({ type: 'mantra', content: mantraText });
// Update the last index to move past the current mantra/slok
lastIndex = mantraRegex.lastIndex;
}
// Add any remaining text after the last mantra/slok
const afterLastMantraText = botResponse.substring(lastIndex).trim();
if (afterLastMantraText) {
blocks.push({ type: 'text', content: afterLastMantraText });
}
// Process the blocks sequentially
processSequentially(blocks, language);
// Store the conversation in MongoDB
requestData.bot_data = botResponse; // Add the bot's response before saving
// Determine backend URL for storing the chat
const baseURL = 'https://thevera-botveradb.hf.space';
const storeChatURL = isRegisteredUser
? `${baseURL}/store_registered_chat/${username}`
: `${baseURL}/store_anonymous_chat`;
// Store chat data
return fetch(storeChatURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
});
})
.then(response => {
if (!response.ok) {
throw new Error("Failed to store chat data");
}
return response.json();
})
.then(data => {
console.log(data.message); // Success message for storing chat
micButton.disabled = false; // Re-enable mic button after response
})
.catch(error => {
alert('Failed to connect to the backend. Please try again later.');
console.error('Error:', error);
micButton.disabled = false; // Re-enable mic button after error
hideLoadingIndicator();
});
}
}
// function sendMessage() {
// const message = userInput.value.trim();
// if (message !== "") {
// appendMessage(message, 'user-message');
// userInput.value = '';
// // Show the loading indicator just below the user's message
// showLoadingIndicator();
// // Disable the mic button to prevent multiple submissions during processing
// micButton.disabled = true;
// let username = null;
// let isRegisteredUser = false;
// // Try to get the user info from the token stored in local storage
// try {
// const token = localStorage.getItem('authToken');
// if (token) {
// const userObject = jwt_decode(token); // Decode the JWT token to get user info
// username = userObject.email; // Extract username (email) from the decoded token
// isRegisteredUser = !!username; // Check if user is registered (truthy if email exists)
// }
// } catch (error) {
// console.error("Error decoding token or no token found:", error);
// }
// // Prepare the request data to send to the backend
// const requestData = {
// user_data: message,
// bot_data: null // Placeholder for the bot's response
// };
// // Send the user message to the backend for processing
// fetch('https://thevera-backendvera.hf.space/generate_result', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({ message: message })
// })
// .then(response => {
// if (!response.ok) {
// throw new Error("Failed to generate result");
// }
// return response.json();
// })
// .then(data => {
// hideLoadingIndicator(); // Hide spinner after response
// const botResponses = data.response;
// const language = data.language || 'en';
// const { formattedResponse: botResponse, category } = formatBotResponse(botResponses); // Destructure correctly
// appendMessage(category, 'bot-message'); // Show the category
// // Process bot response with mantras/sloks
// const mantraRegex = /\[\[(.*?)\]\]/g;
// let lastIndex = 0;
// let blocks = [];
// let match;
// while ((match = mantraRegex.exec(botResponse)) !== null) {
// const mantraText = match[1].trim();
// const mantraStartIndex = match.index;
// const beforeMantraText = botResponse.substring(lastIndex, mantraStartIndex).trim();
// if (beforeMantraText) {
// blocks.push({ type: 'text', content: beforeMantraText });
// }
// blocks.push({ type: 'mantra', content: mantraText });
// lastIndex = mantraRegex.lastIndex;
// }
// const afterLastMantraText = botResponse.substring(lastIndex).trim();
// if (afterLastMantraText) {
// blocks.push({ type: 'text', content: afterLastMantraText });
// }
// processSequentially(blocks, language);
// // Store conversation in MongoDB
// requestData.bot_data = botResponse;
// const baseURL = 'https://thevera-botveradb.hf.space';
// const storeChatURL = isRegisteredUser
// ? `${baseURL}/store_registered_chat/${username}`
// : `${baseURL}/store_anonymous_chat`;
// return fetch(storeChatURL, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify(requestData)
// });
// })
// .then(response => {
// if (!response.ok) {
// throw new Error("Failed to store chat data");
// }
// return response.json();
// })
// .then(data => {
// console.log(data.message); // Success message for storing chat
// })
// .catch(error => {
// console.error('Error:', error);
// alert('Failed to connect to the backend. Please try again later.');
// })
// .finally(() => {
// micButton.disabled = false; // Re-enable mic button in all cases
// hideLoadingIndicator(); // Ensure spinner is hidden even on error
// });
// }
// }
// Manta Block Function
// Recursive function to process each block sequentially
function processSequentially(blocks, language, index = 0) {
if (index >= blocks.length) {
return; // Exit if all blocks are processed
}
const block = blocks[index];
if (block.type === 'text') {
// Show and speak the text block
appendMessage(block.content, 'bot-message');
speakTextSequential(block.content, language, () => {
// Move to the next block once speaking is done
processSequentially(blocks, language, index + 1);
});
} else if (block.type === 'mantra') {
// Show and speak the mantra block
displayMantra(block.content);
speakMantraSequential(block.content, 1, () => {
// Move to the next block once the mantra is spoken
processSequentially(blocks, language, index + 1);
});
}
}
// Function to display the mantra block
function displayMantra(mantraText) {
const mantraBlock = document.createElement('div');
mantraBlock.className = 'mantra-block'; // Add class for styling
mantraBlock.innerHTML = `<pre id="mantraText">${mantraText}</pre>
<label for="repeat-count">Repeat mantra:</label>
<div id="repeat-control">
<button onclick="decreaseCount()">-</button>
<input type="number" id="repeat-count" value="1" min="1" max="100" />
<button onclick="increaseCount()">+</button>
</div>
<div class="play-mantra-button-container">
<button class="play-mantra-button" onclick="repeatMantra()">&#9654;</button> <!-- Play button icon -->
</div>`;
document.getElementById('chatBody').appendChild(mantraBlock); // Append to chat body
}
// Function to hide the mantra block when there are no mantras
function hideMantraBlock() {
let mantraBlock = document.getElementById('mantra-block');
mantraBlock.style.display = 'none';
}
// Function to decrease the repeat count
function decreaseCount() {
let repeatCountInput = document.getElementById('repeat-count');
let currentValue = parseInt(repeatCountInput.value);
if (currentValue > 1) {
repeatCountInput.value = currentValue - 1;
}
}
// Function to increase the repeat count
function increaseCount() {
let repeatCountInput = document.getElementById('repeat-count');
let currentValue = parseInt(repeatCountInput.value);
repeatCountInput.value = currentValue + 1;
}
// Function to speak the mantra with repeat functionality
async function speakMantraSequential(mantraText, repeatCount, callback) {
const serverUrl = "https://thevera-botveradb.hf.space/generate_sanskrit_audio";
// Helper function to play audio from the server
const playServerAudio = async (text, repeats) => {
try {
for (let i = 0; i < repeats; i++) {
const response = await fetch(serverUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
if (!response.ok) {
throw new Error("Failed to fetch audio from server.");
}
const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
await new Promise((resolve) => {
audio.onended = resolve;
audio.onerror = (error) => {
console.error("Audio playback error:", error);
resolve(); // Resolve even on error to continue
};
audio.play();
});
}
if (callback) callback(); // Callback after all repetitions are played
} catch (error) {
console.error("Error during server audio generation:", error);
if (callback) callback(); // Call callback even on error
}
};
// Check for sa-IN support in speech synthesis
const voices = speechSynthesis.getVoices();
const saInVoice = voices.find((voice) => voice.lang === "sa-IN");
if (saInVoice) {
// Browser supports Sanskrit, use SpeechSynthesis
const utterance = new SpeechSynthesisUtterance(mantraText);
utterance.voice = saInVoice;
utterance.lang = "sa-IN"; // Sanskrit
utterance.rate = 0.8; // Slow speech for mantras
utterance.pitch = 1.0; // Natural pitch
utterance.onend = function () {
if (repeatCount > 1) {
repeatCount--;
speechSynthesis.speak(utterance); // Repeat if more repetitions are needed
} else if (callback) {
callback(); // Callback when done
}
};
utterance.onerror = function (event) {
console.error("SpeechSynthesisUtterance error:", event.error);
playServerAudio(mantraText, repeatCount); // Fall back to server on error
};
window.speechSynthesis.cancel(); // Cancel any ongoing speech
speechSynthesis.speak(utterance);
} else {
// Fall back to server-side audio generation
playServerAudio(mantraText, repeatCount);
}
}
// Function to handle mantra repetition based on user input
function repeatMantra() {
let mantraText = document.getElementById('mantraText').textContent;
let repeatCount = parseInt(document.getElementById('repeat-count').value);
if (repeatCount < 1) {
alert("Repeat count must be at least 1");
return;
}
// Speak the mantra the number of times entered by the user
speakMantraSequential(mantraText, repeatCount, 1);
}
// Mantra Black Function extends
let isFirstMessage = true; // Flag to track if it's the first message
// Function to speak the mantra
function appendMessage(text, className) {
// Remove the default message when the user sends the first message
if (isFirstMessage) {
const welcomeMessage = document.querySelector('.welcome-message');
if (welcomeMessage) {
welcomeMessage.remove();
}
isFirstMessage = false;
}
const messageElement = document.createElement('div');
messageElement.classList.add('user-message', className);
// Use innerHTML to allow formatted content (e.g., bold, italic, line breaks)
messageElement.innerHTML = text;
chatBody.appendChild(messageElement);
chatBody.scrollTop = chatBody.scrollHeight; // Auto scroll to the latest message
}
// Show donate modal
function showDonateModal() {
document.getElementById('donateModal').style.display = 'block';
}
// Close donate modal
function closeDonateModal() {
document.getElementById('donateModal').style.display = 'none';
}
// Copy UPI ID to clipboard
function copyUpiId() {
const upiId = document.getElementById("upiId").innerText;
navigator.clipboard.writeText(upiId).then(() => {
alert("UPI ID copied to clipboard");
}).catch(err => {
console.error('Could not copy text: ', err);
});
}