| document.addEventListener('DOMContentLoaded', () => { |
|
|
| const API_URL = ''; |
|
|
| |
| const nasaSearchInput = document.getElementById('nasaSearchInput'); |
| const nasaSearchBtn = document.getElementById('nasaSearchBtn'); |
| const searchResultsList = document.getElementById('search-results-list'); |
| const trainChatbotBtn = document.getElementById('trainChatbotBtn'); |
| const chatHistory = document.getElementById('chat-history'); |
| const chatInput = document.getElementById('chat-input'); |
| const chatSendBtn = document.getElementById('chat-send'); |
|
|
| |
| let currentSessionId = null; |
|
|
| |
| function addChatMessage(sender, message, sources = []) { |
| const messageElement = document.createElement('div'); |
| messageElement.classList.add('chat-message', sender.toLowerCase()); |
| messageElement.style.marginBottom = '12px'; |
|
|
| |
| const formattedMessage = message.replace(/\n/g, '<br>'); |
|
|
| let sourcesHTML = ''; |
| if (sources.length > 0) { |
| sourcesHTML = '<div class="sources" style="margin-top: 10px; font-size: 0.8rem; opacity: 0.7;"><strong>Sources:</strong><ul>'; |
| sources.forEach(source => { |
| sourcesHTML += `<li style="margin-left: 20px;">${source}</li>`; |
| }); |
| sourcesHTML += '</ul></div>'; |
| } |
|
|
| messageElement.innerHTML = ` |
| <strong style="display: block; margin-bottom: 5px;">${sender}:</strong> |
| <span>${formattedMessage}</span> |
| ${sourcesHTML} |
| `; |
|
|
| chatHistory.appendChild(messageElement); |
| chatHistory.scrollTop = chatHistory.scrollHeight; |
| } |
|
|
| |
|
|
| |
| nasaSearchBtn.addEventListener('click', async () => { |
| const keyword = nasaSearchInput.value.trim(); |
| if (!keyword) { |
| alert('Please enter a search keyword.'); |
| return; |
| } |
|
|
| searchResultsList.innerHTML = '<p>Searching...</p>'; |
| trainChatbotBtn.style.display = 'none'; |
| nasaSearchBtn.disabled = true; |
| nasaSearchBtn.textContent = 'Searching...'; |
|
|
| try { |
| const response = await fetch(`${API_URL}/api/search`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ keyword }), |
| }); |
|
|
| if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); |
| const results = await response.json(); |
| displaySearchResults(results); |
| } catch (error) { |
| console.error('Search failed:', error); |
| searchResultsList.innerHTML = `<p style="color: #ff5e00;">Search failed. Please check the console or try again.</p>`; |
| } finally { |
| nasaSearchBtn.disabled = false; |
| nasaSearchBtn.textContent = 'Search'; |
| } |
| }); |
|
|
| |
| function displaySearchResults(results) { |
| searchResultsList.innerHTML = ''; |
| if (results.length === 0) { |
| searchResultsList.innerHTML = '<p>No papers with downloadable PDFs found for this keyword.</p>'; |
| return; |
| } |
|
|
| results.forEach(paper => { |
| const paperElement = document.createElement('div'); |
| paperElement.classList.add('paper-item'); |
| paperElement.style.marginBottom = '15px'; |
| paperElement.innerHTML = ` |
| <label style="display: flex; align-items: flex-start; cursor: pointer;"> |
| <input type="checkbox" class="paper-checkbox" data-pdf-url="${paper.pdfLink}" data-paper-id="${paper.id}" style="margin-top: 5px; margin-right: 10px;"> |
| <div> |
| <strong style="color: #fff;">${paper.title}</strong> |
| <p style="font-size: 0.9rem; opacity: 0.7; margin-top: 5px;">${paper.abstract ? paper.abstract.substring(0, 150) + '...' : 'No abstract available.'}</p> |
| </div> |
| </label> |
| `; |
| searchResultsList.appendChild(paperElement); |
| }); |
|
|
| trainChatbotBtn.style.display = 'block'; |
| } |
|
|
| |
| function pollTrainingStatus(jobId) { |
| const interval = setInterval(async () => { |
| try { |
| const response = await fetch(`${API_URL}/api/train/status/${jobId}`); |
| |
| |
| if (response.status === 404) { |
| clearInterval(interval); |
| throw new Error("Training process was interrupted on the server. Please try again."); |
| } |
|
|
| const result = await response.json(); |
|
|
| if (result.status === 'processing' || result.status === 'starting') { |
| addChatMessage('System', result.message); |
| } else if (result.status === 'complete') { |
| clearInterval(interval); |
| currentSessionId = result.sessionId; |
| addChatMessage('Bot', result.message + " You can now ask me questions about them."); |
| trainChatbotBtn.disabled = false; |
| trainChatbotBtn.textContent = 'Train Chatbot on Selected Papers'; |
| } else if (result.status === 'error') { |
| clearInterval(interval); |
| throw new Error(result.message || 'Training job failed on the server.'); |
| } |
| } catch (error) { |
| clearInterval(interval); |
| console.error('Polling failed:', error); |
| addChatMessage('System', `Error during training: ${error.message}`); |
| trainChatbotBtn.disabled = false; |
| trainChatbotBtn.textContent = 'Train Chatbot on Selected Papers'; |
| } |
| }, 3000); |
| } |
|
|
| |
| function showSelectedPapers(papers) { |
| const searchPanel = document.getElementById('search-panel'); |
| const selectedPapersPanel = document.getElementById('selected-papers-panel'); |
| const selectedPapersList = document.getElementById('selected-papers-list'); |
|
|
| selectedPapersList.innerHTML = ''; |
| papers.forEach(paper => { |
| const paperElement = document.createElement('div'); |
| paperElement.innerHTML = ` |
| <a href="${paper.url}" target="_blank" rel="noopener noreferrer" class="selected-paper-link" style="display: block; padding: 10px; border-radius: 5px; text-decoration: none; color: #fff; background: rgba(255,255,255,0.05); margin-bottom: 10px; transition: background 0.3s;"> |
| ${paper.title} |
| </a> |
| `; |
| selectedPapersList.appendChild(paperElement); |
| }); |
|
|
| searchPanel.style.display = 'none'; |
| selectedPapersPanel.style.display = 'block'; |
| } |
|
|
| |
| trainChatbotBtn.addEventListener('click', async () => { |
| const selectedCheckboxes = document.querySelectorAll('.paper-checkbox:checked'); |
| const papers = Array.from(selectedCheckboxes).map(cb => ({ |
| id: cb.dataset.paperId, |
| url: cb.dataset.pdfUrl, |
| title: cb.closest('.paper-item').querySelector('strong').textContent, |
| })); |
|
|
| if (papers.length === 0) { |
| alert('Please select at least one paper to train the chatbot.'); |
| return; |
| } |
|
|
| addChatMessage('System', `Training chatbot on ${papers.length} paper(s)... This may take a moment.`); |
| trainChatbotBtn.disabled = true; |
| trainChatbotBtn.textContent = 'Training...'; |
|
|
| |
| showSelectedPapers(papers); |
|
|
| |
| document.getElementById('main-app-container').classList.add('knowledge-map-active'); |
|
|
| try { |
| const response = await fetch(`${API_URL}/api/train`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ papers }), |
| }); |
|
|
| const result = await response.json(); |
| if (response.status === 202) { |
| addChatMessage('System', result.message); |
| pollTrainingStatus(result.jobId); |
| } else { |
| throw new Error(result.error || 'Failed to start training job.'); |
| } |
| } catch (error) { |
| console.error('Training failed:', error); |
| addChatMessage('System', `Error during training: ${error.message}`); |
| } |
| }); |
|
|
| |
| async function handleSendMessage() { |
| const query = chatInput.value.trim(); |
| if (!query) return; |
|
|
| if (!currentSessionId) { |
| addChatMessage('System', 'Error: No active training session. Please train the chatbot first.'); |
| return; |
| } |
|
|
| addChatMessage('You', query); |
| chatInput.value = ''; |
| chatSendBtn.disabled = true; |
|
|
| try { |
| const response = await fetch(`${API_URL}/api/ask`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ query, sessionId: currentSessionId }), |
| }); |
|
|
| const result = await response.json(); |
| if (!response.ok) throw new Error(result.error || 'Failed to get an answer.'); |
|
|
| addChatMessage('Bot', result.answer, result.sources); |
| } catch (error) { |
| console.error('Ask failed:', error); |
| addChatMessage('System', `Error: ${error.message}`); |
| } finally { |
| chatSendBtn.disabled = false; |
| } |
| } |
|
|
| chatSendBtn.addEventListener('click', handleSendMessage); |
| chatInput.addEventListener('keypress', (e) => { |
| if (e.key === 'Enter') { |
| handleSendMessage(); |
| } |
| }); |
|
|
| |
| |
|
|
| |
| function createParticles() { |
| const particlesContainer = document.getElementById('particles'); |
| const particleCount = 30; |
|
|
| for (let i = 0; i < particleCount; i++) { |
| const particle = document.createElement('div'); |
| particle.className = 'particle'; |
| particle.style.left = Math.random() * 100 + '%'; |
| particle.style.animationDelay = Math.random() * 15 + 's'; |
| particle.style.animationDuration = (Math.random() * 10 + 15) + 's'; |
| |
| |
| if (Math.random() > 0.5) { |
| particle.style.setProperty('--particle-color', '#00B2FF'); |
| const before = particle.style.getPropertyValue('--particle-color'); |
| particle.style.background = '#00B2FF'; |
| } |
| |
| particlesContainer.appendChild(particle); |
| } |
| } |
|
|
| |
| const menuToggle = document.getElementById('menuToggle'); |
| const navLinks = document.getElementById('navLinks'); |
|
|
| menuToggle.addEventListener('click', () => { |
| menuToggle.classList.toggle('active'); |
| navLinks.classList.toggle('active'); |
| }); |
|
|
| |
| document.querySelectorAll('.nav-links a').forEach(link => { |
| link.addEventListener('click', () => { |
| menuToggle.classList.remove('active'); |
| navLinks.classList.remove('active'); |
| }); |
| }); |
|
|
| |
| const sections = document.querySelectorAll('section'); |
| const navItems = document.querySelectorAll('.nav-link'); |
|
|
| function updateActiveNav() { |
| const scrollPosition = window.pageYOffset + 100; |
|
|
| sections.forEach((section, index) => { |
| const sectionTop = section.offsetTop; |
| const sectionHeight = section.offsetHeight; |
|
|
| if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) { |
| navItems.forEach(item => item.classList.remove('active')); |
| const currentNav = document.querySelector(`.nav-link[href="#${section.id}"]`); |
| if (currentNav) currentNav.classList.add('active'); |
| } |
| }); |
| } |
|
|
| |
| window.addEventListener('scroll', function() { |
| const navbar = document.getElementById('navbar'); |
| if (window.scrollY > 50) { |
| navbar.classList.add('scrolled'); |
| } else { |
| navbar.classList.remove('scrolled'); |
| } |
| updateActiveNav(); |
| }); |
|
|
| |
| updateActiveNav(); |
|
|
| |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { |
| anchor.addEventListener('click', function (e) { |
| e.preventDefault(); |
| const target = document.querySelector(this.getAttribute('href')); |
| if (target) { |
| target.scrollIntoView({ |
| behavior: 'smooth', |
| block: 'start' |
| }); |
| } |
| }); |
| }); |
|
|
|
|
| |
| const tabs = document.querySelectorAll('.tab-item'); |
| const panels = document.querySelectorAll('.content-panel'); |
|
|
| tabs.forEach(tab => { |
| tab.addEventListener('click', () => { |
| const tabId = tab.getAttribute('data-tab'); |
| |
| tabs.forEach(t => t.classList.remove('active')); |
| panels.forEach(p => p.classList.remove('active')); |
| |
| tab.classList.add('active'); |
| document.getElementById(tabId).classList.add('active'); |
| }); |
| }); |
|
|
| |
| document.getElementById('contactForm').addEventListener('submit', function(e) { |
| e.preventDefault(); |
| |
| alert('Message sent! We\'ll get back to you soon.'); |
| this.reset(); |
| }); |
|
|
| |
| createParticles(); |
|
|
| |
| const textSets = document.querySelectorAll('.text-set'); |
| let currentIndex = 0; |
| let isAnimating = false; |
|
|
| function wrapTextInSpans(element) { |
| const text = element.textContent; |
| element.innerHTML = text.split('').map((char, i) => |
| `<span class="char" style="animation-delay: ${i * 0.05}s">${char === ' ' ? ' ' : char}</span>` |
| ).join(''); |
| } |
|
|
| function animateTextIn(textSet) { |
| const glitchText = textSet.querySelector('.glitch-text'); |
| const subtitle = textSet.querySelector('.subtitle'); |
| |
| |
| wrapTextInSpans(glitchText); |
| |
| |
| glitchText.setAttribute('data-text', glitchText.textContent); |
| |
| |
| setTimeout(() => { |
| subtitle.classList.add('visible'); |
| }, 800); |
| } |
|
|
| function animateTextOut(textSet) { |
| const chars = textSet.querySelectorAll('.char'); |
| const subtitle = textSet.querySelector('.subtitle'); |
| |
| |
| chars.forEach((char, i) => { |
| char.style.animationDelay = `${i * 0.02}s`; |
| char.classList.add('out'); |
| }); |
| |
| |
| subtitle.classList.remove('visible'); |
| } |
|
|
| function rotateText() { |
| if (isAnimating) return; |
| isAnimating = true; |
|
|
| const currentSet = textSets[currentIndex]; |
| const nextIndex = (currentIndex + 1) % textSets.length; |
| const nextSet = textSets[nextIndex]; |
|
|
| |
| animateTextOut(currentSet); |
|
|
| |
| setTimeout(() => { |
| currentSet.classList.remove('active'); |
| nextSet.classList.add('active'); |
| animateTextIn(nextSet); |
| |
| currentIndex = nextIndex; |
| isAnimating = false; |
| }, 600); |
| } |
|
|
| |
| textSets[0].classList.add('active'); |
| animateTextIn(textSets[0]); |
|
|
| |
| setTimeout(() => { |
| setInterval(rotateText, 5000); |
| }, 4000); |
|
|
| |
| setInterval(() => { |
| const glitchTexts = document.querySelectorAll('.glitch-text'); |
| glitchTexts.forEach(text => { |
| if (Math.random() > 0.95) { |
| text.style.animation = 'none'; |
| setTimeout(() => { |
| text.style.animation = ''; |
| }, 200); |
| } |
| }); |
| }, 3000); |
| }); |