Docfile commited on
Commit
eb83c33
·
verified ·
1 Parent(s): 8936f52

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +160 -258
templates/index.html CHANGED
@@ -371,6 +371,7 @@
371
  position: relative;
372
  top: 1px;
373
  transition: all 0.2s ease-in-out;
 
374
  }
375
  .deepthink-label input[type="checkbox"]:checked {
376
  background-color: var(--primary);
@@ -387,12 +388,25 @@
387
  left: 50%;
388
  transform: translate(-50%, -50%);
389
  }
390
- .deepthink-label:hover input[type="checkbox"] {
 
 
 
 
 
 
 
 
 
 
 
 
391
  border-color: var(--primary-dark);
392
  }
393
  .deepthink-tooltip {
394
  position: relative;
395
- display: inline-block;
 
396
  margin-left: 6px;
397
  }
398
  .deepthink-tooltip .tooltip-text {
@@ -412,6 +426,7 @@
412
  transition: opacity 0.3s;
413
  font-size: 0.75rem; /* text-xs */
414
  line-height: 1.4;
 
415
  }
416
  .deepthink-tooltip .tooltip-text::after {
417
  content: "";
@@ -427,6 +442,11 @@
427
  visibility: visible;
428
  opacity: 1;
429
  }
 
 
 
 
 
430
 
431
  </style>
432
  </head>
@@ -533,20 +553,20 @@
533
  </div>
534
  </div>
535
 
536
- <!-- MODIFICATION: Ajout de la checkbox DeepThink -->
537
  <div class="flex items-center justify-start pt-2">
538
  <label for="deepthink-checkbox" class="deepthink-label">
539
- <input type="checkbox" id="deepthink-checkbox" name="use_deepthink_visual"> <!-- Name is different to avoid direct form submission -->
540
- Utiliser DeepThink
541
  <div class="deepthink-tooltip">
542
  <i class="fas fa-info-circle text-gray-400"></i>
543
- <span class="tooltip-text">Utilise un modèle plus avancé (Gemini Pro) pour une meilleure qualité, mais peut être plus lent et coûteux.</span>
544
  </div>
545
  </label>
 
546
  </div>
547
  <!-- Fin de la modification -->
548
 
549
-
550
  <button type="submit"
551
  class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-6 py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg">
552
  <div class="flex items-center justify-center space-x-3">
@@ -639,9 +659,12 @@
639
  </footer>
640
 
641
  <script>
642
- // Gestionnaire global pour les fichiers ajoutés (évite les doublons)
643
- const uploadedFiles = new Map();
644
 
 
 
 
645
  function initializeFileUpload() {
646
  const dropZone = document.getElementById('drop-zone');
647
  const fileInput = document.getElementById('image-upload');
@@ -666,7 +689,6 @@
666
  fileInput.addEventListener('change', () => {
667
  const files = fileInput.files;
668
  handleFiles(files);
669
- // Réinitialiser l'input pour permettre de re-sélectionner le même fichier
670
  fileInput.value = '';
671
  });
672
 
@@ -681,25 +703,19 @@
681
  }
682
 
683
  function handleFiles(files) {
684
- const currentFiles = fileInput.files; // Nécéssaire pour DataTransferItemList
685
  const dataTransfer = new DataTransfer();
686
-
687
- // Ajouter les fichiers déjà présents dans uploadedFiles
688
  uploadedFiles.forEach(file => dataTransfer.items.add(file));
689
-
690
  for (let i = 0; i < files.length; i++) {
691
  const file = files[i];
692
  if (!file.type.startsWith('image/')) continue;
693
-
694
- // Eviter les doublons basés sur le nom et la taille
695
  const fileId = `${file.name}-${file.size}`;
696
  if (uploadedFiles.has(fileId)) continue;
697
-
698
  uploadedFiles.set(fileId, file);
699
- dataTransfer.items.add(file); // Ajouter le nouveau fichier à DataTransfer
700
  renderPreview(file, fileId);
701
  }
702
- fileInput.files = dataTransfer.files; // Mettre à jour l'input file
703
  }
704
 
705
  function renderPreview(file, fileId) {
@@ -707,14 +723,12 @@
707
  reader.onload = (e) => {
708
  const previewItem = document.createElement('div');
709
  previewItem.classList.add('image-preview-item', 'scale-in');
710
- previewItem.dataset.fileId = fileId; // Stocker l'ID unique
711
  previewItem.innerHTML = `
712
  <img src="${e.target.result}" alt="${file.name}">
713
  <button class="remove-image" title="Supprimer"><i class="fas fa-times"></i></button>
714
  `;
715
  imagePreview.appendChild(previewItem);
716
-
717
- // Remove image event
718
  previewItem.querySelector('.remove-image').addEventListener('click', () => {
719
  removeFile(fileId, previewItem);
720
  });
@@ -723,14 +737,10 @@
723
  }
724
 
725
  function removeFile(fileId, previewElement) {
726
- uploadedFiles.delete(fileId); // Retirer du Map
727
-
728
- // Mettre à jour l'input file
729
  const dataTransfer = new DataTransfer();
730
  uploadedFiles.forEach(file => dataTransfer.items.add(file));
731
  fileInput.files = dataTransfer.files;
732
-
733
- // Retirer l'aperçu de l'UI
734
  previewElement.style.opacity = '0';
735
  previewElement.style.transform = 'scale(0.9)';
736
  setTimeout(() => {
@@ -741,7 +751,10 @@
741
  }
742
  }
743
 
744
- function sauvegarderReponse(titre, contenu) {
 
 
 
745
  const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
746
  const dateSauvegarde = new Date().toISOString();
747
  const MAX_SAUVEGARDES = 20;
@@ -818,13 +831,7 @@
818
 
819
  sauvegardes.forEach((sauvegarde, index) => {
820
  const date = new Date(sauvegarde.date);
821
- const formattedDate = date.toLocaleDateString('fr-FR', {
822
- day: 'numeric',
823
- month: 'short',
824
- year: 'numeric',
825
- hour: '2-digit',
826
- minute: '2-digit'
827
- });
828
  const displayTitle = sauvegarde.titre.length > 60 ? sauvegarde.titre.substring(0, 57) + '...' : sauvegarde.titre;
829
 
830
  const sauvegardeDiv = document.createElement('div');
@@ -832,29 +839,17 @@
832
  sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
833
  sauvegardeDiv.innerHTML = `
834
  <div class="flex items-start cursor-pointer item-header">
835
- <div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0">
836
- <i class="fas fa-file-alt text-blue-600"></i>
837
- </div>
838
  <div class="flex-grow overflow-hidden">
839
  <h3 class="text-lg font-semibold text-gray-800 truncate" title="${sauvegarde.titre}">${displayTitle}</h3>
840
- <p class="text-sm text-gray-600 mb-2 flex items-center">
841
- <i class="fas fa-clock text-xs mr-1 text-gray-400"></i>
842
- ${formattedDate}
843
- </p>
844
  </div>
845
  <div class="flex space-x-2 ml-2 flex-shrink-0">
846
- <button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1" title="Copier">
847
- <i class="fas fa-copy"></i>
848
- </button>
849
- <button class="text-gray-400 hover:text-red-600 focus:outline-none delete-btn p-1" title="Supprimer">
850
- <i class="fas fa-trash-alt"></i>
851
- </button>
852
  </div>
853
  </div>
854
- <div class="backup-content mt-4 text-sm text-gray-700 prose max-w-none border-t border-gray-100 pt-3">
855
- <!-- Contenu chargé dynamiquement -->
856
- </div>
857
- `;
858
  backupsList.appendChild(sauvegardeDiv);
859
 
860
  const backupContentDiv = sauvegardeDiv.querySelector('.backup-content');
@@ -864,21 +859,15 @@
864
  const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
865
  document.querySelectorAll('.backup-content-expanded').forEach(content => {
866
  if (content !== backupContentDiv) {
867
- content.classList.remove('backup-content-expanded');
868
- content.innerHTML = '';
869
  }
870
  });
871
  if (!isExpanded) {
872
  backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu);
873
  backupContentDiv.classList.add('backup-content-expanded');
874
- setTimeout(() => {
875
- MathJax.typesetPromise([backupContentDiv]).catch(function (err) {
876
- console.error('MathJax typesetting error:', err);
877
- });
878
- }, 50);
879
  } else {
880
- backupContentDiv.classList.remove('backup-content-expanded');
881
- backupContentDiv.innerHTML = '';
882
  }
883
  });
884
 
@@ -886,15 +875,8 @@
886
  copyButton.addEventListener('click', (e) => {
887
  e.stopPropagation();
888
  navigator.clipboard.writeText(sauvegarde.contenu).then(() => {
889
- const icon = copyButton.querySelector('i');
890
- icon.className = 'fas fa-check text-green-500';
891
- setTimeout(() => {
892
- icon.className = 'fas fa-copy';
893
- }, 2000);
894
- }).catch(err => {
895
- console.error('Erreur de copie:', err);
896
- displayNotification("Erreur lors de la copie", "error");
897
- });
898
  });
899
 
900
  const deleteButton = sauvegardeDiv.querySelector('.delete-btn');
@@ -905,41 +887,22 @@
905
  storedSauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
906
  const itemToDelete = storedSauvegardes[itemIndex];
907
  const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
908
- const originalIndex = originalSauvegardes.findIndex(s => s.date === itemToDelete?.date && s.titre === itemToDelete?.titre); // Added safe navigation
909
 
910
- if (originalIndex === -1) {
911
- console.error("Impossible de trouver l'élément à supprimer dans le stockage original.");
912
- displayNotification("Erreur lors de la suppression", "error");
913
- return;
914
- }
915
 
916
  const confirmationModal = document.createElement('div');
917
  confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-30 scale-in';
918
  confirmationModal.innerHTML = `
919
  <div class="bg-white rounded-lg p-6 max-w-sm mx-4 shadow-xl">
920
- <div class="flex items-center mb-4">
921
- <div class="bg-red-100 rounded-full p-2 mr-3">
922
- <i class="fas fa-exclamation-triangle text-red-500"></i>
923
- </div>
924
- <h3 class="text-lg font-semibold text-gray-800">Confirmation</h3>
925
- </div>
926
- <p class="text-gray-600 mb-6 text-sm">Êtes-vous sûr de vouloir supprimer cette sauvegarde ?</p>
927
- <div class="flex justify-end space-x-3">
928
- <button class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 cancel-btn text-sm">Annuler</button>
929
- <button class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 confirm-btn text-sm">Supprimer</button>
930
- </div>
931
- </div>
932
- `;
933
  document.body.appendChild(confirmationModal);
934
-
935
  confirmationModal.querySelector('.cancel-btn').addEventListener('click', () => closeModal(confirmationModal));
936
- confirmationModal.querySelector('.confirm-btn').addEventListener('click', () => {
937
- supprimerSauvegarde(originalIndex);
938
- closeModal(confirmationModal);
939
- });
940
- confirmationModal.addEventListener('click', (event) => {
941
- if (event.target === confirmationModal) closeModal(confirmationModal);
942
- });
943
  });
944
  });
945
  }
@@ -947,93 +910,113 @@
947
  function closeModal(modalElement) {
948
  modalElement.style.opacity = '0';
949
  modalElement.style.transition = 'opacity 0.3s ease';
950
- setTimeout(() => {
951
- if (document.body.contains(modalElement)) {
952
- document.body.removeChild(modalElement);
953
- }
954
- }, 300);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
955
  }
956
 
957
-
958
  async function submitFrancaisForm() {
959
  const form = document.getElementById('francais-form');
960
  const output = document.getElementById('francais-output');
961
  const sujetTextarea = document.getElementById('sujet-francais');
962
- const deepThinkCheckbox = document.getElementById('deepthink-checkbox'); // MODIFICATION: Récupérer la checkbox
963
 
964
  form.addEventListener('submit', async (e) => {
965
  e.preventDefault();
966
 
967
  const sujetValue = sujetTextarea.value.trim();
 
 
 
968
  if (!sujetValue) {
969
- output.innerHTML = `
970
- <div class="alert-message alert-warning">
971
- <i class="fas fa-exclamation-triangle"></i>
972
- <div>
973
- <p>Champ obligatoire</p>
974
- <p>Veuillez entrer un sujet avant de générer.</p>
975
- </div>
976
- </div>`;
977
- sujetTextarea.focus();
978
- sujetTextarea.classList.add('border-red-500');
979
- setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
980
  return;
981
  }
982
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
  output.innerHTML = `
984
  <div class="flex flex-col items-center justify-center py-8">
985
- <div class="loader mb-4">
986
- <div></div><div></div><div></div><div></div>
987
- </div>
988
  <p class="text-sm font-medium text-gray-600">Génération en cours...</p>
989
- ${deepThinkCheckbox.checked ? '<p class="text-xs text-blue-500 mt-1">Mode DeepThink activé</p>' : ''}
990
  </div>`;
991
 
 
992
  const formData = new FormData(form);
993
- // MODIFICATION: Ajouter l'état de la checkbox aux données envoyées
994
- formData.append('use_deepthink', deepThinkCheckbox.checked);
995
 
996
  try {
997
- const response = await fetch('/api/francais', {
998
- method: 'POST',
999
- body: formData
1000
- });
1001
-
1002
  const result = await response.json();
1003
 
1004
- if (!response.ok) {
1005
- throw new Error(result.output || `Erreur HTTP ${response.status}`);
1006
- }
1007
 
 
1008
  output.style.opacity = '0';
1009
- output.style.transition = ''; // Remove transition before changing content
1010
  output.innerHTML = marked.parse(result.output);
1011
-
1012
- // Re-apply transition for fade-in
1013
  requestAnimationFrame(() => {
1014
  output.style.transition = 'opacity 0.3s ease-in-out';
1015
  output.style.opacity = '1';
1016
  });
1017
 
1018
-
1019
  const titreSauvegarde = sujetValue.length > 100 ? sujetValue.substring(0, 97) + '...' : sujetValue;
1020
- const deepThinkSuffix = deepThinkCheckbox.checked ? ' [DeepThink]' : ''; // Ajoute un suffixe au titre si DeepThink est utilisé
1021
  sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
1022
 
1023
- MathJax.typesetPromise([output]).catch(function (err) {
1024
- console.error('MathJax typesetting error:', err);
1025
- });
 
 
 
1026
 
1027
- } catch (error) {
1028
- console.error("Erreur lors de la soumission:", error);
1029
- output.innerHTML = `
1030
- <div class="alert-message alert-danger">
1031
- <i class="fas fa-exclamation-circle"></i>
1032
- <div>
1033
- <p>Une erreur est survenue</p>
1034
- <p>${error.message || "Veuillez réessayer ou vérifier votre connexion."}</p>
1035
- </div>
1036
- </div>`;
1037
  }
1038
  });
1039
  }
@@ -1045,147 +1028,76 @@
1045
 
1046
  form.addEventListener('submit', async (e) => {
1047
  e.preventDefault();
1048
-
1049
- if (uploadedFiles.size === 0) { // Use the size of our managed Map
1050
- output.innerHTML = `
1051
- <div class="alert-message alert-warning">
1052
- <i class="fas fa-exclamation-triangle"></i>
1053
- <div>
1054
- <p>Aucune image</p>
1055
- <p>Veuillez ajouter au moins une image pour l'analyse.</p>
1056
- </div>
1057
- </div>`;
1058
  return;
1059
  }
1060
-
1061
  output.innerHTML = `
1062
  <div class="flex flex-col items-center justify-center py-8">
1063
- <div class="loader mb-4">
1064
- <div></div><div></div><div></div><div></div>
1065
- </div>
1066
  <p class="text-sm font-medium text-gray-600">Analyse en cours...</p>
1067
  <p class="text-xs text-gray-400 mt-2">Cela peut prendre un moment</p>
1068
  </div>`;
1069
-
1070
  const formData = new FormData();
1071
- uploadedFiles.forEach((file) => {
1072
- formData.append('images', file, file.name);
1073
- });
1074
-
1075
  try {
1076
- const response = await fetch('/api/etude-texte', {
1077
- method: 'POST',
1078
- body: formData
1079
- });
1080
-
1081
  const result = await response.json();
1082
-
1083
- if (!response.ok) {
1084
- throw new Error(result.output || `Erreur HTTP ${response.status}`);
1085
- }
1086
-
1087
  output.style.opacity = '0';
1088
- output.style.transition = ''; // Remove transition before changing content
1089
  output.innerHTML = marked.parse(result.output);
1090
-
1091
- // Re-apply transition for fade-in
1092
- requestAnimationFrame(() => {
1093
- output.style.transition = 'opacity 0.3s ease-in-out';
1094
- output.style.opacity = '1';
1095
- });
1096
-
1097
- const titre = `Analyse d'image(s) - ${new Date().toLocaleDateString('fr-FR')}`;
1098
  sauvegarderReponse(titre, result.output);
1099
-
1100
- MathJax.typesetPromise([output]).catch(function (err) {
1101
- console.error('MathJax typesetting error:', err);
1102
- });
1103
-
1104
  } catch (error) {
1105
- console.error("Erreur lors de l'analyse:", error);
1106
- output.innerHTML = `
1107
- <div class="alert-message alert-danger">
1108
- <i class="fas fa-exclamation-circle"></i>
1109
- <div>
1110
- <p>Une erreur est survenue</p>
1111
- <p>${error.message || "Veuillez réessayer ou vérifier votre connexion."}</p>
1112
- </div>
1113
- </div>`;
1114
  }
1115
  });
1116
  }
1117
 
1118
- // Animation des cartes au défilement
1119
- function animateOnScroll() {
 
1120
  const cards = document.querySelectorAll('.card-hover');
1121
- if (!('IntersectionObserver' in window)) {
1122
- cards.forEach(card => card.style.opacity = '1');
1123
- return;
1124
- }
1125
  const observer = new IntersectionObserver((entries) => {
1126
  entries.forEach((entry) => {
1127
  if (entry.isIntersecting) {
1128
- // Use requestAnimationFrame for smoother start
1129
- requestAnimationFrame(() => {
1130
- entry.target.style.opacity = '1';
1131
- entry.target.style.transform = 'translateY(0)';
1132
- });
1133
  observer.unobserve(entry.target);
1134
  }
1135
  });
1136
  }, { threshold: 0.1 });
1137
-
1138
- cards.forEach(card => {
1139
- card.style.opacity = '0';
1140
- card.style.transform = 'translateY(30px)';
1141
- // Rely on the .scale-in animation defined in CSS for entrance
1142
- observer.observe(card);
1143
- });
1144
  }
1145
-
1146
- // Effet de focus amélioré pour les zones de texte et compteur de caractères
1147
  function enhanceTextareaFocus() {
1148
  const textarea = document.getElementById('sujet-francais');
1149
  const counter = document.getElementById('character-count');
1150
  textarea.addEventListener('input', () => {
1151
- const length = textarea.value.length;
1152
- counter.textContent = `${length} caractère${length !== 1 ? 's' : ''}`;
1153
- textarea.classList.remove('border-red-500');
1154
  });
1155
  }
1156
-
1157
- // Effet de hover pour les boutons radio
1158
  function enhanceRadioButtons() {
1159
  const radioGroups = document.querySelectorAll('input[type="radio"] + span');
1160
  radioGroups.forEach(label => {
1161
- const input = label.previousElementSibling;
1162
- if (input.disabled) return;
1163
-
1164
- label.addEventListener('mouseenter', () => {
1165
- if (!input.checked) {
1166
- // Use Tailwind classes for consistency
1167
- label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-0.5');
1168
- }
1169
- });
1170
- label.addEventListener('mouseleave', () => {
1171
- if (!input.checked) {
1172
- label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-0.5');
1173
- }
1174
- });
1175
  input.addEventListener('change', () => {
1176
  const groupName = input.name;
1177
  document.querySelectorAll(`input[name="${groupName}"] + span`).forEach(otherLabel => {
1178
- if (otherLabel !== label) {
1179
- otherLabel.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-0.5', 'border-primary'); // Ensure clean state
1180
- } else {
1181
- label.classList.remove('shadow-md', 'transform', '-translate-y-0.5'); // Remove hover effects if checked
1182
- }
1183
  });
1184
  });
1185
  });
1186
  }
1187
 
1188
- // Initialisation
 
1189
  document.addEventListener('DOMContentLoaded', () => {
1190
  initializeFileUpload();
1191
  submitFrancaisForm();
@@ -1193,27 +1105,17 @@
1193
  animateOnScroll();
1194
  enhanceTextareaFocus();
1195
  enhanceRadioButtons();
 
1196
  afficherSauvegardes();
1197
 
1198
  // MathJax Config
1199
- window.MathJax = {
1200
- tex: { inlineMath: [['$', '$'], ['\\(', '\\)']], displayMath: [['$$', '$$'], ['\\[', '\\]']] },
1201
- svg: { fontCache: 'global' },
1202
- options: { skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'], ignoreHtmlClass: 'tex2jax_ignore', processHtmlClass: 'tex2jax_process' }
1203
- };
1204
 
1205
- // Welcome Message
1206
  const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
1207
  if (showWelcome) {
1208
  const welcomeMessageContainer = document.createElement('div');
1209
- welcomeMessageContainer.innerHTML = `
1210
- <div class="fixed bottom-6 right-6 bg-white rounded-xl shadow-lg p-5 transform transition-all duration-500 opacity-0 translate-y-4 max-w-xs z-[100]">
1211
- <div class="flex items-start space-x-4">
1212
- <div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-3 mt-1 flex-shrink-0"> <i class="fas fa-robot text-white text-xl"></i> </div>
1213
- <div> <p class="text-sm font-semibold text-gray-800">Bienvenue sur Mariam AI !</p> <p class="text-xs text-gray-500 mt-1">Votre assistant est prêt. Posez un sujet ou analysez un texte.</p> </div>
1214
- </div>
1215
- <button class="absolute top-2 right-2 text-gray-400 hover:text-gray-600 focus:outline-none close-welcome p-1"> <i class="fas fa-times text-xs"></i> </button>
1216
- </div>`;
1217
  document.body.appendChild(welcomeMessageContainer);
1218
  const welcomeMessage = welcomeMessageContainer.firstElementChild;
1219
  setTimeout(() => { welcomeMessage.classList.remove('opacity-0', 'translate-y-4'); }, 500);
 
371
  position: relative;
372
  top: 1px;
373
  transition: all 0.2s ease-in-out;
374
+ cursor: pointer; /* Add cursor pointer */
375
  }
376
  .deepthink-label input[type="checkbox"]:checked {
377
  background-color: var(--primary);
 
388
  left: 50%;
389
  transform: translate(-50%, -50%);
390
  }
391
+ .deepthink-label input[type="checkbox"]:disabled {
392
+ background-color: #e2e8f0; /* gray-200 */
393
+ border-color: #cbd5e0; /* gray-300 */
394
+ cursor: not-allowed;
395
+ }
396
+ .deepthink-label input[type="checkbox"]:disabled + span { /* Style text when disabled */
397
+ color: #a0aec0; /* gray-400 */
398
+ }
399
+ .deepthink-label input[type="checkbox"]:disabled:checked::after {
400
+ color: #a0aec0; /* gray-400 */
401
+ }
402
+
403
+ .deepthink-label:hover input[type="checkbox"]:not(:disabled) {
404
  border-color: var(--primary-dark);
405
  }
406
  .deepthink-tooltip {
407
  position: relative;
408
+ display: inline-flex; /* Use inline-flex for alignment */
409
+ align-items: center; /* Align icon vertically */
410
  margin-left: 6px;
411
  }
412
  .deepthink-tooltip .tooltip-text {
 
426
  transition: opacity 0.3s;
427
  font-size: 0.75rem; /* text-xs */
428
  line-height: 1.4;
429
+ pointer-events: none; /* Prevent tooltip from interfering */
430
  }
431
  .deepthink-tooltip .tooltip-text::after {
432
  content: "";
 
442
  visibility: visible;
443
  opacity: 1;
444
  }
445
+ /* MODIFICATION: Style pour le statut DeepThink */
446
+ #deepthink-status {
447
+ font-style: italic;
448
+ margin-left: 0.5rem; /* ml-2 */
449
+ }
450
 
451
  </style>
452
  </head>
 
553
  </div>
554
  </div>
555
 
556
+ <!-- MODIFICATION: Ajout span pour statut -->
557
  <div class="flex items-center justify-start pt-2">
558
  <label for="deepthink-checkbox" class="deepthink-label">
559
+ <input type="checkbox" id="deepthink-checkbox" name="use_deepthink_visual">
560
+ <span>Utiliser DeepThink</span> <!-- Mettre le texte dans un span pour le style :disabled -->
561
  <div class="deepthink-tooltip">
562
  <i class="fas fa-info-circle text-gray-400"></i>
563
+ <span class="tooltip-text">Utilise un modèle plus avancé (Gemini Pro) pour une meilleure qualité, mais peut être plus lent. Limité à 1 utilisation par jour.</span>
564
  </div>
565
  </label>
566
+ <span id="deepthink-status" class="text-xs text-gray-500"></span> <!-- Indicateur de statut -->
567
  </div>
568
  <!-- Fin de la modification -->
569
 
 
570
  <button type="submit"
571
  class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-6 py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg">
572
  <div class="flex items-center justify-center space-x-3">
 
659
  </footer>
660
 
661
  <script>
662
+ // --- Constantes ---
663
+ const DEEPTHINK_STORAGE_KEY = 'deepThinkLastUsedDate'; // Clé localStorage
664
 
665
+ // --- Gestionnaire de fichiers ---
666
+ const uploadedFiles = new Map();
667
+ // ... (initializeFileUpload, highlightDropZone, unhighlightDropZone, handleFiles, renderPreview, removeFile restent identiques) ...
668
  function initializeFileUpload() {
669
  const dropZone = document.getElementById('drop-zone');
670
  const fileInput = document.getElementById('image-upload');
 
689
  fileInput.addEventListener('change', () => {
690
  const files = fileInput.files;
691
  handleFiles(files);
 
692
  fileInput.value = '';
693
  });
694
 
 
703
  }
704
 
705
  function handleFiles(files) {
706
+ const currentFiles = fileInput.files;
707
  const dataTransfer = new DataTransfer();
 
 
708
  uploadedFiles.forEach(file => dataTransfer.items.add(file));
 
709
  for (let i = 0; i < files.length; i++) {
710
  const file = files[i];
711
  if (!file.type.startsWith('image/')) continue;
 
 
712
  const fileId = `${file.name}-${file.size}`;
713
  if (uploadedFiles.has(fileId)) continue;
 
714
  uploadedFiles.set(fileId, file);
715
+ dataTransfer.items.add(file);
716
  renderPreview(file, fileId);
717
  }
718
+ fileInput.files = dataTransfer.files;
719
  }
720
 
721
  function renderPreview(file, fileId) {
 
723
  reader.onload = (e) => {
724
  const previewItem = document.createElement('div');
725
  previewItem.classList.add('image-preview-item', 'scale-in');
726
+ previewItem.dataset.fileId = fileId;
727
  previewItem.innerHTML = `
728
  <img src="${e.target.result}" alt="${file.name}">
729
  <button class="remove-image" title="Supprimer"><i class="fas fa-times"></i></button>
730
  `;
731
  imagePreview.appendChild(previewItem);
 
 
732
  previewItem.querySelector('.remove-image').addEventListener('click', () => {
733
  removeFile(fileId, previewItem);
734
  });
 
737
  }
738
 
739
  function removeFile(fileId, previewElement) {
740
+ uploadedFiles.delete(fileId);
 
 
741
  const dataTransfer = new DataTransfer();
742
  uploadedFiles.forEach(file => dataTransfer.items.add(file));
743
  fileInput.files = dataTransfer.files;
 
 
744
  previewElement.style.opacity = '0';
745
  previewElement.style.transform = 'scale(0.9)';
746
  setTimeout(() => {
 
751
  }
752
  }
753
 
754
+
755
+ // --- Gestion des sauvegardes ---
756
+ // ... (sauvegarderReponse, displayNotification, supprimerSauvegarde, afficherSauvegardes, closeModal restent identiques) ...
757
+ function sauvegarderReponse(titre, contenu) {
758
  const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
759
  const dateSauvegarde = new Date().toISOString();
760
  const MAX_SAUVEGARDES = 20;
 
831
 
832
  sauvegardes.forEach((sauvegarde, index) => {
833
  const date = new Date(sauvegarde.date);
834
+ const formattedDate = date.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' });
 
 
 
 
 
 
835
  const displayTitle = sauvegarde.titre.length > 60 ? sauvegarde.titre.substring(0, 57) + '...' : sauvegarde.titre;
836
 
837
  const sauvegardeDiv = document.createElement('div');
 
839
  sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
840
  sauvegardeDiv.innerHTML = `
841
  <div class="flex items-start cursor-pointer item-header">
842
+ <div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <i class="fas fa-file-alt text-blue-600"></i> </div>
 
 
843
  <div class="flex-grow overflow-hidden">
844
  <h3 class="text-lg font-semibold text-gray-800 truncate" title="${sauvegarde.titre}">${displayTitle}</h3>
845
+ <p class="text-sm text-gray-600 mb-2 flex items-center"> <i class="fas fa-clock text-xs mr-1 text-gray-400"></i> ${formattedDate} </p>
 
 
 
846
  </div>
847
  <div class="flex space-x-2 ml-2 flex-shrink-0">
848
+ <button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1" title="Copier"> <i class="fas fa-copy"></i> </button>
849
+ <button class="text-gray-400 hover:text-red-600 focus:outline-none delete-btn p-1" title="Supprimer"> <i class="fas fa-trash-alt"></i> </button>
 
 
 
 
850
  </div>
851
  </div>
852
+ <div class="backup-content mt-4 text-sm text-gray-700 prose max-w-none border-t border-gray-100 pt-3"></div>`;
 
 
 
853
  backupsList.appendChild(sauvegardeDiv);
854
 
855
  const backupContentDiv = sauvegardeDiv.querySelector('.backup-content');
 
859
  const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
860
  document.querySelectorAll('.backup-content-expanded').forEach(content => {
861
  if (content !== backupContentDiv) {
862
+ content.classList.remove('backup-content-expanded'); content.innerHTML = '';
 
863
  }
864
  });
865
  if (!isExpanded) {
866
  backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu);
867
  backupContentDiv.classList.add('backup-content-expanded');
868
+ setTimeout(() => { MathJax.typesetPromise([backupContentDiv]).catch(err => console.error('MathJax error:', err)); }, 50);
 
 
 
 
869
  } else {
870
+ backupContentDiv.classList.remove('backup-content-expanded'); backupContentDiv.innerHTML = '';
 
871
  }
872
  });
873
 
 
875
  copyButton.addEventListener('click', (e) => {
876
  e.stopPropagation();
877
  navigator.clipboard.writeText(sauvegarde.contenu).then(() => {
878
+ const icon = copyButton.querySelector('i'); icon.className = 'fas fa-check text-green-500'; setTimeout(() => { icon.className = 'fas fa-copy'; }, 2000);
879
+ }).catch(err => { console.error('Copy error:', err); displayNotification("Erreur copie", "error"); });
 
 
 
 
 
 
 
880
  });
881
 
882
  const deleteButton = sauvegardeDiv.querySelector('.delete-btn');
 
887
  storedSauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
888
  const itemToDelete = storedSauvegardes[itemIndex];
889
  const originalSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
890
+ const originalIndex = originalSauvegardes.findIndex(s => s.date === itemToDelete?.date && s.titre === itemToDelete?.titre);
891
 
892
+ if (originalIndex === -1) { console.error("Item not found for deletion."); displayNotification("Erreur suppression", "error"); return; }
 
 
 
 
893
 
894
  const confirmationModal = document.createElement('div');
895
  confirmationModal.className = 'fixed inset-0 flex items-center justify-center z-[100] bg-black bg-opacity-30 scale-in';
896
  confirmationModal.innerHTML = `
897
  <div class="bg-white rounded-lg p-6 max-w-sm mx-4 shadow-xl">
898
+ <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-lg font-semibold text-gray-800">Confirmation</h3> </div>
899
+ <p class="text-gray-600 mb-6 text-sm">Supprimer cette sauvegarde ?</p>
900
+ <div class="flex justify-end space-x-3"> <button class="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 cancel-btn text-sm">Annuler</button> <button class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 confirm-btn text-sm">Supprimer</button> </div>
901
+ </div>`;
 
 
 
 
 
 
 
 
 
902
  document.body.appendChild(confirmationModal);
 
903
  confirmationModal.querySelector('.cancel-btn').addEventListener('click', () => closeModal(confirmationModal));
904
+ confirmationModal.querySelector('.confirm-btn').addEventListener('click', () => { supprimerSauvegarde(originalIndex); closeModal(confirmationModal); });
905
+ confirmationModal.addEventListener('click', (event) => { if (event.target === confirmationModal) closeModal(confirmationModal); });
 
 
 
 
 
906
  });
907
  });
908
  }
 
910
  function closeModal(modalElement) {
911
  modalElement.style.opacity = '0';
912
  modalElement.style.transition = 'opacity 0.3s ease';
913
+ setTimeout(() => { if (document.body.contains(modalElement)) document.body.removeChild(modalElement); }, 300);
914
+ }
915
+
916
+
917
+ // --- Gestion DeepThink ---
918
+ /**
919
+ * Vérifie si DeepThink a déjà été utilisé aujourd'hui et met à jour l'UI.
920
+ */
921
+ function checkDeepThinkAvailability() {
922
+ const checkbox = document.getElementById('deepthink-checkbox');
923
+ const statusSpan = document.getElementById('deepthink-status');
924
+ if (!checkbox || !statusSpan) return; // Safety check
925
+
926
+ const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
927
+ const todayStr = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
928
+
929
+ if (lastUsedDateStr === todayStr) {
930
+ checkbox.checked = false; // Ensure it's unchecked if disabled
931
+ checkbox.disabled = true;
932
+ statusSpan.textContent = "(déjà utilisé aujourd'hui)";
933
+ statusSpan.classList.add('text-red-500'); // Make status noticeable
934
+ } else {
935
+ checkbox.disabled = false;
936
+ statusSpan.textContent = ""; // Clear status
937
+ statusSpan.classList.remove('text-red-500');
938
+ }
939
  }
940
 
941
+ // --- Soumission des formulaires ---
942
  async function submitFrancaisForm() {
943
  const form = document.getElementById('francais-form');
944
  const output = document.getElementById('francais-output');
945
  const sujetTextarea = document.getElementById('sujet-francais');
946
+ const deepThinkCheckbox = document.getElementById('deepthink-checkbox');
947
 
948
  form.addEventListener('submit', async (e) => {
949
  e.preventDefault();
950
 
951
  const sujetValue = sujetTextarea.value.trim();
952
+ const isDeepThinkChecked = deepThinkCheckbox.checked; // Store initial checked state
953
+
954
+ // --- Validation avant envoi ---
955
  if (!sujetValue) {
956
+ 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>`;
957
+ sujetTextarea.focus();
958
+ sujetTextarea.classList.add('border-red-500');
959
+ setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
 
 
 
 
 
 
 
960
  return;
961
  }
962
 
963
+ // MODIFICATION: Vérifier la limite DeepThink avant l'envoi si coché
964
+ if (isDeepThinkChecked) {
965
+ const lastUsedDateStr = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
966
+ const todayStr = new Date().toISOString().split('T')[0];
967
+ if (lastUsedDateStr === todayStr) {
968
+ output.innerHTML = `<div class="alert-message alert-warning"><i class="fas fa-exclamation-triangle"></i><div><p>Limite atteinte</p><p>Vous avez déjà utilisé DeepThink aujourd'hui.</p></div></div>`;
969
+ checkDeepThinkAvailability(); // Re-disable checkbox just in case
970
+ return; // Stop submission
971
+ }
972
+ }
973
+ // --- Fin Validation ---
974
+
975
+
976
+ // --- Affichage chargement ---
977
  output.innerHTML = `
978
  <div class="flex flex-col items-center justify-center py-8">
979
+ <div class="loader mb-4"><div></div><div></div><div></div><div></div></div>
 
 
980
  <p class="text-sm font-medium text-gray-600">Génération en cours...</p>
981
+ ${isDeepThinkChecked ? '<p class="text-xs text-blue-500 mt-1">Mode DeepThink activé</p>' : ''}
982
  </div>`;
983
 
984
+ // --- Préparation et Envoi ---
985
  const formData = new FormData(form);
986
+ formData.append('use_deepthink', isDeepThinkChecked); // Use stored checked state
 
987
 
988
  try {
989
+ const response = await fetch('/api/francais', { method: 'POST', body: formData });
 
 
 
 
990
  const result = await response.json();
991
 
992
+ if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
 
 
993
 
994
+ // --- Affichage Succès ---
995
  output.style.opacity = '0';
996
+ output.style.transition = '';
997
  output.innerHTML = marked.parse(result.output);
 
 
998
  requestAnimationFrame(() => {
999
  output.style.transition = 'opacity 0.3s ease-in-out';
1000
  output.style.opacity = '1';
1001
  });
1002
 
1003
+ // --- Sauvegarde & Mise à jour Limite DeepThink ---
1004
  const titreSauvegarde = sujetValue.length > 100 ? sujetValue.substring(0, 97) + '...' : sujetValue;
1005
+ const deepThinkSuffix = isDeepThinkChecked ? ' [DeepThink]' : '';
1006
  sauvegarderReponse(titreSauvegarde + deepThinkSuffix, result.output);
1007
 
1008
+ // MODIFICATION: Si DeepThink a été utilisé avec succès, enregistrer la date et mettre à jour l'UI
1009
+ if (isDeepThinkChecked) {
1010
+ const todayStr = new Date().toISOString().split('T')[0];
1011
+ localStorage.setItem(DEEPTHINK_STORAGE_KEY, todayStr);
1012
+ checkDeepThinkAvailability(); // Met à jour l'état de la checkbox et le message
1013
+ }
1014
 
1015
+ MathJax.typesetPromise([output]).catch(err => console.error('MathJax error:', err));
1016
+
1017
+ } catch (error) { // --- Gestion Erreur ---
1018
+ console.error("Erreur soumission (français):", error);
1019
+ 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 pour détails."}</p></div></div>`;
 
 
 
 
 
1020
  }
1021
  });
1022
  }
 
1028
 
1029
  form.addEventListener('submit', async (e) => {
1030
  e.preventDefault();
1031
+ if (uploadedFiles.size === 0) {
1032
+ 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>`;
 
 
 
 
 
 
 
 
1033
  return;
1034
  }
 
1035
  output.innerHTML = `
1036
  <div class="flex flex-col items-center justify-center py-8">
1037
+ <div class="loader mb-4"><div></div><div></div><div></div><div></div></div>
 
 
1038
  <p class="text-sm font-medium text-gray-600">Analyse en cours...</p>
1039
  <p class="text-xs text-gray-400 mt-2">Cela peut prendre un moment</p>
1040
  </div>`;
 
1041
  const formData = new FormData();
1042
+ uploadedFiles.forEach((file) => formData.append('images', file, file.name));
 
 
 
1043
  try {
1044
+ const response = await fetch('/api/etude-texte', { method: 'POST', body: formData });
 
 
 
 
1045
  const result = await response.json();
1046
+ if (!response.ok) throw new Error(result.output || `Erreur HTTP ${response.status}`);
 
 
 
 
1047
  output.style.opacity = '0';
1048
+ output.style.transition = '';
1049
  output.innerHTML = marked.parse(result.output);
1050
+ requestAnimationFrame(() => { output.style.transition = 'opacity 0.3s ease-in-out'; output.style.opacity = '1'; });
1051
+ const titre = `Analyse image(s) - ${new Date().toLocaleDateString('fr-FR')}`;
 
 
 
 
 
 
1052
  sauvegarderReponse(titre, result.output);
1053
+ MathJax.typesetPromise([output]).catch(err => console.error('MathJax error:', err));
 
 
 
 
1054
  } catch (error) {
1055
+ console.error("Erreur soumission (étude texte):", error);
1056
+ 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 pour détails."}</p></div></div>`;
 
 
 
 
 
 
 
1057
  }
1058
  });
1059
  }
1060
 
1061
+ // --- Animations & Améliorations UI ---
1062
+ // ... (animateOnScroll, enhanceTextareaFocus, enhanceRadioButtons restent identiques) ...
1063
+ function animateOnScroll() {
1064
  const cards = document.querySelectorAll('.card-hover');
1065
+ if (!('IntersectionObserver' in window)) { cards.forEach(card => card.style.opacity = '1'); return; }
 
 
 
1066
  const observer = new IntersectionObserver((entries) => {
1067
  entries.forEach((entry) => {
1068
  if (entry.isIntersecting) {
1069
+ requestAnimationFrame(() => { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; });
 
 
 
 
1070
  observer.unobserve(entry.target);
1071
  }
1072
  });
1073
  }, { threshold: 0.1 });
1074
+ cards.forEach(card => { card.style.opacity = '0'; card.style.transform = 'translateY(30px)'; observer.observe(card); });
 
 
 
 
 
 
1075
  }
 
 
1076
  function enhanceTextareaFocus() {
1077
  const textarea = document.getElementById('sujet-francais');
1078
  const counter = document.getElementById('character-count');
1079
  textarea.addEventListener('input', () => {
1080
+ const length = textarea.value.length; counter.textContent = `${length} caractère${length !== 1 ? 's' : ''}`; textarea.classList.remove('border-red-500');
 
 
1081
  });
1082
  }
 
 
1083
  function enhanceRadioButtons() {
1084
  const radioGroups = document.querySelectorAll('input[type="radio"] + span');
1085
  radioGroups.forEach(label => {
1086
+ const input = label.previousElementSibling; if (input.disabled) return;
1087
+ label.addEventListener('mouseenter', () => { if (!input.checked) label.classList.add('border-blue-400', 'shadow-md', 'transform' ,'-translate-y-0.5'); });
1088
+ label.addEventListener('mouseleave', () => { if (!input.checked) label.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-0.5'); });
 
 
 
 
 
 
 
 
 
 
 
1089
  input.addEventListener('change', () => {
1090
  const groupName = input.name;
1091
  document.querySelectorAll(`input[name="${groupName}"] + span`).forEach(otherLabel => {
1092
+ if (otherLabel !== label) otherLabel.classList.remove('border-blue-400', 'shadow-md', 'transform', '-translate-y-0.5', 'border-primary');
1093
+ else label.classList.remove('shadow-md', 'transform', '-translate-y-0.5');
 
 
 
1094
  });
1095
  });
1096
  });
1097
  }
1098
 
1099
+
1100
+ // --- Initialisation Générale ---
1101
  document.addEventListener('DOMContentLoaded', () => {
1102
  initializeFileUpload();
1103
  submitFrancaisForm();
 
1105
  animateOnScroll();
1106
  enhanceTextareaFocus();
1107
  enhanceRadioButtons();
1108
+ checkDeepThinkAvailability(); // MODIFICATION: Vérifier la limite au chargement
1109
  afficherSauvegardes();
1110
 
1111
  // MathJax Config
1112
+ window.MathJax = { tex: { inlineMath: [['$', '$'], ['\\(', '\\)']], displayMath: [['$$', '$$'], ['\\[', '\\]']] }, svg: { fontCache: 'global' }, options: { skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'], ignoreHtmlClass: 'tex2jax_ignore', processHtmlClass: 'tex2jax_process' } };
 
 
 
 
1113
 
1114
+ // Welcome Message (session based)
1115
  const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
1116
  if (showWelcome) {
1117
  const welcomeMessageContainer = document.createElement('div');
1118
+ welcomeMessageContainer.innerHTML = `<div class="fixed bottom-6 right-6 bg-white rounded-xl shadow-lg p-5 transform transition-all duration-500 opacity-0 translate-y-4 max-w-xs z-[100]"><div class="flex items-start space-x-4"><div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-3 mt-1 flex-shrink-0"> <i class="fas fa-robot text-white 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. Posez un sujet ou analysez un texte.</p> </div></div><button class="absolute top-2 right-2 text-gray-400 hover:text-gray-600 focus:outline-none close-welcome p-1"> <i class="fas fa-times text-xs"></i> </button></div>`;
 
 
 
 
 
 
 
1119
  document.body.appendChild(welcomeMessageContainer);
1120
  const welcomeMessage = welcomeMessageContainer.firstElementChild;
1121
  setTimeout(() => { welcomeMessage.classList.remove('opacity-0', 'translate-y-4'); }, 500);