Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <!-- Updated Title --> | |
| <title>Sentry - Document Assistant</title> | |
| <!-- jQuery --> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
| <!-- Font Awesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" integrity="sha512-9usAa10IRO0HhonpyAIVpjrylPvoDwiPUiKdWk5t3PyolY1cOd4DSE0Ga+ri4AuTroPR5aQvXU9xC6qOPnzFeg==" crossorigin="anonymous" referrerpolicy="no-referrer" /> | |
| <style> | |
| /* --- CSS Variables for Theme --- */ | |
| :root { | |
| --primary-color: #2f3136; /* Darker background */ | |
| --secondary-color: #36393f; /* Main chat container background */ | |
| --tertiary-color: #40444b; /* Input fields, message bubbles */ | |
| --text-color: #dcddde; /* Main text color (light gray) */ | |
| --text-muted-color: #b9bbbe; /* Slightly dimmer text */ | |
| --accent-color: #5865f2; /* Sentry/Discord Blurple (Updated) */ | |
| --accent-hover-color: #4e5ae0; /* Darker accent for hover */ | |
| --user-message-bg: var(--accent-color); /* User message bubble */ | |
| --assistant-message-bg: var(--tertiary-color); /* Assistant message bubble */ | |
| --input-bg: var(--tertiary-color); | |
| --border-color: #202225; /* Darkest borders */ | |
| --success-color: #43b581; /* Green */ | |
| --error-color: #f04747; /* Red */ | |
| --font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
| --border-radius: 8px; | |
| --scrollbar-thumb-color: #202225; | |
| --scrollbar-track-color: var(--secondary-color); | |
| } | |
| /* --- General Body Styling --- */ | |
| body { | |
| font-family: var(--font-family); | |
| background-color: var(--primary-color); | |
| color: var(--text-color); | |
| margin: 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| padding: 15px; /* Reduced padding for smaller screens */ | |
| box-sizing: border-box; | |
| } | |
| /* --- Main Chat Container --- */ | |
| .chat-container { | |
| background-color: var(--secondary-color); | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); | |
| width: 100%; | |
| max-width: 750px; /* Slightly wider */ | |
| height: 90vh; /* Adjust height as needed */ | |
| max-height: 800px; /* Max height constraint */ | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; /* Contain children */ | |
| } | |
| /* --- Header --- */ | |
| header { | |
| background-color: var(--primary-color); | |
| padding: 15px 20px; | |
| border-bottom: 1px solid var(--border-color); | |
| text-align: center; | |
| flex-shrink: 0; /* Prevent header from shrinking */ | |
| } | |
| header h1 { | |
| margin: 0; | |
| font-size: 1.3em; | |
| color: #fff; | |
| font-weight: 600; | |
| } | |
| /* --- Upload Section --- */ | |
| .upload-section { | |
| padding: 12px 20px; /* Reduced padding */ | |
| border-bottom: 1px solid var(--border-color); | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| background-color: var(--secondary-color); | |
| flex-shrink: 0; /* Prevent shrinking */ | |
| } | |
| /* Hide default file input */ | |
| #pdfUpload { | |
| display: none; | |
| } | |
| /* Style the label like a button */ | |
| .upload-label { | |
| background-color: var(--accent-color); | |
| color: white; | |
| padding: 8px 15px; | |
| border-radius: 5px; | |
| cursor: pointer; | |
| transition: background-color 0.2s ease, opacity 0.2s ease; | |
| font-size: 0.9em; | |
| white-space: nowrap; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; /* Space between icon and text */ | |
| } | |
| .upload-label:hover { | |
| background-color: var(--accent-hover-color); | |
| } | |
| .upload-label[disabled] { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| } | |
| #uploadStatus { | |
| font-size: 0.85em; | |
| color: var(--text-muted-color); | |
| flex-grow: 1; /* Take remaining space */ | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| white-space: nowrap; | |
| line-height: 1.3; | |
| } | |
| #uploadStatus i { /* Style icons in status */ | |
| margin-right: 5px; | |
| } | |
| /* --- Chat Area --- */ | |
| #chat { | |
| flex-grow: 1; /* Take available space */ | |
| overflow-y: auto; /* Enable vertical scrolling */ | |
| padding: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 18px; /* Increased space between messages */ | |
| } | |
| /* Scrollbar styling (Webkit) */ | |
| #chat::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| #chat::-webkit-scrollbar-track { | |
| background: var(--scrollbar-track-color); | |
| border-radius: 4px; | |
| } | |
| #chat::-webkit-scrollbar-thumb { | |
| background-color: var(--scrollbar-thumb-color); | |
| border-radius: 4px; | |
| } | |
| #chat::-webkit-scrollbar-thumb:hover { | |
| background-color: var(--tertiary-color); /* Slightly lighter on hover */ | |
| } | |
| /* Scrollbar styling (Firefox) */ | |
| #chat { | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-track-color); | |
| } | |
| /* --- Message Styling --- */ | |
| .message { | |
| display: flex; | |
| max-width: 80%; /* Max width of message bubble */ | |
| opacity: 0; /* Start hidden for animation */ | |
| animation: fadeIn 0.4s ease forwards; | |
| position: relative; /* For potential absolute elements later */ | |
| line-height: 1.45; /* Improved readability */ | |
| } | |
| @keyframes fadeIn { | |
| to { opacity: 1; } | |
| } | |
| /* Common structure for icon + text block */ | |
| .message-inner-wrapper { | |
| display: flex; | |
| gap: 10px; /* Space between icon and text block */ | |
| width: 100%; /* Ensure wrapper takes full width */ | |
| } | |
| .sender-icon { | |
| font-size: 1.1em; /* Adjust icon size */ | |
| color: var(--text-muted-color); /* Default icon color */ | |
| margin-top: 2px; /* Fine-tune vertical alignment */ | |
| flex-shrink: 0; /* Prevent icon from shrinking */ | |
| } | |
| .message-text-block { | |
| display: flex; | |
| flex-direction: column; /* Stack name and content */ | |
| flex-grow: 1; /* Allow text block to grow */ | |
| } | |
| .sender-name { | |
| font-weight: 600; /* Bolder name */ | |
| margin-bottom: 5px; /* Space below name */ | |
| font-size: 0.88em; | |
| color: var(--text-muted-color); | |
| } | |
| .message-content { | |
| padding: 10px 15px; | |
| border-radius: var(--border-radius); | |
| word-wrap: break-word; | |
| font-size: 0.95em; | |
| background-color: var(--assistant-message-bg); /* Default background */ | |
| color: var(--text-color); | |
| } | |
| /* User Message Specific Styles */ | |
| .message.user { | |
| margin-left: auto; /* Align bubble to the right */ | |
| flex-direction: row-reverse; /* Put icon on the right */ | |
| } | |
| .message.user .message-inner-wrapper { | |
| flex-direction: row-reverse; /* Reverse icon and text block order */ | |
| } | |
| .message.user .sender-icon { | |
| color: var(--text-muted-color); /* Optional: Different user icon color */ | |
| } | |
| .message.user .sender-name { | |
| text-align: right; /* Align name to the right */ | |
| color: inherit; /* Use bubble text color */ | |
| } | |
| .message.user .message-content { | |
| background-color: var(--user-message-bg); | |
| color: white; /* Text color for user bubble */ | |
| border-bottom-right-radius: 4px; /* Subtle shape difference */ | |
| } | |
| /* Assistant Message Specific Styles */ | |
| .message.assistant { | |
| margin-right: auto; /* Align bubble to the left */ | |
| } | |
| .message.assistant .sender-icon { | |
| color: var(--accent-color); /* Sentry icon color */ | |
| } | |
| .message.assistant .sender-name { | |
| color: var(--accent-color); /* Sentry name color */ | |
| } | |
| .message.assistant .message-content { | |
| background-color: var(--assistant-message-bg); | |
| border-bottom-left-radius: 4px; /* Subtle shape difference */ | |
| } | |
| /* System Message Styling (for errors, info) */ | |
| .message.system { | |
| font-style: italic; | |
| color: var(--text-muted-color); /* Default system text color */ | |
| text-align: center; | |
| width: 100%; | |
| max-width: 100%; | |
| font-size: 0.9em; | |
| margin: 8px 0; /* Adjust spacing */ | |
| gap: 0; /* No gap needed */ | |
| } | |
| .message.system .message-inner-wrapper { /* System messages don't need the icon/name wrapper */ | |
| justify-content: center; | |
| } | |
| .message.system .message-content { | |
| background: none; | |
| padding: 0; | |
| display: inline-block; /* Center the text block */ | |
| } | |
| .message.system .message-content.error { | |
| color: var(--error-color); | |
| } | |
| .message.system .message-content.info { | |
| color: var(--success-color); | |
| } | |
| /* --- Typing Indicator --- */ | |
| .typing-indicator { | |
| display: flex; | |
| align-items: center; | |
| padding: 0px 20px 5px 20px; /* Add padding */ | |
| opacity: 0; | |
| transition: opacity 0.3s ease, height 0.3s ease; | |
| height: 0; /* Start hidden */ | |
| overflow: hidden; | |
| flex-shrink: 0; /* Prevent shrinking */ | |
| gap: 8px; /* Space between icon and text/dots */ | |
| } | |
| .typing-indicator.visible { | |
| opacity: 1; | |
| height: 25px; /* Make visible */ | |
| } | |
| .typing-indicator .sender-icon { /* Reuse sender icon style */ | |
| font-size: 0.95em; | |
| color: var(--accent-color); | |
| } | |
| .typing-indicator span { | |
| font-size: 0.88em; | |
| color: var(--text-muted-color); | |
| margin-right: 5px; | |
| } | |
| .typing-indicator .dot { | |
| display: inline-block; | |
| width: 6px; | |
| height: 6px; | |
| background-color: var(--text-muted-color); | |
| border-radius: 50%; | |
| margin: 0 2px; | |
| animation: typing 1.2s infinite ease-in-out; | |
| } | |
| .typing-indicator .dot:nth-child(1) { animation-delay: 0.0s; } | |
| .typing-indicator .dot:nth-child(2) { animation-delay: 0.2s; } | |
| .typing-indicator .dot:nth-child(3) { animation-delay: 0.4s; } | |
| @keyframes typing { | |
| 0%, 100% { transform: translateY(0); opacity: 0.5; } | |
| 50% { transform: translateY(-5px); opacity: 1; } | |
| } | |
| /* --- Input Area --- */ | |
| .input-area { | |
| display: flex; | |
| padding: 15px 20px; | |
| border-top: 1px solid var(--border-color); | |
| background-color: var(--secondary-color); /* Match chat bg */ | |
| flex-shrink: 0; /* Prevent shrinking */ | |
| gap: 10px; /* Space between textarea and button */ | |
| } | |
| #userInput { | |
| flex-grow: 1; | |
| background-color: var(--input-bg); | |
| color: var(--text-color); | |
| border: 1px solid var(--border-color); /* Subtle border */ | |
| border-radius: var(--border-radius); | |
| padding: 10px 15px; | |
| resize: none; /* Prevent manual resizing */ | |
| font-family: var(--font-family); | |
| font-size: 0.95em; | |
| max-height: 120px; /* Limit growth */ | |
| box-sizing: border-box; | |
| overflow-y: auto; /* Allow scrolling if text exceeds max height */ | |
| line-height: 1.4; /* Match message line height */ | |
| transition: border-color 0.2s ease, box-shadow 0.2s ease; | |
| } | |
| #userInput:focus { | |
| outline: none; | |
| border-color: var(--accent-color); | |
| box-shadow: 0 0 0 1px var(--accent-color); | |
| } | |
| #userInput::placeholder { | |
| color: var(--text-muted-color); | |
| opacity: 0.8; | |
| } | |
| #sendButton { | |
| background-color: var(--accent-color); | |
| color: white; | |
| border: none; | |
| border-radius: var(--border-radius); | |
| padding: 0 15px; | |
| cursor: pointer; | |
| font-size: 1.1em; /* Icon size */ | |
| transition: background-color 0.2s ease, opacity 0.2s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| height: 42px; /* Match input height approx */ | |
| width: 45px; /* Fixed width for the button */ | |
| flex-shrink: 0; /* Prevent button from shrinking */ | |
| } | |
| #sendButton:hover { | |
| background-color: var(--accent-hover-color); | |
| } | |
| #sendButton:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| background-color: var(--tertiary-color); /* Dim background when disabled */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Main Chat Container --> | |
| <div class="chat-container"> | |
| <!-- Header --> | |
| <header> | |
| <h1>SentryLabs Document Assistant</h1> | |
| </header> | |
| <!-- PDF Upload Section --> | |
| <div class="upload-section"> | |
| <label for="pdfUpload" class="upload-label" id="uploadLabel"> | |
| <i class="fas fa-file-pdf"></i> Choose PDF | |
| </label> | |
| <input type="file" id="pdfUpload" accept=".pdf" /> | |
| <span id="uploadStatus">Upload a document for analysis.</span> | |
| <!-- Hidden button, not really used now --> | |
| <button id="uploadButton" style="display: none;">Upload</button> | |
| </div> | |
| <!-- Chat Messages Area --> | |
| <div id="chat"> | |
| <!-- Initial Greeting from Sentry --> | |
| <div class="message assistant"> | |
| <div class="message-inner-wrapper"> | |
| <i class="fas fa-shield-alt sender-icon"></i> <!-- Sentry Icon --> | |
| <div class="message-text-block"> | |
| <div class="sender-name">Sentry</div> | |
| <div class="message-content"> | |
| I am Sentry, your SentryLabs assistant. Please upload a PDF document using the button above to begin our analysis. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Typing Indicator --> | |
| <div class="typing-indicator" id="typingIndicator"> | |
| <i class="fas fa-shield-alt sender-icon"></i> | |
| <span>Sentry is analyzing...</span> | |
| <div class="dot"></div> | |
| <div class="dot"></div> | |
| <div class="dot"></div> | |
| </div> | |
| <!-- User Input Area --> | |
| <div class="input-area"> | |
| <textarea id="userInput" placeholder="Ask Sentry about the document..." rows="1" disabled></textarea> <!-- Start disabled --> | |
| <button id="sendButton" title="Send Message" disabled> <!-- Start disabled --> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- JavaScript --> | |
| <script> | |
| $(document).ready(function() { | |
| // --- State Variables --- | |
| let chatHistory = []; // Stores pairs: [[userMsg, assistantMsg], ...] | |
| let pdfUploaded = false; | |
| // --- DOM Elements --- | |
| const chatBox = $('#chat'); | |
| const userInput = $('#userInput'); | |
| const sendButton = $('#sendButton'); | |
| const uploadLabel = $('#uploadLabel'); | |
| const pdfUploadInput = $('#pdfUpload'); | |
| const uploadStatus = $('#uploadStatus'); | |
| const typingIndicator = $('#typingIndicator'); | |
| // --- Helper Functions --- | |
| // Function to add a message to the chat interface | |
| function addMessage(sender, text, type = 'normal') { | |
| // Sanitize text to prevent basic HTML injection, preserve newlines for <br> conversion | |
| const sanitizedHtml = $('<div>').text(text).html().replace(/\n/g, '<br>'); | |
| let messageClass = sender; // 'user', 'assistant', or 'system' | |
| let senderName = ''; | |
| let senderIconHtml = ''; // Icon HTML string | |
| // Define names and icons based on sender | |
| if (sender === 'user') { | |
| senderName = 'You'; | |
| // Optional: User icon | |
| // senderIconHtml = '<i class="fas fa-user sender-icon"></i>'; | |
| } else if (sender === 'assistant') { | |
| senderName = 'Sentry'; | |
| senderIconHtml = '<i class="fas fa-shield-alt sender-icon"></i>'; | |
| } | |
| let messageHtml; | |
| // Handle different message types (normal, error, info) | |
| if (type === 'error' || type === 'info') { | |
| messageClass = 'system'; | |
| // Simple centered text for system messages | |
| messageHtml = `<div class="message-inner-wrapper"> | |
| <div class="message-content ${type}">${sanitizedHtml}</div> | |
| </div>`; | |
| } else { | |
| // Standard message structure with icon, name, content | |
| const nameHtml = senderName ? `<div class="sender-name">${senderName}</div>` : ''; | |
| messageHtml = ` | |
| <div class="message-inner-wrapper"> | |
| ${senderIconHtml} | |
| <div class="message-text-block"> | |
| ${nameHtml} | |
| <div class="message-content">${sanitizedHtml}</div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| // Create message element and append to chat | |
| const messageElement = $(`<div class="message ${messageClass}">${messageHtml}</div>`); | |
| chatBox.append(messageElement); | |
| // Scroll to the bottom of the chat | |
| scrollToBottom(); | |
| } | |
| // Function to scroll chat box to the bottom | |
| function scrollToBottom() { | |
| chatBox.animate({ scrollTop: chatBox[0].scrollHeight }, 300); | |
| } | |
| // Function to show/hide typing indicator | |
| function showTypingIndicator(show) { | |
| if (show) { | |
| typingIndicator.addClass('visible'); | |
| } else { | |
| typingIndicator.removeClass('visible'); | |
| } | |
| // Adjust scroll after potential layout shift from indicator | |
| setTimeout(scrollToBottom, 50); | |
| } | |
| // Adjust textarea height dynamically based on content | |
| function adjustTextareaHeight() { | |
| const textarea = userInput[0]; | |
| textarea.style.height = 'auto'; // Reset height to calculate scroll height accurately | |
| // Calculate the height needed for the content, plus a small buffer if desired | |
| const scrollHeight = textarea.scrollHeight; | |
| textarea.style.height = scrollHeight + 'px'; | |
| // Apply max-height constraint from CSS | |
| const maxHeight = parseInt(userInput.css('max-height')); | |
| if (scrollHeight > maxHeight) { | |
| textarea.style.overflowY = 'auto'; // Show scrollbar if content exceeds max height | |
| } else { | |
| textarea.style.overflowY = 'hidden'; // Hide scrollbar if content fits | |
| } | |
| } | |
| // --- Event Handlers --- | |
| // Trigger hidden file input when the styled label is clicked | |
| uploadLabel.on('click', function() { | |
| if (!$(this).prop('disabled')) { // Only trigger if not disabled | |
| pdfUploadInput.click(); | |
| } | |
| }); | |
| // Handle file selection via the hidden input | |
| pdfUploadInput.on('change', function() { | |
| const file = this.files[0]; | |
| if (file) { | |
| if (file.type === "application/pdf") { | |
| uploadStatus.text(`Selected: ${file.name}`).css('color', 'var(--text-muted-color)'); | |
| uploadFile(file); // Automatically start the upload | |
| } else { | |
| uploadStatus.html('<i class="fas fa-exclamation-triangle"></i> Error: Please select a PDF file.').css('color', 'var(--error-color)'); | |
| this.value = ''; // Reset file input to allow re-selection of the same file if needed | |
| } | |
| } | |
| }); | |
| // Handle sending a message | |
| function sendMessage() { | |
| const message = userInput.val().trim(); | |
| // Prevent sending empty messages or if PDF isn't uploaded | |
| if (message === "" || !pdfUploaded) return; | |
| // Display user's message | |
| addMessage('user', message); | |
| const currentUserMessage = message; // Store for history pairing | |
| // Clear input, disable controls, show typing indicator | |
| userInput.val('').prop('disabled', true); | |
| sendButton.prop('disabled', true); | |
| showTypingIndicator(true); | |
| adjustTextareaHeight(); // Reset height after clearing | |
| // Prepare history for the API (only completed pairs) | |
| const historyForApi = chatHistory.slice(); // Send a copy | |
| // Make the AJAX call to the backend | |
| $.ajax({ | |
| url: '/ask_question', | |
| type: 'POST', | |
| contentType: 'application/json', | |
| data: JSON.stringify({ | |
| message: currentUserMessage, | |
| history: historyForApi | |
| }), | |
| success: function(response) { | |
| if (response && response.response) { | |
| // Display Sentry's response | |
| addMessage('assistant', response.response); | |
| // Add the completed user/assistant pair to history | |
| chatHistory.push([currentUserMessage, response.response]); | |
| } else { | |
| // Handle cases where backend might return empty response field | |
| const errorMsg = "Received an unexpected empty response from Sentry."; | |
| addMessage('system', errorMsg, 'error'); | |
| // Record the turn with an empty assistant message for history integrity | |
| chatHistory.push([currentUserMessage, ""]); | |
| } | |
| }, | |
| error: function(jqXHR, textStatus, errorThrown) { | |
| // Display error message in chat | |
| let errorMsg = 'An error occurred while communicating with Sentry.'; | |
| if (jqXHR.responseJSON && jqXHR.responseJSON.response) { // Check for error in 'response' key first | |
| errorMsg = jqXHR.responseJSON.response; | |
| } else if (jqXHR.responseJSON && jqXHR.responseJSON.error) { // Check 'error' key | |
| errorMsg = jqXHR.responseJSON.error; | |
| } | |
| addMessage('system', errorMsg, 'error'); | |
| // Record the turn with an empty assistant message for history integrity | |
| chatHistory.push([currentUserMessage, ""]); | |
| }, | |
| complete: function() { | |
| // Re-enable input, hide typing indicator | |
| userInput.prop('disabled', false).focus(); // Re-enable and focus | |
| showTypingIndicator(false); | |
| // Re-evaluate send button state (might have typed while waiting) | |
| sendButton.prop('disabled', userInput.val().trim() === ''); | |
| } | |
| }); | |
| } | |
| // Attach send handler to button click | |
| sendButton.click(sendMessage); | |
| // Attach send handler to Enter key press in textarea (allow Shift+Enter for newline) | |
| userInput.keypress(function(e) { | |
| if (e.which === 13 && !e.shiftKey) { // Enter key pressed without Shift | |
| e.preventDefault(); // Prevent default newline behavior | |
| sendMessage(); | |
| } | |
| }); | |
| // Enable/disable send button based on input content and PDF status | |
| // Also adjust textarea height on input | |
| userInput.on('input keyup', function() { | |
| adjustTextareaHeight(); | |
| const isEmpty = $(this).val().trim() === ''; | |
| sendButton.prop('disabled', isEmpty || !pdfUploaded); | |
| }); | |
| // --- File Upload Function --- | |
| function uploadFile(file) { | |
| const formData = new FormData(); | |
| formData.append('pdf', file); | |
| // Update UI to show uploading state | |
| uploadStatus.html(`<i class="fas fa-spinner fa-spin"></i> Processing: ${file.name}...`).css('color', 'var(--text-muted-color)'); | |
| uploadLabel.prop('disabled', true).css('opacity', 0.6); // Disable upload button visually | |
| userInput.prop('disabled', true); // Disable input during upload | |
| sendButton.prop('disabled', true); // Disable send during upload | |
| $.ajax({ | |
| url: '/upload_pdf', | |
| type: 'POST', | |
| data: formData, | |
| contentType: false, // Important for FormData | |
| processData: false, // Important for FormData | |
| success: function(response) { | |
| // Success: Update status, enable input/send | |
| uploadStatus.html(`<i class="fas fa-check-circle"></i> Document ready for analysis.`).css('color', 'var(--success-color)'); | |
| pdfUploaded = true; | |
| userInput.prop('disabled', false).attr('placeholder', 'Ask Sentry about the document...').focus(); | |
| // Enable send button only if there's text already (unlikely but possible) | |
| sendButton.prop('disabled', userInput.val().trim() === ''); | |
| // Optional: Clear chat history if you want each PDF to start fresh | |
| // chatHistory = []; | |
| // chatBox.find('.message:not(:first-child)').remove(); // Remove all but initial greeting | |
| }, | |
| error: function(jqXHR, textStatus, errorThrown) { | |
| // Error: Show error message, keep controls disabled | |
| let errorMsg = 'Failed to process the PDF.'; | |
| if (jqXHR.responseJSON && jqXHR.responseJSON.error) { | |
| errorMsg = jqXHR.responseJSON.error; | |
| } // Add more specific parsing if needed | |
| uploadStatus.html(`<i class="fas fa-exclamation-triangle"></i> Error: ${errorMsg}`).css('color', 'var(--error-color)'); | |
| pdfUploaded = false; | |
| userInput.prop('disabled', true).attr('placeholder', 'Upload failed. Please try again.'); | |
| sendButton.prop('disabled', true); | |
| // Add error to chat as well | |
| addMessage('system', `PDF processing failed: ${errorMsg}`, 'error'); | |
| }, | |
| complete: function() { | |
| // Always re-enable the upload button regardless of outcome | |
| uploadLabel.prop('disabled', false).css('opacity', 1); | |
| // Clear the file input value so the user can re-upload the same file if needed after an error | |
| pdfUploadInput.val(''); | |
| } | |
| }); | |
| } | |
| // --- Initial Page Load Setup --- | |
| adjustTextareaHeight(); // Initial adjustment for placeholder | |
| // Initial state is set directly in the HTML (disabled input/button) | |
| }); | |
| </script> | |
| </body> | |
| </html> |