mohamedhassan22 commited on
Commit
4b4f6d3
·
verified ·
1 Parent(s): 452a375

Update static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +355 -349
static/index.html CHANGED
@@ -4,7 +4,7 @@
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Multimodal RAG • AI Research Assistant</title>
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link
@@ -21,138 +21,83 @@
21
  --border-color: #334155;
22
  --accent-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
23
  }
 
24
  * {
25
  box-sizing: border-box;
26
  margin: 0;
27
  padding: 0;
28
  }
 
 
 
 
 
 
29
  body {
30
  font-family: 'Outfit', sans-serif;
31
  background-color: var(--bg-dark);
32
  color: var(--text-main);
33
- min-height: 100vh;
34
  line-height: 1.6;
35
  background-image:
36
  radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.1) 0%, transparent 20%),
37
  radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.1) 0%, transparent 20%);
38
- }
39
- .container {
40
- max-width: 1200px;
41
- margin: 0 auto;
42
- padding: 2rem;
43
  display: flex;
44
  flex-direction: column;
45
- gap: 2rem;
46
  }
 
 
47
  header {
48
  text-align: center;
49
- padding: 4rem 0 2rem;
50
- animation: fadeInDown 0.8s ease-out;
 
 
 
51
  }
 
52
  h1 {
53
  font-family: 'Space Grotesk', sans-serif;
54
- font-size: 3.5rem;
55
  font-weight: 700;
56
  background: var(--accent-gradient);
57
  -webkit-background-clip: text;
58
  -webkit-text-fill-color: transparent;
59
  background-clip: text;
60
- margin-bottom: 0.5rem;
61
  letter-spacing: -0.02em;
62
  }
 
63
  .subtitle {
64
  color: var(--text-muted);
65
- font-size: 1.25rem;
66
  font-weight: 300;
67
  }
 
68
  .subtitle a {
69
  color: var(--primary);
70
  text-decoration: none;
71
  }
 
72
  .subtitle a:hover {
73
  text-decoration: underline;
74
  }
75
- /* Search Section */
76
- .search-container {
77
- max-width: 800px;
78
- margin: 0 auto;
79
- width: 100%;
80
- position: relative;
81
- z-index: 10;
82
- }
83
- .input-group {
84
- position: relative;
85
- display: flex;
86
- gap: 1rem;
87
- background: rgba(30, 41, 59, 0.7);
88
- padding: 0.5rem;
89
- border-radius: 1rem;
90
- border: 1px solid var(--border-color);
91
- backdrop-filter: blur(12px);
92
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
93
- transition: all 0.3s ease;
94
- }
95
- .input-group:focus-within {
96
- border-color: var(--primary);
97
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
98
- transform: translateY(-2px);
99
- }
100
- input[type="text"] {
101
- flex: 1;
102
- background: transparent;
103
- border: none;
104
- padding: 1rem 1.5rem;
105
- font-size: 1.1rem;
106
- color: white;
107
- font-family: 'Outfit', sans-serif;
108
- width: 100%;
109
- }
110
- input[type="text"]:focus {
111
- outline: none;
112
- }
113
- input[type="text"]::placeholder {
114
- color: #64748b;
115
- }
116
- button#searchBtn {
117
- background: var(--accent-gradient);
118
- color: white;
119
- border: none;
120
- padding: 0 2rem;
121
- border-radius: 0.75rem;
122
- font-weight: 600;
123
- font-size: 1rem;
124
- cursor: pointer;
125
- transition: opacity 0.2s;
126
- display: flex;
127
- align-items: center;
128
- gap: 0.5rem;
129
- }
130
- button#searchBtn:hover {
131
- opacity: 0.9;
132
- }
133
- button#searchBtn:disabled {
134
- opacity: 0.5;
135
- cursor: not-allowed;
136
- }
137
 
138
- /* Action Buttons */
139
  .action-buttons {
140
- max-width: 800px;
141
- margin: 0 auto;
142
- width: 100%;
143
  display: flex;
144
- gap: 1rem;
145
- justify-content: flex-end;
 
146
  }
147
 
148
- button#downloadBtn {
149
  background: rgba(139, 92, 246, 0.2);
150
  color: #c4b5fd;
151
  border: 1px solid #8b5cf6;
152
- padding: 0.75rem 1.5rem;
153
- border-radius: 0.75rem;
154
  font-weight: 600;
155
- font-size: 0.95rem;
156
  cursor: pointer;
157
  transition: all 0.2s;
158
  display: flex;
@@ -160,69 +105,98 @@
160
  gap: 0.5rem;
161
  }
162
 
163
- button#downloadBtn:hover:not(:disabled) {
164
  background: rgba(139, 92, 246, 0.3);
165
  border-color: #a78bfa;
166
  }
167
 
168
- button#downloadBtn:disabled {
169
  opacity: 0.5;
170
  cursor: not-allowed;
171
  }
172
 
173
- button#clearBtn {
174
  background: rgba(239, 68, 68, 0.2);
175
  color: #fca5a5;
176
- border: 1px solid #ef4444;
177
- padding: 0.75rem 1.5rem;
178
- border-radius: 0.75rem;
179
- font-weight: 600;
180
- font-size: 0.95rem;
181
- cursor: pointer;
182
- transition: all 0.2s;
183
- display: flex;
184
- align-items: center;
185
- gap: 0.5rem;
186
  }
187
 
188
- button#clearBtn:hover {
189
  background: rgba(239, 68, 68, 0.3);
190
  border-color: #f87171;
191
  }
192
 
193
- button#clearBtn:disabled {
194
- opacity: 0.5;
195
- cursor: not-allowed;
 
 
 
 
196
  }
197
 
198
- /* Loading State */
199
- #loading {
200
- display: none;
201
  text-align: center;
 
202
  padding: 2rem;
203
  }
204
- .spinner {
205
- width: 40px;
206
- height: 40px;
207
- border: 3px solid rgba(59, 130, 246, 0.3);
208
- border-radius: 50%;
209
- border-top-color: var(--primary);
210
- animation: spin 1s linear infinite;
211
- margin: 0 auto 1rem;
212
  }
213
- @keyframes spin {
214
- to {
215
- transform: rotate(360deg);
216
- }
217
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  /* Chat History */
219
  #chatHistory {
220
- max-width: 1000px;
 
221
  margin: 0 auto;
222
  display: flex;
223
  flex-direction: column;
224
- gap: 2rem;
225
  }
 
226
  .message-block {
227
  border: 1px solid var(--border-color);
228
  border-radius: 1rem;
@@ -230,22 +204,28 @@
230
  background: var(--bg-card);
231
  animation: fadeInUp 0.4s ease-out;
232
  }
 
233
  .user-question {
234
  margin-bottom: 1rem;
235
  color: var(--text-main);
 
236
  }
 
237
  .user-question strong {
238
  color: var(--primary);
239
  }
 
240
  .assistant-answer {
241
  margin-bottom: 1rem;
242
  line-height: 1.8;
 
243
  }
 
244
  .assistant-answer strong {
245
  color: #8b5cf6;
246
  }
247
 
248
- /* Text Sources - Now displayed FIRST */
249
  .sources-section {
250
  margin-top: 1rem;
251
  }
@@ -254,7 +234,7 @@
254
  font-weight: 600;
255
  color: var(--primary);
256
  margin-bottom: 0.75rem;
257
- font-size: 1rem;
258
  display: flex;
259
  align-items: center;
260
  gap: 0.5rem;
@@ -264,32 +244,32 @@
264
  background: rgba(30, 41, 59, 0.4);
265
  border: 1px solid var(--border-color);
266
  border-radius: 0.75rem;
267
- padding: 1.25rem;
268
  margin-bottom: 0.75rem;
269
  transition: all 0.2s;
270
  cursor: pointer;
271
  }
 
272
  .source-card:hover {
273
  background: rgba(30, 41, 59, 0.8);
274
  border-color: var(--primary);
275
  transform: translateX(5px);
276
  }
 
277
  .source-title {
278
  font-weight: 600;
279
  color: var(--primary);
280
  margin-bottom: 0.5rem;
281
- font-size: 0.95rem;
282
  display: flex;
283
  align-items: center;
284
  gap: 0.5rem;
285
  }
286
- .source-title svg {
287
- flex-shrink: 0;
288
- }
289
  .source-excerpt {
290
  font-style: italic;
291
  color: #cbd5e1;
292
- font-size: 0.9rem;
293
  background: rgba(0, 0, 0, 0.2);
294
  padding: 0.75rem;
295
  border-radius: 0.5rem;
@@ -297,14 +277,16 @@
297
  word-wrap: break-word;
298
  line-height: 1.6;
299
  }
 
300
  .source-meta {
301
  margin-top: 0.75rem;
302
- font-size: 0.8rem;
303
  color: var(--text-muted);
304
  display: flex;
305
  justify-content: space-between;
306
  align-items: center;
307
  }
 
308
  .view-source-link {
309
  color: var(--primary);
310
  text-decoration: none;
@@ -314,22 +296,24 @@
314
  gap: 0.25rem;
315
  transition: all 0.2s;
316
  }
 
317
  .view-source-link:hover {
318
  color: #60a5fa;
319
  text-decoration: underline;
320
  }
321
 
322
- /* Image Grids - Now displayed SECOND */
323
  .images-section {
324
  margin-top: 1.5rem;
325
  }
326
 
327
  .images-grid {
328
  display: grid;
329
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
330
- gap: 1.5rem;
331
  margin-top: 1rem;
332
  }
 
333
  .image-card {
334
  background: rgba(30, 41, 59, 0.6);
335
  border-radius: 0.75rem;
@@ -337,215 +321,297 @@
337
  border: 1px solid var(--border-color);
338
  transition: all 0.3s ease;
339
  }
 
340
  .image-card:hover {
341
  transform: translateY(-5px);
342
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
343
  border-color: var(--primary);
344
  }
 
345
  .image-card a {
346
  display: block;
347
  overflow: hidden;
348
- height: 200px;
349
  }
 
350
  .image-card img {
351
  width: 100%;
352
  height: 100%;
353
  object-fit: cover;
354
  transition: transform 0.5s ease;
355
  }
 
356
  .image-card:hover img {
357
  transform: scale(1.1);
358
  }
 
359
  .image-meta {
360
- padding: 1rem;
361
  border-top: 1px solid var(--border-color);
362
  }
 
363
  .image-filename {
364
  font-weight: 600;
365
  color: #f1f5f9;
366
- font-size: 0.9rem;
367
  white-space: nowrap;
368
  overflow: hidden;
369
  text-overflow: ellipsis;
370
  margin-bottom: 0.25rem;
371
  }
 
372
  .image-source {
373
- font-size: 0.75rem;
374
  color: var(--text-muted);
375
  }
376
 
377
- /* Follow-up Question Box */
378
- .followup-container {
379
- max-width: 800px;
380
- margin: 1.5rem auto 0;
 
381
  padding: 1.5rem;
382
- background: rgba(30, 41, 59, 0.5);
 
383
  border-radius: 1rem;
384
- border: 1px dashed var(--border-color);
 
 
385
  }
386
 
387
- .followup-label {
388
- font-size: 0.9rem;
389
- color: var(--text-muted);
390
- margin-bottom: 0.75rem;
391
  display: flex;
392
- align-items: center;
393
- gap: 0.5rem;
394
  }
395
 
396
- .followup-input-group {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  display: flex;
 
398
  gap: 0.75rem;
 
 
 
 
 
 
 
 
 
 
399
  }
400
 
401
- .followup-input-group input {
402
  flex: 1;
403
- background: rgba(15, 23, 42, 0.6);
404
- border: 1px solid var(--border-color);
405
  padding: 0.75rem 1rem;
406
- border-radius: 0.5rem;
407
  font-size: 1rem;
408
  color: white;
409
  font-family: 'Outfit', sans-serif;
410
- transition: all 0.2s;
 
 
 
411
  }
412
 
413
- .followup-input-group input:focus {
414
  outline: none;
415
- border-color: var(--primary);
416
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
417
  }
418
 
419
- .followup-input-group input::placeholder {
420
  color: #64748b;
421
  }
422
 
423
- .followup-btn {
424
- background: rgba(59, 130, 246, 0.2);
425
- color: #93c5fd;
426
- border: 1px solid var(--primary);
427
- padding: 0.75rem 1.5rem;
428
- border-radius: 0.5rem;
429
- font-weight: 600;
430
- font-size: 0.95rem;
431
  cursor: pointer;
432
  transition: all 0.2s;
433
- white-space: nowrap;
 
 
 
434
  }
435
 
436
- .followup-btn:hover:not(:disabled) {
437
- background: rgba(59, 130, 246, 0.3);
438
- border-color: #60a5fa;
439
  }
440
 
441
- .followup-btn:disabled {
442
  opacity: 0.5;
443
  cursor: not-allowed;
444
  }
445
 
446
- /* Animations */
447
- @keyframes fadeInDown {
448
- from {
449
- opacity: 0;
450
- transform: translateY(-20px);
451
- }
452
- to {
453
- opacity: 1;
454
- transform: translateY(0);
455
- }
456
- }
457
- @keyframes fadeInUp {
458
- from {
459
- opacity: 0;
460
- transform: translateY(20px);
461
- }
462
- to {
463
- opacity: 1;
464
- transform: translateY(0);
465
- }
466
- }
467
  /* Scrollbar */
468
  ::-webkit-scrollbar {
469
  width: 8px;
470
  }
 
471
  ::-webkit-scrollbar-track {
472
  background: var(--bg-dark);
473
  }
 
474
  ::-webkit-scrollbar-thumb {
475
  background: var(--border-color);
476
  border-radius: 4px;
477
  }
 
478
  ::-webkit-scrollbar-thumb:hover {
479
  background: #475569;
480
  }
 
 
 
 
 
 
 
 
 
 
 
 
481
  </style>
482
  </head>
483
 
484
  <body>
485
- <div class="container">
486
- <header>
487
- <h1>WHEC - Chatbot</h1>
488
- <p class="subtitle">Based on documents at WHEC (Warrior Heat- and Exertion-Related Events Collaborative)
489
- <a href="https://www.hprc-online.org/resources-partners/whec" target="_blank">page</a>
490
- </p>
491
- </header>
492
-
493
- <div class="search-container">
494
- <div class="input-group">
495
- <input type="text" id="questionInput" placeholder="Ask a question about exertion-related injuries..."
496
- autocomplete="off">
497
- <button id="searchBtn" onclick="askQuestion()">
498
- <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
499
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
500
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
501
- </svg>
502
- Analyze
503
- </button>
504
- </div>
505
- </div>
506
 
507
- <!-- Action Buttons -->
508
  <div class="action-buttons" id="actionButtons" style="display: none;">
509
  <button id="downloadBtn" onclick="downloadPDFReport()">
510
- <svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
511
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
512
  d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
513
  </svg>
514
- Download PDF Report
515
  </button>
516
  <button id="clearBtn" onclick="clearHistory()">
517
- <svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
518
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
519
  d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
520
  </svg>
521
  Clear Chat
522
  </button>
523
  </div>
524
-
525
- <!-- CHAT HISTORY -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
526
  <div id="chatHistory"></div>
527
-
528
- <div id="loading">
 
529
  <div class="spinner"></div>
530
- <p style="color: var(--text-muted); font-size: 0.9rem;">Processing multimodal embeddings...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
531
  </div>
532
  </div>
533
 
534
  <script>
535
- const input = document.getElementById('questionInput');
536
- const searchBtn = document.getElementById('searchBtn');
537
- const loading = document.getElementById('loading');
538
- const chatHistoryContainer = document.getElementById('chatHistory');
 
 
539
  const actionButtons = document.getElementById('actionButtons');
540
  const downloadBtn = document.getElementById('downloadBtn');
541
 
542
- // Store conversation data for report generation
543
  let conversationData = [];
544
 
545
- // Allow Enter key to submit
546
- input.addEventListener('keypress', function (e) {
547
- if (e.key === 'Enter') {
548
- askQuestion();
 
 
 
 
 
 
 
549
  }
550
  });
551
 
@@ -555,16 +621,33 @@
555
  return div.innerHTML;
556
  }
557
 
558
- async function askQuestion(questionText = null) {
559
- // Get question from parameter or main input
560
- const question = questionText || input.value.trim();
 
 
 
 
561
  if (!question) return;
562
 
563
- // UI State updates
564
- searchBtn.disabled = true;
565
- searchBtn.innerHTML = '<span style="font-size: 0.9em">Processing...</span>';
566
- input.disabled = true;
567
- loading.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
568
 
569
  try {
570
  const response = await fetch('/query', {
@@ -579,7 +662,7 @@
579
 
580
  const data = await response.json();
581
 
582
- // Store conversation data for report
583
  conversationData.push({
584
  question: question,
585
  answer: data.answer,
@@ -588,10 +671,10 @@
588
  timestamp: new Date().toISOString()
589
  });
590
 
591
- // Show action buttons once we have content
592
  actionButtons.style.display = 'flex';
593
 
594
- // Create a chat message container
595
  const messageBlock = document.createElement('div');
596
  messageBlock.className = 'message-block';
597
 
@@ -607,9 +690,7 @@
607
  assistantAnswer.innerHTML = `<strong>Assistant:</strong><br>${escapeHtml(data.answer).replace(/\n/g, '<br>')}`;
608
  messageBlock.appendChild(assistantAnswer);
609
 
610
- // ========================================
611
- // CHANGE 2: Display TEXT SOURCES FIRST
612
- // ========================================
613
  if (data.texts && data.texts.length > 0) {
614
  const sourcesSection = document.createElement('div');
615
  sourcesSection.className = 'sources-section';
@@ -617,7 +698,7 @@
617
  const sourcesHeader = document.createElement('div');
618
  sourcesHeader.className = 'sources-header';
619
  sourcesHeader.innerHTML = `
620
- <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
621
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
622
  d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
623
  </svg>
@@ -627,21 +708,18 @@
627
 
628
  const topTexts = data.texts.slice(0, 3);
629
 
630
- topTexts.forEach((txt, index) => {
631
  const div = document.createElement('div');
632
  div.className = 'source-card';
633
  const truncatedText = txt.text.length > 250 ? txt.text.substring(0, 250) + '...' : txt.text;
634
 
635
- // ========================================
636
- // CHANGE 1: Add clickable link to source
637
- // ========================================
638
  const sourceLink = txt.link ? `<a href="${escapeHtml(txt.link)}" target="_blank" class="view-source-link" onclick="event.stopPropagation()">
639
  View in document →
640
  </a>` : '';
641
 
642
  div.innerHTML = `
643
  <div class="source-title">
644
- <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
645
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
646
  d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
647
  </svg>
@@ -654,7 +732,6 @@
654
  </div>
655
  `;
656
 
657
- // Make entire card clickable
658
  if (txt.link) {
659
  div.style.cursor = 'pointer';
660
  div.onclick = () => window.open(txt.link, '_blank');
@@ -666,11 +743,9 @@
666
  messageBlock.appendChild(sourcesSection);
667
  }
668
 
669
- // ========================================
670
- // CHANGE 2: Display IMAGES SECOND (and limit to 1-2)
671
- // ========================================
672
  if (data.images && data.images.length > 0) {
673
- const relevantImages = data.images.filter(img => (img.score || 0) >= 0.3).slice(0, 2); // Max 2 images
674
 
675
  if (relevantImages.length > 0) {
676
  const imagesSection = document.createElement('div');
@@ -679,7 +754,7 @@
679
  const imagesHeader = document.createElement('div');
680
  imagesHeader.className = 'sources-header';
681
  imagesHeader.innerHTML = `
682
- <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
683
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
684
  d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
685
  </svg>
@@ -712,92 +787,36 @@
712
  }
713
  }
714
 
715
- // ========================================
716
- // CHANGE 3: Add follow-up question box
717
- // ========================================
718
- const followupContainer = document.createElement('div');
719
- followupContainer.className = 'followup-container';
720
- followupContainer.innerHTML = `
721
- <div class="followup-label">
722
- <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
723
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
724
- d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"></path>
725
- </svg>
726
- Ask a follow-up question
727
- </div>
728
- <div class="followup-input-group">
729
- <input type="text" placeholder="e.g., Can you explain that in more detail?" class="followup-input">
730
- <button class="followup-btn" onclick="askFollowup(this)">
731
- <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="display: inline; vertical-align: middle;">
732
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
733
- </svg>
734
- </button>
735
- </div>
736
- `;
737
- messageBlock.appendChild(followupContainer);
738
-
739
- // Add enter key support for follow-up input
740
- const followupInput = followupContainer.querySelector('.followup-input');
741
- followupInput.addEventListener('keypress', function(e) {
742
- if (e.key === 'Enter') {
743
- askFollowup(followupContainer.querySelector('.followup-btn'));
744
- }
745
- });
746
-
747
- chatHistoryContainer.appendChild(messageBlock);
748
 
749
- // Scroll to the new message
750
  setTimeout(() => {
751
- messageBlock.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
752
  }, 100);
753
 
754
  } catch (error) {
755
- alert('Error querying system. Please try again.');
756
- console.error('Query error:', error);
757
  } finally {
758
- loading.style.display = 'none';
759
- searchBtn.disabled = false;
760
- searchBtn.innerHTML = `
761
- <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
762
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
763
- </svg>
764
- Analyze
765
- `;
766
- input.disabled = false;
767
- input.focus();
768
- input.value = '';
769
  }
770
  }
771
 
772
- // New function to handle follow-up questions
773
- function askFollowup(button) {
774
- const container = button.closest('.followup-container');
775
- const followupInput = container.querySelector('.followup-input');
776
- const followupQuestion = followupInput.value.trim();
777
-
778
- if (!followupQuestion) return;
779
-
780
- // Disable the follow-up input and button
781
- followupInput.disabled = true;
782
- button.disabled = true;
783
- button.innerHTML = '...';
784
-
785
- // Ask the question
786
- askQuestion(followupQuestion);
787
-
788
- // Clear the follow-up input
789
- followupInput.value = '';
790
- }
791
-
792
  async function downloadPDFReport() {
793
  if (conversationData.length === 0) {
794
  alert('No conversation to download');
795
  return;
796
  }
797
 
798
- // Disable button and show loading state
799
  downloadBtn.disabled = true;
800
- downloadBtn.innerHTML = '<span style="font-size: 0.9em">Generating PDF...</span>';
 
801
 
802
  try {
803
  const response = await fetch('/generate-report', {
@@ -806,14 +825,9 @@
806
  body: JSON.stringify({ conversations: conversationData })
807
  });
808
 
809
- if (!response.ok) {
810
- throw new Error(`Server error: ${response.status}`);
811
- }
812
 
813
- // Get the PDF blob
814
  const blob = await response.blob();
815
-
816
- // Create download link
817
  const url = URL.createObjectURL(blob);
818
  const a = document.createElement('a');
819
  a.href = url;
@@ -824,34 +838,26 @@
824
  URL.revokeObjectURL(url);
825
 
826
  } catch (error) {
827
- alert('Error generating PDF report. Please try again.');
828
- console.error('PDF generation error:', error);
829
  } finally {
830
- // Re-enable button
831
  downloadBtn.disabled = false;
832
- downloadBtn.innerHTML = `
833
- <svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
834
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
835
- d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
836
- </svg>
837
- Download PDF Report
838
- `;
839
  }
840
  }
841
 
842
  function clearHistory() {
843
- if (conversationData.length === 0) {
844
- return;
845
- }
846
 
847
- if (!confirm('Are you sure you want to clear the conversation history? This cannot be undone.')) {
848
- return;
849
- }
850
 
851
  conversationData = [];
852
- chatHistoryContainer.innerHTML = '';
853
  actionButtons.style.display = 'none';
 
854
  }
 
 
 
855
  </script>
856
  </body>
857
 
 
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>WHEC Chatbot • AI Research Assistant</title>
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
  <link
 
21
  --border-color: #334155;
22
  --accent-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
23
  }
24
+
25
  * {
26
  box-sizing: border-box;
27
  margin: 0;
28
  padding: 0;
29
  }
30
+
31
+ html, body {
32
+ height: 100%;
33
+ overflow: hidden;
34
+ }
35
+
36
  body {
37
  font-family: 'Outfit', sans-serif;
38
  background-color: var(--bg-dark);
39
  color: var(--text-main);
 
40
  line-height: 1.6;
41
  background-image:
42
  radial-gradient(circle at 10% 20%, rgba(59, 130, 246, 0.1) 0%, transparent 20%),
43
  radial-gradient(circle at 90% 80%, rgba(139, 92, 246, 0.1) 0%, transparent 20%);
 
 
 
 
 
44
  display: flex;
45
  flex-direction: column;
 
46
  }
47
+
48
+ /* Header - Fixed at top */
49
  header {
50
  text-align: center;
51
+ padding: 2rem 2rem 1rem;
52
+ border-bottom: 1px solid var(--border-color);
53
+ background: rgba(15, 23, 42, 0.8);
54
+ backdrop-filter: blur(10px);
55
+ flex-shrink: 0;
56
  }
57
+
58
  h1 {
59
  font-family: 'Space Grotesk', sans-serif;
60
+ font-size: 2rem;
61
  font-weight: 700;
62
  background: var(--accent-gradient);
63
  -webkit-background-clip: text;
64
  -webkit-text-fill-color: transparent;
65
  background-clip: text;
66
+ margin-bottom: 0.25rem;
67
  letter-spacing: -0.02em;
68
  }
69
+
70
  .subtitle {
71
  color: var(--text-muted);
72
+ font-size: 0.9rem;
73
  font-weight: 300;
74
  }
75
+
76
  .subtitle a {
77
  color: var(--primary);
78
  text-decoration: none;
79
  }
80
+
81
  .subtitle a:hover {
82
  text-decoration: underline;
83
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ /* Action Buttons - In header */
86
  .action-buttons {
 
 
 
87
  display: flex;
88
+ gap: 0.75rem;
89
+ justify-content: center;
90
+ margin-top: 1rem;
91
  }
92
 
93
+ .action-buttons button {
94
  background: rgba(139, 92, 246, 0.2);
95
  color: #c4b5fd;
96
  border: 1px solid #8b5cf6;
97
+ padding: 0.5rem 1rem;
98
+ border-radius: 0.5rem;
99
  font-weight: 600;
100
+ font-size: 0.85rem;
101
  cursor: pointer;
102
  transition: all 0.2s;
103
  display: flex;
 
105
  gap: 0.5rem;
106
  }
107
 
108
+ .action-buttons button:hover:not(:disabled) {
109
  background: rgba(139, 92, 246, 0.3);
110
  border-color: #a78bfa;
111
  }
112
 
113
+ .action-buttons button:disabled {
114
  opacity: 0.5;
115
  cursor: not-allowed;
116
  }
117
 
118
+ #clearBtn {
119
  background: rgba(239, 68, 68, 0.2);
120
  color: #fca5a5;
121
+ border-color: #ef4444;
 
 
 
 
 
 
 
 
 
122
  }
123
 
124
+ #clearBtn:hover {
125
  background: rgba(239, 68, 68, 0.3);
126
  border-color: #f87171;
127
  }
128
 
129
+ /* Chat Container - Scrollable middle section */
130
+ .chat-container {
131
+ flex: 1;
132
+ overflow-y: auto;
133
+ padding: 2rem;
134
+ display: flex;
135
+ flex-direction: column;
136
  }
137
 
138
+ /* Empty State - Shows when no messages */
139
+ .empty-state {
140
+ margin: auto;
141
  text-align: center;
142
+ max-width: 600px;
143
  padding: 2rem;
144
  }
145
+
146
+ .empty-state h2 {
147
+ font-size: 1.5rem;
148
+ color: var(--text-main);
149
+ margin-bottom: 1rem;
 
 
 
150
  }
151
+
152
+ .empty-state p {
153
+ color: var(--text-muted);
154
+ margin-bottom: 1.5rem;
155
  }
156
+
157
+ .suggestions {
158
+ display: grid;
159
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
160
+ gap: 1rem;
161
+ margin-top: 1rem;
162
+ }
163
+
164
+ .suggestion-card {
165
+ background: rgba(30, 41, 59, 0.5);
166
+ border: 1px solid var(--border-color);
167
+ border-radius: 0.75rem;
168
+ padding: 1rem;
169
+ cursor: pointer;
170
+ transition: all 0.2s;
171
+ text-align: left;
172
+ }
173
+
174
+ .suggestion-card:hover {
175
+ background: rgba(30, 41, 59, 0.8);
176
+ border-color: var(--primary);
177
+ transform: translateY(-2px);
178
+ }
179
+
180
+ .suggestion-card .icon {
181
+ font-size: 1.5rem;
182
+ margin-bottom: 0.5rem;
183
+ }
184
+
185
+ .suggestion-card .text {
186
+ color: var(--text-main);
187
+ font-size: 0.9rem;
188
+ }
189
+
190
  /* Chat History */
191
  #chatHistory {
192
+ max-width: 900px;
193
+ width: 100%;
194
  margin: 0 auto;
195
  display: flex;
196
  flex-direction: column;
197
+ gap: 1.5rem;
198
  }
199
+
200
  .message-block {
201
  border: 1px solid var(--border-color);
202
  border-radius: 1rem;
 
204
  background: var(--bg-card);
205
  animation: fadeInUp 0.4s ease-out;
206
  }
207
+
208
  .user-question {
209
  margin-bottom: 1rem;
210
  color: var(--text-main);
211
+ font-size: 0.95rem;
212
  }
213
+
214
  .user-question strong {
215
  color: var(--primary);
216
  }
217
+
218
  .assistant-answer {
219
  margin-bottom: 1rem;
220
  line-height: 1.8;
221
+ font-size: 0.95rem;
222
  }
223
+
224
  .assistant-answer strong {
225
  color: #8b5cf6;
226
  }
227
 
228
+ /* Sources Section */
229
  .sources-section {
230
  margin-top: 1rem;
231
  }
 
234
  font-weight: 600;
235
  color: var(--primary);
236
  margin-bottom: 0.75rem;
237
+ font-size: 0.9rem;
238
  display: flex;
239
  align-items: center;
240
  gap: 0.5rem;
 
244
  background: rgba(30, 41, 59, 0.4);
245
  border: 1px solid var(--border-color);
246
  border-radius: 0.75rem;
247
+ padding: 1rem;
248
  margin-bottom: 0.75rem;
249
  transition: all 0.2s;
250
  cursor: pointer;
251
  }
252
+
253
  .source-card:hover {
254
  background: rgba(30, 41, 59, 0.8);
255
  border-color: var(--primary);
256
  transform: translateX(5px);
257
  }
258
+
259
  .source-title {
260
  font-weight: 600;
261
  color: var(--primary);
262
  margin-bottom: 0.5rem;
263
+ font-size: 0.85rem;
264
  display: flex;
265
  align-items: center;
266
  gap: 0.5rem;
267
  }
268
+
 
 
269
  .source-excerpt {
270
  font-style: italic;
271
  color: #cbd5e1;
272
+ font-size: 0.85rem;
273
  background: rgba(0, 0, 0, 0.2);
274
  padding: 0.75rem;
275
  border-radius: 0.5rem;
 
277
  word-wrap: break-word;
278
  line-height: 1.6;
279
  }
280
+
281
  .source-meta {
282
  margin-top: 0.75rem;
283
+ font-size: 0.75rem;
284
  color: var(--text-muted);
285
  display: flex;
286
  justify-content: space-between;
287
  align-items: center;
288
  }
289
+
290
  .view-source-link {
291
  color: var(--primary);
292
  text-decoration: none;
 
296
  gap: 0.25rem;
297
  transition: all 0.2s;
298
  }
299
+
300
  .view-source-link:hover {
301
  color: #60a5fa;
302
  text-decoration: underline;
303
  }
304
 
305
+ /* Images Section */
306
  .images-section {
307
  margin-top: 1.5rem;
308
  }
309
 
310
  .images-grid {
311
  display: grid;
312
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
313
+ gap: 1rem;
314
  margin-top: 1rem;
315
  }
316
+
317
  .image-card {
318
  background: rgba(30, 41, 59, 0.6);
319
  border-radius: 0.75rem;
 
321
  border: 1px solid var(--border-color);
322
  transition: all 0.3s ease;
323
  }
324
+
325
  .image-card:hover {
326
  transform: translateY(-5px);
327
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
328
  border-color: var(--primary);
329
  }
330
+
331
  .image-card a {
332
  display: block;
333
  overflow: hidden;
334
+ height: 180px;
335
  }
336
+
337
  .image-card img {
338
  width: 100%;
339
  height: 100%;
340
  object-fit: cover;
341
  transition: transform 0.5s ease;
342
  }
343
+
344
  .image-card:hover img {
345
  transform: scale(1.1);
346
  }
347
+
348
  .image-meta {
349
+ padding: 0.75rem;
350
  border-top: 1px solid var(--border-color);
351
  }
352
+
353
  .image-filename {
354
  font-weight: 600;
355
  color: #f1f5f9;
356
+ font-size: 0.8rem;
357
  white-space: nowrap;
358
  overflow: hidden;
359
  text-overflow: ellipsis;
360
  margin-bottom: 0.25rem;
361
  }
362
+
363
  .image-source {
364
+ font-size: 0.7rem;
365
  color: var(--text-muted);
366
  }
367
 
368
+ /* Loading indicator */
369
+ .loading-message {
370
+ max-width: 900px;
371
+ width: 100%;
372
+ margin: 0 auto;
373
  padding: 1.5rem;
374
+ background: var(--bg-card);
375
+ border: 1px solid var(--border-color);
376
  border-radius: 1rem;
377
+ display: none;
378
+ align-items: center;
379
+ gap: 1rem;
380
  }
381
 
382
+ .loading-message.active {
 
 
 
383
  display: flex;
 
 
384
  }
385
 
386
+ .spinner {
387
+ width: 20px;
388
+ height: 20px;
389
+ border: 2px solid rgba(59, 130, 246, 0.3);
390
+ border-radius: 50%;
391
+ border-top-color: var(--primary);
392
+ animation: spin 1s linear infinite;
393
+ }
394
+
395
+ @keyframes spin {
396
+ to { transform: rotate(360deg); }
397
+ }
398
+
399
+ /* Input Container - Fixed at bottom */
400
+ .input-container {
401
+ border-top: 1px solid var(--border-color);
402
+ background: rgba(15, 23, 42, 0.95);
403
+ backdrop-filter: blur(10px);
404
+ padding: 1.5rem 2rem;
405
+ flex-shrink: 0;
406
+ }
407
+
408
+ .input-wrapper {
409
+ max-width: 900px;
410
+ margin: 0 auto;
411
+ position: relative;
412
+ }
413
+
414
+ .input-group {
415
+ position: relative;
416
  display: flex;
417
+ align-items: center;
418
  gap: 0.75rem;
419
+ background: rgba(30, 41, 59, 0.7);
420
+ padding: 0.5rem;
421
+ border-radius: 1.5rem;
422
+ border: 1px solid var(--border-color);
423
+ transition: all 0.2s ease;
424
+ }
425
+
426
+ .input-group:focus-within {
427
+ border-color: var(--primary);
428
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
429
  }
430
 
431
+ .input-group textarea {
432
  flex: 1;
433
+ background: transparent;
434
+ border: none;
435
  padding: 0.75rem 1rem;
 
436
  font-size: 1rem;
437
  color: white;
438
  font-family: 'Outfit', sans-serif;
439
+ resize: none;
440
+ max-height: 200px;
441
+ min-height: 24px;
442
+ overflow-y: auto;
443
  }
444
 
445
+ .input-group textarea:focus {
446
  outline: none;
 
 
447
  }
448
 
449
+ .input-group textarea::placeholder {
450
  color: #64748b;
451
  }
452
 
453
+ .send-button {
454
+ background: var(--accent-gradient);
455
+ color: white;
456
+ border: none;
457
+ padding: 0.75rem;
458
+ border-radius: 1rem;
 
 
459
  cursor: pointer;
460
  transition: all 0.2s;
461
+ display: flex;
462
+ align-items: center;
463
+ justify-content: center;
464
+ flex-shrink: 0;
465
  }
466
 
467
+ .send-button:hover:not(:disabled) {
468
+ opacity: 0.9;
469
+ transform: scale(1.05);
470
  }
471
 
472
+ .send-button:disabled {
473
  opacity: 0.5;
474
  cursor: not-allowed;
475
  }
476
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  /* Scrollbar */
478
  ::-webkit-scrollbar {
479
  width: 8px;
480
  }
481
+
482
  ::-webkit-scrollbar-track {
483
  background: var(--bg-dark);
484
  }
485
+
486
  ::-webkit-scrollbar-thumb {
487
  background: var(--border-color);
488
  border-radius: 4px;
489
  }
490
+
491
  ::-webkit-scrollbar-thumb:hover {
492
  background: #475569;
493
  }
494
+
495
+ /* Animations */
496
+ @keyframes fadeInUp {
497
+ from {
498
+ opacity: 0;
499
+ transform: translateY(20px);
500
+ }
501
+ to {
502
+ opacity: 1;
503
+ transform: translateY(0);
504
+ }
505
+ }
506
  </style>
507
  </head>
508
 
509
  <body>
510
+ <!-- Fixed Header -->
511
+ <header>
512
+ <h1>WHEC Chatbot</h1>
513
+ <p class="subtitle">
514
+ AI Research Assistant for WHEC (Warrior Heat- and Exertion-Related Events Collaborative)
515
+ <a href="https://www.hprc-online.org/resources-partners/whec" target="_blank">Learn more</a>
516
+ </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
 
518
  <div class="action-buttons" id="actionButtons" style="display: none;">
519
  <button id="downloadBtn" onclick="downloadPDFReport()">
520
+ <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
521
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
522
  d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
523
  </svg>
524
+ Download Report
525
  </button>
526
  <button id="clearBtn" onclick="clearHistory()">
527
+ <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
528
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
529
  d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
530
  </svg>
531
  Clear Chat
532
  </button>
533
  </div>
534
+ </header>
535
+
536
+ <!-- Scrollable Chat Area -->
537
+ <div class="chat-container" id="chatContainer">
538
+ <!-- Empty State -->
539
+ <div class="empty-state" id="emptyState">
540
+ <h2>Welcome to WHEC Research Assistant</h2>
541
+ <p>Ask questions about heat and exertion-related events, medical research, and military health topics.</p>
542
+
543
+ <div class="suggestions">
544
+ <div class="suggestion-card" onclick="askSuggestion('What is WHEC?')">
545
+ <div class="icon">🏥</div>
546
+ <div class="text">What is WHEC?</div>
547
+ </div>
548
+ <div class="suggestion-card" onclick="askSuggestion('What are the symptoms of heat stroke?')">
549
+ <div class="icon">🌡️</div>
550
+ <div class="text">Symptoms of heat stroke</div>
551
+ </div>
552
+ <div class="suggestion-card" onclick="askSuggestion('How to prevent exertional heat illness?')">
553
+ <div class="icon">🛡️</div>
554
+ <div class="text">Prevention strategies</div>
555
+ </div>
556
+ <div class="suggestion-card" onclick="askSuggestion('What is the Heat Toolkit?')">
557
+ <div class="icon">📚</div>
558
+ <div class="text">About Heat Toolkit</div>
559
+ </div>
560
+ </div>
561
+ </div>
562
+
563
+ <!-- Chat History -->
564
  <div id="chatHistory"></div>
565
+
566
+ <!-- Loading Indicator -->
567
+ <div class="loading-message" id="loadingMessage">
568
  <div class="spinner"></div>
569
+ <span>Searching documents and generating response...</span>
570
+ </div>
571
+ </div>
572
+
573
+ <!-- Fixed Input at Bottom -->
574
+ <div class="input-container">
575
+ <div class="input-wrapper">
576
+ <div class="input-group">
577
+ <textarea
578
+ id="messageInput"
579
+ placeholder="Ask about exertion-related injuries, heat illness, or military health topics..."
580
+ rows="1"
581
+ ></textarea>
582
+ <button class="send-button" id="sendButton" onclick="sendMessage()">
583
+ <svg width="20" height="20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
584
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
585
+ d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
586
+ </svg>
587
+ </button>
588
+ </div>
589
  </div>
590
  </div>
591
 
592
  <script>
593
+ const messageInput = document.getElementById('messageInput');
594
+ const sendButton = document.getElementById('sendButton');
595
+ const chatHistory = document.getElementById('chatHistory');
596
+ const chatContainer = document.getElementById('chatContainer');
597
+ const emptyState = document.getElementById('emptyState');
598
+ const loadingMessage = document.getElementById('loadingMessage');
599
  const actionButtons = document.getElementById('actionButtons');
600
  const downloadBtn = document.getElementById('downloadBtn');
601
 
 
602
  let conversationData = [];
603
 
604
+ // Auto-resize textarea
605
+ messageInput.addEventListener('input', function() {
606
+ this.style.height = 'auto';
607
+ this.style.height = Math.min(this.scrollHeight, 200) + 'px';
608
+ });
609
+
610
+ // Send on Enter (Shift+Enter for new line)
611
+ messageInput.addEventListener('keydown', function(e) {
612
+ if (e.key === 'Enter' && !e.shiftKey) {
613
+ e.preventDefault();
614
+ sendMessage();
615
  }
616
  });
617
 
 
621
  return div.innerHTML;
622
  }
623
 
624
+ function askSuggestion(question) {
625
+ messageInput.value = question;
626
+ sendMessage();
627
+ }
628
+
629
+ async function sendMessage() {
630
+ const question = messageInput.value.trim();
631
  if (!question) return;
632
 
633
+ // Hide empty state
634
+ if (emptyState) {
635
+ emptyState.style.display = 'none';
636
+ }
637
+
638
+ // Clear input
639
+ messageInput.value = '';
640
+ messageInput.style.height = 'auto';
641
+
642
+ // Disable input
643
+ messageInput.disabled = true;
644
+ sendButton.disabled = true;
645
+
646
+ // Show loading
647
+ loadingMessage.classList.add('active');
648
+
649
+ // Scroll to bottom
650
+ chatContainer.scrollTop = chatContainer.scrollHeight;
651
 
652
  try {
653
  const response = await fetch('/query', {
 
662
 
663
  const data = await response.json();
664
 
665
+ // Store conversation
666
  conversationData.push({
667
  question: question,
668
  answer: data.answer,
 
671
  timestamp: new Date().toISOString()
672
  });
673
 
674
+ // Show action buttons
675
  actionButtons.style.display = 'flex';
676
 
677
+ // Create message block
678
  const messageBlock = document.createElement('div');
679
  messageBlock.className = 'message-block';
680
 
 
690
  assistantAnswer.innerHTML = `<strong>Assistant:</strong><br>${escapeHtml(data.answer).replace(/\n/g, '<br>')}`;
691
  messageBlock.appendChild(assistantAnswer);
692
 
693
+ // Text sources
 
 
694
  if (data.texts && data.texts.length > 0) {
695
  const sourcesSection = document.createElement('div');
696
  sourcesSection.className = 'sources-section';
 
698
  const sourcesHeader = document.createElement('div');
699
  sourcesHeader.className = 'sources-header';
700
  sourcesHeader.innerHTML = `
701
+ <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
702
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
703
  d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
704
  </svg>
 
708
 
709
  const topTexts = data.texts.slice(0, 3);
710
 
711
+ topTexts.forEach((txt) => {
712
  const div = document.createElement('div');
713
  div.className = 'source-card';
714
  const truncatedText = txt.text.length > 250 ? txt.text.substring(0, 250) + '...' : txt.text;
715
 
 
 
 
716
  const sourceLink = txt.link ? `<a href="${escapeHtml(txt.link)}" target="_blank" class="view-source-link" onclick="event.stopPropagation()">
717
  View in document →
718
  </a>` : '';
719
 
720
  div.innerHTML = `
721
  <div class="source-title">
722
+ <svg width="12" height="12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
723
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
724
  d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
725
  </svg>
 
732
  </div>
733
  `;
734
 
 
735
  if (txt.link) {
736
  div.style.cursor = 'pointer';
737
  div.onclick = () => window.open(txt.link, '_blank');
 
743
  messageBlock.appendChild(sourcesSection);
744
  }
745
 
746
+ // Images
 
 
747
  if (data.images && data.images.length > 0) {
748
+ const relevantImages = data.images.filter(img => (img.score || 0) >= 0.3).slice(0, 2);
749
 
750
  if (relevantImages.length > 0) {
751
  const imagesSection = document.createElement('div');
 
754
  const imagesHeader = document.createElement('div');
755
  imagesHeader.className = 'sources-header';
756
  imagesHeader.innerHTML = `
757
+ <svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24">
758
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
759
  d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
760
  </svg>
 
787
  }
788
  }
789
 
790
+ chatHistory.appendChild(messageBlock);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
791
 
792
+ // Scroll to bottom smoothly
793
  setTimeout(() => {
794
+ chatContainer.scrollTop = chatContainer.scrollHeight;
795
  }, 100);
796
 
797
  } catch (error) {
798
+ alert('Error: ' + error.message);
799
+ console.error('Error:', error);
800
  } finally {
801
+ // Hide loading
802
+ loadingMessage.classList.remove('active');
803
+
804
+ // Re-enable input
805
+ messageInput.disabled = false;
806
+ sendButton.disabled = false;
807
+ messageInput.focus();
 
 
 
 
808
  }
809
  }
810
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
811
  async function downloadPDFReport() {
812
  if (conversationData.length === 0) {
813
  alert('No conversation to download');
814
  return;
815
  }
816
 
 
817
  downloadBtn.disabled = true;
818
+ const originalText = downloadBtn.innerHTML;
819
+ downloadBtn.innerHTML = '<span style="font-size: 0.85em">Generating...</span>';
820
 
821
  try {
822
  const response = await fetch('/generate-report', {
 
825
  body: JSON.stringify({ conversations: conversationData })
826
  });
827
 
828
+ if (!response.ok) throw new Error(`Server error: ${response.status}`);
 
 
829
 
 
830
  const blob = await response.blob();
 
 
831
  const url = URL.createObjectURL(blob);
832
  const a = document.createElement('a');
833
  a.href = url;
 
838
  URL.revokeObjectURL(url);
839
 
840
  } catch (error) {
841
+ alert('Error generating report: ' + error.message);
 
842
  } finally {
 
843
  downloadBtn.disabled = false;
844
+ downloadBtn.innerHTML = originalText;
 
 
 
 
 
 
845
  }
846
  }
847
 
848
  function clearHistory() {
849
+ if (conversationData.length === 0) return;
 
 
850
 
851
+ if (!confirm('Clear all conversation history? This cannot be undone.')) return;
 
 
852
 
853
  conversationData = [];
854
+ chatHistory.innerHTML = '';
855
  actionButtons.style.display = 'none';
856
+ emptyState.style.display = 'block';
857
  }
858
+
859
+ // Focus input on load
860
+ messageInput.focus();
861
  </script>
862
  </body>
863