// Global state management const state = { isLoading: false, currentPdfUrl: null }; // Add state management for the current paper let currentPaperState = { pdfUrl: null, title: null }; // Add state management for search results let searchState = { lastResults: null, lastQuery: null }; // Add missing sortBy variable let sortBy = 'relevance'; // Default sort option // Utility Functions function getCsrfToken() { return document.querySelector('meta[name="csrf-token"]').getAttribute('content'); } function showLoading() { const overlay = document.getElementById('loadingOverlay'); overlay.classList.remove('hidden'); state.isLoading = true; } function hideLoading() { const overlay = document.getElementById('loadingOverlay'); overlay.classList.add('hidden'); state.isLoading = false; } // Search Functionality async function performSearch() { const searchQuery = document.getElementById('searchInput').value.trim(); const maxResults = parseInt(document.getElementById('maxResults').value); if (!searchQuery) { alert('Please enter a search term'); return; } // Show loading overlay document.getElementById('loadingOverlay').classList.remove('hidden'); try { const response = await fetch('/search', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() }, body: JSON.stringify({ paper_name: searchQuery, sort_by: sortBy, max_results: maxResults }) }); const data = await response.json(); // Save the results to the state searchState.lastResults = data; searchState.lastQuery = searchQuery; // Get results section and clear it const resultsSection = document.getElementById('resultsSection'); resultsSection.innerHTML = ''; if (!response.ok) { throw new Error(data.error || 'Search failed'); } // Show results section showResults(); if (data.length === 0) { resultsSection.innerHTML = `

No papers found matching your search.

`; return; } // Create results container const resultsGrid = document.createElement('div'); resultsGrid.className = 'results-grid'; // Add each paper to the grid using the new function data.forEach(paper => { const paperCard = createPaperCard(paper); resultsGrid.appendChild(paperCard); }); resultsSection.appendChild(resultsGrid); } catch (error) { console.error('Search error:', error); document.getElementById('resultsSection').innerHTML = `

Failed to search papers: ${error.message}

`; } finally { // Hide loading overlay document.getElementById('loadingOverlay').classList.add('hidden'); } } // Helper function to escape HTML and prevent XSS function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // Display Functions function displayResults(results) { const resultsSection = document.getElementById('resultsSection'); resultsSection.innerHTML = ''; const resultsGrid = document.createElement('div'); resultsGrid.className = 'results-grid'; results.forEach((paper, index) => { // Create paper card const paperCard = document.createElement('div'); paperCard.className = 'paper-card'; // Build the card HTML - removed save button paperCard.innerHTML = `
${paper.published}

${paper.title}

${paper.authors}

${paper.abstract}

`; // Add the paper card to the grid resultsGrid.appendChild(paperCard); // Add click handlers for remaining buttons const viewPdfButton = paperCard.querySelector('.view-pdf'); viewPdfButton.addEventListener('click', () => window.open(paper.pdf_link, '_blank')); const arxivButton = paperCard.querySelector('.arxiv'); arxivButton.addEventListener('click', () => window.open(paper.arxiv_link, '_blank')); const analyzeButton = paperCard.querySelector('.analyze'); analyzeButton.addEventListener('click', () => analyzePaper(paper.pdf_link, paper.title)); }); resultsSection.appendChild(resultsGrid); } // Analysis Functions async function analyzePaper(pdfUrl, paperTitle) { // Update current paper state currentPaperState.pdfUrl = pdfUrl; currentPaperState.title = paperTitle; // Show loading overlay showLoading(); try { const response = await fetch('/perform-rag', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() }, body: JSON.stringify({ pdf_url: pdfUrl, paper_title: paperTitle }) }); const data = await response.json(); if (data.error) { throw new Error(data.error); } // Show results section with enhanced tab styling const resultsSection = document.getElementById('resultsSection'); resultsSection.innerHTML = `

${paperTitle}

Research Paper Analysis

${marked.parse(data.analysis.executive_summary)}
`; // Add event listener for Enter key in chat input const chatInput = document.getElementById('chatInput'); if (chatInput) { chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendMessage(); } }); } // Add animation keyframes const style = document.createElement('style'); style.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); } catch (error) { console.error('Analysis error:', error); document.getElementById('resultsSection').innerHTML = `
Failed to analyze paper: ${error.message}
`; } finally { hideLoading(); } } // Chat functionality async function sendMessage() { const chatInput = document.getElementById('chatInput'); const userMessage = chatInput.value.trim(); if (!userMessage) return; // Clear input chatInput.value = ''; // Add user message to chat addMessageToChat('user', userMessage); // Show typing indicator addTypingIndicator(); try { console.log('Sending chat request with:', { question: userMessage, pdf_url: currentPaperState.pdfUrl }); const response = await fetch('/chat-with-paper', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCsrfToken() }, body: JSON.stringify({ question: userMessage, // Changed from message to question pdf_url: currentPaperState.pdfUrl // Removed title parameter as it's not required by the backend }) }); // Remove typing indicator removeTypingIndicator(); if (!response.ok) { const errorText = await response.text(); console.error('Server error response:', errorText); throw new Error(`Server error: ${response.status} - ${errorText}`); } const data = await response.json(); console.log('Received chat response:', data); if (data.error) { throw new Error(data.error); } // Add AI response to chat addMessageToChat('ai', data.response || data.message || data.answer || "I couldn't generate a proper response."); // Scroll to bottom of chat const chatMessages = document.getElementById('chatMessages'); if (chatMessages) { chatMessages.scrollTop = chatMessages.scrollHeight; } } catch (error) { console.error('Chat error:', error); removeTypingIndicator(); addMessageToChat('ai', `I'm sorry, I encountered an error: ${error.message}. Please try again.`); } } // Handle quick questions function handleQuickQuestion(question) { const chatInput = document.getElementById('chatInput'); if (chatInput) { chatInput.value = question; sendMessage(); } } // Add message to chat function addMessageToChat(sender, message) { const chatMessages = document.getElementById('chatMessages'); if (!chatMessages) return; const messageWrapper = document.createElement('div'); messageWrapper.className = 'message-wrapper'; const avatar = document.createElement('div'); avatar.className = sender === 'user' ? 'message-avatar user-avatar' : 'message-avatar ai-avatar'; avatar.textContent = sender === 'user' ? 'You' : 'AI'; const messageElement = document.createElement('div'); messageElement.className = sender === 'user' ? 'message user-message' : 'message ai-message'; // Safely handle the message content if (typeof message === 'string') { // For AI messages, use marked to parse markdown if (sender === 'ai') { try { messageElement.innerHTML = marked.parse(message); } catch (e) { console.error('Error parsing markdown:', e); messageElement.textContent = message; } } else { // For user messages, just use text messageElement.textContent = message; } } else { // Handle non-string messages safely messageElement.textContent = JSON.stringify(message); } messageWrapper.appendChild(avatar); messageWrapper.appendChild(messageElement); chatMessages.appendChild(messageWrapper); // Scroll to bottom chatMessages.scrollTop = chatMessages.scrollHeight; // Add improved message styling if it doesn't exist if (!document.getElementById('improved-message-style')) { const style = document.createElement('style'); style.id = 'improved-message-style'; style.textContent = ` .message-wrapper { display: flex; margin-bottom: 16px; max-width: 100%; animation: fadeIn 0.3s ease; } .message-avatar { width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 0.85rem; flex-shrink: 0; margin-right: 12px; } .user-avatar { background: linear-gradient(135deg, #10b981 0%, #059669 100%); color: white; } .ai-avatar { background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); color: white; } .message { padding: 16px; border-radius: 12px; font-size: 0.95rem; line-height: 1.6; max-width: calc(100% - 60px); overflow-wrap: break-word; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .user-message { background: rgba(16, 185, 129, 0.1); color: #e2e8f0; border: 1px solid rgba(16, 185, 129, 0.2); border-radius: 12px 12px 2px 12px; align-self: flex-end; } .ai-message { background: rgba(99, 102, 241, 0.1); color: #e2e8f0; border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 2px 12px 12px 12px; } /* Style for markdown content in AI messages */ .ai-message p { margin: 0 0 12px 0; } .ai-message p:last-child { margin-bottom: 0; } .ai-message ul, .ai-message ol { margin: 12px 0; padding-left: 24px; } .ai-message li { margin-bottom: 6px; } .ai-message h1, .ai-message h2, .ai-message h3, .ai-message h4 { margin: 16px 0 12px 0; font-weight: 600; line-height: 1.3; } .ai-message h1 { font-size: 1.4rem; } .ai-message h2 { font-size: 1.3rem; } .ai-message h3 { font-size: 1.2rem; } .ai-message h4 { font-size: 1.1rem; } .ai-message code { background: rgba(30, 41, 59, 0.5); padding: 2px 6px; border-radius: 4px; font-family: monospace; font-size: 0.9em; } .ai-message pre { background: rgba(30, 41, 59, 0.5); padding: 12px; border-radius: 8px; overflow-x: auto; margin: 12px 0; border: 1px solid rgba(99, 102, 241, 0.2); } .ai-message pre code { background: transparent; padding: 0; border-radius: 0; display: block; line-height: 1.5; } .ai-message blockquote { border-left: 4px solid rgba(99, 102, 241, 0.4); padding-left: 12px; margin: 12px 0; font-style: italic; color: #94a3b8; } .ai-message a { color: #818cf8; text-decoration: underline; text-underline-offset: 2px; } .ai-message a:hover { color: #a5b4fc; } .ai-message table { border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 0.9em; } .ai-message th, .ai-message td { border: 1px solid rgba(99, 102, 241, 0.2); padding: 8px 12px; text-align: left; } .ai-message th { background: rgba(99, 102, 241, 0.1); font-weight: 600; } /* Responsive adjustments */ @media (max-width: 768px) { .message { padding: 14px; font-size: 0.9rem; line-height: 1.5; } .message-avatar { width: 32px; height: 32px; font-size: 0.75rem; margin-right: 10px; } .ai-message pre { padding: 10px; } } `; document.head.appendChild(style); } } // Add typing indicator function addTypingIndicator() { const chatMessages = document.getElementById('chatMessages'); if (!chatMessages) return; const typingIndicator = document.createElement('div'); typingIndicator.className = 'message-wrapper typing-indicator'; typingIndicator.innerHTML = `
AI
`; chatMessages.appendChild(typingIndicator); chatMessages.scrollTop = chatMessages.scrollHeight; // Add the CSS for the typing animation if it doesn't exist if (!document.getElementById('typing-animation-style')) { const style = document.createElement('style'); style.id = 'typing-animation-style'; style.textContent = ` .typing-message { padding: 10px 15px; min-height: 20px; display: flex; align-items: center; } .typing-dots { display: flex; align-items: center; height: 20px; } .typing-dots span { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background-color: rgba(255, 255, 255, 0.7); margin: 0 3px; animation: typingAnimation 1.4s infinite ease-in-out; } .typing-dots span:nth-child(1) { animation-delay: 0s; } .typing-dots span:nth-child(2) { animation-delay: 0.2s; } .typing-dots span:nth-child(3) { animation-delay: 0.4s; } @keyframes typingAnimation { 0%, 60%, 100% { transform: translateY(0); opacity: 0.6; } 30% { transform: translateY(-5px); opacity: 1; } } /* Make the typing indicator look better on mobile */ @media (max-width: 768px) { .typing-dots span { width: 6px; height: 6px; margin: 0 2px; } .typing-message { padding: 8px 12px; } } `; document.head.appendChild(style); } } // Remove typing indicator function removeTypingIndicator() { const typingIndicator = document.querySelector('.typing-indicator'); if (typingIndicator) { // Add a fade-out effect typingIndicator.style.transition = 'opacity 0.3s ease'; typingIndicator.style.opacity = '0'; // Remove after animation completes setTimeout(() => { if (typingIndicator.parentNode) { typingIndicator.parentNode.removeChild(typingIndicator); } }, 300); } } // Create chat interface HTML function createChatInterface() { return `
AI
Hello! I'm here to help you understand this research paper better. Feel free to ask any questions about the paper's content, methodology, findings, or implications.
`; } // Navigation Functions function showHome() { hideAllSections(); document.getElementById('homeSection').classList.add('active'); document.getElementById('backButton').style.display = 'none'; } function showResults() { hideAllSections(); document.getElementById('resultsSection').classList.add('active'); document.getElementById('currentSection').textContent = 'Search Results'; document.getElementById('backButton').style.display = 'block'; } function hideAllSections() { const sections = ['homeSection', 'historySection', 'savedSection', 'resultsSection']; sections.forEach(section => { document.getElementById(section).classList.remove('active'); }); } function goBack() { showHome(); } // Load search history function loadSearchHistory() { const historyGrid = document.getElementById('searchHistory'); try { const history = JSON.parse(localStorage.getItem('searchHistory') || '[]'); if (history.length === 0) { historyGrid.innerHTML = '

No search history yet

'; return; } historyGrid.innerHTML = ''; history.forEach(item => { const historyCard = document.createElement('div'); historyCard.className = 'history-card'; historyCard.innerHTML = `

${item.query}

${new Date(item.timestamp).toLocaleDateString()}

`; historyGrid.appendChild(historyCard); }); } catch (error) { console.error('Error loading history:', error); historyGrid.innerHTML = '

Error loading history

'; } } // Add notification system function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.classList.add('show'); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => notification.remove(), 300); }, 2000); }, 100); } // Add history functionality function saveToHistory(query, results) { try { const history = JSON.parse(localStorage.getItem('searchHistory') || '[]'); history.unshift({ query, timestamp: new Date().toISOString(), results: results.slice(0, 3) // Save only first 3 results to save space }); // Keep only last 10 searches localStorage.setItem('searchHistory', JSON.stringify(history.slice(0, 10))); } catch (error) { console.error('Error saving to history:', error); } } // Add function to repeat search from history function repeatSearch(query) { const searchInput = document.getElementById('searchInput'); searchInput.value = query; showHome(); performSearch(); } // Add tab switching functionality function switchTab(tabName) { // Update tab buttons document.querySelectorAll('.tab-button').forEach(button => { button.classList.remove('active'); if (button.textContent.toLowerCase().includes(tabName)) { button.classList.add('active'); } }); // Update tab content document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); document.getElementById(`${tabName}Tab`).classList.add('active'); } // Add function to return to search results function showSearchResults() { if (lastSearchResults) { displayResults(lastSearchResults); // Update navigation state document.getElementById('currentSection').textContent = 'Search Results'; } else { showHome(); } } // Add function to handle back to search function backToSearchResults() { if (searchState.lastResults) { const resultsSection = document.getElementById('resultsSection'); resultsSection.innerHTML = ''; // Create results container const resultsGrid = document.createElement('div'); resultsGrid.className = 'results-grid'; // Recreate the results from the saved state using the new function searchState.lastResults.forEach(paper => { const paperCard = createPaperCard(paper); resultsGrid.appendChild(paperCard); }); resultsSection.appendChild(resultsGrid); } else { // If no previous results, redirect to home showHome(); } } // Add function to handle search history function updateSearchHistory(searchTerm) { try { let searches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); // Add new search with timestamp searches.unshift({ term: searchTerm, date: new Date().toISOString() }); // Keep only the most recent 9 searches searches = searches.slice(0, 9); localStorage.setItem('recentSearches', JSON.stringify(searches)); displaySearchHistory(); } catch (error) { console.error('Error updating search history:', error); } } // Add function to display search history function displaySearchHistory() { const historyContainer = document.getElementById('searchHistory'); if (!historyContainer) return; try { const searches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); historyContainer.innerHTML = `

Recent Searches

${searches.map(search => `

${search.term}

${new Date(search.date).toLocaleDateString('en-GB')}
`).join('')}
`; } catch (error) { console.error('Error displaying search history:', error); } } // Add function to handle search again function searchAgain(term) { document.getElementById('searchInput').value = term; handleSearch(term); } // Update the search container HTML in your JavaScript function initializeSearchInterface() { const searchSection = document.querySelector('.search-section'); if (searchSection) { searchSection.innerHTML = `
`; // Add event listeners const searchInput = document.getElementById('searchInput'); const searchButton = document.getElementById('searchButton'); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { performSearch(); } }); searchButton.addEventListener('click', performSearch); } } // Event Listeners document.addEventListener('DOMContentLoaded', () => { // Navigation initialization showHome(); initializeSearchInterface(); // Search button click const searchButton = document.getElementById('searchButton'); searchButton.addEventListener('click', performSearch); // Search input enter key const searchInput = document.getElementById('searchInput'); searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { performSearch(); } }); }); function addThinkingMessage(id) { const chatMessages = document.getElementById('chatMessages'); const thinkingDiv = document.createElement('div'); thinkingDiv.id = id; thinkingDiv.className = 'message-wrapper thinking-indicator'; thinkingDiv.innerHTML = `
AI
`; chatMessages.appendChild(thinkingDiv); chatMessages.scrollTop = chatMessages.scrollHeight; return thinkingDiv; } // Add new function to remove thinking message function removeThinkingMessage(id) { const thinkingDiv = document.getElementById(id); if (thinkingDiv) { thinkingDiv.remove(); } } // Function to create consistent paper cards with properly styled buttons function createPaperCard(paper) { const paperCard = document.createElement('div'); paperCard.className = 'paper-card'; // Format the paper data with proper HTML structure paperCard.innerHTML = `
${escapeHtml(paper.published)}
Category: ${escapeHtml(paper.category || 'N/A')}

${escapeHtml(paper.title)}

Authors: ${escapeHtml(paper.authors)}

${escapeHtml(paper.abstract.substring(0, 200))}${paper.abstract.length > 200 ? '...' : ''}

PDF ARXIV
`; return paperCard; } // Add comprehensive button styling const buttonStyle = document.createElement('style'); buttonStyle.id = 'paper-buttons-style'; buttonStyle.textContent = ` .paper-card { background: rgba(15, 23, 42, 0.6); border-radius: 0.75rem; padding: 1.5rem; margin-bottom: 1.5rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); border: 1px solid rgba(99, 102, 241, 0.1); transition: transform 0.3s ease, box-shadow 0.3s ease; } .paper-card:hover { transform: translateY(-5px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); } .paper-actions { display: flex; justify-content: center; gap: 0.75rem; margin-top: 1.25rem; } .paper-button { display: inline-flex; align-items: center; justify-content: center; padding: 0.75rem 1.5rem; border-radius: 0.5rem; font-weight: 600; font-size: 0.9rem; cursor: pointer; transition: all 0.2s ease; text-decoration: none; border: none; min-width: 100px; letter-spacing: 0.5px; } .pdf-button { background: rgba(30, 41, 59, 0.8); color: #e2e8f0; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); } .pdf-button:hover { background: rgba(44, 55, 74, 0.9); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25); } .arxiv-button { background: rgba(99, 102, 241, 0.2); color: #e2e8f0; box-shadow: 0 2px 4px rgba(99, 102, 241, 0.15); } .arxiv-button:hover { background: rgba(99, 102, 241, 0.3); transform: translateY(-2px); box-shadow: 0 4px 8px rgba(99, 102, 241, 0.25); } .analyze-button { background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); color: white; box-shadow: 0 2px 4px rgba(99, 102, 241, 0.3); } .analyze-button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(99, 102, 241, 0.4); background: linear-gradient(135deg, #818cf8 0%, #6366f1 100%); } `; document.head.appendChild(buttonStyle);