Abs6187 commited on
Commit
907608e
·
verified ·
1 Parent(s): 2494ccd

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +282 -205
templates/index.html CHANGED
@@ -211,6 +211,16 @@
211
  </div>
212
  <input id="file-upload-hidden" name="file" type="file" accept="image/*" class="hidden"/>
213
  </div>
 
 
 
 
 
 
 
 
 
 
214
  </div>
215
 
216
  <!-- Submit Button -->
@@ -325,9 +335,14 @@
325
  const chatSubmit = document.getElementById('chat-submit');
326
  const chatForm = document.getElementById('chat-form');
327
  const promptsContainer = document.getElementById('predefined-prompts-container');
 
 
 
 
328
 
329
  let predictionContext = null;
330
  let chatHistory = [];
 
331
 
332
  // File upload handling
333
  uploadZone.addEventListener('click', () => fileUploadHidden.click());
@@ -362,209 +377,271 @@
362
  uploadZone.style.display = 'block';
363
  fileUploadHidden.value = '';
364
  });
365
-
366
- function handleFileSelect(file) {
367
- if (!file.type.startsWith('image/')) {
368
- alert('Please select an image file.');
369
- return;
370
- }
371
- if (file.size > 10 * 1024 * 1024) {
372
- alert('File size must be less than 10MB.');
373
- return;
374
- }
375
-
376
- const reader = new FileReader();
377
- reader.onload = (e) => {
378
- imagePreview.src = e.target.result;
379
- imagePreviewContainer.classList.remove('hidden');
380
- uploadZone.style.display = 'none';
381
- };
382
- reader.readAsDataURL(file);
383
- }
384
-
385
- uploadForm.addEventListener('submit', async (e) => {
386
- e.preventDefault();
387
- if (!fileUploadHidden.files[0]) {
388
- alert("Please select an image file first.");
389
- return;
390
- }
391
-
392
- const submitButton = uploadForm.querySelector('button[type="submit"]');
393
- const originalText = submitButton.innerHTML;
394
- submitButton.disabled = true;
395
- submitButton.innerHTML = `
396
- <div class="flex items-center">
397
- <div class="flex space-x-1 mr-2">
398
- <div class="typing-dot"></div>
399
- <div class="typing-dot"></div>
400
- <div class="typing-dot"></div>
401
- </div>
402
- Analyzing...
403
- </div>
404
- `;
405
-
406
- // Reset states
407
- resultsSection.classList.add('hidden');
408
- chatCard.classList.add('opacity-50', 'pointer-events-none');
409
- chatStatus.textContent = 'Analyzing';
410
- chatStatus.className = 'bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs';
411
- chatInput.disabled = true;
412
- chatSubmit.disabled = true;
413
-
414
- chatMessages.innerHTML = `
415
- <div class="bot-message p-3 rounded-xl max-w-[90%]">
416
- <div class="flex items-center mb-2">
417
- <div class="flex space-x-1 mr-2">
418
- <div class="typing-dot"></div>
419
- <div class="typing-dot"></div>
420
- <div class="typing-dot"></div>
421
- </div>
422
- <span class="text-xs text-gray-500">ANALYZING</span>
423
- </div>
424
- Processing your medical image...
425
- </div>
426
- `;
427
-
428
- predictionContext = null;
429
- chatHistory = [];
430
- promptsContainer.innerHTML = '';
431
-
432
- try {
433
- const formData = new FormData(uploadForm);
434
- const response = await fetch('/predict', { method: 'POST', body: formData });
435
- const data = await response.json();
436
-
437
- if (data.error) {
438
- alert(`Error: ${data.error}`);
439
- return;
440
- }
441
-
442
- predictionContext = data;
443
- resultsSection.classList.remove('hidden');
444
-
445
- predictionOutput.innerHTML = `
446
- <div class="bg-gradient-to-r from-indigo-50 to-purple-50 p-6 rounded-xl border border-indigo-200">
447
- <div class="flex items-center justify-between mb-4">
448
- <span class="text-sm font-semibold text-indigo-600 bg-indigo-100 px-3 py-1 rounded-full">${data.model_used}</span>
449
- <span class="text-2xl font-bold text-gray-900">${(data.confidence * 100).toFixed(1)}%</span>
450
- </div>
451
- <h3 class="text-2xl font-bold text-gray-900 mb-3">${data.prediction}</h3>
452
- <div class="bg-gray-200 rounded-full h-3 overflow-hidden">
453
- <div class="progress-bar h-3 rounded-full" style="width: ${(data.confidence * 100).toFixed(0)}%"></div>
454
- </div>
455
- </div>
456
- `;
457
-
458
- originalImage.src = `${data.original_image}?t=${new Date().getTime()}`;
459
-
460
- if (data.gradcam_image) {
461
- gradcamImage.src = `${data.gradcam_image}?t=${new Date().getTime()}`;
462
- gradcamImage.classList.remove('hidden');
463
- gradcamPlaceholder.classList.add('hidden');
464
- } else {
465
- gradcamImage.classList.add('hidden');
466
- gradcamPlaceholder.classList.remove('hidden');
467
- }
468
-
469
- // Activate chat
470
- chatCard.classList.remove('opacity-50', 'pointer-events-none');
471
- chatStatus.textContent = 'Active';
472
- chatStatus.className = 'bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs';
473
-
474
- chatMessages.innerHTML = '';
475
- addMessage("Analysis complete! Ask me anything about the results.", 'bot');
476
- chatInput.disabled = false;
477
- chatSubmit.disabled = false;
478
-
479
- if (data.predefined_prompts) {
480
- data.predefined_prompts.forEach(prompt => {
481
- const btn = document.createElement('button');
482
- btn.textContent = prompt;
483
- btn.className = 'bg-indigo-100 text-indigo-700 text-sm px-3 py-1 rounded-full hover:bg-indigo-200 transition-all';
484
- btn.onclick = () => {
485
- chatInput.value = prompt;
486
- chatForm.requestSubmit();
487
- };
488
- promptsContainer.appendChild(btn);
489
- });
490
- }
491
-
492
- } catch (error) {
493
- alert('An unexpected error occurred.');
494
- console.error("Prediction Error:", error);
495
- } finally {
496
- submitButton.disabled = false;
497
- submitButton.innerHTML = originalText;
498
- }
499
- });
500
-
501
- chatForm.addEventListener('submit', async (e) => {
502
- e.preventDefault();
503
- const message = chatInput.value.trim();
504
- if (!message || !predictionContext) return;
505
-
506
- addMessage(message, 'user');
507
- chatHistory.push({ role: 'user', parts: [message] });
508
- chatInput.value = '';
509
- promptsContainer.innerHTML = '';
510
-
511
- const thinkingMsg = addMessage('', 'bot');
512
- thinkingMsg.innerHTML = `
513
- <div class="flex items-center">
514
- <div class="flex space-x-1 mr-2">
515
- <div class="typing-dot"></div>
516
- <div class="typing-dot"></div>
517
- <div class="typing-dot"></div>
518
- </div>
519
- <span class="text-sm text-gray-500">Thinking...</span>
520
- </div>
521
- `;
522
-
523
- try {
524
- const response = await fetch('/chat', {
525
- method: 'POST',
526
- headers: { 'Content-Type': 'application/json' },
527
- body: JSON.stringify({ message, context: predictionContext, history: chatHistory })
528
- });
529
- const data = await response.json();
530
-
531
- thinkingMsg.remove();
532
- if (data.error) {
533
- addMessage(`Error: ${data.error}`, 'bot');
534
- chatHistory.pop();
535
- } else {
536
- addMessage(data.response, 'bot');
537
- chatHistory.push({ role: 'model', parts: [data.response] });
538
- }
539
- } catch (error) {
540
- thinkingMsg.remove();
541
- addMessage('Connection error. Please try again.', 'bot');
542
- chatHistory.pop();
543
- console.error("Chat Error:", error);
544
- }
545
- });
546
-
547
- function addMessage(text, type) {
548
- const msgDiv = document.createElement('div');
549
- msgDiv.className = `p-3 rounded-xl max-w-[90%] ${type}-message mb-3`;
550
-
551
- if (type === 'user') {
552
- msgDiv.classList.add('ml-auto', 'text-right');
553
- msgDiv.textContent = text;
554
- } else {
555
- msgDiv.classList.add('mr-auto');
556
- if (text) {
557
- msgDiv.innerHTML = marked.parse(text);
558
- }
559
- }
560
-
561
- chatMessages.appendChild(msgDiv);
562
- chatMessages.scrollTop = chatMessages.scrollHeight;
563
- return msgDiv;
564
- }
565
-
566
- // Smooth scrolling
567
- document.documentElement.style.scrollBehavior = 'smooth';
568
- </script>
569
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
  </html>
 
211
  </div>
212
  <input id="file-upload-hidden" name="file" type="file" accept="image/*" class="hidden"/>
213
  </div>
214
+
215
+ <!-- Examples Gallery -->
216
+ <div class="mt-6">
217
+ <div class="flex items-center justify-between mb-3">
218
+ <h3 class="text-lg font-semibold text-gray-700">Or pick an example</h3>
219
+ <button type="button" id="refresh-examples" class="text-sm text-indigo-700 hover:underline">Refresh</button>
220
+ </div>
221
+ <div id="examples-container" class="grid grid-cols-2 sm:grid-cols-3 gap-3"></div>
222
+ <p id="examples-empty" class="text-sm text-gray-500 hidden">No examples found.</p>
223
+ </div>
224
  </div>
225
 
226
  <!-- Submit Button -->
 
335
  const chatSubmit = document.getElementById('chat-submit');
336
  const chatForm = document.getElementById('chat-form');
337
  const promptsContainer = document.getElementById('predefined-prompts-container');
338
+ const modelSelect = document.getElementById('model-select');
339
+ const examplesContainer = document.getElementById('examples-container');
340
+ const examplesEmpty = document.getElementById('examples-empty');
341
+ const refreshExamplesBtn = document.getElementById('refresh-examples');
342
 
343
  let predictionContext = null;
344
  let chatHistory = [];
345
+ let cachedExamples = [];
346
 
347
  // File upload handling
348
  uploadZone.addEventListener('click', () => fileUploadHidden.click());
 
377
  uploadZone.style.display = 'block';
378
  fileUploadHidden.value = '';
379
  });
380
+
381
+ function handleFileSelect(file) {
382
+ if (!file.type.startsWith('image/')) {
383
+ alert('Please select an image file.');
384
+ return;
385
+ }
386
+ if (file.size > 10 * 1024 * 1024) {
387
+ alert('File size must be less than 10MB.');
388
+ return;
389
+ }
390
+
391
+ const reader = new FileReader();
392
+ reader.onload = (e) => {
393
+ imagePreview.src = e.target.result;
394
+ imagePreviewContainer.classList.remove('hidden');
395
+ uploadZone.style.display = 'none';
396
+ };
397
+ reader.readAsDataURL(file);
398
+ }
399
+
400
+ uploadForm.addEventListener('submit', async (e) => {
401
+ e.preventDefault();
402
+ if (!fileUploadHidden.files[0]) {
403
+ alert("Please select an image file first.");
404
+ return;
405
+ }
406
+
407
+ const submitButton = uploadForm.querySelector('button[type="submit"]');
408
+ const originalText = submitButton.innerHTML;
409
+ submitButton.disabled = true;
410
+ submitButton.innerHTML = `
411
+ <div class="flex items-center">
412
+ <div class="flex space-x-1 mr-2">
413
+ <div class="typing-dot"></div>
414
+ <div class="typing-dot"></div>
415
+ <div class="typing-dot"></div>
416
+ </div>
417
+ Analyzing...
418
+ </div>
419
+ `;
420
+
421
+ // Reset states
422
+ resultsSection.classList.add('hidden');
423
+ chatCard.classList.add('opacity-50', 'pointer-events-none');
424
+ chatStatus.textContent = 'Analyzing';
425
+ chatStatus.className = 'bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs';
426
+ chatInput.disabled = true;
427
+ chatSubmit.disabled = true;
428
+
429
+ chatMessages.innerHTML = `
430
+ <div class="bot-message p-3 rounded-xl max-w-[90%]">
431
+ <div class="flex items-center mb-2">
432
+ <div class="flex space-x-1 mr-2">
433
+ <div class="typing-dot"></div>
434
+ <div class="typing-dot"></div>
435
+ <div class="typing-dot"></div>
436
+ </div>
437
+ <span class="text-xs text-gray-500">ANALYZING</span>
438
+ </div>
439
+ Processing your medical image...
440
+ </div>
441
+ `;
442
+
443
+ predictionContext = null;
444
+ chatHistory = [];
445
+ promptsContainer.innerHTML = '';
446
+
447
+ try {
448
+ const formData = new FormData(uploadForm);
449
+ const response = await fetch('/predict', { method: 'POST', body: formData });
450
+ const data = await response.json();
451
+
452
+ if (data.error) {
453
+ alert(`Error: ${data.error}`);
454
+ return;
455
+ }
456
+
457
+ predictionContext = data;
458
+ resultsSection.classList.remove('hidden');
459
+
460
+ predictionOutput.innerHTML = `
461
+ <div class="bg-gradient-to-r from-indigo-50 to-purple-50 p-6 rounded-xl border border-indigo-200">
462
+ <div class="flex items-center justify-between mb-4">
463
+ <span class="text-sm font-semibold text-indigo-600 bg-indigo-100 px-3 py-1 rounded-full">${data.model_used}</span>
464
+ <span class="text-2xl font-bold text-gray-900">${(data.confidence * 100).toFixed(1)}%</span>
465
+ </div>
466
+ <h3 class="text-2xl font-bold text-gray-900 mb-3">${data.prediction}</h3>
467
+ <div class="bg-gray-200 rounded-full h-3 overflow-hidden">
468
+ <div class="progress-bar h-3 rounded-full" style="width: ${(data.confidence * 100).toFixed(0)}%"></div>
469
+ </div>
470
+ </div>
471
+ `;
472
+
473
+ originalImage.src = `${data.original_image}?t=${new Date().getTime()}`;
474
+
475
+ if (data.gradcam_image) {
476
+ gradcamImage.src = `${data.gradcam_image}?t=${new Date().getTime()}`;
477
+ gradcamImage.classList.remove('hidden');
478
+ gradcamPlaceholder.classList.add('hidden');
479
+ } else {
480
+ gradcamImage.classList.add('hidden');
481
+ gradcamPlaceholder.classList.remove('hidden');
482
+ }
483
+
484
+ // Activate chat
485
+ chatCard.classList.remove('opacity-50', 'pointer-events-none');
486
+ chatStatus.textContent = 'Active';
487
+ chatStatus.className = 'bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs';
488
+
489
+ chatMessages.innerHTML = '';
490
+ addMessage("Analysis complete! Ask me anything about the results.", 'bot');
491
+ chatInput.disabled = false;
492
+ chatSubmit.disabled = false;
493
+
494
+ if (data.predefined_prompts) {
495
+ data.predefined_prompts.forEach(prompt => {
496
+ const btn = document.createElement('button');
497
+ btn.textContent = prompt;
498
+ btn.className = 'bg-indigo-100 text-indigo-700 text-sm px-3 py-1 rounded-full hover:bg-indigo-200 transition-all';
499
+ btn.onclick = () => {
500
+ chatInput.value = prompt;
501
+ chatForm.requestSubmit();
502
+ };
503
+ promptsContainer.appendChild(btn);
504
+ });
505
+ }
506
+
507
+ } catch (error) {
508
+ alert('An unexpected error occurred.');
509
+ console.error("Prediction Error:", error);
510
+ } finally {
511
+ submitButton.disabled = false;
512
+ submitButton.innerHTML = originalText;
513
+ }
514
+ });
515
+
516
+ chatForm.addEventListener('submit', async (e) => {
517
+ e.preventDefault();
518
+ const message = chatInput.value.trim();
519
+ if (!message || !predictionContext) return;
520
+
521
+ addMessage(message, 'user');
522
+ chatHistory.push({ role: 'user', parts: [message] });
523
+ chatInput.value = '';
524
+ promptsContainer.innerHTML = '';
525
+
526
+ const thinkingMsg = addMessage('', 'bot');
527
+ thinkingMsg.innerHTML = `
528
+ <div class="flex items-center">
529
+ <div class="flex space-x-1 mr-2">
530
+ <div class="typing-dot"></div>
531
+ <div class="typing-dot"></div>
532
+ <div class="typing-dot"></div>
533
+ </div>
534
+ <span class="text-sm text-gray-500">Thinking...</span>
535
+ </div>
536
+ `;
537
+
538
+ try {
539
+ const response = await fetch('/chat', {
540
+ method: 'POST',
541
+ headers: { 'Content-Type': 'application/json' },
542
+ body: JSON.stringify({ message, context: predictionContext, history: chatHistory })
543
+ });
544
+ const data = await response.json();
545
+
546
+ thinkingMsg.remove();
547
+ if (data.error) {
548
+ addMessage(`Error: ${data.error}`, 'bot');
549
+ chatHistory.pop();
550
+ } else {
551
+ addMessage(data.response, 'bot');
552
+ chatHistory.push({ role: 'model', parts: [data.response] });
553
+ }
554
+ } catch (error) {
555
+ thinkingMsg.remove();
556
+ addMessage('Connection error. Please try again.', 'bot');
557
+ chatHistory.pop();
558
+ console.error("Chat Error:", error);
559
+ }
560
+ });
561
+
562
+ function addMessage(text, type) {
563
+ const msgDiv = document.createElement('div');
564
+ msgDiv.className = `p-3 rounded-xl max-w-[90%] ${type}-message mb-3`;
565
+
566
+ if (type === 'user') {
567
+ msgDiv.classList.add('ml-auto', 'text-right');
568
+ msgDiv.textContent = text;
569
+ } else {
570
+ msgDiv.classList.add('mr-auto');
571
+ if (text) {
572
+ msgDiv.innerHTML = marked.parse(text);
573
+ }
574
+ }
575
+
576
+ chatMessages.appendChild(msgDiv);
577
+ chatMessages.scrollTop = chatMessages.scrollHeight;
578
+ return msgDiv;
579
+ }
580
+
581
+ // Load example images when model changes
582
+ async function loadExamples() {
583
+ try {
584
+ const res = await fetch('/example_images');
585
+ const data = await res.json();
586
+ cachedExamples = Array.isArray(data.images) ? data.images : [];
587
+ renderExamples();
588
+ } catch (e) {
589
+ console.error('Failed to load examples', e);
590
+ cachedExamples = [];
591
+ renderExamples();
592
+ }
593
+ }
594
+
595
+ function renderExamples() {
596
+ examplesContainer.innerHTML = '';
597
+ if (!cachedExamples.length) {
598
+ examplesEmpty.classList.remove('hidden');
599
+ return;
600
+ }
601
+ examplesEmpty.classList.add('hidden');
602
+ // Show up to 9 examples
603
+ cachedExamples.slice(0, 9).forEach((url) => {
604
+ const card = document.createElement('button');
605
+ card.type = 'button';
606
+ card.className = 'relative group rounded-xl overflow-hidden shadow hover:shadow-lg transition focus:outline-none focus:ring-2 focus:ring-indigo-500';
607
+ card.title = 'Use this example';
608
+ card.onclick = () => selectExample(url);
609
+ card.innerHTML = `
610
+ <img src="${url}" alt="example" class="w-full h-24 object-cover">
611
+ <div class="absolute inset-0 bg-black/0 group-hover:bg-black/20"></div>
612
+ `;
613
+ examplesContainer.appendChild(card);
614
+ });
615
+ }
616
+
617
+ async function selectExample(url) {
618
+ try {
619
+ const resp = await fetch(url);
620
+ const blob = await resp.blob();
621
+ const inferredName = url.split('/').pop() || 'example.png';
622
+ const file = new File([blob], inferredName, { type: blob.type || 'image/png' });
623
+
624
+ // Put into the hidden file input
625
+ const dt = new DataTransfer();
626
+ dt.items.add(file);
627
+ fileUploadHidden.files = dt.files;
628
+
629
+ // Reuse existing preview logic
630
+ handleFileSelect(file);
631
+ } catch (e) {
632
+ console.error('Failed to select example', e);
633
+ alert('Unable to load example image.');
634
+ }
635
+ }
636
+
637
+ modelSelect.addEventListener('change', loadExamples);
638
+ refreshExamplesBtn.addEventListener('click', loadExamples);
639
+
640
+ // Initial load for default selection
641
+ loadExamples();
642
+
643
+ // Smooth scrolling
644
+ document.documentElement.style.scrollBehavior = 'smooth';
645
+ </script>
646
+ </body>
647
  </html>