Docfile commited on
Commit
c22a0fa
·
verified ·
1 Parent(s): 3a25a69

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +88 -153
templates/index.html CHANGED
@@ -150,32 +150,36 @@
150
 
151
  <body class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800">
152
  <nav class="glassmorphism sticky top-0 z-50 border-b border-blue-100">
153
- <div class="container mx-auto px-4 sm:px-6 py-3 sm:py-4"> {/* Ajustement padding nav */}
 
154
  <div class="flex items-center justify-between">
155
  <div class="flex items-center space-x-3 sm:space-x-4">
156
  <div class="bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg p-2 shadow-lg transform hover:scale-110 transition-transform duration-300">
157
- <i class="fas fa-robot text-white text-lg sm:text-xl"></i> {/* Taille icône */}
158
  </div>
159
- <h1 class="text-xl sm:text-2xl font-bold gradient-text">Mariam AI</h1> {/* Taille titre */}
160
  </div>
161
- <div class="text-xs sm:text-sm text-blue-900 font-medium bg-blue-50 py-1 px-3 sm:px-4 rounded-full shadow-sm hidden sm:block">Assistant Français</div> {/* Masqué sur xs */}
162
- <div class="text-xs text-blue-900 font-medium bg-blue-50 py-1 px-3 rounded-full shadow-sm sm:hidden">Assistant</div> {/* Version courte pour xs */}
163
  </div>
164
  </div>
165
  </nav>
166
 
167
- <main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12"> {/* Ajustement padding main */}
 
168
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8">
169
  <!-- Section Travail Argumentatif -->
170
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in">
171
- <div class="p-6 sm:p-8"> {/* Ajustement padding carte */}
 
172
  <div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
173
  <div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform rotate-3">
174
  <i class="fas fa-pen-fancy text-blue-600 text-lg sm:text-xl"></i>
175
  </div>
176
  <h2 class="text-xl sm:text-2xl font-bold text-gray-800">Travail Argumentatif</h2>
177
  </div>
178
- <form id="francais-form" class="space-y-6 sm:space-y-8"> {/* Ajustement espacement */}
 
179
  <div class="space-y-2">
180
  <label for="sujet-francais" class="block text-sm font-medium text-gray-700 flex items-center">
181
  <i class="fas fa-book-open mr-2 text-blue-500"></i>Sujet <span class="text-red-500 ml-1">*</span>
@@ -192,7 +196,7 @@
192
  <label class="block text-sm font-medium text-gray-700 flex items-center">
193
  <i class="fas fa-tasks mr-2 text-blue-500"></i>Type d'argumentation
194
  </label>
195
- {/* Grid s'adapte déjà bien (2 cols par défaut, 4 sur sm+) */}
196
  <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
197
  <label class="relative">
198
  <input type="radio" name="choix" value="Etaye" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
@@ -249,8 +253,8 @@
249
  </div>
250
  </button>
251
  </form>
252
- {/* Ajustement padding et min-height */}
253
- <div id="francais-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]">
254
  <!-- Le contenu généré sera inséré ici -->
255
  </div>
256
  </div>
@@ -258,7 +262,8 @@
258
 
259
  <!-- Section Étude de texte -->
260
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in" style="animation-delay: 0.1s;">
261
- <div class="p-6 sm:p-8"> {/* Ajustement padding carte */}
 
262
  <div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
263
  <div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform -rotate-3">
264
  <i class="fas fa-file-alt text-blue-600 text-lg sm:text-xl"></i>
@@ -292,8 +297,8 @@
292
  </div>
293
  </button>
294
  </form>
295
- {/* Ajustement padding et min-height */}
296
- <div id="etude-texte-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]">
297
  <!-- Le contenu analysé sera inséré ici -->
298
  </div>
299
  </div>
@@ -343,7 +348,7 @@
343
  const dropZone = document.getElementById('drop-zone');
344
  const fileInput = document.getElementById('image-upload');
345
  const imagePreview = document.getElementById('image-preview');
346
- if(!dropZone || !fileInput || !imagePreview) return; // Safety check
347
 
348
  dropZone.addEventListener('click', () => fileInput.click());
349
  ['dragenter', 'dragover'].forEach(ev => dropZone.addEventListener(ev, highlightDropZone));
@@ -401,7 +406,7 @@
401
  sauvegardes.push({ titre: titre || "Sauvegarde", contenu, date: dateSauvegarde });
402
  try {
403
  localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
404
- displayNotification("Sauvegardé", "success"); // Shorter message
405
  afficherSauvegardes();
406
  } catch (e) {
407
  console.error("Erreur sauvegarde localStorage:", e);
@@ -411,13 +416,9 @@
411
 
412
  function displayNotification(message, type = 'success') {
413
  const notification = document.createElement('div');
414
- const colors = {
415
- success: 'bg-green-50 border-green-200 text-green-700',
416
- error: 'bg-red-50 border-red-200 text-red-700',
417
- warning: 'bg-yellow-50 border-yellow-200 text-yellow-700'
418
- };
419
  const icons = { success: 'fa-check-circle text-green-500', error: 'fa-exclamation-circle text-red-500', warning: 'fa-exclamation-triangle text-yellow-500' };
420
- notification.className = `fixed bottom-4 right-4 sm:bottom-6 sm:right-6 border ${colors[type] || colors.success} px-4 py-3 rounded-lg shadow-lg z-[100] flex items-center scale-in max-w-[calc(100%-2rem)] sm:max-w-sm`; // Responsive max-width
421
  notification.innerHTML = `<i class="fas ${icons[type] || icons.success} mr-3 text-lg flex-shrink-0"></i><span class="text-sm font-medium">${message}</span>`;
422
  document.body.appendChild(notification);
423
  const duration = type === 'error' ? 6000 : 3000;
@@ -434,24 +435,15 @@
434
  sauvegardes.splice(originalIndex, 1);
435
  localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
436
  afficherSauvegardes();
437
- } else {
438
- console.error("Index de suppression invalide:", originalIndex);
439
- displayNotification("Erreur suppression: Index invalide", "error");
440
- }
441
- } catch (e) {
442
- console.error("Erreur suppression sauvegarde:", e);
443
- displayNotification("Erreur suppression", "error");
444
- }
445
  }
446
 
447
  function afficherSauvegardes() {
448
  const backupsList = document.getElementById('backups-list');
449
  if(!backupsList) return;
450
  let sauvegardes = [];
451
- try {
452
- sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
453
- } catch(e) { console.error("Erreur lecture sauvegardes:", e); }
454
-
455
  sauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
456
  backupsList.innerHTML = '';
457
 
@@ -460,20 +452,20 @@
460
  return;
461
  }
462
 
463
- sauvegardes.forEach((sauvegarde, index) => {
464
  const date = new Date(sauvegarde.date);
465
- const formattedDate = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
466
- const displayTitle = (sauvegarde.titre || "Sans titre").length > 50 ? sauvegarde.titre.substring(0, 47) + '...' : (sauvegarde.titre || "Sans titre");
467
 
468
  const sauvegardeDiv = document.createElement('div');
469
- sauvegardeDiv.dataset.originalDate = sauvegarde.date; // Use date for more reliable deletion lookup
470
  sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
471
  sauvegardeDiv.innerHTML = `
472
  <div class="flex items-start cursor-pointer item-header">
473
  <div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <i class="fas fa-file-alt text-blue-600 text-sm"></i> </div>
474
  <div class="flex-grow overflow-hidden mr-2">
475
- <h3 class="text-base sm:text-lg font-semibold text-gray-800 truncate" title="${sauvegarde.titre || 'Sans titre'}">${displayTitle}</h3>
476
- <p class="text-xs sm:text-sm text-gray-600 mb-1 flex items-center"> <i class="fas fa-clock text-xs mr-1 text-gray-400"></i> ${formattedDate} </p>
477
  </div>
478
  <div class="flex space-x-1 sm:space-x-2 ml-auto flex-shrink-0">
479
  <button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1 rounded-full hover:bg-gray-100" title="Copier"> <i class="fas fa-copy text-xs sm:text-sm"></i> </button>
@@ -488,41 +480,29 @@
488
 
489
  headerDiv.addEventListener('click', () => {
490
  const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
491
- document.querySelectorAll('.backup-content-expanded').forEach(content => {
492
- if (content !== backupContentDiv) { content.classList.remove('backup-content-expanded'); content.innerHTML = ''; }
493
- });
494
  if (!isExpanded) {
495
- try { backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu || ''); } catch(e) { backupContentDiv.innerHTML = "Erreur d'affichage du contenu."; console.error(e); }
496
  backupContentDiv.classList.add('backup-content-expanded');
497
- // MathJax retiré
498
- } else {
499
- backupContentDiv.classList.remove('backup-content-expanded'); backupContentDiv.innerHTML = '';
500
- }
501
  });
502
 
503
  sauvegardeDiv.querySelector('.copy-btn').addEventListener('click', (e) => {
504
  e.stopPropagation();
505
  navigator.clipboard.writeText(sauvegarde.contenu || '').then(() => {
506
- const icon = e.currentTarget.querySelector('i'); icon.className = 'fas fa-check text-green-500 text-xs sm:text-sm'; setTimeout(() => { icon.className = 'fas fa-copy text-xs sm:text-sm'; }, 2000);
507
  }).catch(err => { console.error('Copy error:', err); displayNotification("Erreur copie", "error"); });
508
  });
509
 
510
  sauvegardeDiv.querySelector('.delete-btn').addEventListener('click', (e) => {
511
  e.stopPropagation();
512
  const itemDate = sauvegardeDiv.dataset.originalDate;
513
-
514
- // Trouver l'index original basé sur la date (plus fiable que l'index d'affichage trié)
515
- let originalIndex = -1;
516
- try {
517
- const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
518
- originalIndex = originalSauvegardes.findIndex(s => s.date === itemDate);
519
- } catch (err) { console.error("Erreur recherche sauvegarde:", err); }
520
-
521
-
522
- if (originalIndex === -1) { console.error("Item not found for deletion by date:", itemDate); displayNotification("Erreur suppression", "error"); return; }
523
 
524
  const confirmationModal = document.createElement('div');
525
- confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-50 scale-in p-4'; // Ajout p-4 pour mobile
526
  confirmationModal.innerHTML = `
527
  <div class="bg-white rounded-lg p-5 sm:p-6 max-w-sm w-full shadow-xl">
528
  <div class="flex items-center mb-4"> <div class="bg-red-100 rounded-full p-2 mr-3"><i class="fas fa-exclamation-triangle text-red-500"></i></div> <h3 class="text-base sm:text-lg font-semibold text-gray-800">Confirmation</h3> </div>
@@ -538,12 +518,10 @@
538
  }
539
 
540
  function closeModal(modalElement) {
541
- modalElement.style.opacity = '0';
542
- modalElement.style.transition = 'opacity 0.3s ease';
543
  setTimeout(() => { if (document.body.contains(modalElement)) document.body.removeChild(modalElement); }, 300);
544
  }
545
 
546
-
547
  // --- Gestion DeepThink ---
548
  function checkDeepThinkAvailability() {
549
  const checkbox = document.getElementById('deepthink-checkbox');
@@ -552,17 +530,9 @@
552
  try {
553
  const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
554
  const todayStr = new Date().toISOString().split('T')[0];
555
- if (lastUsedDateStr === todayStr) {
556
- checkbox.checked = false; checkbox.disabled = true;
557
- statusSpan.textContent = "(utilisé)"; statusSpan.classList.add('text-red-500');
558
- } else {
559
- checkbox.disabled = false; statusSpan.textContent = ""; statusSpan.classList.remove('text-red-500');
560
- }
561
- } catch (e) {
562
- console.error("Erreur accès localStorage (DeepThink):", e);
563
- checkbox.disabled = true; // Disable if error
564
- statusSpan.textContent = "(erreur)";
565
- }
566
  }
567
 
568
  // --- Soumission des formulaires ---
@@ -571,15 +541,14 @@
571
  const output = document.getElementById('francais-output');
572
  const sujetTextarea = document.getElementById('sujet-francais');
573
  const deepThinkCheckbox = document.getElementById('deepthink-checkbox');
574
- const submitButton = form.querySelector('button[type="submit"]'); // Get button for disabling
575
-
576
- if(!form || !output || !sujetTextarea || !deepThinkCheckbox || !submitButton) return; // Safety check
577
 
578
  form.addEventListener('submit', async (e) => {
579
  e.preventDefault();
580
- const originalButtonContent = submitButton.innerHTML; // Store original content
581
- submitButton.disabled = true; // Disable button
582
- submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2">Génération...</span></div>`; // Show loader in button
583
 
584
  const sujetValue = sujetTextarea.value.trim();
585
  const isDeepThinkChecked = deepThinkCheckbox.checked;
@@ -588,57 +557,40 @@
588
  if (!sujetValue) {
589
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Champ obligatoire</p><p>Veuillez entrer un sujet.</p></div></div>`;
590
  sujetTextarea.focus(); sujetTextarea.classList.add('border-red-500'); setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
591
- submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; // Re-enable button
592
- return;
593
  }
594
  if (isDeepThinkChecked) {
595
  try {
596
  const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
597
  const todayStr = new Date().toISOString().split('T')[0];
598
  if (lastUsedDateStr === todayStr) {
599
- output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Limite atteinte</p><p>DeepThink déjà utilisé aujourd'hui.</p></div></div>`;
600
- checkDeepThinkAvailability();
601
- submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; // Re-enable button
602
- return;
603
  }
604
- } catch(e) { /* Ignore localStorage error here, proceed without check */ }
605
  }
 
606
 
607
- // Clear previous output and show minimal loader
608
- output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
609
-
610
-
611
- const formData = new FormData(form);
612
- formData.append('use_deepthink', isDeepThinkChecked);
613
-
614
  try {
615
  const response = await fetch('/api/francais', { method: 'POST', body: formData });
616
  const result = await response.json();
617
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
618
 
619
- // Render output
620
- try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage de la réponse."; console.error(e); }
621
 
622
- const titreSauvegarde = sujetValue.substring(0, 50) + (sujetValue.length > 50 ? '...' : ''); // Shorter title
623
- const deepThinkSuffix = isDeepThinkChecked ? ' [DeepThink]' : '';
624
  sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
625
 
626
  if (isDeepThinkChecked) {
627
- try {
628
- const todayStr = new Date().toISOString().split('T')[0];
629
- localStorage.setItem(DEEPTHINK_STORAGE_KEY, todayStr);
630
- checkDeepThinkAvailability();
631
- } catch(e) { console.error("Erreur sauvegarde date DeepThink:", e); }
632
  }
633
- // MathJax retiré
634
-
635
  } catch (error) {
636
- console.error("Erreur soumission (français):", error);
637
  output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
638
- } finally {
639
- submitButton.disabled = false; // Always re-enable button
640
- submitButton.innerHTML = originalButtonContent; // Restore original content
641
- }
642
  });
643
  }
644
 
@@ -646,45 +598,35 @@
646
  const form = document.getElementById('etude-texte-form');
647
  const output = document.getElementById('etude-texte-output');
648
  const fileInput = document.getElementById('image-upload');
649
- const submitButton = form.querySelector('button[type="submit"]'); // Get button for disabling
650
-
651
- if(!form || !output || !fileInput || !submitButton) return; // Safety check
652
 
653
  form.addEventListener('submit', async (e) => {
654
  e.preventDefault();
655
- const originalButtonContent = submitButton.innerHTML; // Store original content
656
- submitButton.disabled = true; // Disable button
657
- submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2">Analyse...</span></div>`; // Show loader in button
658
-
659
 
660
  if (uploadedFiles.size === 0) {
661
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Aucune image</p><p>Ajoutez au moins une image.</p></div></div>`;
662
- submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; // Re-enable button
663
- return;
664
  }
665
-
666
- // Clear previous output and show minimal loader
667
  output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
668
 
669
- const formData = new FormData();
670
- uploadedFiles.forEach((file) => formData.append('images', file, file.name));
671
  try {
672
  const response = await fetch('/api/etude-texte', { method: 'POST', body: formData });
673
  const result = await response.json();
674
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
675
 
676
- try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage de la réponse."; console.error(e); }
677
 
678
- const titre = `Analyse image(s) - ${new Date().toLocaleDateString('fr-FR')}`;
679
  sauvegarderReponse(titre, result.output);
680
- // MathJax retiré
681
  } catch (error) {
682
- console.error("Erreur soumission (étude texte):", error);
683
  output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
684
- } finally {
685
- submitButton.disabled = false; // Always re-enable button
686
- submitButton.innerHTML = originalButtonContent; // Restore original content
687
- }
688
  });
689
  }
690
 
@@ -693,22 +635,18 @@
693
  const cards = document.querySelectorAll('.card-hover');
694
  if (!('IntersectionObserver' in window)) { cards.forEach(card => card.style.opacity = '1'); return; }
695
  const observer = new IntersectionObserver((entries) => {
696
- entries.forEach((entry) => {
697
- if (entry.isIntersecting) { requestAnimationFrame(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }); observer.unobserve(entry.target); }
698
- });
699
- }, { threshold: 0.05 }); // Lower threshold for earlier trigger
700
- cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; observer.observe(card); }); // Smaller initial translate
701
  }
702
  function enhanceTextareaFocus() {
703
- const textarea = document.getElementById('sujet-francais');
704
- const counter = document.getElementById('character-count');
705
- if(!textarea || !counter) return;
706
- textarea.addEventListener('input', () => { const length = textarea.value.length; counter.textContent = `${length} caractère${length !== 1 ? 's' : ''}`; textarea.classList.remove('border-red-500'); });
707
  }
708
  function enhanceRadioButtons() {
709
  document.querySelectorAll('input[type="radio"] + span').forEach(label => {
710
  const input = label.previousElementSibling; if (input.disabled) return;
711
- label.addEventListener('mouseenter', () => { if (!input.checked) label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-px'); }); // Smaller translate
712
  label.addEventListener('mouseleave', () => { if (!input.checked) label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-px'); });
713
  input.addEventListener('change', () => {
714
  const groupName = input.name;
@@ -720,7 +658,6 @@
720
  });
721
  }
722
 
723
-
724
  // --- Initialisation Générale ---
725
  document.addEventListener('DOMContentLoaded', () => {
726
  initializeFileUpload();
@@ -731,23 +668,21 @@
731
  enhanceRadioButtons();
732
  checkDeepThinkAvailability();
733
  afficherSauvegardes();
734
- // MathJax retiré
735
 
736
- // Welcome Message (session based)
737
  try {
738
  const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
739
  if (showWelcome) {
740
- const welcomeMessageContainer = document.createElement('div');
741
- welcomeMessageContainer.innerHTML = `<div class="fixed bottom-4 right-4 sm:bottom-6 sm:right-6 bg-white rounded-xl shadow-lg p-4 sm:p-5 transform transition-all duration-500 opacity-0 translate-y-4 max-w-[calc(100%-2rem)] sm:max-w-xs z-[100]"><div class="flex items-start space-x-3 sm:space-x-4"><div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-2 sm:p-3 mt-1 flex-shrink-0"> <i class="fas fa-robot text-white text-lg sm:text-xl"></i> </div><div> <p class="text-sm font-semibold text-gray-800">Bienvenue sur Mariam AI !</p> <p class="text-xs text-gray-500 mt-1">Assistant prêt.</p> </div></div><button class="absolute top-1 right-1 text-gray-400 hover:text-gray-600 focus:outline-none close-welcome p-1"> <i class="fas fa-times text-xs"></i> </button></div>`;
742
- document.body.appendChild(welcomeMessageContainer);
743
- const welcomeMessage = welcomeMessageContainer.firstElementChild;
744
- setTimeout(() => { welcomeMessage.classList.remove('opacity-0', 'translate-y-4'); }, 500);
745
- welcomeMessage.querySelector('.close-welcome').addEventListener('click', () => closeWelcomeMessage(welcomeMessage));
746
- setTimeout(() => { if (document.body.contains(welcomeMessage)) closeWelcomeMessage(welcomeMessage); }, 7000);
747
  sessionStorage.setItem('welcomeShown', 'true');
748
  }
749
- } catch(e) { console.warn("Erreur accès sessionStorage (Welcome Msg):", e); }
750
-
751
  function closeWelcomeMessage(element) {
752
  element.classList.add('opacity-0', 'translate-y-4'); element.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
753
  setTimeout(() => { if (element?.parentElement) element.parentElement.remove(); }, 300);
 
150
 
151
  <body class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50 text-gray-800">
152
  <nav class="glassmorphism sticky top-0 z-50 border-b border-blue-100">
153
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
154
+ <div class="container mx-auto px-4 sm:px-6 py-3 sm:py-4"> <!-- Ajustement padding nav -->
155
  <div class="flex items-center justify-between">
156
  <div class="flex items-center space-x-3 sm:space-x-4">
157
  <div class="bg-gradient-to-r from-blue-600 to-blue-800 rounded-lg p-2 shadow-lg transform hover:scale-110 transition-transform duration-300">
158
+ <i class="fas fa-robot text-white text-lg sm:text-xl"></i> <!-- Taille icône -->
159
  </div>
160
+ <h1 class="text-xl sm:text-2xl font-bold gradient-text">Mariam AI</h1> <!-- Taille titre -->
161
  </div>
162
+ <div class="text-xs sm:text-sm text-blue-900 font-medium bg-blue-50 py-1 px-3 sm:px-4 rounded-full shadow-sm hidden sm:block">Assistant Français</div> <!-- Masqué sur xs -->
163
+ <div class="text-xs text-blue-900 font-medium bg-blue-50 py-1 px-3 rounded-full shadow-sm sm:hidden">Assistant</div> <!-- Version courte pour xs -->
164
  </div>
165
  </div>
166
  </nav>
167
 
168
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
169
+ <main class="container mx-auto px-4 sm:px-6 py-8 sm:py-12"> <!-- Ajustement padding main -->
170
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 sm:gap-8">
171
  <!-- Section Travail Argumentatif -->
172
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in">
173
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
174
+ <div class="p-6 sm:p-8"> <!-- Ajustement padding carte -->
175
  <div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
176
  <div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform rotate-3">
177
  <i class="fas fa-pen-fancy text-blue-600 text-lg sm:text-xl"></i>
178
  </div>
179
  <h2 class="text-xl sm:text-2xl font-bold text-gray-800">Travail Argumentatif</h2>
180
  </div>
181
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
182
+ <form id="francais-form" class="space-y-6 sm:space-y-8"> <!-- Ajustement espacement -->
183
  <div class="space-y-2">
184
  <label for="sujet-francais" class="block text-sm font-medium text-gray-700 flex items-center">
185
  <i class="fas fa-book-open mr-2 text-blue-500"></i>Sujet <span class="text-red-500 ml-1">*</span>
 
196
  <label class="block text-sm font-medium text-gray-700 flex items-center">
197
  <i class="fas fa-tasks mr-2 text-blue-500"></i>Type d'argumentation
198
  </label>
199
+ <!-- Grid s'adapte déjà bien (2 cols par défaut, 4 sur sm+) -->
200
  <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4">
201
  <label class="relative">
202
  <input type="radio" name="choix" value="Etaye" class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked>
 
253
  </div>
254
  </button>
255
  </form>
256
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
257
+ <div id="francais-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]"> <!-- Ajustement padding et min-height -->
258
  <!-- Le contenu généré sera inséré ici -->
259
  </div>
260
  </div>
 
262
 
263
  <!-- Section Étude de texte -->
264
  <div class="card-hover glassmorphism rounded-2xl overflow-hidden scale-in" style="animation-delay: 0.1s;">
265
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
266
+ <div class="p-6 sm:p-8"> <!-- Ajustement padding carte -->
267
  <div class="flex items-center space-x-3 sm:space-x-4 mb-6 sm:mb-8">
268
  <div class="bg-blue-100 rounded-lg p-2 sm:p-3 transform -rotate-3">
269
  <i class="fas fa-file-alt text-blue-600 text-lg sm:text-xl"></i>
 
297
  </div>
298
  </button>
299
  </form>
300
+ <!-- CORRECTION: Remplacement {* *} par <!-- -->
301
+ <div id="etude-texte-output" class="mt-6 sm:mt-8 p-4 sm:p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[150px]"> <!-- Ajustement padding et min-height -->
302
  <!-- Le contenu analysé sera inséré ici -->
303
  </div>
304
  </div>
 
348
  const dropZone = document.getElementById('drop-zone');
349
  const fileInput = document.getElementById('image-upload');
350
  const imagePreview = document.getElementById('image-preview');
351
+ if(!dropZone || !fileInput || !imagePreview) return;
352
 
353
  dropZone.addEventListener('click', () => fileInput.click());
354
  ['dragenter', 'dragover'].forEach(ev => dropZone.addEventListener(ev, highlightDropZone));
 
406
  sauvegardes.push({ titre: titre || "Sauvegarde", contenu, date: dateSauvegarde });
407
  try {
408
  localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
409
+ displayNotification("Sauvegardé", "success");
410
  afficherSauvegardes();
411
  } catch (e) {
412
  console.error("Erreur sauvegarde localStorage:", e);
 
416
 
417
  function displayNotification(message, type = 'success') {
418
  const notification = document.createElement('div');
419
+ const colors = { success: 'bg-green-50 border-green-200 text-green-700', error: 'bg-red-50 border-red-200 text-red-700', warning: 'bg-yellow-50 border-yellow-200 text-yellow-700' };
 
 
 
 
420
  const icons = { success: 'fa-check-circle text-green-500', error: 'fa-exclamation-circle text-red-500', warning: 'fa-exclamation-triangle text-yellow-500' };
421
+ notification.className = `fixed bottom-4 right-4 sm:bottom-6 sm:right-6 border ${colors[type] || colors.success} px-4 py-3 rounded-lg shadow-lg z-[100] flex items-center scale-in max-w-[calc(100%-2rem)] sm:max-w-sm`;
422
  notification.innerHTML = `<i class="fas ${icons[type] || icons.success} mr-3 text-lg flex-shrink-0"></i><span class="text-sm font-medium">${message}</span>`;
423
  document.body.appendChild(notification);
424
  const duration = type === 'error' ? 6000 : 3000;
 
435
  sauvegardes.splice(originalIndex, 1);
436
  localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
437
  afficherSauvegardes();
438
+ } else { console.error("Index suppression invalide:", originalIndex); displayNotification("Erreur suppression", "error"); }
439
+ } catch (e) { console.error("Erreur suppression sauvegarde:", e); displayNotification("Erreur suppression", "error"); }
 
 
 
 
 
 
440
  }
441
 
442
  function afficherSauvegardes() {
443
  const backupsList = document.getElementById('backups-list');
444
  if(!backupsList) return;
445
  let sauvegardes = [];
446
+ try { sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); } catch(e) { console.error("Erreur lecture sauvegardes:", e); }
 
 
 
447
  sauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
448
  backupsList.innerHTML = '';
449
 
 
452
  return;
453
  }
454
 
455
+ sauvegardes.forEach((sauvegarde) => { // Removed unused index
456
  const date = new Date(sauvegarde.date);
457
+ const formattedDate = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }); // Shorter date
458
+ const displayTitle = (sauvegarde.titre || "Sans titre").length > 40 ? sauvegarde.titre.substring(0, 37) + '...' : (sauvegarde.titre || "Sans titre"); // Shorter title
459
 
460
  const sauvegardeDiv = document.createElement('div');
461
+ sauvegardeDiv.dataset.originalDate = sauvegarde.date;
462
  sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
463
  sauvegardeDiv.innerHTML = `
464
  <div class="flex items-start cursor-pointer item-header">
465
  <div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <i class="fas fa-file-alt text-blue-600 text-sm"></i> </div>
466
  <div class="flex-grow overflow-hidden mr-2">
467
+ <h3 class="text-base font-semibold text-gray-800 truncate" title="${sauvegarde.titre || 'Sans titre'}">${displayTitle}</h3>
468
+ <p class="text-xs text-gray-500 mb-1 flex items-center"> <i class="fas fa-clock text-xs mr-1 text-gray-400"></i> ${formattedDate} </p>
469
  </div>
470
  <div class="flex space-x-1 sm:space-x-2 ml-auto flex-shrink-0">
471
  <button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1 rounded-full hover:bg-gray-100" title="Copier"> <i class="fas fa-copy text-xs sm:text-sm"></i> </button>
 
480
 
481
  headerDiv.addEventListener('click', () => {
482
  const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
483
+ document.querySelectorAll('.backup-content-expanded').forEach(content => { if (content !== backupContentDiv) { content.classList.remove('backup-content-expanded'); content.innerHTML = ''; } });
 
 
484
  if (!isExpanded) {
485
+ try { backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu || ''); } catch(e) { backupContentDiv.innerHTML = "Erreur d'affichage."; console.error(e); }
486
  backupContentDiv.classList.add('backup-content-expanded');
487
+ } else { backupContentDiv.classList.remove('backup-content-expanded'); backupContentDiv.innerHTML = ''; }
 
 
 
488
  });
489
 
490
  sauvegardeDiv.querySelector('.copy-btn').addEventListener('click', (e) => {
491
  e.stopPropagation();
492
  navigator.clipboard.writeText(sauvegarde.contenu || '').then(() => {
493
+ const icon = e.currentTarget.querySelector('i'); icon.className = 'fas fa-check text-green-500 text-xs sm:text-sm'; setTimeout(() => { icon.className = 'fas fa-copy text-xs sm:text-sm'; }, 1500); // Shorter duration
494
  }).catch(err => { console.error('Copy error:', err); displayNotification("Erreur copie", "error"); });
495
  });
496
 
497
  sauvegardeDiv.querySelector('.delete-btn').addEventListener('click', (e) => {
498
  e.stopPropagation();
499
  const itemDate = sauvegardeDiv.dataset.originalDate;
500
+ let originalIndex = -1;
501
+ try { const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); originalIndex = originalSauvegardes.findIndex(s => s.date === itemDate); } catch (err) { console.error("Erreur recherche sauvegarde:", err); }
502
+ if (originalIndex === -1) { console.error("Item not found by date:", itemDate); displayNotification("Erreur suppression", "error"); return; }
 
 
 
 
 
 
 
503
 
504
  const confirmationModal = document.createElement('div');
505
+ confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-50 scale-in p-4';
506
  confirmationModal.innerHTML = `
507
  <div class="bg-white rounded-lg p-5 sm:p-6 max-w-sm w-full shadow-xl">
508
  <div class="flex items-center mb-4"> <div class="bg-red-100 rounded-full p-2 mr-3"><i class="fas fa-exclamation-triangle text-red-500"></i></div> <h3 class="text-base sm:text-lg font-semibold text-gray-800">Confirmation</h3> </div>
 
518
  }
519
 
520
  function closeModal(modalElement) {
521
+ modalElement.style.opacity = '0'; modalElement.style.transition = 'opacity 0.3s ease';
 
522
  setTimeout(() => { if (document.body.contains(modalElement)) document.body.removeChild(modalElement); }, 300);
523
  }
524
 
 
525
  // --- Gestion DeepThink ---
526
  function checkDeepThinkAvailability() {
527
  const checkbox = document.getElementById('deepthink-checkbox');
 
530
  try {
531
  const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
532
  const todayStr = new Date().toISOString().split('T')[0];
533
+ if (lastUsedDateStr === todayStr) { checkbox.checked = false; checkbox.disabled = true; statusSpan.textContent = "(utilisé)"; statusSpan.classList.add('text-red-500'); }
534
+ else { checkbox.disabled = false; statusSpan.textContent = ""; statusSpan.classList.remove('text-red-500'); }
535
+ } catch (e) { console.error("Erreur localStorage (DeepThink):", e); checkbox.disabled = true; statusSpan.textContent = "(erreur)"; }
 
 
 
 
 
 
 
 
536
  }
537
 
538
  // --- Soumission des formulaires ---
 
541
  const output = document.getElementById('francais-output');
542
  const sujetTextarea = document.getElementById('sujet-francais');
543
  const deepThinkCheckbox = document.getElementById('deepthink-checkbox');
544
+ const submitButton = form.querySelector('button[type="submit"]');
545
+ if(!form || !output || !sujetTextarea || !deepThinkCheckbox || !submitButton) return;
 
546
 
547
  form.addEventListener('submit', async (e) => {
548
  e.preventDefault();
549
+ const originalButtonContent = submitButton.innerHTML;
550
+ submitButton.disabled = true;
551
+ submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2 text-sm sm:text-base">Génération...</span></div>`; // Responsive text
552
 
553
  const sujetValue = sujetTextarea.value.trim();
554
  const isDeepThinkChecked = deepThinkCheckbox.checked;
 
557
  if (!sujetValue) {
558
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Champ obligatoire</p><p>Veuillez entrer un sujet.</p></div></div>`;
559
  sujetTextarea.focus(); sujetTextarea.classList.add('border-red-500'); setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
560
+ submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; return;
 
561
  }
562
  if (isDeepThinkChecked) {
563
  try {
564
  const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
565
  const todayStr = new Date().toISOString().split('T')[0];
566
  if (lastUsedDateStr === todayStr) {
567
+ output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Limite atteinte</p><p>DeepThink déjà utilisé.</p></div></div>`; // Shorter message
568
+ checkDeepThinkAvailability(); submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; return;
 
 
569
  }
570
+ } catch(e) { /* Proceed without check if localStorage fails */ }
571
  }
572
+ output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
573
 
574
+ const formData = new FormData(form); formData.append('use_deepthink', isDeepThinkChecked);
 
 
 
 
 
 
575
  try {
576
  const response = await fetch('/api/francais', { method: 'POST', body: formData });
577
  const result = await response.json();
578
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
579
 
580
+ try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage."; console.error(e); }
 
581
 
582
+ const titreSauvegarde = sujetValue.substring(0, 40) + (sujetValue.length > 40 ? '...' : ''); // Even shorter title
583
+ const deepThinkSuffix = isDeepThinkChecked ? ' [DT]' : ''; // Shorter suffix
584
  sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
585
 
586
  if (isDeepThinkChecked) {
587
+ try { const todayStr = new Date().toISOString().split('T')[0]; localStorage.setItem(DEEPTHINK_STORAGE_KEY, todayStr); checkDeepThinkAvailability(); }
588
+ catch(e) { console.error("Erreur sauvegarde date DT:", e); }
 
 
 
589
  }
 
 
590
  } catch (error) {
591
+ console.error("Erreur soumission (fr):", error);
592
  output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
593
+ } finally { submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; }
 
 
 
594
  });
595
  }
596
 
 
598
  const form = document.getElementById('etude-texte-form');
599
  const output = document.getElementById('etude-texte-output');
600
  const fileInput = document.getElementById('image-upload');
601
+ const submitButton = form.querySelector('button[type="submit"]');
602
+ if(!form || !output || !fileInput || !submitButton) return;
 
603
 
604
  form.addEventListener('submit', async (e) => {
605
  e.preventDefault();
606
+ const originalButtonContent = submitButton.innerHTML;
607
+ submitButton.disabled = true;
608
+ submitButton.innerHTML = `<div class="flex items-center justify-center"><div class="loader"><div></div><div></div><div></div><div></div></div><span class="ml-2 text-sm sm:text-base">Analyse...</span></div>`; // Responsive text
 
609
 
610
  if (uploadedFiles.size === 0) {
611
  output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Aucune image</p><p>Ajoutez au moins une image.</p></div></div>`;
612
+ submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; return;
 
613
  }
 
 
614
  output.innerHTML = `<div class="flex justify-center items-center p-6"><div class="loader"><div></div><div></div><div></div><div></div></div></div>`;
615
 
616
+ const formData = new FormData(); uploadedFiles.forEach((file) => formData.append('images', file, file.name));
 
617
  try {
618
  const response = await fetch('/api/etude-texte', { method: 'POST', body: formData });
619
  const result = await response.json();
620
  if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
621
 
622
+ try { output.innerHTML = marked.parse(result.output || ''); } catch (e) { output.innerHTML = "Erreur d'affichage."; console.error(e); }
623
 
624
+ const titre = `Analyse img ${new Date().toLocaleDateString('fr-FR', { month: 'short', day: 'numeric' })}`; // Shorter title
625
  sauvegarderReponse(titre, result.output);
 
626
  } catch (error) {
627
+ console.error("Erreur soumission (img):", error);
628
  output.innerHTML = `<div class="alert-message alert-danger"><i class="fas fa-exclamation-circle"></i><div><p>Erreur</p><p>${error.message || "Vérifiez la console."}</p></div></div>`;
629
+ } finally { submitButton.disabled = false; submitButton.innerHTML = originalButtonContent; }
 
 
 
630
  });
631
  }
632
 
 
635
  const cards = document.querySelectorAll('.card-hover');
636
  if (!('IntersectionObserver' in window)) { cards.forEach(card => card.style.opacity = '1'); return; }
637
  const observer = new IntersectionObserver((entries) => {
638
+ entries.forEach((entry) => { if (entry.isIntersecting) { requestAnimationFrame(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; }); observer.unobserve(entry.target); } });
639
+ }, { threshold: 0.05 });
640
+ cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(20px)'; observer.observe(card); });
 
 
641
  }
642
  function enhanceTextareaFocus() {
643
+ const textarea = document.getElementById('sujet-francais'); const counter = document.getElementById('character-count'); if(!textarea || !counter) return;
644
+ textarea.addEventListener('input', () => { const length = textarea.value.length; counter.textContent = `${length}c`; textarea.classList.remove('border-red-500'); }); // Shorter counter
 
 
645
  }
646
  function enhanceRadioButtons() {
647
  document.querySelectorAll('input[type="radio"] + span').forEach(label => {
648
  const input = label.previousElementSibling; if (input.disabled) return;
649
+ label.addEventListener('mouseenter', () => { if (!input.checked) label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-px'); });
650
  label.addEventListener('mouseleave', () => { if (!input.checked) label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-px'); });
651
  input.addEventListener('change', () => {
652
  const groupName = input.name;
 
658
  });
659
  }
660
 
 
661
  // --- Initialisation Générale ---
662
  document.addEventListener('DOMContentLoaded', () => {
663
  initializeFileUpload();
 
668
  enhanceRadioButtons();
669
  checkDeepThinkAvailability();
670
  afficherSauvegardes();
 
671
 
672
+ // Welcome Message
673
  try {
674
  const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
675
  if (showWelcome) {
676
+ const welcomeContainer = document.createElement('div');
677
+ welcomeContainer.innerHTML = `<div class="fixed bottom-4 right-4 sm:bottom-6 sm:right-6 bg-white rounded-xl shadow-lg p-4 transform transition-all duration-500 opacity-0 translate-y-4 max-w-[calc(100%-2rem)] sm:max-w-xs z-[100]"><div class="flex items-start space-x-3"><div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-2 mt-1 flex-shrink-0"><i class="fas fa-robot text-white text-lg"></i></div><div><p class="text-sm font-semibold text-gray-800">Bienvenue !</p><p class="text-xs text-gray-500 mt-1">Assistant prêt.</p></div></div><button class="absolute top-1 right-1 text-gray-400 hover:text-gray-600 close-welcome p-1"><i class="fas fa-times text-xs"></i></button></div>`;
678
+ document.body.appendChild(welcomeContainer);
679
+ const welcomeMsg = welcomeContainer.firstElementChild;
680
+ setTimeout(() => { welcomeMsg.classList.remove('opacity-0', 'translate-y-4'); }, 500);
681
+ welcomeMsg.querySelector('.close-welcome').addEventListener('click', () => closeWelcomeMessage(welcomeMsg));
682
+ setTimeout(() => { if (document.body.contains(welcomeMsg)) closeWelcomeMessage(welcomeMsg); }, 6000); // Shorter duration
683
  sessionStorage.setItem('welcomeShown', 'true');
684
  }
685
+ } catch(e) { console.warn("SessionStorage (Welcome):", e); }
 
686
  function closeWelcomeMessage(element) {
687
  element.classList.add('opacity-0', 'translate-y-4'); element.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
688
  setTimeout(() => { if (element?.parentElement) element.parentElement.remove(); }, 300);