NanobotzAI commited on
Commit
ad35b35
·
verified ·
1 Parent(s): f1790d7

Update index.html

Browse files

update to reflect sentry

Files changed (1) hide show
  1. index.html +417 -237
index.html CHANGED
@@ -3,25 +3,35 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Document Assistant</title>
 
 
7
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
 
8
  <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" />
9
  <style>
 
10
  :root {
11
- --primary-color: #36393f; /* Dark background */
12
- --secondary-color: #40444b; /* Slightly lighter dark */
13
- --text-color: #dcddde; /* Light gray text */
14
- --accent-color: #7289da; /* Blurple */
15
- --user-message-bg: #7289da; /* User message bubble */
16
- --assistant-message-bg: #4f545c; /* Assistant message bubble */
17
- --input-bg: #40444b;
18
- --border-color: #2f3136;
19
- --success-color: #43b581;
20
- --error-color: #f04747;
 
 
 
21
  --font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
22
  --border-radius: 8px;
 
 
23
  }
24
 
 
25
  body {
26
  font-family: var(--font-family);
27
  background-color: var(--primary-color);
@@ -31,42 +41,49 @@
31
  justify-content: center;
32
  align-items: center;
33
  min-height: 100vh;
34
- padding: 20px;
35
  box-sizing: border-box;
36
  }
37
 
 
38
  .chat-container {
39
  background-color: var(--secondary-color);
40
  border-radius: var(--border-radius);
41
- box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
42
  width: 100%;
43
- max-width: 700px;
44
- height: 85vh; /* Limit height */
 
45
  display: flex;
46
  flex-direction: column;
47
  overflow: hidden; /* Contain children */
48
  }
49
 
 
50
  header {
51
  background-color: var(--primary-color);
52
  padding: 15px 20px;
53
  border-bottom: 1px solid var(--border-color);
54
  text-align: center;
 
55
  }
56
 
57
  header h1 {
58
  margin: 0;
59
- font-size: 1.4em;
60
  color: #fff;
 
61
  }
62
 
 
63
  .upload-section {
64
- padding: 15px 20px;
65
  border-bottom: 1px solid var(--border-color);
66
  display: flex;
67
  align-items: center;
68
- gap: 10px;
69
- background-color: var(--secondary-color); /* Match container bg */
 
70
  }
71
 
72
  /* Hide default file input */
@@ -81,130 +98,259 @@
81
  padding: 8px 15px;
82
  border-radius: 5px;
83
  cursor: pointer;
84
- transition: background-color 0.2s ease;
85
  font-size: 0.9em;
86
  white-space: nowrap;
 
 
 
87
  }
88
 
89
  .upload-label:hover {
90
- background-color: #677bc4; /* Slightly darker accent */
 
 
 
 
91
  }
92
 
93
  #uploadStatus {
94
  font-size: 0.85em;
95
- color: var(--text-color);
96
  flex-grow: 1; /* Take remaining space */
97
  overflow: hidden;
98
  text-overflow: ellipsis;
99
  white-space: nowrap;
 
100
  }
 
 
 
101
 
 
102
  #chat {
103
  flex-grow: 1; /* Take available space */
104
- overflow-y: auto;
105
  padding: 20px;
106
  display: flex;
107
  flex-direction: column;
108
- gap: 15px; /* Space between messages */
109
  }
110
 
111
- /* Scrollbar styling (optional, WebKit browsers) */
112
  #chat::-webkit-scrollbar {
113
  width: 8px;
114
  }
115
  #chat::-webkit-scrollbar-track {
116
- background: var(--secondary-color);
 
117
  }
118
  #chat::-webkit-scrollbar-thumb {
119
- background-color: var(--input-bg);
120
  border-radius: 4px;
121
  }
122
  #chat::-webkit-scrollbar-thumb:hover {
123
- background-color: #5c616a;
 
 
 
 
 
124
  }
125
 
126
-
127
  .message {
128
  display: flex;
129
- max-width: 75%;
130
  opacity: 0; /* Start hidden for animation */
131
- animation: fadeIn 0.3s ease forwards;
 
 
132
  }
133
 
134
  @keyframes fadeIn {
135
  to { opacity: 1; }
136
  }
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  .message-content {
139
  padding: 10px 15px;
140
  border-radius: var(--border-radius);
141
- line-height: 1.4;
142
  word-wrap: break-word;
143
  font-size: 0.95em;
 
 
144
  }
145
 
 
146
  .message.user {
147
- margin-left: auto; /* Align right */
148
- flex-direction: row-reverse; /* Keep content left within flex */
 
 
 
149
  }
 
 
 
 
 
 
 
150
  .message.user .message-content {
151
  background-color: var(--user-message-bg);
152
- color: white;
153
- border-bottom-right-radius: 2px; /* Subtle shape difference */
154
  }
155
 
 
156
  .message.assistant {
157
- margin-right: auto; /* Align left */
 
 
 
158
  }
 
 
 
159
  .message.assistant .message-content {
160
  background-color: var(--assistant-message-bg);
161
- color: var(--text-color);
162
- border-bottom-left-radius: 2px; /* Subtle shape difference */
163
  }
164
 
165
- .message.system { /* For status messages like errors */
 
166
  font-style: italic;
167
- color: var(--error-color);
168
  text-align: center;
169
  width: 100%;
170
  max-width: 100%;
171
  font-size: 0.9em;
172
- margin: 5px 0;
 
173
  }
 
 
 
174
  .message.system .message-content {
175
  background: none;
176
  padding: 0;
 
177
  }
 
 
 
 
 
 
178
 
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  .input-area {
181
  display: flex;
182
  padding: 15px 20px;
183
  border-top: 1px solid var(--border-color);
184
- background-color: var(--primary-color);
 
 
185
  }
186
 
187
  #userInput {
188
  flex-grow: 1;
189
  background-color: var(--input-bg);
190
  color: var(--text-color);
191
- border: none;
192
  border-radius: var(--border-radius);
193
  padding: 10px 15px;
194
  resize: none; /* Prevent manual resizing */
195
  font-family: var(--font-family);
196
  font-size: 0.95em;
197
- margin-right: 10px;
198
- height: 40px; /* Initial height */
199
- max-height: 100px; /* Limit growth */
200
  box-sizing: border-box;
201
  overflow-y: auto; /* Allow scrolling if text exceeds max height */
 
 
202
  }
203
 
204
  #userInput:focus {
205
  outline: none;
206
- box-shadow: 0 0 0 2px var(--accent-color);
 
207
  }
 
 
 
 
 
208
 
209
  #sendButton {
210
  background-color: var(--accent-color);
@@ -213,307 +359,341 @@
213
  border-radius: var(--border-radius);
214
  padding: 0 15px;
215
  cursor: pointer;
216
- font-size: 1.2em; /* Icon size */
217
  transition: background-color 0.2s ease, opacity 0.2s ease;
218
  display: flex;
219
  align-items: center;
220
  justify-content: center;
221
- height: 40px;
222
- width: 50px; /* Fixed width for the button */
 
223
  }
224
 
225
  #sendButton:hover {
226
- background-color: #677bc4;
227
  }
228
 
229
  #sendButton:disabled {
230
  opacity: 0.5;
231
  cursor: not-allowed;
232
- }
233
-
234
- .typing-indicator {
235
- display: flex;
236
- align-items: center;
237
- padding: 5px 0 0 0; /* Add slight top padding */
238
- opacity: 0;
239
- transition: opacity 0.3s ease;
240
- height: 0; /* Start hidden */
241
- overflow: hidden;
242
- }
243
- .typing-indicator.visible {
244
- opacity: 1;
245
- height: 20px; /* Make visible */
246
- }
247
-
248
- .typing-indicator span {
249
- font-size: 0.85em;
250
- color: var(--text-color);
251
- margin-right: 5px;
252
- }
253
- .typing-indicator .dot {
254
- display: inline-block;
255
- width: 6px;
256
- height: 6px;
257
- background-color: var(--text-color);
258
- border-radius: 50%;
259
- margin: 0 2px;
260
- animation: typing 1s infinite ease-in-out;
261
- }
262
- .typing-indicator .dot:nth-child(2) { animation-delay: 0.2s; }
263
- .typing-indicator .dot:nth-child(3) { animation-delay: 0.4s; }
264
-
265
- @keyframes typing {
266
- 0%, 100% { transform: translateY(0); }
267
- 50% { transform: translateY(-4px); }
268
  }
269
 
270
  </style>
271
  </head>
272
  <body>
 
273
  <div class="chat-container">
 
274
  <header>
275
- <h1>Document Assistant</h1>
276
  </header>
277
 
 
278
  <div class="upload-section">
279
- <label for="pdfUpload" class="upload-label">
280
  <i class="fas fa-file-pdf"></i> Choose PDF
281
  </label>
282
  <input type="file" id="pdfUpload" accept=".pdf" />
283
- <span id="uploadStatus">No PDF uploaded yet.</span>
284
- <button id="uploadButton" style="display: none;">Upload</button> <!-- Hidden, triggered by JS -->
 
285
  </div>
286
 
 
287
  <div id="chat">
288
- <!-- Chat messages will appear here -->
289
  <div class="message assistant">
290
- <div class="message-content">
291
- Please upload a PDF document using the button above to begin.
292
- </div>
 
 
 
 
 
 
293
  </div>
294
  </div>
295
 
296
  <!-- Typing Indicator -->
297
  <div class="typing-indicator" id="typingIndicator">
298
- <span>Assistant is typing</span>
 
299
  <div class="dot"></div>
300
  <div class="dot"></div>
301
  <div class="dot"></div>
302
  </div>
303
 
 
304
  <div class="input-area">
305
- <textarea id="userInput" placeholder="Ask a question about the PDF..." rows="1"></textarea>
306
  <button id="sendButton" title="Send Message" disabled> <!-- Start disabled -->
307
  <i class="fas fa-paper-plane"></i>
308
  </button>
309
  </div>
310
  </div>
311
 
 
312
  <script>
313
  $(document).ready(function() {
314
- let chatHistory = []; // Use a JS array for history
 
315
  let pdfUploaded = false;
316
 
 
 
 
 
 
 
 
 
 
 
 
317
  // Function to add a message to the chat interface
318
  function addMessage(sender, text, type = 'normal') {
319
- // Sanitize text to prevent basic HTML injection
320
- const sanitizedText = $('<div>').text(text).html();
321
- let messageClass = sender; // 'user' or 'assistant' or 'system'
322
- let content = `<div class="message-content">${sanitizedText.replace(/\n/g, '<br>')}</div>`; // Replace newlines with <br>
323
-
324
- // Use system class for errors or status messages
325
- if (type === 'error') {
326
- messageClass = 'system';
327
- content = `<div class="message-content">${sanitizedText}</div>`; // No extra formatting needed
328
- } else if (type === 'info') {
329
- messageClass = 'system'; // Or style differently if needed
330
- content = `<div class="message-content" style="color: var(--success-color);">${sanitizedText}</div>`;
 
 
331
  }
332
 
333
- const messageElement = $(`<div class="message ${messageClass}">${content}</div>`);
334
- $('#chat').append(messageElement);
335
- // Scroll to bottom smoothly
336
- $('#chat').animate({ scrollTop: $('#chat')[0].scrollHeight }, 300);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  }
338
 
339
  // Function to show/hide typing indicator
340
  function showTypingIndicator(show) {
341
  if (show) {
342
- $('#typingIndicator').addClass('visible');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
  } else {
344
- $('#typingIndicator').removeClass('visible');
345
  }
346
- // Adjust scroll after potential layout shift
347
- setTimeout(() => {$('#chat').scrollTop($('#chat')[0].scrollHeight);}, 50);
348
  }
349
 
350
- // --- Upload Handling ---
351
- // Trigger hidden file input when label is clicked
352
- $('.upload-label').on('click', function() {
353
- $('#pdfUpload').click();
 
 
 
354
  });
355
 
356
- // Handle file selection
357
- $('#pdfUpload').on('change', function() {
358
  const file = this.files[0];
359
  if (file) {
360
  if (file.type === "application/pdf") {
361
- $('#uploadStatus').text(`Selected: ${file.name}`).css('color', 'var(--text-color)');
362
- // Automatically trigger the hidden upload button's logic
363
- uploadFile(file);
364
  } else {
365
- $('#uploadStatus').text('Error: Please select a PDF file.').css('color', 'var(--error-color)');
366
- this.value = ''; // Reset file input
367
  }
368
- } else {
369
- // Optional: Handle case where selection is cancelled
370
- // $('#uploadStatus').text('No file selected.');
371
  }
372
  });
373
 
374
- // Actual upload logic (separated function)
375
- function uploadFile(file) {
376
- const formData = new FormData();
377
- formData.append('pdf', file);
378
-
379
- $('#uploadStatus').text(`Uploading: ${file.name}...`).css('color', 'var(--text-color)');
380
- $('.upload-label').prop('disabled', true).css('opacity', 0.6); // Disable button visually
381
-
382
- $.ajax({
383
- url: '/upload_pdf',
384
- type: 'POST',
385
- data: formData,
386
- contentType: false,
387
- processData: false,
388
- success: function(response) {
389
- $('#uploadStatus').html(`<i class="fas fa-check-circle" style="color: var(--success-color);"></i> ${response.message || 'PDF processed successfully!'}`).css('color', 'var(--success-color)');
390
- // addMessage('system', response.message || 'PDF processed successfully!', 'info'); // Add message to chat
391
- pdfUploaded = true;
392
- $('#sendButton').prop('disabled', false); // Enable send button
393
- $('#userInput').prop('disabled', false).attr('placeholder', 'Ask a question about the PDF...'); // Enable input
394
- },
395
- error: function(jqXHR, textStatus, errorThrown) {
396
- let errorMsg = 'Error uploading PDF.';
397
- if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
398
- errorMsg = jqXHR.responseJSON.error;
399
- } else if (jqXHR.responseText) {
400
- // Try to get text if JSON parsing fails
401
- try {
402
- const errObj = JSON.parse(jqXHR.responseText);
403
- if (errObj.error) errorMsg = errObj.error;
404
- } catch (e) { /* Ignore parsing error */ }
405
- }
406
- $('#uploadStatus').text(`Error: ${errorMsg}`).css('color', 'var(--error-color)');
407
- // addMessage('system', `Upload failed: ${errorMsg}`, 'error');
408
- pdfUploaded = false;
409
- $('#sendButton').prop('disabled', true);
410
- $('#userInput').prop('disabled', true).attr('placeholder', 'Upload a PDF first');
411
- },
412
- complete: function() {
413
- $('.upload-label').prop('disabled', false).css('opacity', 1); // Re-enable button
414
- }
415
- });
416
- }
417
-
418
-
419
- // --- Chat Message Handling ---
420
  function sendMessage() {
421
- const message = $('#userInput').val().trim();
422
- if (message === "" || !pdfUploaded) return; // Don't send empty or if no PDF
 
423
 
 
424
  addMessage('user', message);
425
- chatHistory.push(["user", message]); // Add user message to history
426
 
427
- $('#userInput').val('').prop('disabled', true); // Clear and disable input
428
- $('#sendButton').prop('disabled', true); // Disable send button
429
- showTypingIndicator(true); // Show typing indicator
 
 
430
 
431
- // Prepare history for API (send pairs [user, assistant])
432
- const historyForApi = [];
433
- for(let i = 0; i < chatHistory.length; i += 2) {
434
- if (chatHistory[i] && chatHistory[i+1]) {
435
- historyForApi.push([ chatHistory[i][1], chatHistory[i+1][1] ]);
436
- }
437
- }
438
- // If the last message was user, don't include it in history pairs yet
439
- // The backend structure expects pairs, the current user query is sent separately
440
 
 
441
  $.ajax({
442
  url: '/ask_question',
443
  type: 'POST',
444
  contentType: 'application/json',
445
  data: JSON.stringify({
446
- message: message,
447
- // Send history structure expected by backend (pairs of user/assistant)
448
  history: historyForApi
449
  }),
450
  success: function(response) {
451
  if (response && response.response) {
 
452
  addMessage('assistant', response.response);
453
- chatHistory.push(["assistant", response.response]); // Add assistant response
 
454
  } else {
455
- addMessage('assistant', "Received an empty response.", 'error');
456
- chatHistory.push(["assistant", ""]); // Add empty response pair
 
 
 
457
  }
458
  },
459
  error: function(jqXHR, textStatus, errorThrown) {
460
- let errorMsg = 'Error getting response from assistant.';
461
- if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
462
- errorMsg = jqXHR.responseJSON.error;
463
- } else if (jqXHR.responseJSON && jqXHR.responseJSON.response){
464
- // Sometimes errors might be in 'response' field
465
- errorMsg = jqXHR.responseJSON.response;
466
  }
467
  addMessage('system', errorMsg, 'error');
468
- // Add a placeholder in history to maintain pairing if needed, or adjust logic
469
- chatHistory.push(["assistant", ""]); // Indicate error turn
470
  },
471
  complete: function() {
472
- $('#userInput').prop('disabled', false); // Re-enable input
473
- $('#sendButton').prop('disabled', !$('#userInput').val().trim()); // Re-enable send if input has text
474
- showTypingIndicator(false); // Hide typing indicator
475
- $('#userInput').focus(); // Focus back on input
476
- adjustTextareaHeight(); // Adjust height in case of past multiline input
477
  }
478
  });
479
  }
480
 
481
- // Send message on button click
482
- $('#sendButton').click(sendMessage);
483
 
484
- // Send message on Enter key press in textarea (Shift+Enter for newline)
485
- $('#userInput').keypress(function(e) {
486
- if (e.which === 13 && !e.shiftKey) {
487
- e.preventDefault(); // Prevent newline
488
  sendMessage();
489
  }
490
  });
491
 
492
- // Enable/disable send button based on input
493
- $('#userInput').on('input keyup', function() {
 
494
  adjustTextareaHeight();
495
  const isEmpty = $(this).val().trim() === '';
496
- $('#sendButton').prop('disabled', isEmpty || !pdfUploaded);
497
  });
498
 
499
- // Adjust textarea height dynamically
500
- function adjustTextareaHeight() {
501
- const textarea = $('#userInput')[0];
502
- textarea.style.height = 'auto'; // Reset height
503
- textarea.style.height = (textarea.scrollHeight) + 'px'; // Set to scroll height
504
- // Check against max-height defined in CSS
505
- const maxHeight = parseInt($(textarea).css('max-height'));
506
- if (textarea.scrollHeight > maxHeight) {
507
- textarea.style.overflowY = 'auto'; // Ensure scrollbar appears if needed
508
- } else {
509
- textarea.style.overflowY = 'hidden'; // Hide scrollbar if not needed
510
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
512
 
513
- // Initial state
514
- $('#userInput').prop('disabled', true).attr('placeholder', 'Upload a PDF first');
515
- $('#sendButton').prop('disabled', true);
516
- adjustTextareaHeight(); // Initial height adjustment
517
  });
518
  </script>
519
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <!-- Updated Title -->
7
+ <title>Sentry - Document Assistant</title>
8
+ <!-- jQuery -->
9
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
10
+ <!-- Font Awesome for Icons -->
11
  <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" />
12
  <style>
13
+ /* --- CSS Variables for Theme --- */
14
  :root {
15
+ --primary-color: #2f3136; /* Darker background */
16
+ --secondary-color: #36393f; /* Main chat container background */
17
+ --tertiary-color: #40444b; /* Input fields, message bubbles */
18
+ --text-color: #dcddde; /* Main text color (light gray) */
19
+ --text-muted-color: #b9bbbe; /* Slightly dimmer text */
20
+ --accent-color: #5865f2; /* Sentry/Discord Blurple (Updated) */
21
+ --accent-hover-color: #4e5ae0; /* Darker accent for hover */
22
+ --user-message-bg: var(--accent-color); /* User message bubble */
23
+ --assistant-message-bg: var(--tertiary-color); /* Assistant message bubble */
24
+ --input-bg: var(--tertiary-color);
25
+ --border-color: #202225; /* Darkest borders */
26
+ --success-color: #43b581; /* Green */
27
+ --error-color: #f04747; /* Red */
28
  --font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
29
  --border-radius: 8px;
30
+ --scrollbar-thumb-color: #202225;
31
+ --scrollbar-track-color: var(--secondary-color);
32
  }
33
 
34
+ /* --- General Body Styling --- */
35
  body {
36
  font-family: var(--font-family);
37
  background-color: var(--primary-color);
 
41
  justify-content: center;
42
  align-items: center;
43
  min-height: 100vh;
44
+ padding: 15px; /* Reduced padding for smaller screens */
45
  box-sizing: border-box;
46
  }
47
 
48
+ /* --- Main Chat Container --- */
49
  .chat-container {
50
  background-color: var(--secondary-color);
51
  border-radius: var(--border-radius);
52
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
53
  width: 100%;
54
+ max-width: 750px; /* Slightly wider */
55
+ height: 90vh; /* Adjust height as needed */
56
+ max-height: 800px; /* Max height constraint */
57
  display: flex;
58
  flex-direction: column;
59
  overflow: hidden; /* Contain children */
60
  }
61
 
62
+ /* --- Header --- */
63
  header {
64
  background-color: var(--primary-color);
65
  padding: 15px 20px;
66
  border-bottom: 1px solid var(--border-color);
67
  text-align: center;
68
+ flex-shrink: 0; /* Prevent header from shrinking */
69
  }
70
 
71
  header h1 {
72
  margin: 0;
73
+ font-size: 1.3em;
74
  color: #fff;
75
+ font-weight: 600;
76
  }
77
 
78
+ /* --- Upload Section --- */
79
  .upload-section {
80
+ padding: 12px 20px; /* Reduced padding */
81
  border-bottom: 1px solid var(--border-color);
82
  display: flex;
83
  align-items: center;
84
+ gap: 12px;
85
+ background-color: var(--secondary-color);
86
+ flex-shrink: 0; /* Prevent shrinking */
87
  }
88
 
89
  /* Hide default file input */
 
98
  padding: 8px 15px;
99
  border-radius: 5px;
100
  cursor: pointer;
101
+ transition: background-color 0.2s ease, opacity 0.2s ease;
102
  font-size: 0.9em;
103
  white-space: nowrap;
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 8px; /* Space between icon and text */
107
  }
108
 
109
  .upload-label:hover {
110
+ background-color: var(--accent-hover-color);
111
+ }
112
+ .upload-label[disabled] {
113
+ opacity: 0.6;
114
+ cursor: not-allowed;
115
  }
116
 
117
  #uploadStatus {
118
  font-size: 0.85em;
119
+ color: var(--text-muted-color);
120
  flex-grow: 1; /* Take remaining space */
121
  overflow: hidden;
122
  text-overflow: ellipsis;
123
  white-space: nowrap;
124
+ line-height: 1.3;
125
  }
126
+ #uploadStatus i { /* Style icons in status */
127
+ margin-right: 5px;
128
+ }
129
 
130
+ /* --- Chat Area --- */
131
  #chat {
132
  flex-grow: 1; /* Take available space */
133
+ overflow-y: auto; /* Enable vertical scrolling */
134
  padding: 20px;
135
  display: flex;
136
  flex-direction: column;
137
+ gap: 18px; /* Increased space between messages */
138
  }
139
 
140
+ /* Scrollbar styling (Webkit) */
141
  #chat::-webkit-scrollbar {
142
  width: 8px;
143
  }
144
  #chat::-webkit-scrollbar-track {
145
+ background: var(--scrollbar-track-color);
146
+ border-radius: 4px;
147
  }
148
  #chat::-webkit-scrollbar-thumb {
149
+ background-color: var(--scrollbar-thumb-color);
150
  border-radius: 4px;
151
  }
152
  #chat::-webkit-scrollbar-thumb:hover {
153
+ background-color: var(--tertiary-color); /* Slightly lighter on hover */
154
+ }
155
+ /* Scrollbar styling (Firefox) */
156
+ #chat {
157
+ scrollbar-width: thin;
158
+ scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-track-color);
159
  }
160
 
161
+ /* --- Message Styling --- */
162
  .message {
163
  display: flex;
164
+ max-width: 80%; /* Max width of message bubble */
165
  opacity: 0; /* Start hidden for animation */
166
+ animation: fadeIn 0.4s ease forwards;
167
+ position: relative; /* For potential absolute elements later */
168
+ line-height: 1.45; /* Improved readability */
169
  }
170
 
171
  @keyframes fadeIn {
172
  to { opacity: 1; }
173
  }
174
 
175
+ /* Common structure for icon + text block */
176
+ .message-inner-wrapper {
177
+ display: flex;
178
+ gap: 10px; /* Space between icon and text block */
179
+ width: 100%; /* Ensure wrapper takes full width */
180
+ }
181
+
182
+ .sender-icon {
183
+ font-size: 1.1em; /* Adjust icon size */
184
+ color: var(--text-muted-color); /* Default icon color */
185
+ margin-top: 2px; /* Fine-tune vertical alignment */
186
+ flex-shrink: 0; /* Prevent icon from shrinking */
187
+ }
188
+
189
+ .message-text-block {
190
+ display: flex;
191
+ flex-direction: column; /* Stack name and content */
192
+ flex-grow: 1; /* Allow text block to grow */
193
+ }
194
+
195
+ .sender-name {
196
+ font-weight: 600; /* Bolder name */
197
+ margin-bottom: 5px; /* Space below name */
198
+ font-size: 0.88em;
199
+ color: var(--text-muted-color);
200
+ }
201
+
202
  .message-content {
203
  padding: 10px 15px;
204
  border-radius: var(--border-radius);
 
205
  word-wrap: break-word;
206
  font-size: 0.95em;
207
+ background-color: var(--assistant-message-bg); /* Default background */
208
+ color: var(--text-color);
209
  }
210
 
211
+ /* User Message Specific Styles */
212
  .message.user {
213
+ margin-left: auto; /* Align bubble to the right */
214
+ flex-direction: row-reverse; /* Put icon on the right */
215
+ }
216
+ .message.user .message-inner-wrapper {
217
+ flex-direction: row-reverse; /* Reverse icon and text block order */
218
  }
219
+ .message.user .sender-icon {
220
+ color: var(--text-muted-color); /* Optional: Different user icon color */
221
+ }
222
+ .message.user .sender-name {
223
+ text-align: right; /* Align name to the right */
224
+ color: inherit; /* Use bubble text color */
225
+ }
226
  .message.user .message-content {
227
  background-color: var(--user-message-bg);
228
+ color: white; /* Text color for user bubble */
229
+ border-bottom-right-radius: 4px; /* Subtle shape difference */
230
  }
231
 
232
+ /* Assistant Message Specific Styles */
233
  .message.assistant {
234
+ margin-right: auto; /* Align bubble to the left */
235
+ }
236
+ .message.assistant .sender-icon {
237
+ color: var(--accent-color); /* Sentry icon color */
238
  }
239
+ .message.assistant .sender-name {
240
+ color: var(--accent-color); /* Sentry name color */
241
+ }
242
  .message.assistant .message-content {
243
  background-color: var(--assistant-message-bg);
244
+ border-bottom-left-radius: 4px; /* Subtle shape difference */
 
245
  }
246
 
247
+ /* System Message Styling (for errors, info) */
248
+ .message.system {
249
  font-style: italic;
250
+ color: var(--text-muted-color); /* Default system text color */
251
  text-align: center;
252
  width: 100%;
253
  max-width: 100%;
254
  font-size: 0.9em;
255
+ margin: 8px 0; /* Adjust spacing */
256
+ gap: 0; /* No gap needed */
257
  }
258
+ .message.system .message-inner-wrapper { /* System messages don't need the icon/name wrapper */
259
+ justify-content: center;
260
+ }
261
  .message.system .message-content {
262
  background: none;
263
  padding: 0;
264
+ display: inline-block; /* Center the text block */
265
  }
266
+ .message.system .message-content.error {
267
+ color: var(--error-color);
268
+ }
269
+ .message.system .message-content.info {
270
+ color: var(--success-color);
271
+ }
272
 
273
 
274
+ /* --- Typing Indicator --- */
275
+ .typing-indicator {
276
+ display: flex;
277
+ align-items: center;
278
+ padding: 0px 20px 5px 20px; /* Add padding */
279
+ opacity: 0;
280
+ transition: opacity 0.3s ease, height 0.3s ease;
281
+ height: 0; /* Start hidden */
282
+ overflow: hidden;
283
+ flex-shrink: 0; /* Prevent shrinking */
284
+ gap: 8px; /* Space between icon and text/dots */
285
+ }
286
+ .typing-indicator.visible {
287
+ opacity: 1;
288
+ height: 25px; /* Make visible */
289
+ }
290
+ .typing-indicator .sender-icon { /* Reuse sender icon style */
291
+ font-size: 0.95em;
292
+ color: var(--accent-color);
293
+ }
294
+ .typing-indicator span {
295
+ font-size: 0.88em;
296
+ color: var(--text-muted-color);
297
+ margin-right: 5px;
298
+ }
299
+ .typing-indicator .dot {
300
+ display: inline-block;
301
+ width: 6px;
302
+ height: 6px;
303
+ background-color: var(--text-muted-color);
304
+ border-radius: 50%;
305
+ margin: 0 2px;
306
+ animation: typing 1.2s infinite ease-in-out;
307
+ }
308
+ .typing-indicator .dot:nth-child(1) { animation-delay: 0.0s; }
309
+ .typing-indicator .dot:nth-child(2) { animation-delay: 0.2s; }
310
+ .typing-indicator .dot:nth-child(3) { animation-delay: 0.4s; }
311
+
312
+ @keyframes typing {
313
+ 0%, 100% { transform: translateY(0); opacity: 0.5; }
314
+ 50% { transform: translateY(-5px); opacity: 1; }
315
+ }
316
+
317
+ /* --- Input Area --- */
318
  .input-area {
319
  display: flex;
320
  padding: 15px 20px;
321
  border-top: 1px solid var(--border-color);
322
+ background-color: var(--secondary-color); /* Match chat bg */
323
+ flex-shrink: 0; /* Prevent shrinking */
324
+ gap: 10px; /* Space between textarea and button */
325
  }
326
 
327
  #userInput {
328
  flex-grow: 1;
329
  background-color: var(--input-bg);
330
  color: var(--text-color);
331
+ border: 1px solid var(--border-color); /* Subtle border */
332
  border-radius: var(--border-radius);
333
  padding: 10px 15px;
334
  resize: none; /* Prevent manual resizing */
335
  font-family: var(--font-family);
336
  font-size: 0.95em;
337
+ max-height: 120px; /* Limit growth */
 
 
338
  box-sizing: border-box;
339
  overflow-y: auto; /* Allow scrolling if text exceeds max height */
340
+ line-height: 1.4; /* Match message line height */
341
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
342
  }
343
 
344
  #userInput:focus {
345
  outline: none;
346
+ border-color: var(--accent-color);
347
+ box-shadow: 0 0 0 1px var(--accent-color);
348
  }
349
+ #userInput::placeholder {
350
+ color: var(--text-muted-color);
351
+ opacity: 0.8;
352
+ }
353
+
354
 
355
  #sendButton {
356
  background-color: var(--accent-color);
 
359
  border-radius: var(--border-radius);
360
  padding: 0 15px;
361
  cursor: pointer;
362
+ font-size: 1.1em; /* Icon size */
363
  transition: background-color 0.2s ease, opacity 0.2s ease;
364
  display: flex;
365
  align-items: center;
366
  justify-content: center;
367
+ height: 42px; /* Match input height approx */
368
+ width: 45px; /* Fixed width for the button */
369
+ flex-shrink: 0; /* Prevent button from shrinking */
370
  }
371
 
372
  #sendButton:hover {
373
+ background-color: var(--accent-hover-color);
374
  }
375
 
376
  #sendButton:disabled {
377
  opacity: 0.5;
378
  cursor: not-allowed;
379
+ background-color: var(--tertiary-color); /* Dim background when disabled */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  }
381
 
382
  </style>
383
  </head>
384
  <body>
385
+ <!-- Main Chat Container -->
386
  <div class="chat-container">
387
+ <!-- Header -->
388
  <header>
389
+ <h1>SentryLabs Document Assistant</h1>
390
  </header>
391
 
392
+ <!-- PDF Upload Section -->
393
  <div class="upload-section">
394
+ <label for="pdfUpload" class="upload-label" id="uploadLabel">
395
  <i class="fas fa-file-pdf"></i> Choose PDF
396
  </label>
397
  <input type="file" id="pdfUpload" accept=".pdf" />
398
+ <span id="uploadStatus">Upload a document for analysis.</span>
399
+ <!-- Hidden button, not really used now -->
400
+ <button id="uploadButton" style="display: none;">Upload</button>
401
  </div>
402
 
403
+ <!-- Chat Messages Area -->
404
  <div id="chat">
405
+ <!-- Initial Greeting from Sentry -->
406
  <div class="message assistant">
407
+ <div class="message-inner-wrapper">
408
+ <i class="fas fa-shield-alt sender-icon"></i> <!-- Sentry Icon -->
409
+ <div class="message-text-block">
410
+ <div class="sender-name">Sentry</div>
411
+ <div class="message-content">
412
+ I am Sentry, your SentryLabs assistant. Please upload a PDF document using the button above to begin our analysis.
413
+ </div>
414
+ </div>
415
+ </div>
416
  </div>
417
  </div>
418
 
419
  <!-- Typing Indicator -->
420
  <div class="typing-indicator" id="typingIndicator">
421
+ <i class="fas fa-shield-alt sender-icon"></i>
422
+ <span>Sentry is analyzing...</span>
423
  <div class="dot"></div>
424
  <div class="dot"></div>
425
  <div class="dot"></div>
426
  </div>
427
 
428
+ <!-- User Input Area -->
429
  <div class="input-area">
430
+ <textarea id="userInput" placeholder="Ask Sentry about the document..." rows="1" disabled></textarea> <!-- Start disabled -->
431
  <button id="sendButton" title="Send Message" disabled> <!-- Start disabled -->
432
  <i class="fas fa-paper-plane"></i>
433
  </button>
434
  </div>
435
  </div>
436
 
437
+ <!-- JavaScript -->
438
  <script>
439
  $(document).ready(function() {
440
+ // --- State Variables ---
441
+ let chatHistory = []; // Stores pairs: [[userMsg, assistantMsg], ...]
442
  let pdfUploaded = false;
443
 
444
+ // --- DOM Elements ---
445
+ const chatBox = $('#chat');
446
+ const userInput = $('#userInput');
447
+ const sendButton = $('#sendButton');
448
+ const uploadLabel = $('#uploadLabel');
449
+ const pdfUploadInput = $('#pdfUpload');
450
+ const uploadStatus = $('#uploadStatus');
451
+ const typingIndicator = $('#typingIndicator');
452
+
453
+ // --- Helper Functions ---
454
+
455
  // Function to add a message to the chat interface
456
  function addMessage(sender, text, type = 'normal') {
457
+ // Sanitize text to prevent basic HTML injection, preserve newlines for <br> conversion
458
+ const sanitizedHtml = $('<div>').text(text).html().replace(/\n/g, '<br>');
459
+ let messageClass = sender; // 'user', 'assistant', or 'system'
460
+ let senderName = '';
461
+ let senderIconHtml = ''; // Icon HTML string
462
+
463
+ // Define names and icons based on sender
464
+ if (sender === 'user') {
465
+ senderName = 'You';
466
+ // Optional: User icon
467
+ // senderIconHtml = '<i class="fas fa-user sender-icon"></i>';
468
+ } else if (sender === 'assistant') {
469
+ senderName = 'Sentry';
470
+ senderIconHtml = '<i class="fas fa-shield-alt sender-icon"></i>';
471
  }
472
 
473
+ let messageHtml;
474
+
475
+ // Handle different message types (normal, error, info)
476
+ if (type === 'error' || type === 'info') {
477
+ messageClass = 'system';
478
+ // Simple centered text for system messages
479
+ messageHtml = `<div class="message-inner-wrapper">
480
+ <div class="message-content ${type}">${sanitizedHtml}</div>
481
+ </div>`;
482
+ } else {
483
+ // Standard message structure with icon, name, content
484
+ const nameHtml = senderName ? `<div class="sender-name">${senderName}</div>` : '';
485
+ messageHtml = `
486
+ <div class="message-inner-wrapper">
487
+ ${senderIconHtml}
488
+ <div class="message-text-block">
489
+ ${nameHtml}
490
+ <div class="message-content">${sanitizedHtml}</div>
491
+ </div>
492
+ </div>
493
+ `;
494
+ }
495
+
496
+ // Create message element and append to chat
497
+ const messageElement = $(`<div class="message ${messageClass}">${messageHtml}</div>`);
498
+ chatBox.append(messageElement);
499
+
500
+ // Scroll to the bottom of the chat
501
+ scrollToBottom();
502
+ }
503
+
504
+ // Function to scroll chat box to the bottom
505
+ function scrollToBottom() {
506
+ chatBox.animate({ scrollTop: chatBox[0].scrollHeight }, 300);
507
  }
508
 
509
  // Function to show/hide typing indicator
510
  function showTypingIndicator(show) {
511
  if (show) {
512
+ typingIndicator.addClass('visible');
513
+ } else {
514
+ typingIndicator.removeClass('visible');
515
+ }
516
+ // Adjust scroll after potential layout shift from indicator
517
+ setTimeout(scrollToBottom, 50);
518
+ }
519
+
520
+ // Adjust textarea height dynamically based on content
521
+ function adjustTextareaHeight() {
522
+ const textarea = userInput[0];
523
+ textarea.style.height = 'auto'; // Reset height to calculate scroll height accurately
524
+ // Calculate the height needed for the content, plus a small buffer if desired
525
+ const scrollHeight = textarea.scrollHeight;
526
+ textarea.style.height = scrollHeight + 'px';
527
+
528
+ // Apply max-height constraint from CSS
529
+ const maxHeight = parseInt(userInput.css('max-height'));
530
+ if (scrollHeight > maxHeight) {
531
+ textarea.style.overflowY = 'auto'; // Show scrollbar if content exceeds max height
532
  } else {
533
+ textarea.style.overflowY = 'hidden'; // Hide scrollbar if content fits
534
  }
 
 
535
  }
536
 
537
+ // --- Event Handlers ---
538
+
539
+ // Trigger hidden file input when the styled label is clicked
540
+ uploadLabel.on('click', function() {
541
+ if (!$(this).prop('disabled')) { // Only trigger if not disabled
542
+ pdfUploadInput.click();
543
+ }
544
  });
545
 
546
+ // Handle file selection via the hidden input
547
+ pdfUploadInput.on('change', function() {
548
  const file = this.files[0];
549
  if (file) {
550
  if (file.type === "application/pdf") {
551
+ uploadStatus.text(`Selected: ${file.name}`).css('color', 'var(--text-muted-color)');
552
+ uploadFile(file); // Automatically start the upload
 
553
  } else {
554
+ uploadStatus.html('<i class="fas fa-exclamation-triangle"></i> Error: Please select a PDF file.').css('color', 'var(--error-color)');
555
+ this.value = ''; // Reset file input to allow re-selection of the same file if needed
556
  }
 
 
 
557
  }
558
  });
559
 
560
+ // Handle sending a message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  function sendMessage() {
562
+ const message = userInput.val().trim();
563
+ // Prevent sending empty messages or if PDF isn't uploaded
564
+ if (message === "" || !pdfUploaded) return;
565
 
566
+ // Display user's message
567
  addMessage('user', message);
568
+ const currentUserMessage = message; // Store for history pairing
569
 
570
+ // Clear input, disable controls, show typing indicator
571
+ userInput.val('').prop('disabled', true);
572
+ sendButton.prop('disabled', true);
573
+ showTypingIndicator(true);
574
+ adjustTextareaHeight(); // Reset height after clearing
575
 
576
+ // Prepare history for the API (only completed pairs)
577
+ const historyForApi = chatHistory.slice(); // Send a copy
 
 
 
 
 
 
 
578
 
579
+ // Make the AJAX call to the backend
580
  $.ajax({
581
  url: '/ask_question',
582
  type: 'POST',
583
  contentType: 'application/json',
584
  data: JSON.stringify({
585
+ message: currentUserMessage,
 
586
  history: historyForApi
587
  }),
588
  success: function(response) {
589
  if (response && response.response) {
590
+ // Display Sentry's response
591
  addMessage('assistant', response.response);
592
+ // Add the completed user/assistant pair to history
593
+ chatHistory.push([currentUserMessage, response.response]);
594
  } else {
595
+ // Handle cases where backend might return empty response field
596
+ const errorMsg = "Received an unexpected empty response from Sentry.";
597
+ addMessage('system', errorMsg, 'error');
598
+ // Record the turn with an empty assistant message for history integrity
599
+ chatHistory.push([currentUserMessage, ""]);
600
  }
601
  },
602
  error: function(jqXHR, textStatus, errorThrown) {
603
+ // Display error message in chat
604
+ let errorMsg = 'An error occurred while communicating with Sentry.';
605
+ if (jqXHR.responseJSON && jqXHR.responseJSON.response) { // Check for error in 'response' key first
606
+ errorMsg = jqXHR.responseJSON.response;
607
+ } else if (jqXHR.responseJSON && jqXHR.responseJSON.error) { // Check 'error' key
608
+ errorMsg = jqXHR.responseJSON.error;
609
  }
610
  addMessage('system', errorMsg, 'error');
611
+ // Record the turn with an empty assistant message for history integrity
612
+ chatHistory.push([currentUserMessage, ""]);
613
  },
614
  complete: function() {
615
+ // Re-enable input, hide typing indicator
616
+ userInput.prop('disabled', false).focus(); // Re-enable and focus
617
+ showTypingIndicator(false);
618
+ // Re-evaluate send button state (might have typed while waiting)
619
+ sendButton.prop('disabled', userInput.val().trim() === '');
620
  }
621
  });
622
  }
623
 
624
+ // Attach send handler to button click
625
+ sendButton.click(sendMessage);
626
 
627
+ // Attach send handler to Enter key press in textarea (allow Shift+Enter for newline)
628
+ userInput.keypress(function(e) {
629
+ if (e.which === 13 && !e.shiftKey) { // Enter key pressed without Shift
630
+ e.preventDefault(); // Prevent default newline behavior
631
  sendMessage();
632
  }
633
  });
634
 
635
+ // Enable/disable send button based on input content and PDF status
636
+ // Also adjust textarea height on input
637
+ userInput.on('input keyup', function() {
638
  adjustTextareaHeight();
639
  const isEmpty = $(this).val().trim() === '';
640
+ sendButton.prop('disabled', isEmpty || !pdfUploaded);
641
  });
642
 
643
+ // --- File Upload Function ---
644
+ function uploadFile(file) {
645
+ const formData = new FormData();
646
+ formData.append('pdf', file);
647
+
648
+ // Update UI to show uploading state
649
+ uploadStatus.html(`<i class="fas fa-spinner fa-spin"></i> Processing: ${file.name}...`).css('color', 'var(--text-muted-color)');
650
+ uploadLabel.prop('disabled', true).css('opacity', 0.6); // Disable upload button visually
651
+ userInput.prop('disabled', true); // Disable input during upload
652
+ sendButton.prop('disabled', true); // Disable send during upload
653
+
654
+ $.ajax({
655
+ url: '/upload_pdf',
656
+ type: 'POST',
657
+ data: formData,
658
+ contentType: false, // Important for FormData
659
+ processData: false, // Important for FormData
660
+ success: function(response) {
661
+ // Success: Update status, enable input/send
662
+ uploadStatus.html(`<i class="fas fa-check-circle"></i> Document ready for analysis.`).css('color', 'var(--success-color)');
663
+ pdfUploaded = true;
664
+ userInput.prop('disabled', false).attr('placeholder', 'Ask Sentry about the document...').focus();
665
+ // Enable send button only if there's text already (unlikely but possible)
666
+ sendButton.prop('disabled', userInput.val().trim() === '');
667
+ // Optional: Clear chat history if you want each PDF to start fresh
668
+ // chatHistory = [];
669
+ // chatBox.find('.message:not(:first-child)').remove(); // Remove all but initial greeting
670
+ },
671
+ error: function(jqXHR, textStatus, errorThrown) {
672
+ // Error: Show error message, keep controls disabled
673
+ let errorMsg = 'Failed to process the PDF.';
674
+ if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
675
+ errorMsg = jqXHR.responseJSON.error;
676
+ } // Add more specific parsing if needed
677
+ uploadStatus.html(`<i class="fas fa-exclamation-triangle"></i> Error: ${errorMsg}`).css('color', 'var(--error-color)');
678
+ pdfUploaded = false;
679
+ userInput.prop('disabled', true).attr('placeholder', 'Upload failed. Please try again.');
680
+ sendButton.prop('disabled', true);
681
+ // Add error to chat as well
682
+ addMessage('system', `PDF processing failed: ${errorMsg}`, 'error');
683
+ },
684
+ complete: function() {
685
+ // Always re-enable the upload button regardless of outcome
686
+ uploadLabel.prop('disabled', false).css('opacity', 1);
687
+ // Clear the file input value so the user can re-upload the same file if needed after an error
688
+ pdfUploadInput.val('');
689
+ }
690
+ });
691
  }
692
 
693
+ // --- Initial Page Load Setup ---
694
+ adjustTextareaHeight(); // Initial adjustment for placeholder
695
+ // Initial state is set directly in the HTML (disabled input/button)
696
+
697
  });
698
  </script>
699
  </body>