Digambar29's picture
Initial clean commit with corrected .gitignore
4c86652
document.addEventListener('DOMContentLoaded', () => {
const API_URL = ''; // Use relative path for API calls
// --- Application Elements ---
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');
// --- Application State ---
let currentSessionId = null;
// --- Helper Functions ---
function addChatMessage(sender, message, sources = []) {
const messageElement = document.createElement('div');
messageElement.classList.add('chat-message', sender.toLowerCase());
messageElement.style.marginBottom = '12px';
// Use innerText for safety, but replace newlines with <br> for rendering
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;
}
// --- Event Listeners ---
// 1. Search NASA Papers
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';
}
});
// 2. Display Search Results
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';
}
// 3a. Poll for training status
function pollTrainingStatus(jobId) {
const interval = setInterval(async () => {
try {
const response = await fetch(`${API_URL}/api/train/status/${jobId}`);
// Gracefully handle server restarts where the job is lost
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); // Check every 3 seconds
}
// 3b. Display selected papers and hide search results
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 = ''; // Clear previous list
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';
}
// 3. Train Chatbot
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...';
// Show the selected papers list and hide the search results
showSelectedPapers(papers);
// Activate the three-column layout for the knowledge map
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) { // 202 Accepted
addChatMessage('System', result.message);
pollTrainingStatus(result.jobId); // Start polling for status
} 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}`);
}
});
// 4. Handle Chat Messages
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();
}
});
// --- Original Theme JavaScript ---
// (The existing code for particles, menu, text rotation, etc., goes here)
// Create floating particles
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';
// Randomly assign orange or blue color
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);
}
}
// Mobile menu toggle
const menuToggle = document.getElementById('menuToggle');
const navLinks = document.getElementById('navLinks');
menuToggle.addEventListener('click', () => {
menuToggle.classList.toggle('active');
navLinks.classList.toggle('active');
});
// Close mobile menu when clicking a link
document.querySelectorAll('.nav-links a').forEach(link => {
link.addEventListener('click', () => {
menuToggle.classList.remove('active');
navLinks.classList.remove('active');
});
});
// Active navigation highlighting
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');
}
});
}
// Navbar scroll effect
window.addEventListener('scroll', function() {
const navbar = document.getElementById('navbar');
if (window.scrollY > 50) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
updateActiveNav();
});
// Initial active nav update
updateActiveNav();
// Smooth scrolling for navigation links
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'
});
}
});
});
// Feature tabs functionality
const tabs = document.querySelectorAll('.tab-item');
const panels = document.querySelectorAll('.content-panel');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabId = tab.getAttribute('data-tab');
// Remove active class from all tabs and panels
tabs.forEach(t => t.classList.remove('active'));
panels.forEach(p => p.classList.remove('active'));
// Add active class to clicked tab and corresponding panel
tab.classList.add('active');
document.getElementById(tabId).classList.add('active');
});
});
// Form submission
document.getElementById('contactForm').addEventListener('submit', function(e) {
e.preventDefault();
// Add your form submission logic here
alert('Message sent! We\'ll get back to you soon.');
this.reset();
});
// Initialize particles
createParticles();
// Text rotation with character animation
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 === ' ' ? '&nbsp;' : char}</span>`
).join('');
}
function animateTextIn(textSet) {
const glitchText = textSet.querySelector('.glitch-text');
const subtitle = textSet.querySelector('.subtitle');
// Wrap text in spans for animation
wrapTextInSpans(glitchText);
// Update data attribute for glitch effect
glitchText.setAttribute('data-text', glitchText.textContent);
// Show subtitle after main text
setTimeout(() => {
subtitle.classList.add('visible');
}, 800);
}
function animateTextOut(textSet) {
const chars = textSet.querySelectorAll('.char');
const subtitle = textSet.querySelector('.subtitle');
// Animate characters out
chars.forEach((char, i) => {
char.style.animationDelay = `${i * 0.02}s`;
char.classList.add('out');
});
// Hide subtitle
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];
// Animate out current text
animateTextOut(currentSet);
// After out animation, switch sets
setTimeout(() => {
currentSet.classList.remove('active');
nextSet.classList.add('active');
animateTextIn(nextSet);
currentIndex = nextIndex;
isAnimating = false;
}, 600);
}
// Initialize first text set
textSets[0].classList.add('active');
animateTextIn(textSets[0]);
// Start rotation after initial display
setTimeout(() => {
setInterval(rotateText, 5000); // Change every 5 seconds
}, 4000);
// Add random glitch effect
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);
});