Spaces:
Sleeping
Sleeping
// 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 = ` | |
<div class="no-results"> | |
<p>No papers found matching your search.</p> | |
</div>`; | |
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 = ` | |
<div class="error-message"> | |
<p>Failed to search papers: ${error.message}</p> | |
</div>`; | |
} 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, """) | |
.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 = ` | |
<div class="paper-date">${paper.published}</div> | |
<h2 class="paper-title">${paper.title}</h2> | |
<div class="paper-author">${paper.authors}</div> | |
<p class="paper-abstract">${paper.abstract}</p> | |
<div class="paper-actions"> | |
<div class="action-row"> | |
<button class="action-link view-pdf">VIEW PDF</button> | |
<button class="action-link arxiv">ARXIV</button> | |
<button class="action-button analyze">ANALYZE</button> | |
</div> | |
</div> | |
`; | |
// 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 = ` | |
<div class="analysis-container"> | |
<div class="analysis-header"> | |
<button onclick="backToSearchResults()" class="back-button"> | |
<span>←</span> Back to Results | |
</button> | |
<h2 class="current-paper-title">${paperTitle}</h2> | |
</div> | |
<div style="display: flex; gap: 0.75rem; margin-bottom: 2rem; padding: 0.5rem; background: rgba(15, 23, 42, 0.6); border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.07); backdrop-filter: blur(10px); border: 1px solid rgba(99, 102, 241, 0.15); flex-wrap: wrap;"> | |
<button id="paperAnalysisTab" | |
style="flex: 1; min-width: 140px; padding: 0.85rem 1rem; background: linear-gradient(135deg, rgba(99, 102, 241, 0.25) 0%, rgba(79, 70, 229, 0.35) 100%); color: #ffffff; font-size: 0.95rem; font-weight: 600; cursor: pointer; border-radius: 0.6rem; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); white-space: nowrap; position: relative; box-shadow: 0 2px 10px rgba(99, 102, 241, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1); border: none; outline: none; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); letter-spacing: 0.3px; display: flex; align-items: center; justify-content: center; gap: 0.5rem;" | |
onclick="document.getElementById('analysisContent').style.display='block'; | |
document.getElementById('chatContent').style.display='none'; | |
this.style.background='linear-gradient(135deg, rgba(99, 102, 241, 0.25) 0%, rgba(79, 70, 229, 0.35) 100%)'; | |
this.style.boxShadow='0 2px 10px rgba(99, 102, 241, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1)'; | |
this.style.color='#ffffff'; | |
document.getElementById('chatWithPaperTab').style.background='rgba(30, 41, 59, 0.4)'; | |
document.getElementById('chatWithPaperTab').style.boxShadow='none'; | |
document.getElementById('chatWithPaperTab').style.color='rgba(255, 255, 255, 0.6)';"> | |
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg> | |
Paper Analysis | |
</button> | |
<button id="chatWithPaperTab" | |
style="flex: 1; min-width: 140px; padding: 0.85rem 1rem; background: rgba(30, 41, 59, 0.4); color: rgba(255, 255, 255, 0.6); font-size: 0.95rem; font-weight: 600; cursor: pointer; border-radius: 0.6rem; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); white-space: nowrap; position: relative; box-shadow: none; border: none; outline: none; letter-spacing: 0.3px; display: flex; align-items: center; justify-content: center; gap: 0.5rem;" | |
onclick="document.getElementById('chatContent').style.display='block'; | |
document.getElementById('analysisContent').style.display='none'; | |
this.style.background='linear-gradient(135deg, rgba(99, 102, 241, 0.25) 0%, rgba(79, 70, 229, 0.35) 100%)'; | |
this.style.boxShadow='0 2px 10px rgba(99, 102, 241, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1)'; | |
this.style.color='#ffffff'; | |
document.getElementById('paperAnalysisTab').style.background='rgba(30, 41, 59, 0.4)'; | |
document.getElementById('paperAnalysisTab').style.boxShadow='none'; | |
document.getElementById('paperAnalysisTab').style.color='rgba(255, 255, 255, 0.6)';"> | |
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path><path d="M13 8l-5 5"></path><path d="M13 13l-5-5"></path></svg> | |
Chat with Paper | |
</button> | |
</div> | |
<div id="analysisContent" style="display: block; width: 100%; animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);"> | |
<div class="analysis-section"> | |
<h3 style="font-size: 1.5rem; margin-bottom: 1.25rem; color: #f8fafc; font-weight: 600; display: flex; align-items: center; gap: 0.5rem;"> | |
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg> | |
Research Paper Analysis | |
</h3> | |
<div class="analysis-content" style="background: rgba(15, 23, 42, 0.4); padding: 1.5rem; border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.05); border: 1px solid rgba(99, 102, 241, 0.1);"> | |
${marked.parse(data.analysis.executive_summary)} | |
</div> | |
</div> | |
</div> | |
<div id="chatContent" style="display: none; width: 100%; animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1);"> | |
<div class="chat-container" style="background: rgba(15, 23, 42, 0.4); border-radius: 0.75rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.05); border: 1px solid rgba(99, 102, 241, 0.1); overflow: hidden; display: flex; flex-direction: column; height: 500px;"> | |
<div class="quick-questions" style="padding: 1rem; display: flex; gap: 0.5rem; overflow-x: auto; border-bottom: 1px solid rgba(255, 255, 255, 0.05); background: rgba(15, 23, 42, 0.6);"> | |
<button class="quick-question-btn" style="padding: 0.6rem 1rem; background: rgba(99, 102, 241, 0.15); color: #d1d5db; border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 2rem; font-size: 0.85rem; white-space: nowrap; transition: all 0.2s ease; cursor: pointer;" | |
onclick="handleQuickQuestion('What are the main findings?')" | |
onmouseover="this.style.background='rgba(99, 102, 241, 0.25)'; this.style.color='white';" | |
onmouseout="this.style.background='rgba(99, 102, 241, 0.15)'; this.style.color='#d1d5db';"> | |
What are the main findings? | |
</button> | |
<button class="quick-question-btn" style="padding: 0.6rem 1rem; background: rgba(99, 102, 241, 0.15); color: #d1d5db; border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 2rem; font-size: 0.85rem; white-space: nowrap; transition: all 0.2s ease; cursor: pointer;" | |
onclick="handleQuickQuestion('Explain the methodology')" | |
onmouseover="this.style.background='rgba(99, 102, 241, 0.25)'; this.style.color='white';" | |
onmouseout="this.style.background='rgba(99, 102, 241, 0.15)'; this.style.color='#d1d5db';"> | |
Explain the methodology | |
</button> | |
<button class="quick-question-btn" style="padding: 0.6rem 1rem; background: rgba(99, 102, 241, 0.15); color: #d1d5db; border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 2rem; font-size: 0.85rem; white-space: nowrap; transition: all 0.2s ease; cursor: pointer;" | |
onclick="handleQuickQuestion('Key contributions?')" | |
onmouseover="this.style.background='rgba(99, 102, 241, 0.25)'; this.style.color='white';" | |
onmouseout="this.style.background='rgba(99, 102, 241, 0.15)'; this.style.color='#d1d5db';"> | |
Key contributions? | |
</button> | |
<button class="quick-question-btn" style="padding: 0.6rem 1rem; background: rgba(99, 102, 241, 0.15); color: #d1d5db; border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 2rem; font-size: 0.85rem; white-space: nowrap; transition: all 0.2s ease; cursor: pointer;" | |
onclick="handleQuickQuestion('Future research directions?')" | |
onmouseover="this.style.background='rgba(99, 102, 241, 0.25)'; this.style.color='white';" | |
onmouseout="this.style.background='rgba(99, 102, 241, 0.15)'; this.style.color='#d1d5db';"> | |
Future research directions? | |
</button> | |
</div> | |
<div class="chat-messages" id="chatMessages" style="flex: 1; overflow-y: auto; padding: 1.25rem; display: flex; flex-direction: column; gap: 1rem;"> | |
<div class="message-wrapper" style="display: flex; gap: 0.75rem; max-width: 85%;"> | |
<div class="message-avatar ai-avatar" style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); display: flex; align-items: center; justify-content: center; font-weight: 600; color: white; font-size: 0.85rem; flex-shrink: 0;">AI</div> | |
<div class="message ai-message" style="background: rgba(30, 41, 59, 0.6); padding: 1rem; border-radius: 0 1rem 1rem 1rem; color: #e2e8f0; font-size: 0.95rem; line-height: 1.5; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); border: 1px solid rgba(99, 102, 241, 0.1);"> | |
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. | |
</div> | |
</div> | |
</div> | |
<div class="chat-input-area" style="padding: 1rem; border-top: 1px solid rgba(255, 255, 255, 0.05); background: rgba(15, 23, 42, 0.6); display: flex; gap: 0.75rem;"> | |
<input type="text" | |
class="chat-input" | |
placeholder="Ask a question about the paper..." | |
id="chatInput" | |
style="flex: 1; padding: 0.85rem 1.25rem; background: rgba(30, 41, 59, 0.6); border: 1px solid rgba(99, 102, 241, 0.2); border-radius: 0.5rem; color: white; font-size: 0.95rem; outline: none; transition: all 0.2s ease;" | |
onfocus="this.style.borderColor='rgba(99, 102, 241, 0.5)'; this.style.boxShadow='0 0 0 2px rgba(99, 102, 241, 0.15)';" | |
onblur="this.style.borderColor='rgba(99, 102, 241, 0.2)'; this.style.boxShadow='none';"> | |
<button class="chat-send-button" | |
onclick="sendMessage()" | |
style="padding: 0.85rem 1.5rem; background: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%); color: white; border: none; border-radius: 0.5rem; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(99, 102, 241, 0.3);" | |
onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.4)';" | |
onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 2px 5px rgba(99, 102, 241, 0.3)';"> | |
<span>Send</span> | |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/> | |
</svg> | |
</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
`; | |
// 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 = ` | |
<div class="error-message"> | |
Failed to analyze paper: ${error.message} | |
</div> | |
`; | |
} 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 = ` | |
<div class="message-avatar ai-avatar">AI</div> | |
<div class="message ai-message typing-message"> | |
<div class="typing-dots"> | |
<span></span> | |
<span></span> | |
<span></span> | |
</div> | |
</div> | |
`; | |
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 ` | |
<div class="chat-container"> | |
<div class="quick-questions"> | |
<button class="quick-question-btn" onclick="handleQuickQuestion('What are the main findings?')"> | |
Main findings | |
</button> | |
<button class="quick-question-btn" onclick="handleQuickQuestion('Explain the methodology')"> | |
Methodology | |
</button> | |
<button class="quick-question-btn" onclick="handleQuickQuestion('Key contributions?')"> | |
Key contributions | |
</button> | |
<button class="quick-question-btn" onclick="handleQuickQuestion('Future research directions?')"> | |
Future directions | |
</button> | |
</div> | |
<div class="chat-messages" id="chatMessages"> | |
<div class="message-wrapper"> | |
<div class="message-avatar ai-avatar">AI</div> | |
<div class="message ai-message"> | |
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. | |
</div> | |
</div> | |
</div> | |
<div class="chat-input-area"> | |
<input type="text" | |
class="chat-input" | |
placeholder="Ask a question about the paper..." | |
id="chatInput"> | |
<button class="chat-send-button" onclick="sendMessage()"> | |
<span>Send</span> | |
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> | |
<path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/> | |
</svg> | |
</button> | |
</div> | |
</div> | |
`; | |
} | |
// 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 = '<p class="no-history">No search history yet</p>'; | |
return; | |
} | |
historyGrid.innerHTML = ''; | |
history.forEach(item => { | |
const historyCard = document.createElement('div'); | |
historyCard.className = 'history-card'; | |
historyCard.innerHTML = ` | |
<h3>${item.query}</h3> | |
<p class="timestamp">${new Date(item.timestamp).toLocaleDateString()}</p> | |
<button onclick="repeatSearch('${item.query}')" class="action-button">Search Again</button> | |
`; | |
historyGrid.appendChild(historyCard); | |
}); | |
} catch (error) { | |
console.error('Error loading history:', error); | |
historyGrid.innerHTML = '<p class="error-message">Error loading history</p>'; | |
} | |
} | |
// 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 = ` | |
<h2 class="history-title">Recent Searches</h2> | |
<div class="search-history-grid"> | |
${searches.map(search => ` | |
<div class="history-item"> | |
<h3 class="search-term">${search.term}</h3> | |
<div class="search-date"> | |
${new Date(search.date).toLocaleDateString('en-GB')} | |
</div> | |
<button | |
onclick="searchAgain('${search.term}')" | |
class="search-again-btn"> | |
SEARCH AGAIN | |
</button> | |
</div> | |
`).join('')} | |
</div> | |
`; | |
} 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 = ` | |
<div class="search-container"> | |
<input | |
type="text" | |
id="searchInput" | |
class="search-input" | |
placeholder="Enter research paper title" | |
autocomplete="off" | |
> | |
<select id="maxResults" class="filter-select"> | |
<option value="10">10 results</option> | |
<option value="25">25 results</option> | |
<option value="50">50 results</option> | |
</select> | |
<button id="searchButton" class="search-button"> | |
SCAN | |
</button> | |
</div> | |
`; | |
// 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 = ` | |
<div class="message-avatar ai-avatar">AI</div> | |
<div class="message ai-message ai-thinking"> | |
<div class="ai-thinking-dots"> | |
<div class="ai-thinking-dot"></div> | |
<div class="ai-thinking-dot"></div> | |
<div class="ai-thinking-dot"></div> | |
</div> | |
</div> | |
`; | |
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 = ` | |
<div class="paper-header"> | |
<div class="paper-date">${escapeHtml(paper.published)}</div> | |
<div class="paper-category">Category: ${escapeHtml(paper.category || 'N/A')}</div> | |
</div> | |
<h3 class="paper-title">${escapeHtml(paper.title)}</h3> | |
<div class="paper-authors">Authors: ${escapeHtml(paper.authors)}</div> | |
<p class="paper-abstract">${escapeHtml(paper.abstract.substring(0, 200))}${paper.abstract.length > 200 ? '...' : ''}</p> | |
<div class="paper-actions"> | |
<a href="${paper.pdf_link}" target="_blank" class="paper-button pdf-button">PDF</a> | |
<a href="${paper.arxiv_link}" target="_blank" class="paper-button arxiv-button">ARXIV</a> | |
<button onclick="analyzePaper('${paper.pdf_link}', '${escapeHtml(paper.title)}')" class="paper-button analyze-button">Analyze</button> | |
</div> | |
`; | |
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); | |