// DOM Elements const uploadBox = document.getElementById('upload-box'); const pdfUpload = document.getElementById('pdf-upload'); const welcomeScreen = document.getElementById('welcome-screen'); const chatContainer = document.getElementById('chat-container'); const chatMessages = document.getElementById('chat-messages'); const chatInput = document.getElementById('chat-input'); const sendButton = document.getElementById('send-button'); const searchToggle = document.getElementById('search-toggle'); const modelSelect = document.getElementById('model-select'); const sessionInfo = document.getElementById('session-info'); const currentFileName = document.getElementById('current-file-name'); const clearHistoryBtn = document.getElementById('clear-history'); const removePdfBtn = document.getElementById('remove-pdf'); const newChatBtn = document.getElementById('new-chat'); const loadingOverlay = document.getElementById('loading-overlay'); const loadingText = document.getElementById('loading-text'); const getStartedBtn = document.getElementById('get-started-btn'); const contextContent = document.getElementById('context-content'); const contextSidebar = document.getElementById('context-sidebar'); const toggleContextBtn = document.getElementById('toggle-context'); const menuToggle = document.getElementById('menu-toggle'); const sidebar = document.querySelector('.sidebar'); // App state let currentSessionId = null; let lastContextData = null; let isMobile = window.innerWidth <= 768; // Event listeners uploadBox.addEventListener('click', () => pdfUpload.click()); pdfUpload.addEventListener('change', handleFileUpload); sendButton.addEventListener('click', sendMessage); chatInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); chatInput.addEventListener('input', () => { sendButton.disabled = chatInput.value.trim() === ''; }); clearHistoryBtn.addEventListener('click', clearChatHistory); removePdfBtn.addEventListener('click', removePdf); newChatBtn.addEventListener('click', resetApp); getStartedBtn.addEventListener('click', () => { uploadBox.click(); }); toggleContextBtn.addEventListener('click', toggleContextSidebar); // Mobile menu event listeners if (menuToggle) { menuToggle.addEventListener('click', () => { sidebar.classList.toggle('show'); }); } // Handle window resize window.addEventListener('resize', () => { const wasMobile = isMobile; isMobile = window.innerWidth <= 768; // If we're transitioning between mobile/desktop if (wasMobile !== isMobile) { updateMobileUI(); } }); // Initialize the app initializeApp(); // Functions function initializeApp() { updateMobileUI(); // Check if the page was refreshed const pageWasRefreshed = ( window.performance && window.performance.navigation && window.performance.navigation.type === 1 ) || document.referrer === document.location.href; // If page was refreshed, clear any saved session if (pageWasRefreshed) { localStorage.removeItem('pdf_insight_session'); return; } // Check if we have a session in localStorage const savedSession = localStorage.getItem('pdf_insight_session'); if (savedSession) { try { const session = JSON.parse(savedSession); currentSessionId = session.id; currentFileName.textContent = session.fileName; // Show chat interface welcomeScreen.classList.add('hidden'); chatContainer.classList.remove('hidden'); sessionInfo.classList.remove('hidden'); // Load chat history fetchChatHistory(); } catch (e) { console.error('Failed to load saved session:', e); localStorage.removeItem('pdf_insight_session'); } } } // Update UI based on mobile/desktop view function updateMobileUI() { if (isMobile) { if (menuToggle) menuToggle.classList.remove('hidden'); contextSidebar.classList.add('collapsed'); } else { if (menuToggle) menuToggle.classList.add('hidden'); sidebar.classList.remove('show'); } } async function handleFileUpload(e) { const file = e.target.files[0]; if (!file) return; try { // Show loading overlay showLoading('Processing Document...'); const formData = new FormData(); formData.append('file', file); formData.append('model_name', modelSelect.value); const response = await fetch('/upload-pdf', { method: 'POST', body: formData }); const data = await response.json(); if (data.status === 'success') { currentSessionId = data.session_id; currentFileName.textContent = file.name; // Save session to localStorage localStorage.setItem('pdf_insight_session', JSON.stringify({ id: currentSessionId, fileName: file.name })); // Show chat interface welcomeScreen.classList.add('hidden'); chatContainer.classList.remove('hidden'); sessionInfo.classList.remove('hidden'); // Reset chat history view chatMessages.innerHTML = `

Upload successful! You can now ask questions about "${file.name}".

`; // Enable input chatInput.disabled = false; chatInput.placeholder = 'Ask a question about the document...'; // Close sidebar on mobile after uploading if (isMobile) { sidebar.classList.remove('show'); } } else { // Enhanced error display let errorDetails = ''; if (data.detail) { errorDetails = data.detail; } if (data.type) { errorDetails = `${data.type}: ${errorDetails}`; } showError('Error: ' + (errorDetails || 'Failed to process document')); // Add a more detailed error in the chat area if we're already in chat mode if (!welcomeScreen.classList.contains('hidden')) { const errorMessageDiv = document.createElement('div'); errorMessageDiv.className = 'system-message error'; errorMessageDiv.innerHTML = `

Error Processing Document

${errorDetails}

Please make sure you have set up all required API keys in the .env file.

`; chatMessages.appendChild(errorMessageDiv); } } } catch (error) { console.error('Error uploading file:', error); showError('Failed to upload document. Please try again.'); } finally { hideLoading(); } } async function sendMessage() { const query = chatInput.value.trim(); if (!query || !currentSessionId) return; // Disable input and show typing indicator chatInput.disabled = true; sendButton.disabled = true; // Add user message to chat const userMessageElement = createMessageElement('user', query); chatMessages.appendChild(userMessageElement); chatMessages.scrollTop = chatMessages.scrollHeight; // Clear input chatInput.value = ''; try { // Show loading state const typingIndicator = createTypingIndicator(); chatMessages.appendChild(typingIndicator); chatMessages.scrollTop = chatMessages.scrollHeight; const response = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId, query: query, use_search: searchToggle.checked, model_name: modelSelect.value }) }); const data = await response.json(); // Remove typing indicator chatMessages.removeChild(typingIndicator); if (data.status === 'success') { // Add assistant message const assistantMessageElement = createMessageElement('assistant', data.answer); chatMessages.appendChild(assistantMessageElement); chatMessages.scrollTop = chatMessages.scrollHeight; // Apply syntax highlighting to code blocks applyCodeHighlighting(); // Update context sidebar updateContextSidebar(data.context_used); lastContextData = data.context_used; } else { showError('Failed to get response: ' + data.detail); } } catch (error) { console.error('Error sending message:', error); showError('Failed to get response. Please try again.'); // Remove typing indicator if it exists const indicator = document.querySelector('.typing-indicator'); if (indicator) { chatMessages.removeChild(indicator); } } finally { // Re-enable input chatInput.disabled = false; chatInput.focus(); } } async function fetchChatHistory() { if (!currentSessionId) return; try { showLoading('Loading chat history...'); const response = await fetch('/chat-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId }) }); const data = await response.json(); if (data.status === 'success' && data.history.length > 0) { // Clear chat messages and add system message chatMessages.innerHTML = `

Continuing your conversation about "${currentFileName.textContent}".

`; // Add all messages from history data.history.forEach(item => { const userMessage = createMessageElement('user', item.user); const assistantMessage = createMessageElement('assistant', item.assistant); chatMessages.appendChild(userMessage); chatMessages.appendChild(assistantMessage); }); // Apply syntax highlighting to code blocks applyCodeHighlighting(); // Scroll to bottom chatMessages.scrollTop = chatMessages.scrollHeight; } } catch (error) { console.error('Error fetching chat history:', error); showError('Failed to load chat history.'); } finally { hideLoading(); } } async function clearChatHistory() { if (!currentSessionId) return; try { showLoading('Clearing chat history...'); const response = await fetch('/clear-history', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId }) }); const data = await response.json(); if (data.status === 'success') { // Reset chat messages chatMessages.innerHTML = `

Chat history cleared. You can continue asking questions about "${currentFileName.textContent}".

`; // Reset context contextContent.innerHTML = '

No context available yet. Ask a question first.

'; lastContextData = null; } else { showError('Failed to clear chat history: ' + data.detail); } } catch (error) { console.error('Error clearing chat history:', error); showError('Failed to clear chat history.'); } finally { hideLoading(); } } async function removePdf() { if (!currentSessionId) return; try { showLoading('Removing PDF from the system...'); const response = await fetch('/remove-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: currentSessionId }) }); const data = await response.json(); if (data.status === 'success') { // Reset the app resetApp(); showError('PDF file has been removed from the system.'); } else { showError('Failed to remove PDF: ' + data.detail); } } catch (error) { console.error('Error removing PDF:', error); showError('Failed to remove PDF. Please try again.'); } finally { hideLoading(); } } function resetApp() { // Clear current session currentSessionId = null; lastContextData = null; localStorage.removeItem('pdf_insight_session'); // Reset UI welcomeScreen.classList.remove('hidden'); chatContainer.classList.add('hidden'); sessionInfo.classList.add('hidden'); // Reset file input pdfUpload.value = ''; // Reset chat messages chatMessages.innerHTML = `

Upload successful! You can now ask questions about the document.

`; // Reset context sidebar contextContent.innerHTML = '

No context available yet. Ask a question first.

'; } function createMessageElement(type, content) { const div = document.createElement('div'); div.className = `message ${type}-message`; // For user messages, just escape HTML if (type === 'user') { div.innerHTML = `
${escapeHTML(content)}
${formatTimestamp(new Date())}
`; } // For assistant messages, render with Markdown else { // Configure marked.js options marked.setOptions({ breaks: true, // Add
on single line breaks gfm: true, // GitHub Flavored Markdown sanitize: false // Allow HTML in the input }); // Process the content with marked const renderedContent = marked.parse(content); div.innerHTML = `
${renderedContent}
${formatTimestamp(new Date())}
`; } return div; } function createTypingIndicator() { const div = document.createElement('div'); div.className = 'message assistant-message typing-indicator'; div.innerHTML = `
`; return div; } function updateContextSidebar(contextData) { if (!contextData || contextData.length === 0) { contextContent.innerHTML = '

No context available for this response.

'; return; } contextContent.innerHTML = ''; contextData.forEach((item, index) => { const contextItem = document.createElement('div'); contextItem.className = 'context-item'; const score = Math.round((1 - item.score) * 100); // Convert distance to similarity score contextItem.innerHTML = ` Relevance: ${score}%
${truncateText(item.text, 300)}
`; contextContent.appendChild(contextItem); }); } function toggleContextSidebar() { contextSidebar.classList.toggle('collapsed'); // Update icon const icon = toggleContextBtn.querySelector('i'); if (contextSidebar.classList.contains('collapsed')) { icon.className = 'fas fa-angle-left'; } else { icon.className = 'fas fa-angle-right'; } } function applyCodeHighlighting() { // Find all code blocks in the assistant messages document.querySelectorAll('.assistant-message pre code').forEach(block => { hljs.highlightElement(block); }); } function showLoading(message) { loadingText.textContent = message || 'Loading...'; loadingOverlay.classList.remove('hidden'); } function hideLoading() { loadingOverlay.classList.add('hidden'); } function showError(message) { // Add error message to chat const errorDiv = document.createElement('div'); errorDiv.className = 'system-message error'; errorDiv.innerHTML = `

${message}

`; chatMessages.appendChild(errorDiv); chatMessages.scrollTop = chatMessages.scrollHeight; // Also show an alert for critical errors if (message.includes('API_KEY') || message.includes('environment variables')) { alert('Configuration Error: ' + message); } // Remove after 10 seconds setTimeout(() => { if (errorDiv.parentNode === chatMessages) { chatMessages.removeChild(errorDiv); } }, 10000); } // Helper functions function formatTimestamp(date) { return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); } function truncateText(text, maxLength) { if (text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; } function escapeHTML(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }