diff --git "a/static/js/script.js" "b/static/js/script.js" new file mode 100644--- /dev/null +++ "b/static/js/script.js" @@ -0,0 +1,2867 @@ +// 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 ... +});