// Research Radar - Enhanced Interactive JavaScript class ResearchRadar { constructor() { this.currentSection = 'search'; this.currentPage = 'landing'; this.isLoading = false; this.chatHistory = []; this.currentDocumentId = null; this.uploadProgress = 0; this.searchCache = new Map(); this.init(); } init() { this.setupEventListeners(); this.setupDragAndDrop(); this.initializeChat(); this.setupChatInput(); this.setupSearchSuggestions(); this.updateStatusIndicator(); this.setupPageNavigation(); this.setupEnhancedChat(); this.setupMobileNav(); this.setupVhUnit(); } setupEventListeners() { console.log('Setting up event listeners...'); // Landing page CTA buttons - Critical buttons that need to work this.setupLandingPageButtons(); // Navigation for app page const navLinks = document.querySelectorAll('.app-nav .nav-link'); console.log(`Found ${navLinks.length} navigation links`); navLinks.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const section = link.dataset.section; console.log(`Navigation clicked: ${section}`); if (section) { this.switchSection(section); } }); }); // Quick search cards const quickSearchCards = document.querySelectorAll('.quick-search-card'); console.log(`Found ${quickSearchCards.length} quick search cards`); quickSearchCards.forEach(card => { card.addEventListener('click', (e) => { const query = card.dataset.query; console.log(`Quick search card clicked: ${query}`); if (query) { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = query; this.searchPapers(); } } }); }); // Chat suggestions document.querySelectorAll('.suggestion-chip').forEach(chip => { chip.addEventListener('click', (e) => { const question = chip.dataset.question; if (question) { document.getElementById('chatInput').value = question; this.sendChatMessage(); } }); }); // Example URLs document.querySelectorAll('.example-url').forEach(btn => { btn.addEventListener('click', (e) => { const url = btn.dataset.url; if (url) { document.getElementById('paperUrl').value = url; } }); }); // Clear chat button const clearBtn = document.getElementById('chatClearBtn'); if (clearBtn) { clearBtn.addEventListener('click', () => this.clearChat()); } // Search functionality const searchBtn = document.getElementById('searchBtn'); const searchInput = document.getElementById('searchInput'); console.log('Search button found:', !!searchBtn); console.log('Search input found:', !!searchInput); if (searchBtn) { searchBtn.addEventListener('click', () => { console.log('Search button clicked!'); this.searchPapers(); }); } if (searchInput) { searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { console.log('Search input Enter pressed!'); this.searchPapers(); } }); } // File upload const fileInput = document.getElementById('fileInput'); console.log('File input found:', !!fileInput); if (fileInput) { fileInput.addEventListener('change', (e) => { console.log('File input changed!'); this.handleFileUpload(e); }); } // URL analysis const analyzeUrlBtn = document.getElementById('analyzeUrlBtn'); const paperUrlInput = document.getElementById('paperUrl'); console.log('Analyze URL button found:', !!analyzeUrlBtn); console.log('Paper URL input found:', !!paperUrlInput); if (analyzeUrlBtn) { analyzeUrlBtn.addEventListener('click', () => { console.log('Analyze URL button clicked!'); this.analyzePaperUrl(); }); } if (paperUrlInput) { paperUrlInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { console.log('Paper URL input Enter pressed!'); this.analyzePaperUrl(); } }); } // Chat functionality const chatSendBtn = document.getElementById('chatSendBtn'); const chatInput = document.getElementById('chatInput'); const chatSendBtnPanel = document.getElementById('chatSendBtnPanel'); const chatInputPanel = document.getElementById('chatInputPanel'); if (chatSendBtn) { chatSendBtn.addEventListener('click', () => this.sendChatMessage()); } if (chatInput) { chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.sendChatMessage(); } }); } // Panel chat functionality if (chatSendBtnPanel) { chatSendBtnPanel.addEventListener('click', () => this.sendChatMessagePanel()); } if (chatInputPanel) { chatInputPanel.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendChatMessagePanel(); } }); } } setupLandingPageButtons() { console.log('Setting up landing page buttons...'); // Get Started button (nav) const getStartedBtn = document.querySelector('.nav-cta-btn'); console.log('Get Started button found:', !!getStartedBtn); if (getStartedBtn) { // Remove existing onclick to prevent conflicts getStartedBtn.removeAttribute('onclick'); getStartedBtn.addEventListener('click', (e) => { e.preventDefault(); console.log('Get Started button clicked!'); this.navigateToApp('search'); }); } // Start Exploring button const startExploringBtn = document.querySelector('.cta-button.primary'); console.log('Start Exploring button found:', !!startExploringBtn); if (startExploringBtn) { // Remove existing onclick to prevent conflicts startExploringBtn.removeAttribute('onclick'); startExploringBtn.addEventListener('click', (e) => { e.preventDefault(); console.log('Start Exploring button clicked!'); this.navigateToApp('search'); }); } // Upload Paper button const uploadPaperBtn = document.querySelector('.cta-button.secondary'); console.log('Upload Paper button found:', !!uploadPaperBtn); if (uploadPaperBtn) { // Remove existing onclick to prevent conflicts uploadPaperBtn.removeAttribute('onclick'); uploadPaperBtn.addEventListener('click', (e) => { e.preventDefault(); console.log('Upload Paper button clicked!'); this.navigateToApp('upload'); }); } // Back to Landing button const backToLandingBtn = document.querySelector('.back-to-landing'); console.log('Back to Landing button found:', !!backToLandingBtn); if (backToLandingBtn) { // Remove existing onclick to prevent conflicts backToLandingBtn.removeAttribute('onclick'); backToLandingBtn.addEventListener('click', (e) => { e.preventDefault(); console.log('Back to Landing button clicked!'); this.navigateToLanding(); }); } // Brand logo that navigates to landing const brandLogo = document.querySelector('.app-nav .nav-brand'); console.log('Brand logo found:', !!brandLogo); if (brandLogo) { // Remove existing onclick to prevent conflicts brandLogo.removeAttribute('onclick'); brandLogo.addEventListener('click', (e) => { e.preventDefault(); console.log('Brand logo clicked!'); this.navigateToLanding(); }); } } setupDragAndDrop() { const uploadZone = document.getElementById('uploadZone'); if (!uploadZone) return; ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { uploadZone.addEventListener(eventName, this.preventDefaults, false); }); ['dragenter', 'dragover'].forEach(eventName => { uploadZone.addEventListener(eventName, () => { uploadZone.classList.add('drag-over'); }, false); }); ['dragleave', 'drop'].forEach(eventName => { uploadZone.addEventListener(eventName, () => { uploadZone.classList.remove('drag-over'); }, false); }); uploadZone.addEventListener('drop', (e) => { const files = e.dataTransfer.files; if (files.length > 0) { this.processFile(files[0]); } }, false); } preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } switchSection(sectionName) { // Update navigation document.querySelectorAll('.nav-link').forEach(link => { link.classList.remove('active'); }); document.querySelector(`[data-section="${sectionName}"]`).classList.add('active'); // Update sections document.querySelectorAll('.section').forEach(section => { section.classList.remove('active'); }); document.getElementById(sectionName).classList.add('active'); this.currentSection = sectionName; } showLoading(show = true) { const overlay = document.getElementById('loadingOverlay'); if (overlay) { if (show) { overlay.classList.add('active'); this.isLoading = true; } else { overlay.classList.remove('active'); this.isLoading = false; } } } showToast(message, type = 'info') { const container = document.getElementById('toastContainer'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.innerHTML = `
${message}
`; container.appendChild(toast); // Auto remove after 5 seconds setTimeout(() => { if (toast.parentNode) { toast.style.animation = 'toastSlideOut 0.3s ease-out forwards'; setTimeout(() => { container.removeChild(toast); }, 300); } }, 5000); } getToastIcon(type) { const icons = { 'success': 'check-circle', 'error': 'exclamation-circle', 'warning': 'exclamation-triangle', 'info': 'info-circle' }; return icons[type] || 'info-circle'; } async searchPapers() { const searchInput = document.getElementById('searchInput'); const query = searchInput.value.trim(); if (!query) { this.showToast('Please enter a search query', 'warning'); return; } this.showLoading(true); try { const response = await fetch('/search', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query }) }); const data = await response.json(); if (response.ok) { this.displaySearchResults(data.papers); this.showToast(`Found ${data.papers.length} papers`, 'success'); } else { throw new Error(data.error || 'Search failed'); } } catch (error) { console.error('Search error:', error); this.showToast(error.message, 'error'); } finally { this.showLoading(false); } } displaySearchResults(papers) { const container = document.getElementById('searchResults'); if (!container) return; // Keep papers for later access (e.g., openSummaryChat after ingest/summarize) this.lastSearchResults = papers; if (papers.length === 0) { container.innerHTML = `

No papers found. Try different keywords.

`; return; } container.innerHTML = papers.map((paper, index) => `

${this.escapeHtml(paper.title)}

${paper.authors.slice(0, 3).map(author => this.escapeHtml(author)).join(', ')} ${paper.authors.length > 3 ? ` and ${paper.authors.length - 3} others` : ''}
${paper.published} ${this.escapeHtml(paper.category)}
${this.truncateText(this.escapeHtml(paper.summary), 300)}
View PDF arXiv Page
`).join(''); // Add event listeners to Generate Summary buttons this.setupGenerateSummaryButtons(); } setupGenerateSummaryButtons() { console.log('Setting up Generate Summary buttons...'); const generateButtons = document.querySelectorAll('.generate-summary-btn'); console.log(`Found ${generateButtons.length} Generate Summary buttons`); generateButtons.forEach(button => { button.addEventListener('click', async (e) => { e.preventDefault(); const paperUrl = button.dataset.paperUrl; const paperIndex = button.dataset.paperIndex; const pdfUrl = button.dataset.paperPdfUrl; const paper = this.lastSearchResults && typeof Number(paperIndex) === 'number' ? this.lastSearchResults[Number(paperIndex)] : null; console.log(`Generate Summary button clicked for paper: ${paperUrl}`); if (paperUrl) { // Disable button and show loading state button.disabled = true; button.innerHTML = ' Generating...'; try { // Ingest the paper PDF first, then summarize from doc_id const ingestRes = await fetch('/ingest-paper', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pdf_url: pdfUrl, url: paperUrl }) }); const ingestData = await ingestRes.json(); if (!ingestRes.ok || !ingestData.success) { throw new Error(ingestData.error || 'Failed to ingest paper'); } const docId = ingestData.doc_id; // Persist active document in UI this.setActiveDocument(docId); // Now summarize using doc_id (backend will fetch all chunks from Qdrant) const sumRes = await fetch('/summarize-paper', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ doc_id: docId }) }); const sumData = await sumRes.json(); if (!sumRes.ok || !sumData.success) { throw new Error(sumData.error || 'Failed to generate summary'); } // Open summary + chat view const paperData = paper || { title: '', authors: [], published: '', category: '' }; this.openSummaryChat({ title: paperData.title, authors: paperData.authors, published: paperData.published, category: paperData.category }, sumData.summary); } catch (error) { console.error('Error generating summary:', error); this.showToast('Failed to generate summary. Please try again.', 'error'); } finally { // Re-enable button button.disabled = false; button.innerHTML = ' Generate Summary'; } } else { console.error('No paper URL found for Generate Summary button'); this.showToast('Error: Paper URL not found', 'error'); } }); }); } async handleFileUpload(event) { const file = event.target.files[0]; if (file) { await this.processFile(file); } } async processFile(file) { // Validate file const allowedTypes = ['application/pdf', 'text/plain', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; const maxSize = 16 * 1024 * 1024; // 16MB if (!allowedTypes.includes(file.type)) { this.showToast('Invalid file type. Please upload PDF, TXT, or DOCX files.', 'error'); return; } if (file.size > maxSize) { this.showToast('File too large. Maximum size is 16MB.', 'error'); return; } this.showLoading(true); try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/upload', { method: 'POST', body: formData }); const data = await response.json(); if (response.ok) { this.displayUploadResult(data); this.showToast('File processed successfully!', 'success'); } else { throw new Error(data.error || 'Upload failed'); } } catch (error) { console.error('Upload error:', error); this.showToast(error.message, 'error'); } finally { this.showLoading(false); // Reset file input document.getElementById('fileInput').value = ''; } } displayUploadResult(data) { // Create paper data object for the uploaded file const paperData = { title: data.filename || 'Uploaded Document', authors: ['Uploaded by User'], published: new Date().toLocaleDateString(), category: 'Uploaded Document', filename: data.filename, word_count: data.word_count }; // Store the document ID for chat functionality this.currentDocumentId = data.doc_id; // Open the Summary + Chat panel with the uploaded document this.openSummaryChat(paperData, data.summary); } async analyzePaperUrl() { const urlInput = document.getElementById('paperUrl'); const url = urlInput.value.trim(); if (!url) { this.showToast('Please enter a paper URL', 'warning'); return; } // Basic URL validation if (!url.includes('arxiv.org')) { this.showToast('Please enter a valid arXiv URL', 'warning'); return; } this.showLoading(true); try { const response = await fetch('/summarize-paper', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url }) }); const data = await response.json(); if (response.ok) { this.displayPaperAnalysis(data); this.showToast('Paper analyzed successfully!', 'success'); urlInput.value = ''; // Clear input } else { throw new Error(data.error || 'Analysis failed'); } } catch (error) { console.error('Analysis error:', error); this.showToast(error.message, 'error'); } finally { this.showLoading(false); } } displayPaperAnalysis(data) { // Store the document ID for chat functionality this.currentDocumentId = data.doc_id; // Open the Summary + Chat panel with the analyzed paper this.openSummaryChat(data.paper, data.summary); } async summarizePaper(paperUrl) { // Deprecated in favor of ingest -> summarize flow return this.showToast('Summarization flow updated. Please use Generate Summary button.', 'info'); } openSummaryChat(paperData, summaryText) { // Hide current section and show summary-chat document.querySelectorAll('.section').forEach(section => { section.style.display = 'none'; }); const summarySection = document.getElementById('summary-chat'); summarySection.style.display = 'block'; // Update paper information document.getElementById('paperTitle').textContent = paperData.title || 'Research Paper'; document.getElementById('paperAuthor').textContent = paperData.authors ? paperData.authors.join(', ') : 'Unknown Author'; document.getElementById('paperDate').textContent = paperData.published || new Date().getFullYear(); document.getElementById('paperCategory').textContent = paperData.category || 'Research'; // Show summary this.displaySummaryInPanel(summaryText); // Setup chat panel this.setupChatPanel(); // Store current paper data for chat context this.currentPaper = paperData; // Default to Chat tab for immediate Q&A after tabs are in DOM setTimeout(() => { try { switchTab('chat'); } catch (_) {} const chatInput = document.getElementById('chatInputPanel'); if (chatInput) chatInput.focus(); }, 150); } displaySummaryInPanel(summaryText) { const summaryLoading = document.getElementById('summaryLoading'); const summaryTextEl = document.getElementById('summaryText'); // Hide loading and show summary summaryLoading.style.display = 'none'; summaryTextEl.style.display = 'block'; summaryTextEl.innerHTML = this.formatSummaryText(summaryText); // Update stats this.updateSummaryStats(summaryText); } formatSummaryText(text) { // Convert plain text to formatted HTML return text .replace(/\n\n/g, '

') .replace(/\n/g, '
') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/^/, '

') .replace(/$/, '

'); } updateSummaryStats(text) { const wordCount = text.split(/\s+/).length; const readingTime = Math.ceil(wordCount / 200); // Average reading speed const compressionRatio = Math.round((1 - (text.length / (text.length * 3))) * 100); // Estimate const wc = document.getElementById('wordCount'); const rt = document.getElementById('readingTime'); const cr = document.getElementById('compressionRatio'); if (wc) wc.textContent = wordCount.toLocaleString(); if (rt) rt.textContent = `${readingTime} min`; if (cr) cr.textContent = `${compressionRatio}%`; } setupChatPanel() { const chatInput = document.getElementById('chatInputPanel'); const sendBtn = document.getElementById('chatSendBtnPanel'); // Clear any existing event listeners const newChatInput = chatInput.cloneNode(true); chatInput.parentNode.replaceChild(newChatInput, chatInput); const newSendBtn = sendBtn.cloneNode(true); sendBtn.parentNode.replaceChild(newSendBtn, sendBtn); // Add new event listeners newChatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendChatMessagePanel(); } }); newChatInput.addEventListener('input', () => { this.autoResizeTextarea(newChatInput); }); newSendBtn.addEventListener('click', () => { this.sendChatMessagePanel(); }); } sendChatMessagePanel() { const chatInput = document.getElementById('chatInputPanel'); const message = chatInput.value.trim(); if (!message) return; // Add user message this.addChatMessagePanel(message, 'user'); // Clear input chatInput.value = ''; this.autoResizeTextarea(chatInput); // Show typing indicator and send to backend this.showChatTypingPanel(); this.sendChatToBackend(message); } addChatMessagePanel(message, sender) { const chatPanel = document.getElementById('chatMessagesPanel'); // Remove welcome message if it exists const welcome = chatPanel.querySelector('.chat-welcome'); if (welcome && sender === 'user') { welcome.remove(); } const messageDiv = document.createElement('div'); messageDiv.className = `chat-message-panel ${sender}`; const currentTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); messageDiv.innerHTML = `
${sender === 'bot' ? message : this.escapeHtml(message)}
${currentTime}
`; chatPanel.appendChild(messageDiv); chatPanel.scrollTop = chatPanel.scrollHeight; } showChatTypingPanel() { const chatPanel = document.getElementById('chatMessagesPanel'); const typingDiv = document.createElement('div'); typingDiv.className = 'chat-message-panel bot'; typingDiv.id = 'typingIndicatorPanel'; typingDiv.innerHTML = `
AI is thinking...
`; chatPanel.appendChild(typingDiv); chatPanel.scrollTop = chatPanel.scrollHeight; } hideChatTypingPanel() { const typingIndicator = document.getElementById('typingIndicatorPanel'); if (typingIndicator) { typingIndicator.remove(); } } sendChatToBackend(message) { fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: message }) }) .then(response => response.json()) .then(data => { this.hideChatTypingPanel(); if (data.success) { this.addChatMessagePanel(data.response, 'bot'); } else { this.addChatMessagePanel( `I apologize, but I encountered an error: ${data.error || 'Unknown error'}. Please try again.`, 'bot' ); } }) .catch(error => { console.error('Chat error:', error); this.hideChatTypingPanel(); this.addChatMessagePanel( 'I apologize, but I\'m having trouble connecting right now. Please check your connection and try again.', 'bot' ); }); } initializeChat() { this.chatHistory = []; } async sendChatMessage() { // Redirect to the enhanced chat functionality this.sendMessage(); } addChatMessage(message, sender) { // Redirect to the enhanced chat message functionality this.addMessageToChat(message, sender); } // Utility functions escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } truncateText(text, maxLength) { if (text.length <= maxLength) return text; return text.substr(0, maxLength) + '...'; } // Enhanced UI Methods (Legacy - now handled by setupEnhancedChat) setupChatInput() { // This is now handled by the enhanced chat setup this.setupEnhancedChat(); } setupSearchSuggestions() { const searchInput = document.getElementById('searchInput'); const suggestions = document.getElementById('searchSuggestions'); // Initialize enhanced search features this.initializeQuickSearchCards(); this.initializeSearchTips(); this.initializeRecentSearches(); this.initializeAdvancedFilters(); if (searchInput && suggestions) { searchInput.addEventListener('input', (e) => this.handleSearchInput(e)); searchInput.addEventListener('focus', () => this.showSearchSuggestions()); searchInput.addEventListener('blur', () => this.hideSearchSuggestions()); // Handle suggestion clicks suggestions.querySelectorAll('.suggestion-item').forEach(item => { item.addEventListener('click', () => { const query = item.dataset.query; if (query) { searchInput.value = query; suggestions.classList.remove('show'); this.searchPapers(); this.addToRecentSearches(query); } }); }); } } initializeQuickSearchCards() { const quickSearchCards = document.querySelectorAll('.quick-search-card'); quickSearchCards.forEach(card => { card.addEventListener('click', () => { const query = card.dataset.query; if (query) { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = query; this.searchPapers(); this.addToRecentSearches(query); } } }); }); } initializeSearchTips() { const tipsToggle = document.querySelector('.tips-toggle'); const tipsContent = document.querySelector('.tips-content'); if (tipsToggle && tipsContent) { tipsToggle.addEventListener('click', () => { tipsContent.classList.toggle('show'); const icon = tipsToggle.querySelector('i'); if (icon) { icon.classList.toggle('fa-chevron-down'); icon.classList.toggle('fa-chevron-up'); } }); } } initializeRecentSearches() { this.loadRecentSearches(); } initializeAdvancedFilters() { // Advanced filters toggle is handled by global function } handleSearchInput(e) { const query = e.target.value.trim(); if (query.length > 2) { this.updateSearchSuggestions(query); this.showSearchSuggestions(); } else if (query.length === 0) { this.showSearchSuggestions(); } else { this.hideSearchSuggestions(); } } updateSearchSuggestions(query) { const searchSuggestions = document.getElementById('searchSuggestions'); if (!searchSuggestions) return; // Generate dynamic suggestions based on the query const suggestions = [ { text: query, count: '~1.2k papers' }, { text: query + ' applications', count: '~800 papers' }, { text: query + ' algorithms', count: '~650 papers' }, { text: query + ' recent advances', count: '~420 papers' } ]; const suggestionsSection = searchSuggestions.querySelector('.suggestions-section'); if (suggestionsSection) { const suggestionItems = suggestions.map(s => `
${s.text} ${s.count}
`).join(''); suggestionsSection.innerHTML = `

Suggestions

${suggestionItems} `; // Re-attach event listeners suggestionsSection.querySelectorAll('.suggestion-item').forEach(item => { item.addEventListener('click', () => { const query = item.dataset.query; if (query) { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = query; this.searchPapers(); this.hideSearchSuggestions(); this.addToRecentSearches(query); } } }); }); } } showSearchSuggestions() { const searchSuggestions = document.getElementById('searchSuggestions'); if (searchSuggestions) { searchSuggestions.classList.add('show'); } } hideSearchSuggestions() { setTimeout(() => { const searchSuggestions = document.getElementById('searchSuggestions'); if (searchSuggestions) { searchSuggestions.classList.remove('show'); } }, 200); } addToRecentSearches(query) { let recentSearches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); // Remove if already exists recentSearches = recentSearches.filter(search => search !== query); // Add to beginning recentSearches.unshift(query); // Keep only last 5 recentSearches = recentSearches.slice(0, 5); localStorage.setItem('recentSearches', JSON.stringify(recentSearches)); this.updateRecentSearchesDisplay(); } loadRecentSearches() { const recentSearches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); if (recentSearches.length > 0) { this.updateRecentSearchesDisplay(); } } updateRecentSearchesDisplay() { const recentSearches = JSON.parse(localStorage.getItem('recentSearches') || '[]'); const recentSearchesContainer = document.getElementById('recentSearches'); const recentSearchItems = document.getElementById('recentSearchItems'); if (recentSearches.length > 0 && recentSearchesContainer && recentSearchItems) { const recentHTML = recentSearches.map(search => `
${search}
`).join(''); recentSearchItems.innerHTML = recentHTML; recentSearchesContainer.style.display = 'block'; // Attach event listeners recentSearchItems.querySelectorAll('.suggestion-item').forEach(item => { item.addEventListener('click', () => { const query = item.dataset.query; if (query) { const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.value = query; this.searchPapers(); } } }); }); } } updateStatusIndicator() { const indicator = document.getElementById('statusIndicator'); const chatStatus = document.getElementById('chatStatus'); if (indicator) { const statusText = indicator.querySelector('.status-text'); const statusDot = indicator.querySelector('.status-dot'); if (this.currentDocumentId) { statusText.textContent = 'Document Active'; statusDot.style.background = 'var(--success-color)'; } else { statusText.textContent = 'Ready'; statusDot.style.background = 'var(--warning-color)'; } } if (chatStatus) { const statusIndicator = chatStatus.querySelector('.status-indicator'); if (this.currentDocumentId) { statusIndicator.classList.remove('offline'); statusIndicator.classList.add('online'); statusIndicator.querySelector('span').textContent = 'Document loaded'; } else { statusIndicator.classList.remove('online'); statusIndicator.classList.add('offline'); statusIndicator.querySelector('span').textContent = 'No document selected'; } } } updateUploadProgress(percentage, step) { const progressContainer = document.getElementById('uploadProgress'); const progressFill = document.getElementById('progressFill'); const progressPercentage = document.getElementById('progressPercentage'); const progressSubtitle = document.getElementById('progressSubtitle'); const progressTime = document.getElementById('progressTime'); if (progressContainer && progressFill && progressPercentage) { progressContainer.style.display = 'block'; progressFill.style.width = `${percentage}%`; progressPercentage.textContent = `${percentage}%`; // Update subtitle and time estimate const subtitles = [ 'Preparing your document for analysis...', 'Uploading your document securely...', 'Extracting text and content...', 'AI analyzing document structure...', 'Analysis complete! Ready for questions.' ]; if (progressSubtitle && step <= subtitles.length) { progressSubtitle.textContent = subtitles[step - 1] || subtitles[0]; } if (progressTime) { if (percentage < 100) { const remainingTime = Math.max(1, Math.round((100 - percentage) / 10)); progressTime.textContent = `~${remainingTime}s remaining`; } else { progressTime.textContent = 'Complete!'; } } // Update steps with enhanced system const steps = progressContainer.querySelectorAll('.progress-step'); steps.forEach((stepEl, index) => { const stepNumber = parseInt(stepEl.dataset.step); if (stepNumber <= step) { stepEl.classList.add('active'); } else { stepEl.classList.remove('active'); } }); if (percentage === 100) { setTimeout(() => { progressContainer.style.display = 'none'; }, 3000); } } } autoResizeTextarea(event) { const textarea = event.target; textarea.style.height = 'auto'; const newHeight = Math.min(textarea.scrollHeight, 120); textarea.style.height = newHeight + 'px'; } clearChat() { const chatMessages = document.getElementById('chatMessages'); if (chatMessages) { // Keep the welcome message const welcomeMessage = chatMessages.querySelector('.welcome-message'); chatMessages.innerHTML = ''; if (welcomeMessage) { chatMessages.appendChild(welcomeMessage); } } this.chatHistory = []; this.showToast('Chat cleared', 'success'); } setActiveDocument(docId) { this.currentDocumentId = docId; this.updateStatusIndicator(); // Show chat indicator const chatIndicator = document.getElementById('chatIndicator'); if (chatIndicator) { chatIndicator.classList.add('active'); } } // Override the original methods to include enhanced functionality async processFile(file) { if (!file || !this.allowed_file(file.name)) { this.showToast('Please select a valid file (PDF, TXT, or DOCX)', 'error'); return; } const formData = new FormData(); formData.append('file', file); this.showLoading(true); this.updateUploadProgress(25, 1); try { this.updateUploadProgress(50, 2); const response = await fetch('/upload', { method: 'POST', body: formData }); this.updateUploadProgress(75, 3); const data = await response.json(); if (response.ok) { this.updateUploadProgress(100, 4); this.displayUploadResult(data); this.setActiveDocument(data.doc_id); this.showToast('File uploaded and analyzed successfully!', 'success'); } else { throw new Error(data.error || 'Upload failed'); } } catch (error) { console.error('Upload error:', error); this.showToast(error.message, 'error'); this.updateUploadProgress(0, 0); } finally { this.showLoading(false); } } allowed_file(filename) { const allowedExtensions = ['pdf', 'txt', 'docx']; const extension = filename.split('.').pop().toLowerCase(); return allowedExtensions.includes(extension); } // Page Navigation Methods setupPageNavigation() { // Handle smooth scrolling for landing page links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target && target.closest('#landingPage')) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } setupMobileNav() { // Toggle landing nav links document.querySelectorAll('.navbar .mobile-nav-toggle').forEach(toggle => { toggle.addEventListener('click', () => { const container = toggle.closest('.navbar').querySelector('.landing-nav-links, .nav-links'); if (container) { container.classList.toggle('show'); } }); }); // Close menu on link click (mobile) document.querySelectorAll('.landing-nav-links .nav-link').forEach(link => { link.addEventListener('click', () => { const links = document.querySelector('.landing-nav-links'); links && links.classList.remove('show'); }); }); } setupVhUnit() { const setVh = () => { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); }; setVh(); window.addEventListener('resize', setVh); window.addEventListener('orientationchange', setVh); } // Removed split-mode; tabs-only behavior setupViewToggle() { // Strict tabs: only one panel visible document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); document.querySelector(`[data-tab="summary"]`)?.classList.add('active'); document.getElementById('summary-tab')?.classList.add('active'); history.replaceState(null, null, `#summary`); const tabDisplayName = 'Summary'; showToast(`Focused ${tabDisplayName}`, 'info'); } navigateToApp(section = 'search') { // Hide landing page const landingPage = document.getElementById('landingPage'); const appPage = document.getElementById('appPage'); if (landingPage && appPage) { landingPage.classList.remove('active'); appPage.classList.add('active'); this.currentPage = 'app'; // Switch to the specified section this.switchSection(section); // Show toast message this.showToast(`Welcome to Research Radar! ${section === 'search' ? 'Start searching for papers' : 'Upload your documents'}`, 'success'); // Focus on the relevant input setTimeout(() => { if (section === 'search') { const searchInput = document.getElementById('searchInput'); if (searchInput) searchInput.focus(); } else if (section === 'upload') { // Auto-focus on upload section document.getElementById('upload').scrollIntoView({ behavior: 'smooth' }); } }, 500); } } navigateToLanding() { const landingPage = document.getElementById('landingPage'); const appPage = document.getElementById('appPage'); if (landingPage && appPage) { appPage.classList.remove('active'); landingPage.classList.add('active'); this.currentPage = 'landing'; // Scroll to top window.scrollTo({ top: 0, behavior: 'smooth' }); this.showToast('Welcome back to the homepage!', 'info'); } } // Enhanced section switching for app page switchSection(sectionName) { if (this.currentPage !== 'app') return; // Update navigation document.querySelectorAll('.app-nav .nav-link').forEach(link => { link.classList.remove('active'); }); const activeLink = document.querySelector(`.app-nav [data-section="${sectionName}"]`); if (activeLink) { activeLink.classList.add('active'); } // Update sections document.querySelectorAll('#appPage .section').forEach(section => { section.classList.remove('active'); }); const targetSection = document.getElementById(sectionName); if (targetSection) { targetSection.classList.add('active'); } this.currentSection = sectionName; // Add section-specific functionality this.onSectionChange(sectionName); } onSectionChange(sectionName) { switch(sectionName) { case 'search': // Focus search input after animation setTimeout(() => { const searchInput = document.getElementById('searchInput'); if (searchInput) searchInput.focus(); }, 300); break; case 'upload': // Reset upload progress this.updateUploadProgress(0, 0); break; case 'mypapers': // Load papers when section is accessed this.loadMyPapers(); break; case 'chat': // Focus chat input setTimeout(() => { const chatInput = document.getElementById('chatInput'); if (chatInput) chatInput.focus(); }, 300); break; } } // Enhanced Chat Functionality setupEnhancedChat() { this.messageCount = 0; this.sessionStartTime = Date.now(); this.updateChatStats(); this.setupChatSuggestions(); this.setupChatInput(); this.setupQuickActions(); this.startSessionTimer(); } setupChatSuggestions() { // Handle suggestion chips document.querySelectorAll('.suggestion-chip-enhanced').forEach(chip => { chip.addEventListener('click', (e) => { const question = e.currentTarget.dataset.question; if (question) { const chatInput = document.getElementById('chatInput'); chatInput.value = question; chatInput.focus(); this.autoResizeTextarea(chatInput); } }); }); } setupChatInput() { const chatInput = document.getElementById('chatInput'); const sendBtn = document.getElementById('chatSendBtn'); const clearBtn = document.getElementById('chatClearBtn'); // Auto-resize textarea chatInput.addEventListener('input', () => { this.autoResizeTextarea(chatInput); }); // Handle Enter key chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendMessage(); } else if (e.key === 'Enter' && e.shiftKey) { // Allow new line } }); // Send button sendBtn.addEventListener('click', () => { this.sendMessage(); }); // Clear chat clearBtn.addEventListener('click', () => { this.clearChat(); }); // Attach button (placeholder) document.getElementById('attachBtn')?.addEventListener('click', () => { this.showNotification('File attachment coming soon!', 'info'); }); // Emoji button (placeholder) document.getElementById('emojiBtn')?.addEventListener('click', () => { this.showNotification('Emoji picker coming soon!', 'info'); }); } setupQuickActions() { // Quick action buttons in status card document.querySelectorAll('.quick-action-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); }); }); } autoResizeTextarea(textarea) { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px'; } sendMessage() { const chatInput = document.getElementById('chatInput'); const message = chatInput.value.trim(); if (!message) return; // Add user message to chat this.addMessageToChat(message, 'user'); // Clear input chatInput.value = ''; this.autoResizeTextarea(chatInput); // Show typing indicator this.showTypingIndicator(); // Update stats this.messageCount++; this.updateChatStats(); // Send to backend this.sendChatMessage(message); } addMessageToChat(message, sender = 'user') { const chatMessages = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); const currentTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); if (sender === 'user') { messageDiv.className = 'chat-message user-message'; messageDiv.innerHTML = `
You ${currentTime}

${this.escapeHtml(message)}

`; } else { messageDiv.className = 'chat-message bot-message'; messageDiv.innerHTML = `
Research Assistant ${currentTime} AI
${message}
`; } // Remove welcome message if it's the first user message const welcomeMessage = chatMessages.querySelector('.welcome-message-enhanced'); if (welcomeMessage && sender === 'user') { welcomeMessage.style.animation = 'fadeOut 0.3s ease-out forwards'; setTimeout(() => { welcomeMessage.remove(); }, 300); } chatMessages.appendChild(messageDiv); // Scroll to bottom const container = document.querySelector('.chat-messages-container'); container.scrollTop = container.scrollHeight; // Update chat status this.updateChatStatus('active'); } showTypingIndicator() { const loadingIndicator = document.getElementById('chatLoading'); loadingIndicator.style.display = 'flex'; // Scroll to show typing indicator const container = document.querySelector('.chat-messages-container'); setTimeout(() => { container.scrollTop = container.scrollHeight; }, 100); } hideTypingIndicator() { const loadingIndicator = document.getElementById('chatLoading'); loadingIndicator.style.display = 'none'; } clearChat() { if (confirm('Are you sure you want to clear the chat history?')) { const chatMessages = document.getElementById('chatMessages'); chatMessages.innerHTML = `
Research Assistant Just now AI

👋 Welcome back to Research Radar!

Chat cleared. I'm ready to help you with your research questions.

`; this.messageCount = 0; this.updateChatStats(); this.updateChatStatus('ready'); this.showNotification('Chat cleared successfully', 'success'); } } updateChatStats() { const messageCountEl = document.getElementById('messageCount'); const sessionTimeEl = document.getElementById('sessionTime'); if (messageCountEl) { messageCountEl.textContent = this.messageCount; } } startSessionTimer() { setInterval(() => { const sessionTimeEl = document.getElementById('sessionTime'); if (sessionTimeEl) { const elapsed = Math.floor((Date.now() - this.sessionStartTime) / 1000); const minutes = Math.floor(elapsed / 60); const seconds = elapsed % 60; sessionTimeEl.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } }, 1000); } updateChatStatus(status) { const statusTitle = document.getElementById('statusTitle'); const statusDescription = document.getElementById('statusDescription'); switch (status) { case 'ready': statusTitle.textContent = 'Ready to Chat'; statusDescription.textContent = 'Upload a document or search for papers to get started'; break; case 'active': statusTitle.textContent = 'Chat Active'; statusDescription.textContent = 'AI assistant is ready to answer your questions'; break; case 'processing': statusTitle.textContent = 'Processing...'; statusDescription.textContent = 'AI is analyzing your question'; break; } } sendChatMessage(message) { this.updateChatStatus('processing'); fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: message }) }) .then(response => response.json()) .then(data => { this.hideTypingIndicator(); if (data.success) { this.addMessageToChat(data.response, 'bot'); this.messageCount++; this.updateChatStats(); } else { this.addMessageToChat( `I apologize, but I encountered an error: ${data.error || 'Unknown error'}. Please try again.`, 'bot' ); } this.updateChatStatus('active'); }) .catch(error => { console.error('Chat error:', error); this.hideTypingIndicator(); this.addMessageToChat( 'I apologize, but I\'m having trouble connecting right now. Please check your connection and try again.', 'bot' ); this.updateChatStatus('ready'); }); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Global functions for HTML onclick handlers toggleSuggestions() { const content = document.getElementById('suggestionsContent'); const toggle = document.querySelector('.suggestions-toggle i'); if (content.classList.contains('collapsed')) { content.classList.remove('collapsed'); toggle.className = 'fas fa-chevron-up'; } else { content.classList.add('collapsed'); toggle.className = 'fas fa-chevron-down'; } } showQuickActions() { const quickActions = document.getElementById('chatQuickActions'); quickActions.style.display = 'block'; quickActions.style.animation = 'slideUp 0.3s ease-out'; } hideQuickActions() { const quickActions = document.getElementById('chatQuickActions'); quickActions.style.animation = 'slideDown 0.3s ease-out'; setTimeout(() => { quickActions.style.display = 'none'; }, 300); } generateSummary() { this.addMessageToChat('Please generate a summary of the current document.', 'user'); this.sendChatMessage('Please generate a summary of the current document.'); this.hideQuickActions(); } extractKeyPoints() { this.addMessageToChat('What are the key points from this paper?', 'user'); this.sendChatMessage('What are the key points from this paper?'); this.hideQuickActions(); } findRelatedPapers() { this.addMessageToChat('Can you suggest related papers to this research?', 'user'); this.sendChatMessage('Can you suggest related papers to this research?'); this.hideQuickActions(); } exportChat() { // Get all messages const messages = document.querySelectorAll('.chat-message'); let chatText = 'Research Radar Chat Export\n'; chatText += '='.repeat(50) + '\n\n'; messages.forEach(message => { const sender = message.querySelector('.message-sender')?.textContent || 'Unknown'; const time = message.querySelector('.message-time')?.textContent || ''; const text = message.querySelector('.message-text')?.textContent || ''; if (text.trim()) { chatText += `[${time}] ${sender}:\n${text.trim()}\n\n`; } }); // Create and download file const blob = new Blob([chatText], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `research-radar-chat-${new Date().toISOString().split('T')[0]}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); this.showNotification('Chat exported successfully!', 'success'); this.hideQuickActions(); } // Summary + Chat Functionality openSummaryChat(paperData, summaryText) { console.log('openSummaryChat called with:', { paperData, summaryText }); // Hide current section and show summary-chat document.querySelectorAll('.section').forEach(section => { section.style.display = 'none'; }); const summarySection = document.getElementById('summary-chat'); if (!summarySection) { console.error('summary-chat section not found!'); this.showToast('Error: Summary section not found', 'error'); return; } summarySection.style.display = 'block'; // Update paper information document.getElementById('paperTitle').textContent = paperData.title || 'Research Paper'; document.getElementById('paperAuthor').textContent = paperData.authors ? paperData.authors.join(', ') : 'Unknown Author'; document.getElementById('paperDate').textContent = paperData.published || new Date().getFullYear(); document.getElementById('paperCategory').textContent = paperData.category || 'Research'; // Show summary this.displaySummaryInPanel(summaryText); // Setup chat panel this.setupChatPanel(); // Store current paper data for chat context this.currentPaper = paperData; // Default to Chat tab for immediate Q&A after tabs are in DOM setTimeout(() => { try { switchTab('chat'); } catch (_) {} const chatInput = document.getElementById('chatInputPanel'); if (chatInput) chatInput.focus(); }, 150); } displaySummaryInPanel(summaryText) { const summaryLoading = document.getElementById('summaryLoading'); const summaryTextEl = document.getElementById('summaryText'); // Hide loading and show summary summaryLoading.style.display = 'none'; summaryTextEl.style.display = 'block'; summaryTextEl.innerHTML = this.formatSummaryText(summaryText); // Update stats this.updateSummaryStats(summaryText); } formatSummaryText(text) { // Convert plain text to formatted HTML return text .replace(/\n\n/g, '

') .replace(/\n/g, '
') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') .replace(/^/, '

') .replace(/$/, '

'); } updateSummaryStats(text) { const wordCount = text.split(/\s+/).length; const readingTime = Math.ceil(wordCount / 200); // Average reading speed const compressionRatio = Math.round((1 - (text.length / (text.length * 3))) * 100); // Estimate const wc = document.getElementById('wordCount'); const rt = document.getElementById('readingTime'); const cr = document.getElementById('compressionRatio'); if (wc) wc.textContent = wordCount.toLocaleString(); if (rt) rt.textContent = `${readingTime} min`; if (cr) cr.textContent = `${compressionRatio}%`; } setupChatPanel() { const chatInput = document.getElementById('chatInputPanel'); const sendBtn = document.getElementById('chatSendBtnPanel'); if (!chatInput || !sendBtn) return; // Clear any existing event listeners by cloning const newChatInput = chatInput.cloneNode(true); chatInput.parentNode.replaceChild(newChatInput, chatInput); const newSendBtn = sendBtn.cloneNode(true); sendBtn.parentNode.replaceChild(newSendBtn, sendBtn); // Add new event listeners newChatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.sendChatMessagePanel(); } }); newChatInput.addEventListener('input', () => { this.autoResizeTextarea(newChatInput); }); newSendBtn.addEventListener('click', () => { this.sendChatMessagePanel(); }); } sendChatMessagePanel() { const chatInput = document.getElementById('chatInputPanel'); const message = chatInput.value.trim(); if (!message) return; // Add user message this.addChatMessagePanel(message, 'user'); // Clear input chatInput.value = ''; this.autoResizeTextarea(chatInput); // Show typing indicator and send to backend this.showChatTypingPanel(); this.sendChatToBackend(message); } addChatMessagePanel(message, sender) { const chatPanel = document.getElementById('chatMessagesPanel'); // Remove welcome message if it exists const welcome = chatPanel.querySelector('.chat-welcome'); if (welcome && sender === 'user') { welcome.remove(); } const messageDiv = document.createElement('div'); messageDiv.className = `chat-message-panel ${sender}`; const currentTime = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); messageDiv.innerHTML = `
${sender === 'bot' ? message : this.escapeHtml(message)}
${currentTime}
`; chatPanel.appendChild(messageDiv); chatPanel.scrollTop = chatPanel.scrollHeight; } showChatTypingPanel() { const chatPanel = document.getElementById('chatMessagesPanel'); const typingDiv = document.createElement('div'); typingDiv.className = 'chat-message-panel bot'; typingDiv.id = 'typingIndicatorPanel'; typingDiv.innerHTML = `
AI is thinking...
`; chatPanel.appendChild(typingDiv); chatPanel.scrollTop = chatPanel.scrollHeight; } hideChatTypingPanel() { const typingIndicator = document.getElementById('typingIndicatorPanel'); if (typingIndicator) { typingIndicator.remove(); } } sendChatToBackend(message) { fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: message }) }) .then(response => response.json()) .then(data => { this.hideChatTypingPanel(); if (data.success) { this.addChatMessagePanel(data.response, 'bot'); } else { this.addChatMessagePanel( `I apologize, but I encountered an error: ${data.error || 'Unknown error'}. Please try again.`, 'bot' ); } }) .catch(error => { console.error('Chat error:', error); this.hideChatTypingPanel(); this.addChatMessagePanel( 'I apologize, but I\'m having trouble connecting right now. Please check your connection and try again.', 'bot' ); }); } // My Papers functionality async loadMyPapers() { const loadingEl = document.getElementById('mypapersLoading'); const emptyEl = document.getElementById('mypapersEmpty'); const gridEl = document.getElementById('papersGrid'); if (loadingEl) loadingEl.style.display = 'block'; if (emptyEl) emptyEl.style.display = 'none'; if (gridEl) gridEl.innerHTML = ''; try { const response = await fetch('/documents'); const data = await response.json(); if (response.ok && data.success) { this.displayMyPapers(data.documents); } else { throw new Error(data.error || 'Failed to load papers'); } } catch (error) { console.error('Error loading papers:', error); this.showToast('Failed to load papers', 'error'); if (emptyEl) emptyEl.style.display = 'block'; } finally { if (loadingEl) loadingEl.style.display = 'none'; } } displayMyPapers(documents) { const emptyEl = document.getElementById('mypapersEmpty'); const gridEl = document.getElementById('papersGrid'); if (!documents || documents.length === 0) { if (emptyEl) emptyEl.style.display = 'block'; if (gridEl) gridEl.innerHTML = ''; return; } if (emptyEl) emptyEl.style.display = 'none'; if (!gridEl) return; gridEl.innerHTML = documents.map(doc => this.createPaperCard(doc)).join(''); // Add event listeners to the buttons after creating them this.setupMyPapersButtons(); } setupMyPapersButtons() { console.log('Setting up My Papers buttons...'); // Add event listeners to Open buttons const openButtons = document.querySelectorAll('.paper-action-btn.primary'); console.log(`Found ${openButtons.length} Open buttons`); openButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const docId = button.getAttribute('data-doc-id'); console.log('Open button clicked for docId:', docId); this.openPaperFromMyPapers(docId); }); }); // Add event listeners to Delete buttons const deleteButtons = document.querySelectorAll('.paper-action-btn.secondary'); console.log(`Found ${deleteButtons.length} Delete buttons`); deleteButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const docId = button.getAttribute('data-doc-id'); console.log('Delete button clicked for docId:', docId); this.deletePaper(docId); }); }); } createPaperCard(doc) { const icon = this.getDocumentIcon(doc.type); const authors = Array.isArray(doc.authors) ? doc.authors.join(', ') : doc.authors || 'Unknown'; const date = doc.upload_date || doc.published || 'Unknown Date'; return `
${this.escapeHtml(doc.title)}
${this.escapeHtml(authors)} ${this.escapeHtml(date)} ${doc.word_count ? ` ${doc.word_count.toLocaleString()} words` : ''}
`; } getDocumentIcon(type) { switch (type) { case 'arxiv_paper': return 'fas fa-graduation-cap'; case 'uploaded_document': return 'fas fa-file-upload'; default: return 'fas fa-file-alt'; } } async openPaperFromMyPapers(docId) { console.log('ResearchRadar.openPaperFromMyPapers called with docId:', docId); try { // Set the document as active for chat this.currentDocumentId = docId; // Fetch document summary console.log('Fetching document summary...'); const response = await fetch(`/documents/${docId}/summary`); const data = await response.json(); console.log('Summary response:', data); if (response.ok && data.success) { // Activate the document for chat await fetch(`/documents/${docId}/activate`, { method: 'POST', headers: { 'Content-Type': 'application/json' } }); // Redirect to summary and chat page console.log('Redirecting to summary and chat...'); this.openSummaryChat(data.document, data.summary); this.showToast('Paper loaded successfully', 'success'); } else { throw new Error(data.error || 'Failed to load paper'); } } catch (error) { console.error('Error opening paper:', error); this.showToast('Failed to open paper', 'error'); } } async deletePaper(docId) { console.log('ResearchRadar.deletePaper called with docId:', docId); if (!confirm('Are you sure you want to delete this paper? This action cannot be undone.')) { return; } try { console.log('Deleting document...'); const response = await fetch(`/documents/${docId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); console.log('Delete response:', data); if (response.ok && data.success) { const card = document.querySelector(`[data-doc-id="${docId}"]`); if (card) { card.remove(); } this.showToast('Paper deleted successfully', 'success'); } else { throw new Error(data.error || 'Failed to delete paper'); } } catch (error) { console.error('Error deleting paper:', error); this.showToast('Failed to delete paper', 'error'); } } async clearAllPapers() { if (!confirm('Are you sure you want to clear all papers? This action cannot be undone.')) { return; } try { const response = await fetch('/documents', { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }); const data = await response.json(); if (response.ok && data.success) { const gridEl = document.getElementById('papersGrid'); if (gridEl) { gridEl.innerHTML = ''; document.getElementById('mypapersEmpty').style.display = 'block'; } this.showToast('All papers cleared successfully', 'success'); } else { throw new Error(data.error || 'Failed to clear papers'); } } catch (error) { console.error('Error clearing papers:', error); this.showToast('Failed to clear papers', 'error'); } } } // Global navigation functions function navigateToApp(section = 'search') { console.log(`Global navigateToApp called with section: ${section}`); if (window.researchRadar) { console.log('Using ResearchRadar instance'); window.researchRadar.navigateToApp(section); } else { console.log('ResearchRadar instance not ready, using fallback navigation'); // Fallback navigation if ResearchRadar isn't ready yet const landingPage = document.getElementById('landingPage'); const appPage = document.getElementById('appPage'); if (landingPage && appPage) { landingPage.classList.remove('active'); appPage.classList.add('active'); // Switch to the requested section setTimeout(() => { const sections = document.querySelectorAll('.section'); sections.forEach(s => s.classList.remove('active')); const targetSection = document.getElementById(section); if (targetSection) { targetSection.classList.add('active'); } // Update navigation const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => link.classList.remove('active')); const activeNavLink = document.querySelector(`[data-section="${section}"]`); if (activeNavLink) { activeNavLink.classList.add('active'); } }, 50); } } } function navigateToLanding() { console.log('Global navigateToLanding called'); if (window.researchRadar) { console.log('Using ResearchRadar instance'); window.researchRadar.navigateToLanding(); } else { console.log('ResearchRadar instance not ready, using fallback navigation'); // Fallback navigation if ResearchRadar isn't ready yet const landingPage = document.getElementById('landingPage'); const appPage = document.getElementById('appPage'); if (landingPage && appPage) { appPage.classList.remove('active'); landingPage.classList.add('active'); } } } // Global chat functions function toggleSuggestions() { if (window.researchRadar) { window.researchRadar.toggleSuggestions(); } } function showQuickActions() { if (window.researchRadar) { window.researchRadar.showQuickActions(); } } function hideQuickActions() { if (window.researchRadar) { window.researchRadar.hideQuickActions(); } } function generateSummary() { if (window.researchRadar) { window.researchRadar.generateSummary(); } } function extractKeyPoints() { if (window.researchRadar) { window.researchRadar.extractKeyPoints(); } } function findRelatedPapers() { if (window.researchRadar) { window.researchRadar.findRelatedPapers(); } } // Global functions for My Papers buttons function openPaperFromMyPapers(docId) { console.log('Global openPaperFromMyPapers called with docId:', docId); // Wait for ResearchRadar to be available const waitForResearchRadar = () => { if (window.researchRadar) { window.researchRadar.openPaperFromMyPapers(docId); } else { console.log('ResearchRadar not ready, waiting...'); setTimeout(waitForResearchRadar, 100); } }; waitForResearchRadar(); } function deletePaperFromMyPapers(docId) { console.log('Global deletePaperFromMyPapers called with docId:', docId); // Wait for ResearchRadar to be available const waitForResearchRadar = () => { if (window.researchRadar) { window.researchRadar.deletePaper(docId); } else { console.log('ResearchRadar not ready, waiting...'); setTimeout(waitForResearchRadar, 100); } }; waitForResearchRadar(); } function exportChat() { if (window.researchRadar) { window.researchRadar.exportChat(); } } // Enhanced Search Functions function toggleAdvancedSearch() { const advancedFilters = document.getElementById('advancedFilters'); const toggleBtn = document.querySelector('.advanced-search-btn'); if (advancedFilters) { const isShowing = advancedFilters.classList.toggle('show'); if (toggleBtn) { const icon = toggleBtn.querySelector('i'); if (icon) { if (isShowing) { icon.classList.remove('fa-sliders-h'); icon.classList.add('fa-times'); toggleBtn.classList.add('active'); } else { icon.classList.remove('fa-times'); icon.classList.add('fa-sliders-h'); toggleBtn.classList.remove('active'); } } } } } function toggleSearchTips() { const tipsContent = document.querySelector('.tips-content'); const tipsToggle = document.querySelector('.tips-toggle'); if (tipsContent) { tipsContent.classList.toggle('show'); if (tipsToggle) { const icon = tipsToggle.querySelector('i'); if (icon) { icon.classList.toggle('fa-chevron-down'); icon.classList.toggle('fa-chevron-up'); } } } } function clearSearchHistory() { localStorage.removeItem('recentSearches'); const recentSearchesContainer = document.getElementById('recentSearches'); if (recentSearchesContainer) { recentSearchesContainer.style.display = 'none'; } if (window.researchRadar) { window.researchRadar.showToast('Search history cleared', 'success'); } } // Enhanced Upload Functions function toggleUploadTips() { const tipsContent = document.getElementById('uploadTipsContent'); const tipsToggle = document.querySelector('.upload-tips .tips-toggle'); if (tipsContent) { tipsContent.classList.toggle('show'); if (tipsToggle) { const icon = tipsToggle.querySelector('i'); if (icon) { icon.classList.toggle('fa-chevron-down'); icon.classList.toggle('fa-chevron-up'); } } } } // Additional missing functions for summary-chat functionality function goBackToSearch() { console.log('Global goBackToSearch called'); if (window.researchRadar) { console.log('Using ResearchRadar instance for goBackToSearch'); // Hide summary-chat section const summarySection = document.getElementById('summary-chat'); if (summarySection) { summarySection.classList.remove('active'); summarySection.style.display = 'none'; } // Show search section and restore navigation window.researchRadar.switchSection('search'); console.log('Successfully returned to search section'); } else { console.log('ResearchRadar instance not ready, using fallback navigation'); // Fallback navigation const summarySection = document.getElementById('summary-chat'); const searchSection = document.getElementById('search'); if (summarySection) { summarySection.classList.remove('active'); summarySection.style.display = 'none'; } if (searchSection) { searchSection.classList.add('active'); searchSection.style.display = 'block'; } // Update navigation const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => link.classList.remove('active')); const searchNavLink = document.querySelector('[data-section="search"]'); if (searchNavLink) { searchNavLink.classList.add('active'); } } } function exportSummaryChat() { console.log('Exporting summary and chat...'); if (window.researchRadar) { window.researchRadar.showToast('Export feature coming soon!', 'info'); } } function shareSummary() { console.log('Sharing summary...'); if (window.researchRadar) { window.researchRadar.showToast('Share feature coming soon!', 'info'); } } function regenerateSummary() { console.log('Regenerating summary...'); if (window.researchRadar) { window.researchRadar.showToast('Regenerating summary...', 'info'); } } function copySummary() { const summaryText = document.getElementById('summaryText'); if (summaryText) { navigator.clipboard.writeText(summaryText.textContent).then(() => { if (window.researchRadar) { window.researchRadar.showToast('Summary copied to clipboard!', 'success'); } }); } } function askQuickQuestion(question) { const chatInput = document.getElementById('chatInputPanel'); if (chatInput) { chatInput.value = question; chatInput.focus(); } } // Global summarize paper function (fallback for any remaining onclick handlers) function summarizePaper(paperUrl) { console.log(`Global summarizePaper called with URL: ${paperUrl}`); if (window.researchRadar) { console.log('Using ResearchRadar instance for summarizePaper'); window.researchRadar.summarizePaper(paperUrl); } else { console.error('ResearchRadar instance not available for summarizePaper'); alert('Application not ready. Please try again in a moment.'); } } // Immediate setup for critical buttons (before full initialization) function setupCriticalButtons() { console.log('Setting up critical buttons immediately...'); // Set up the main navigation buttons with fallback functions const buttons = [ { selector: '.nav-cta-btn', action: () => navigateToApp('search'), name: 'Get Started' }, { selector: '.cta-button.primary', action: () => navigateToApp('search'), name: 'Start Exploring' }, { selector: '.cta-button.secondary', action: () => navigateToApp('upload'), name: 'Upload Paper' }, { selector: '.back-to-landing', action: () => navigateToLanding(), name: 'Back to Landing' }, { selector: '.app-nav .nav-brand', action: () => navigateToLanding(), name: 'Brand Logo' } ]; buttons.forEach(({ selector, action, name }) => { const button = document.querySelector(selector); if (button) { console.log(`✅ Setting up ${name} button`); button.removeAttribute('onclick'); button.addEventListener('click', (e) => { e.preventDefault(); console.log(`${name} button clicked!`); action(); }); } else { console.log(`❌ ${name} button not found`); } }); // Also setup summary page buttons if they exist setupSummaryPageButtonsGlobal(); } // Global function to setup summary page buttons function setupSummaryPageButtonsGlobal() { console.log('Setting up summary page buttons globally...'); // Summary page buttons with fallback functions const summaryButtons = [ { selector: '.back-btn', action: () => goBackToSearch(), name: 'Back to Search' }, { selector: '.summary-action-btn[title*="Copy"]', action: () => copySummary(), name: 'Copy Summary' }, { selector: '.summary-action-btn[title*="Regenerate"]', action: () => regenerateSummary(), name: 'Regenerate Summary' }, { selector: '.action-btn-header[title*="Export"]', action: () => exportSummaryChat(), name: 'Export Summary' }, { selector: '.action-btn-header[title*="Share"]', action: () => shareSummary(), name: 'Share Summary' } ]; summaryButtons.forEach(({ selector, action, name }) => { const button = document.querySelector(selector); if (button) { console.log(`✅ Setting up ${name} button`); button.removeAttribute('onclick'); // Clone button to remove all existing event listeners const newButton = button.cloneNode(true); button.parentNode.replaceChild(newButton, button); newButton.addEventListener('click', (e) => { e.preventDefault(); console.log(`${name} button clicked!`); action(); }); } else { console.log(`❌ ${name} button not found`); } }); // Setup quick question buttons const quickQuestionBtns = document.querySelectorAll('.quick-question-btn'); console.log(`Found ${quickQuestionBtns.length} quick question buttons`); quickQuestionBtns.forEach((btn, index) => { btn.removeAttribute('onclick'); // Clone button to remove existing listeners const newBtn = btn.cloneNode(true); btn.parentNode.replaceChild(newBtn, btn); newBtn.addEventListener('click', (e) => { e.preventDefault(); const question = newBtn.textContent.trim(); console.log(`Quick question button ${index + 1} clicked: ${question}`); askQuickQuestion(question); }); }); } // Run critical setup immediately if DOM is already loaded if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupCriticalButtons); } else { setupCriticalButtons(); } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { console.log('DOM Content Loaded - Initializing Research Radar...'); // Small delay to ensure all elements are rendered setTimeout(() => { window.researchRadar = new ResearchRadar(); console.log('🚀 Research Radar - Application initialized successfully!'); // Test if critical elements exist const testElements = [ 'searchInput', 'searchBtn', 'analyzeUrlBtn', 'fileInput', 'searchResults' ]; testElements.forEach(id => { const element = document.getElementById(id); console.log(`Element ${id}:`, element ? 'Found' : 'Not found'); }); // Test if Generate Summary buttons exist (they might be created dynamically) setTimeout(() => { const generateButtons = document.querySelectorAll('.generate-summary-btn'); console.log(`Dynamic Generate Summary buttons found: ${generateButtons.length}`); }, 1000); }, 100); }); // Add some additional CSS for animations const additionalCSS = ` @keyframes toastSlideOut { to { opacity: 0; transform: translateX(100%); } } `; const styleSheet = document.createElement('style'); styleSheet.textContent = additionalCSS; document.head.appendChild(styleSheet); // Tab switching functionality function switchTab(tabName) { // Strict tabs: only one panel visible document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); document.querySelector(`[data-tab="${tabName}"]`)?.classList.add('active'); document.getElementById(`${tabName}-tab`)?.classList.add('active'); if (tabName === 'chat') { setTimeout(() => { const chatInput = document.getElementById('chatInputPanel'); if (chatInput) chatInput.focus(); }, 100); } history.replaceState(null, null, `#${tabName}`); const tabDisplayName = tabName === 'summary' ? 'Summary' : 'Chat'; showToast(`Switched to ${tabDisplayName} tab`, 'info'); } // Initialize tab from URL hash function initializeTabFromHash() { const hash = window.location.hash.substring(1); if (hash === 'summary' || hash === 'chat') { switchTab(hash); } } // Quick question functionality function askQuickQuestion(question) { const chatInput = document.getElementById('chatInputPanel'); if (chatInput) { chatInput.value = question; chatInput.focus(); } } // Enhanced chat input functionality function initializeChatInput() { const chatInput = document.getElementById('chatInputPanel'); const sendBtn = document.getElementById('chatSendBtnPanel'); if (chatInput && sendBtn) { // Auto-resize textarea chatInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 120) + 'px'; }); // Handle Enter key chatInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { if (e.ctrlKey || e.metaKey) { // Ctrl+Enter or Cmd+Enter to send e.preventDefault(); sendChatMessage(); } else if (!e.shiftKey) { // Enter to send (unless Shift+Enter for new line) e.preventDefault(); sendChatMessage(); } } }); // Send button click sendBtn.addEventListener('click', sendChatMessage); } } // Send chat message functionality function sendChatMessage() { const chatInput = document.getElementById('chatInputPanel'); const message = chatInput.value.trim(); if (!message) { if (window.researchRadar) { window.researchRadar.showToast('Please enter a message', 'warning'); } return; } // Add user message to chat addMessageToChat('user', message); // Clear input chatInput.value = ''; chatInput.style.height = 'auto'; // Show typing indicator showTypingIndicator(); // Call backend chat API fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message }) }) .then(res => res.json()) .then(data => { hideTypingIndicator(); if (data && data.success) { addMessageToChat('assistant', data.response || ''); } else { addMessageToChat('assistant', `Error: ${data?.error || 'Unknown error'}`); } }) .catch(err => { console.error('Chat error:', err); hideTypingIndicator(); addMessageToChat('assistant', 'Network error. Please try again.'); }); } // Add message to chat function addMessageToChat(sender, message) { const chatContainer = document.getElementById('chatMessagesPanel'); const messageElement = document.createElement('div'); messageElement.className = `chat-message ${sender}`; const timestamp = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const avatarIcon = sender === 'user' ? 'fa-user' : 'fa-robot'; messageElement.innerHTML = `

${message}

${sender === 'user' ? 'You' : 'AI Assistant'} • ${timestamp}
`; // Remove welcome message if it exists const welcomeMessage = chatContainer.querySelector('.chat-welcome'); if (welcomeMessage) { welcomeMessage.style.display = 'none'; } chatContainer.appendChild(messageElement); // Scroll to bottom chatContainer.scrollTop = chatContainer.scrollHeight; } // Show typing indicator function showTypingIndicator() { const chatContainer = document.getElementById('chatMessagesPanel'); // Prevent adding multiple indicators if (document.getElementById('typingIndicator')) return; const typingIndicator = document.createElement('div'); typingIndicator.className = 'typing-indicator chat-message assistant'; typingIndicator.id = 'typingIndicator'; typingIndicator.innerHTML = `
`; chatContainer.appendChild(typingIndicator); chatContainer.scrollTop = chatContainer.scrollHeight; } // Hide typing indicator function hideTypingIndicator() { const typingIndicator = document.getElementById('typingIndicator'); if (typingIndicator) { typingIndicator.remove(); } } // Initialize when DOM is loaded document.addEventListener('DOMContentLoaded', function() { // ... existing code ... // Initialize tab functionality initializeTabFromHash(); initializeChatInput(); // Listen for hash changes window.addEventListener('hashchange', initializeTabFromHash); // ... existing code ... });