Spaces:
Running
Running
| 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()">▶</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); | |
| }); | |
| } |