Spaces:
Running
Running
| document.addEventListener("DOMContentLoaded", function () { | |
| const loginLink = document.getElementById('login-link'); // Login button container | |
| const userInfoDiv = document.getElementById('user-info'); // User info container (after login) | |
| const logoutButton = document.getElementById('logout-button'); // Logout button | |
| const userNameDisplay = document.getElementById('user-name'); // Where to display the username | |
| const userButton = document.getElementById('user-button'); // User initials button | |
| const token = localStorage.getItem('authToken'); // Get token from localStorage | |
| const menuButton = document.querySelector(".menu-button"); | |
| // const sidebar = document.querySelector(".sidebar"); | |
| const overlay = document.querySelector(".overlay"); | |
| const content = document.querySelector("#content"); // Main content area | |
| // const nextChapterButton = document.getElementById("nextChapterButton"); | |
| const loadingContainer = document.getElementById('loading-container'); // Loading container | |
| const loadingBar = document.getElementById('loading-bar'); // Loading bar | |
| const loadingText = document.getElementById('loading-text'); // Loading text | |
| // Show loading bar when the page starts | |
| loadingContainer.style.display = 'flex'; | |
| // Fetch and initialize data from MongoDB | |
| fetch('https://thevera-botveradb.hf.space/get_chapter_titles') | |
| // fetch('https://thevera-botveradb.hf.space/get_chapters') | |
| .then(response => response.json()) | |
| .then(responseData => { | |
| if (responseData.success) { | |
| initializePage(responseData.data); | |
| } else { | |
| console.error('Error loading data:', responseData.message); | |
| } | |
| // Hide the loading bar after data is loaded | |
| loadingContainer.style.display = 'none'; | |
| }) | |
| .catch(error => { | |
| console.error('Error loading data:', error); | |
| // Hide the loading bar in case of error | |
| loadingContainer.style.display = 'none'; | |
| }); | |
| if (token) { | |
| // Check token validity with backend | |
| fetch('https://thevera-botveradb.hf.space/validate_token', { | |
| method: 'GET', | |
| headers: { | |
| 'Authorization': `Bearer ${token}` | |
| } | |
| }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| if (data.message === "Token is valid!") { | |
| // Token is valid, decode it to get user info | |
| const userObject = jwt_decode(token); // Decode the token to get user info | |
| const email = userObject.email; // Assuming the email is stored in the token | |
| const username = email.split('@')[0]; // Extract username (first part of email) | |
| // Update UI with user's info | |
| userNameDisplay.innerText = username; // Display first part of email as username | |
| userInfoDiv.style.display = 'block'; // Show user info section | |
| // Change user button to show user's initials | |
| const userInitials = username.charAt(0).toUpperCase(); // Get first letter of username | |
| userButton.innerHTML = `<div class="user-initial">${userInitials}</div>`; // Display initials | |
| // Show the logout button and hide the login link | |
| logoutButton.style.display = 'inline'; | |
| loginLink.style.display = 'none'; | |
| } else { | |
| // Token is invalid, clear invalid token | |
| localStorage.removeItem('authToken'); | |
| console.log('Invalid token, user not logged in.'); | |
| loginLink.style.display = 'inline'; // Show login link | |
| } | |
| }) | |
| .catch(error => { | |
| console.error('Error:', error); | |
| // Handle the error (e.g., network issues) | |
| localStorage.removeItem('authToken'); // Clear token in case of error | |
| loginLink.style.display = 'inline'; // Show login link | |
| }); | |
| } else { | |
| // No token found, show login link | |
| console.log('No token found, user not logged in.'); | |
| loginLink.style.display = 'inline'; | |
| } | |
| // Logout functionality | |
| if (logoutButton) { | |
| logoutButton.onclick = function() { | |
| localStorage.removeItem('authToken'); // Clear token | |
| window.location.reload(); // Refresh page to update UI | |
| }; | |
| } | |
| // Optional: If you want to show the dropdown when clicking on the user initials button | |
| if (userButton) { | |
| userButton.addEventListener('click', () => { | |
| const userDropdown = document.getElementById('user-dropdown'); | |
| userDropdown.style.display = userDropdown.style.display === 'block' ? 'none' : 'block'; | |
| }); | |
| } | |
| console.log("DOM loaded: Checking for nextChapterButton"); | |
| // Ensure the button exists before attaching the event listener | |
| if (nextChapterButton) { | |
| nextChapterButton.addEventListener("click", function () { | |
| console.log("Next Chapter button clicked"); | |
| loadNextChapter(); | |
| }); | |
| } else { | |
| console.error("nextChapterButton not found in the DOM."); | |
| } | |
| // Sidebar toggle for mobile and desktop | |
| menuButton.addEventListener("click", function () { | |
| if (sidebar && overlay && content) { | |
| sidebar.classList.toggle("shown"); | |
| overlay.classList.toggle("active"); | |
| // Adjust content margin when sidebar is visible | |
| if (sidebar.classList.contains("shown")) { | |
| content.style.marginLeft = "250px"; // Sidebar width | |
| } else { | |
| content.style.marginLeft = "0"; | |
| } | |
| } | |
| }); | |
| // Close sidebar on overlay click for mobile view | |
| overlay.addEventListener("click", function () { | |
| if (sidebar && overlay && content) { | |
| sidebar.classList.remove("shown"); | |
| overlay.classList.remove("active"); | |
| content.style.marginLeft = "0"; // Reset content margin | |
| } | |
| }); | |
| }); | |
| // Handle toggle for "Shastra Collection" header | |
| document.getElementById('shastraHeader').addEventListener('click', function() { | |
| const shastraList = document.getElementById('shastraList'); | |
| shastraList.classList.toggle('hidden'); // Toggle the 'hidden' class on the sidebar | |
| }); | |
| // Handle auto-click when any content inside the list is clicked | |
| // document.getElementById('shastraList').addEventListener('click', function () { | |
| // document.getElementById('shastraHeader').click(); // Simulate a click on the header | |
| // }); | |
| let chaptersArray = []; | |
| let currentChapterIndex = -1; // Initialize with -1 as default to indicate no chapter is loaded yet | |
| function initializePage(data) { | |
| const shastraList = document.getElementById('shastraList'); | |
| if (!shastraList) { | |
| console.error("Element with ID 'shastraList' not found."); | |
| return; | |
| } | |
| Object.keys(data).forEach((collection) => { | |
| const mainGroupItem = document.createElement('li'); | |
| mainGroupItem.textContent = collection; | |
| mainGroupItem.classList.add('main-group'); | |
| const itemList = document.createElement('ul'); | |
| itemList.classList.add('nested-list'); | |
| itemList.style.display = 'none'; | |
| data[collection].forEach((item) => { | |
| if (item.skandaTitle) { | |
| const skandaGroup = document.createElement('li'); | |
| skandaGroup.textContent = item.skandaTitle; | |
| skandaGroup.classList.add('skanda-group'); | |
| const skandaChapterList = document.createElement('ul'); | |
| skandaChapterList.classList.add('nested-list'); | |
| skandaChapterList.style.display = 'none'; | |
| item.chapters.forEach((chapter) => { | |
| const chapterItem = document.createElement('li'); | |
| chapterItem.textContent = chapter.chapterTitle; | |
| chapterItem.classList.add('chapter-item'); | |
| chapterItem.addEventListener('click', (event) => { | |
| event.stopPropagation(); | |
| fetchChapterData(chapter.chapterTitle).then(chapterDetails => { | |
| if (chapterDetails) { | |
| loadChapter(chapterDetails); | |
| closeSidebarOnMobile(); | |
| } | |
| }); | |
| }); | |
| skandaChapterList.appendChild(chapterItem); | |
| // Populate chaptersArray with chapter metadata (title only for now) | |
| chaptersArray.push({ chapterTitle: chapter.chapterTitle }); | |
| }); | |
| skandaGroup.addEventListener('click', (event) => { | |
| event.stopPropagation(); | |
| skandaChapterList.style.display = skandaChapterList.style.display === 'none' ? 'block' : 'none'; | |
| }); | |
| skandaGroup.appendChild(skandaChapterList); | |
| itemList.appendChild(skandaGroup); | |
| } else { | |
| const chapterItem = document.createElement('li'); | |
| chapterItem.textContent = item.chapterTitle; | |
| chapterItem.classList.add('chapter-item'); | |
| chapterItem.addEventListener('click', (event) => { | |
| event.stopPropagation(); | |
| fetchChapterData(item.chapterTitle).then(chapterDetails => { | |
| if (chapterDetails) { | |
| loadChapter(chapterDetails); | |
| closeSidebarOnMobile(); | |
| } | |
| }); | |
| }); | |
| itemList.appendChild(chapterItem); | |
| // Populate chaptersArray with chapter metadata (title only for now) | |
| chaptersArray.push({ chapterTitle: item.chapterTitle }); | |
| } | |
| }); | |
| mainGroupItem.addEventListener('click', () => { | |
| itemList.style.display = itemList.style.display === 'none' ? 'block' : 'none'; | |
| }); | |
| mainGroupItem.appendChild(itemList); | |
| shastraList.appendChild(mainGroupItem); | |
| }); | |
| } | |
| async function fetchChapterData(chapterTitle) { | |
| try { | |
| const response = await fetch(`https://thevera-botveradb.hf.space/get_chapter_details?chapterTitle=${encodeURIComponent(chapterTitle)}`); | |
| const result = await response.json(); | |
| if (response.ok && result.success) { | |
| return result.data; // Return chapter details | |
| } else { | |
| console.error("Error fetching chapter details:", result.message); | |
| return null; | |
| } | |
| } catch (error) { | |
| console.error("Error in fetchChapterData:", error); | |
| return null; | |
| } | |
| } | |
| // Function to fetch audio URL | |
| async function fetchAudioUrl(chapterTitle) { | |
| try { | |
| const response = await fetch(`https://thevera-botveradb.hf.space/get_audio_url?chapterTitle=${encodeURIComponent(chapterTitle)}`); | |
| const result = await response.json(); | |
| if (response.ok && result.success) { | |
| return result.audioUrl; | |
| } else { | |
| console.error('Error fetching audio:', result.message); | |
| return null; | |
| } | |
| } catch (error) { | |
| console.error('Error fetching audio:', error); | |
| return null; | |
| } | |
| } | |
| async function loadChapter(chapter) { | |
| console.log("Loading chapter..."); | |
| const chapterTitle = document.getElementById('chapterTitle'); | |
| const slokeContent = document.getElementById('slokeContent'); | |
| const videoPlayer = document.getElementById('videoPlayer'); | |
| const transcriptBlock = document.getElementById('transcript'); | |
| // Ensure the elements are loaded correctly | |
| if (!chapterTitle || !slokeContent || !videoPlayer || !transcriptBlock) { | |
| console.error("Missing elements in chapter display."); | |
| return; | |
| } | |
| // Reset content | |
| chapterTitle.textContent = 'Loading...'; // Temporary loading message | |
| slokeContent.innerHTML = ''; | |
| videoPlayer.src = ''; | |
| transcriptBlock.innerHTML = ''; // Clear all transcript content | |
| try { | |
| // Fetch chapter details from the backend | |
| const chapterDetails = await fetchChapterData(chapter.chapterTitle); | |
| if (!chapterDetails) { | |
| chapterTitle.textContent = 'Chapter details not found.'; | |
| return; | |
| } | |
| // Update UI with chapter details | |
| chapterTitle.textContent = chapterDetails.about || chapterDetails.chapterTitle || 'Chapter Details'; | |
| // Process and display the slokes | |
| const slokesArray = chapterDetails.slokes ? chapterDetails.slokes.split('\n') : []; | |
| slokesArray.forEach((slokeText) => { | |
| const slokeElement = document.createElement('p'); | |
| slokeElement.classList.add('sloke-text'); | |
| slokeElement.innerHTML = slokeText.replace(/श्लोक \d+:/g, '<strong>$&</strong>'); | |
| const speakIcon = createSpeakIcon(slokeText); | |
| slokeElement.appendChild(speakIcon); | |
| slokeContent.appendChild(slokeElement); | |
| }); | |
| // Update video and transcript blocks | |
| videoPlayer.src = chapterDetails.video || ''; | |
| const transcriptArray = chapterDetails.transcript ? chapterDetails.transcript.split('\n') : []; | |
| transcriptArray.forEach((transcriptText) => { | |
| const transcriptElement = document.createElement('p'); | |
| transcriptElement.classList.add('transcript-text'); | |
| transcriptElement.textContent = transcriptText; // Add transcript text | |
| transcriptBlock.appendChild(transcriptElement); | |
| }); | |
| // Show the content blocks | |
| document.querySelector('.main').classList.add('active'); | |
| document.querySelector('.sloke-block').classList.add('active'); | |
| document.querySelector('.video-block').classList.add('active'); | |
| document.querySelector('.transcript-block').classList.add('active'); | |
| // Setup play buttons | |
| setupPlayAllButton(slokesArray); | |
| setupPlayAllTranscriptButton(transcriptArray); | |
| // Auto-click to minimize the sidebar group | |
| const chapterGroup = document.querySelector(`.main-group`); | |
| if (chapterGroup) { | |
| chapterGroup.click(); // This simulates a click to collapse the group | |
| } | |
| // Update currentChapterIndex for "Next Chapter" functionality | |
| currentChapterIndex = chaptersArray.findIndex(ch => ch.chapterTitle === chapter.chapterTitle); | |
| console.log("Updated Current Index:", currentChapterIndex); | |
| document.getElementById('shastraHeader').click(); // Simulate a click on the header | |
| } catch (error) { | |
| console.error("Error loading chapter details:", error); | |
| chapterTitle.textContent = 'Failed to load chapter details. Please try again.'; | |
| } | |
| } | |
| function loadNextChapter() { | |
| console.log("Next Chapter clicked..."); | |
| if (currentChapterIndex + 1 < chaptersArray.length) { | |
| currentChapterIndex++; // Increment currentChapterIndex to move to the next chapter | |
| // Auto-click to minimize the sidebar group | |
| const chapterGroup = document.querySelector(`.main-group`); | |
| if (chapterGroup) { | |
| chapterGroup.click(); // This simulates a click to collapse the group | |
| } | |
| loadChapter(chaptersArray[currentChapterIndex]); | |
| } else { | |
| alert("This is the last chapter."); | |
| } | |
| } | |
| function closeSidebarOnMobile() { | |
| const sidebar = document.querySelector(".sidebar"); | |
| const overlay = document.querySelector(".overlay"); | |
| if (window.innerWidth < 768 && sidebar && overlay) { | |
| sidebar.classList.remove("shown"); | |
| overlay.classList.remove("active"); | |
| } | |
| } | |
| // Other functions such as createSpeakIcon, cleanSlokText, playTTS, and setupPlayAllButton remain as you provided. | |
| function createSpeakIcon(slokeText) { | |
| const speakIcon = document.createElement('i'); | |
| speakIcon.classList.add('fas', 'fa-volume-up', 'speak-icon'); | |
| speakIcon.setAttribute('title', 'Speak Slok'); | |
| speakIcon.addEventListener('click', () => { | |
| console.log(`Speaking: ${slokeText}`); | |
| playTTS(slokeText); | |
| }); | |
| return speakIcon; | |
| } | |
| function cleanSlokText(text) { | |
| // Regex to match and remove introductory (e.g., "श्लोक 1:") and ending markers (e.g., "१") | |
| return text.replace(/श्लोक \d+: /g, '').replace(/ \d+$/g, ''); | |
| } | |
| let currentSlokeIndex = 0; // Track the current sloke index | |
| const audioPlayer = document.getElementById('audioPlayer'); | |
| // Play all slokes function (replace your existing setupPlayAllButton function) | |
| function setupPlayAllButton(slokesArray) { | |
| const playAllButton = document.getElementById('playAllButton'); | |
| playAllButton.onclick = async () => { | |
| currentSlokeIndex = 0; // Reset to start | |
| playNextSloke(slokesArray); | |
| }; | |
| } | |
| // Function to play each sloke in sequence | |
| async function playNextSloke(slokesArray) { | |
| if (currentSlokeIndex >= slokesArray.length) { | |
| return; // End if all slokes have been played | |
| } | |
| // Clean and highlight the current sloke text | |
| // const slokeText = cleanSlokText(slokesArray[currentSlokeIndex]); | |
| const slokeText = slokesArray[currentSlokeIndex]; | |
| highlightCurrentSloke(currentSlokeIndex); | |
| // Generate TTS audio for the current sloke text | |
| playTTS(slokeText).then(() => { | |
| // Move to the next sloke after the current one finishes | |
| currentSlokeIndex++; | |
| playNextSloke(slokesArray); | |
| }); | |
| } | |
| // Function to highlight the current sloke and scroll it into view | |
| function highlightCurrentSloke(index) { | |
| const slokeElements = document.querySelectorAll('.sloke-text'); // Assume each sloke text has this class | |
| // Remove previous highlights | |
| slokeElements.forEach(el => el.classList.remove('highlight')); | |
| // Highlight and scroll the current sloke | |
| const currentSloke = slokeElements[index]; | |
| if (currentSloke) { | |
| currentSloke.classList.add('highlight'); | |
| currentSloke.scrollIntoView({ behavior: "smooth", block: "center" }); | |
| } | |
| } | |
| let currentTranscriptIndex = 0; // Track the current transcript index | |
| // Setup Play All for Transcript | |
| function setupPlayAllTranscriptButton(transcriptArray) { | |
| const playAllTranscriptButton = document.getElementById('playTranscriptButton'); | |
| playAllTranscriptButton.onclick = async () => { | |
| currentTranscriptIndex = 0; // Reset to start | |
| playNextTranscript(transcriptArray); | |
| }; | |
| } | |
| // Function to play each transcript in sequence | |
| async function playNextTranscript(transcriptArray) { | |
| if (currentTranscriptIndex >= transcriptArray.length) { | |
| return; // End if all transcripts have been played | |
| } | |
| // Highlight the current transcript text | |
| highlightCurrentTranscript(currentTranscriptIndex); | |
| // Generate TTS audio for the current transcript text | |
| const transcriptText = transcriptArray[currentTranscriptIndex]; | |
| playTTS(transcriptText).then(() => { | |
| // Move to the next transcript after the current one finishes | |
| currentTranscriptIndex++; | |
| playNextTranscript(transcriptArray); | |
| }); | |
| } | |
| // Function to highlight the current transcript text | |
| function highlightCurrentTranscript(index) { | |
| const allTranscripts = document.querySelectorAll('.transcript-text'); | |
| allTranscripts.forEach((transcript, i) => { | |
| if (i === index) { | |
| transcript.classList.add('highlight'); // Add highlight class | |
| } else { | |
| transcript.classList.remove('highlight'); // Remove highlight class | |
| } | |
| }); | |
| } | |
| let currentAudio; // Global variable to hold the audio object for server-generated audio | |
| let isPlaying = false; // Track play/pause state for playback | |
| let currentUtterance; // Track current SpeechSynthesisUtterance for sa-IN playback | |
| // async function playTTS(text) { | |
| // console.log('Playing TTS for:', text); | |
| // text = cleanSlokText(text); | |
| // if (!text || text.trim() === '') { | |
| // console.error('No text provided'); | |
| // return; | |
| // } | |
| // try { | |
| // const audioUrl = await getAudioUrlForSloke(text); | |
| // if (audioUrl) { | |
| // console.log('Fetched audio URL:', audioUrl); | |
| // const audioPlayer = document.getElementById('audioPlayer'); | |
| // audioPlayer.src = audioUrl; | |
| // // Clear any previous errors | |
| // audioPlayer.onerror = null; | |
| // audioPlayer.load(); | |
| // await audioPlayer.play(); | |
| // audioPlayer.onended = () => { | |
| // console.log('Playback completed'); | |
| // }; | |
| // } else { | |
| // console.warn('No audio URL found for the sloke'); | |
| // } | |
| // } catch (error) { | |
| // console.error('Error during playback:', error); | |
| // } | |
| // } | |
| async function playTTS(text) { | |
| console.log('Speaking mantra'); | |
| var cleanText = cleanSlokText(text); | |
| if (!text || text.trim() === '') { | |
| console.error('No text provided'); | |
| return; | |
| } | |
| // Try fetching the saved audio URL from Google Drive or another source | |
| const audioUrl = await getAudioUrlForSloke(text); // Function to fetch the URL of saved audio | |
| if (audioUrl) { | |
| console.log('Fetched audio URL:', audioUrl); | |
| // If we get a valid audio URL, play it | |
| return new Promise((resolve, reject) => { | |
| const audioPlayer = document.getElementById('audioPlayer'); | |
| audioPlayer.src = audioUrl; | |
| audioPlayer.onerror = null; | |
| audioPlayer.load(); | |
| audioPlayer.play(); | |
| // Resolve the promise when playback ends | |
| audioPlayer.onended = () => { | |
| isPlaying = false; | |
| URL.revokeObjectURL(audioUrl); // Clean up URL | |
| resolve(); | |
| }; | |
| // Reject the promise if there's an error | |
| audioPlayer.onerror = (event) => { | |
| console.error('Playback error:', event); | |
| reject(event); | |
| }; | |
| isPlaying = true; | |
| }); | |
| } else { | |
| // If no saved audio is available or there’s an error, fallback to server-based TTS | |
| console.warn('Saved audio not found, using server-side TTS.'); | |
| const serverUrl = "https://thevera-botveradb.hf.space/generate_sanskrit_audio"; | |
| try { | |
| const response = await fetch(serverUrl, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ cleanText }) | |
| }); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch audio from server'); | |
| } | |
| const audioBlob = await response.blob(); | |
| const audioUrl = URL.createObjectURL(audioBlob); | |
| return new Promise((resolve, reject) => { | |
| const audioPlayer = document.getElementById('audioPlayer'); | |
| audioPlayer.src = audioUrl; | |
| audioPlayer.play(); | |
| audioPlayer.onended = () => { | |
| isPlaying = false; | |
| URL.revokeObjectURL(audioUrl); // Clean up URL | |
| resolve(); | |
| }; | |
| audioPlayer.onerror = (event) => { | |
| console.error('Server-based TTS playback error:', event); | |
| reject(event); | |
| }; | |
| isPlaying = true; | |
| }); | |
| } catch (error) { | |
| console.error('Error calling server:', error); | |
| throw error; | |
| } | |
| } | |
| } | |
| // Function to fetch the audio URL for a given sloke | |
| async function getAudioUrlForSloke(text) { | |
| try { | |
| const response = await fetch(`https://thevera-botveradb.hf.space/get_audio_url?slokeText=${encodeURIComponent(text)}`); | |
| if (!response.ok) { | |
| throw new Error('Failed to fetch audio URL'); | |
| } | |
| const data = await response.json(); | |
| return data.audioUrl; // Assuming the API returns { audioUrl: 'URL' } | |
| } catch (error) { | |
| console.error('Error fetching audio URL:', error); | |
| return null; // Return null if an error occurs, so it falls back to server-side TTS | |
| } | |
| } | |
| // async function playTTS(text) { | |
| // console.log('Speaking mantra'); | |
| // text = cleanSlokText(text); | |
| // if (!text || text.trim() === '') { | |
| // console.error('No text provided'); | |
| // return; | |
| // } | |
| // const synth = window.speechSynthesis; | |
| // let voices = synth.getVoices(); | |
| // if (voices.length === 0) { | |
| // voices = await new Promise(resolve => { | |
| // const id = setInterval(() => { | |
| // voices = synth.getVoices(); | |
| // if (voices.length > 0) { | |
| // clearInterval(id); | |
| // resolve(voices); | |
| // } | |
| // }, 10); | |
| // }); | |
| // } | |
| // const sanskritVoice = voices.find(voice => voice.lang === 'sa-IN'); | |
| // if (sanskritVoice) { | |
| // synth.cancel(); // Stop any ongoing speech synthesis | |
| // return new Promise((resolve, reject) => { | |
| // try { | |
| // speakSloke(text); // Use the speakSloke function for speech synthesis | |
| // isPlaying = true; // Set playback status | |
| // resolve(); // Immediately resolve since speakSloke doesn't support events | |
| // } catch (error) { | |
| // console.error('TTS playback error:', error); | |
| // reject(error); // Reject the promise if an error occurs | |
| // } | |
| // }); | |
| // } else { | |
| // // Fallback to server-based TTS audio | |
| // console.warn('sa-IN voice not available, using server.'); | |
| // const serverUrl = "https://thevera-botveradb.hf.space/generate_sanskrit_audio"; | |
| // try { | |
| // 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); | |
| // return new Promise((resolve, reject) => { | |
| // // Use the default audio player for playback | |
| // const audioPlayer = document.getElementById('audioPlayer'); | |
| // audioPlayer.src = audioUrl; | |
| // audioPlayer.play(); | |
| // // Resolve the promise when playback ends | |
| // audioPlayer.onended = () => { | |
| // isPlaying = false; | |
| // URL.revokeObjectURL(audioUrl); // Clean up URL | |
| // resolve(); | |
| // }; | |
| // // Reject the promise if there's an error | |
| // audioPlayer.onerror = (event) => { | |
| // console.error('Server-based TTS playback error:', event); | |
| // reject(event); | |
| // }; | |
| // isPlaying = true; | |
| // }); | |
| // } catch (error) { | |
| // console.error('Error calling server:', error); | |
| // throw error; | |
| // } | |
| // } | |
| // } | |
| // The speakSloke function can now be integrated with playTTS if desired | |
| function speakSloke(text) { | |
| // Normalize and preprocess the text | |
| const normalizedText = text | |
| .normalize("NFC") | |
| .replace(/ऽ/g, " "); // Replace avagraha with a space for better pronunciation | |
| // Split the text into meaningful phrases | |
| const phrases = normalizedText.split(/[।॥]/).filter(phrase => phrase.trim().length > 0); | |
| let phraseIndex = 0; | |
| function speakNextPhrase() { | |
| if (phraseIndex >= phrases.length) return; | |
| const currentPhrase = phrases[phraseIndex].trim(); | |
| const utterance = new SpeechSynthesisUtterance(currentPhrase); | |
| // Set voice and TTS parameters for a bold, heavy tone | |
| utterance.lang = "sa-IN"; | |
| utterance.pitch = 0.8; // Lower pitch for a deeper, heavier voice | |
| utterance.rate = 0.8; // Slower rate for gravitas and clarity | |
| utterance.volume = 1.0; // Full volume for boldness | |
| // Select the male Sanskrit voice | |
| const voices = speechSynthesis.getVoices(); | |
| const sanskritVoice = voices.find(voice => voice.lang === "sa-IN" && voice.name.includes("Male")); | |
| if (sanskritVoice) { | |
| utterance.voice = sanskritVoice; | |
| } else { | |
| throw new Error('sa-IN voice is unavailable'); | |
| } | |
| // Handle completion of the current phrase | |
| utterance.onend = () => { | |
| phraseIndex++; | |
| setTimeout(speakNextPhrase, 500); // Slightly longer pause for dramatic effect | |
| }; | |
| // Handle errors gracefully | |
| utterance.onerror = (error) => { | |
| console.error(`Error speaking phrase: ${currentPhrase}`, error); | |
| phraseIndex++; | |
| setTimeout(speakNextPhrase, 500); | |
| }; | |
| console.log(`Speaking phrase with bold voice: ${currentPhrase}`); // Debugging | |
| speechSynthesis.speak(utterance); | |
| } | |
| speakNextPhrase(); // Start speaking the sloke | |
| } | |
| // Donate Model Function | |
| // Get elements | |
| const donateButton = document.getElementById('donateButton'); | |
| const donateModal = document.getElementById('donateModal'); | |
| const closeBtn = document.querySelector('.close'); | |
| const amountButtons = document.querySelectorAll('.amount-btn'); | |
| const customAmountInput = document.getElementById('customAmount'); | |
| const donateForm = document.getElementById('donateForm'); | |
| const openUpiAppButton = document.getElementById('openUpiApp'); | |
| // Donation amount logic | |
| let selectedAmount = 0; | |
| // Handle predefined amount buttons | |
| amountButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| selectedAmount = parseInt(button.dataset.amount); // Get amount from button's data attribute | |
| customAmountInput.value = ''; // Clear custom amount input | |
| amountButtons.forEach(btn => btn.classList.remove('selected')); // Deselect other buttons | |
| button.classList.add('selected'); // Highlight selected button | |
| }); | |
| }); | |
| // Handle custom amount input | |
| customAmountInput.addEventListener('input', () => { | |
| selectedAmount = parseInt(customAmountInput.value) || 0; // Set amount or default to 0 | |
| amountButtons.forEach(btn => btn.classList.remove('selected')); // Deselect predefined buttons | |
| }); | |
| // Open modal | |
| donateButton.onclick = function () { | |
| donateModal.style.display = "block"; // Show the modal | |
| } | |
| // Close modal | |
| closeBtn.onclick = function () { | |
| donateModal.style.display = "none"; // Hide the modal | |
| } | |
| // Close modal when clicking outside it | |
| window.onclick = function (event) { | |
| if (event.target === donateModal) { | |
| donateModal.style.display = "none"; | |
| } | |
| } | |
| // Submit form | |
| donateForm.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| // Get user details | |
| const name = document.getElementById('name').value.trim(); | |
| const email = document.getElementById('email').value.trim(); | |
| // Validate inputs | |
| if (!name || !email) { | |
| alert("Please enter your name and email."); | |
| return; | |
| } | |
| if (!selectedAmount || isNaN(selectedAmount) || selectedAmount <= 0) { | |
| alert("Please select or enter a valid donation amount."); | |
| return; | |
| } | |
| // Prepare donation data | |
| const donationData = { name, email, amount: selectedAmount }; | |
| try { | |
| logger.log(donationData) | |
| console.log(donationData) | |
| // Save to MongoDB via API | |
| const response = await fetch('https://thevera-botveradb.hf.space/save-donation', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(donationData), | |
| }); | |
| console.log(response) | |
| if (response.ok) { | |
| alert("Thank you for your donation!"); | |
| donateModal.style.display = "none"; | |
| } else { | |
| const errorData = await response.json(); | |
| alert(`Error: ${errorData.error || "Something went wrong. Please try again."}`); | |
| } | |
| } catch (error) { | |
| alert("Unable to process your donation. Please try again later."); | |
| console.error(error); | |
| } | |
| }); | |
| // Open UPI payment app | |
| openUpiAppButton.onclick = function () { | |
| if (!selectedAmount || isNaN(selectedAmount) || selectedAmount <= 0) { | |
| alert("Please select or enter a valid donation amount."); | |
| return; | |
| } | |
| // UPI payment link with amount | |
| const upiLink = `upi://pay?pa=sumandas24-4@okaxis&pn=Suman+Das&am=${selectedAmount}&cu=INR`; | |
| window.open(upiLink, '_blank'); | |
| }; | |
| // When the user clicks on the Donate button, open the modal | |
| // donateButton.onclick = function() { | |
| // donateModal.style.display = "block"; | |
| // } | |
| // When the user clicks on <span> (x), close the modal | |
| // closeBtn.onclick = function() { | |
| // donateModal.style.display = "none"; | |
| // } | |
| // When the user clicks outside the modal, close it | |
| // window.onclick = function(event) { | |
| // if (event.target == donateModal) { | |
| // donateModal.style.display = "none"; | |
| // } | |
| // } | |
| // Open UPI app (works for mobile only) | |
| // openUpiAppButton.onclick = function() { | |
| // const upiLink = "upi://pay?pa=sumandas24-4@okaxis&pn=Suman+Das&url=https://bhagvat-ras.static.hf.space/index.html"; // Replace with actual UPI link | |
| // window.open(upiLink, '_blank'); | |
| // }; | |