Spaces:
Running
Running
Update templates/index.html
Browse files- templates/index.html +352 -353
templates/index.html
CHANGED
@@ -83,14 +83,17 @@
|
|
83 |
/* Styles pour les messages d'erreur/avertissement */
|
84 |
.alert-message {
|
85 |
display: flex;
|
86 |
-
align-items:
|
87 |
-
space-x
|
88 |
padding: 1rem; /* p-4 */
|
89 |
border-radius: 0.5rem; /* rounded-lg */
|
|
|
|
|
90 |
}
|
91 |
.alert-message i {
|
92 |
margin-right: 0.75rem; /* space-x-3 equivalent */
|
93 |
font-size: 1.25rem; /* text-xl */
|
|
|
94 |
}
|
95 |
.alert-message div p:first-child {
|
96 |
font-weight: 500; /* font-medium */
|
@@ -98,30 +101,31 @@
|
|
98 |
.alert-message div p:last-child {
|
99 |
font-size: 0.875rem; /* text-sm */
|
100 |
margin-top: 0.25rem; /* mt-1 */
|
|
|
101 |
}
|
102 |
|
103 |
.alert-warning {
|
104 |
-
color: #92400e; /* text-amber-700
|
105 |
background-color: #fffbeb; /* bg-amber-50 */
|
106 |
-
border:
|
107 |
}
|
108 |
.alert-warning i {
|
109 |
color: var(--warning); /* text-amber-500 */
|
110 |
}
|
111 |
.alert-warning div p:last-child {
|
112 |
-
color: #b45309; /* text-amber-600
|
113 |
}
|
114 |
|
115 |
.alert-danger {
|
116 |
-
color: #991b1b; /* text-red-700
|
117 |
background-color: #fef2f2; /* bg-red-50 */
|
118 |
-
border:
|
119 |
}
|
120 |
.alert-danger i {
|
121 |
color: var(--danger); /* text-red-500 */
|
122 |
}
|
123 |
.alert-danger div p:last-child {
|
124 |
-
color: #b91c1c; /* text-red-600
|
125 |
}
|
126 |
|
127 |
|
@@ -159,16 +163,20 @@
|
|
159 |
margin-top: 10px;
|
160 |
max-height: 0;
|
161 |
overflow: hidden;
|
162 |
-
transition: max-height 0.3s ease, opacity 0.3s ease;
|
163 |
opacity: 0;
|
|
|
164 |
}
|
165 |
|
166 |
.backup-content-expanded {
|
167 |
display: block;
|
168 |
-
max-height:
|
169 |
opacity: 1;
|
|
|
|
|
170 |
}
|
171 |
|
|
|
172 |
/* Styles for multiple image upload */
|
173 |
.image-preview {
|
174 |
display: flex;
|
@@ -344,6 +352,82 @@
|
|
344 |
grid-template-columns: repeat(2, minmax(0, 1fr));
|
345 |
}
|
346 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
347 |
</style>
|
348 |
</head>
|
349 |
|
@@ -381,7 +465,7 @@
|
|
381 |
<div class="focus-ring">
|
382 |
<textarea id="sujet-francais" name="sujet" rows="4"
|
383 |
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:outline-none transition-all duration-200 resize-none"
|
384 |
-
placeholder="Entrez votre sujet ici..." required></textarea>
|
385 |
</div>
|
386 |
<div class="text-xs text-gray-400 text-right" id="character-count">0 caract猫res</div>
|
387 |
</div>
|
@@ -416,12 +500,10 @@
|
|
416 |
</span>
|
417 |
</label>
|
418 |
<label class="relative">
|
419 |
-
<!-- MODIFICATION ICI: disabled retir茅, span class mise 脿 jour, badge retir茅 -->
|
420 |
<input type="radio" name="choix" value="dissertation"
|
421 |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
|
422 |
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
|
423 |
Dissertation
|
424 |
-
<!-- <span class="coming-soon-badge">Bient么t</span> -->
|
425 |
</span>
|
426 |
</label>
|
427 |
</div>
|
@@ -451,6 +533,20 @@
|
|
451 |
</div>
|
452 |
</div>
|
453 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
454 |
<button type="submit"
|
455 |
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">
|
456 |
<div class="flex items-center justify-center space-x-3">
|
@@ -459,7 +555,7 @@
|
|
459 |
</div>
|
460 |
</button>
|
461 |
</form>
|
462 |
-
<div id="francais-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[100px]">
|
463 |
<!-- Le contenu g茅n茅r茅 sera ins茅r茅 ici -->
|
464 |
</div>
|
465 |
</div>
|
@@ -481,7 +577,7 @@
|
|
481 |
</label>
|
482 |
<div class="border-3 border-dashed border-gray-300 rounded-xl p-8 text-center cursor-pointer hover:border-blue-400 transition-all duration-200 group"
|
483 |
id="drop-zone">
|
484 |
-
<input type="file" id="image-upload" name="images" accept="image/*" class="hidden" multiple>
|
485 |
<div class="space-y-4">
|
486 |
<div class="bg-blue-50 rounded-full w-16 h-16 flex items-center justify-center mx-auto transform transition-transform group-hover:scale-110 group-hover:rotate-6">
|
487 |
<i class="fas fa-cloud-upload-alt text-3xl text-blue-500"></i>
|
@@ -491,7 +587,7 @@
|
|
491 |
<p class="text-xs text-gray-400">PNG, JPG, WEBP jusqu'脿 10MB</p>
|
492 |
</div>
|
493 |
</div>
|
494 |
-
<div id="image-preview" class="image-preview"></div>
|
495 |
</div>
|
496 |
|
497 |
<button type="submit"
|
@@ -502,7 +598,7 @@
|
|
502 |
</div>
|
503 |
</button>
|
504 |
</form>
|
505 |
-
<div id="etude-texte-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[100px]">
|
506 |
<!-- Le contenu analys茅 sera ins茅r茅 ici -->
|
507 |
</div>
|
508 |
</div>
|
@@ -638,7 +734,9 @@
|
|
638 |
previewElement.style.opacity = '0';
|
639 |
previewElement.style.transform = 'scale(0.9)';
|
640 |
setTimeout(() => {
|
641 |
-
imagePreview.
|
|
|
|
|
642 |
}, 300);
|
643 |
}
|
644 |
}
|
@@ -646,13 +744,12 @@
|
|
646 |
function sauvegarderReponse(titre, contenu) {
|
647 |
const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
|
648 |
const dateSauvegarde = new Date().toISOString();
|
649 |
-
// Limiter la taille des sauvegardes si n茅cessaire
|
650 |
const MAX_SAUVEGARDES = 20;
|
651 |
if (sauvegardes.length >= MAX_SAUVEGARDES) {
|
652 |
-
sauvegardes.shift();
|
653 |
}
|
654 |
sauvegardes.push({
|
655 |
-
titre: titre || "Sauvegarde sans titre",
|
656 |
contenu,
|
657 |
date: dateSauvegarde
|
658 |
});
|
@@ -660,24 +757,22 @@
|
|
660 |
localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
|
661 |
} catch (e) {
|
662 |
console.error("Erreur lors de la sauvegarde dans localStorage (peut-锚tre plein):", e);
|
663 |
-
// Afficher une erreur 脿 l'utilisateur si localStorage est plein
|
664 |
displayNotification("Erreur de sauvegarde: Espace de stockage local plein.", "error");
|
665 |
-
return;
|
666 |
}
|
667 |
-
|
668 |
-
|
669 |
displayNotification("Sauvegard茅 avec succ猫s", "success");
|
670 |
-
afficherSauvegardes();
|
671 |
}
|
672 |
|
673 |
function displayNotification(message, type = 'success') {
|
674 |
const notification = document.createElement('div');
|
675 |
-
const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-700' : 'bg-red-50 border-red-200 text-red-700';
|
676 |
-
const iconClass = type === 'success' ? 'fa-check-circle text-green-500' : 'fa-exclamation-circle text-red-500';
|
677 |
|
678 |
-
|
|
|
679 |
notification.innerHTML = `
|
680 |
-
<i class="fas ${iconClass} mr-
|
681 |
<span class="text-sm font-medium">${message}</span>
|
682 |
`;
|
683 |
document.body.appendChild(notification);
|
@@ -690,7 +785,7 @@
|
|
690 |
document.body.removeChild(notification);
|
691 |
}
|
692 |
}, 300);
|
693 |
-
}, 3000);
|
694 |
}
|
695 |
|
696 |
|
@@ -702,203 +797,173 @@
|
|
702 |
}
|
703 |
|
704 |
|
705 |
-
function afficherSauvegardes() {
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
return;
|
723 |
-
}
|
724 |
-
|
725 |
-
sauvegardes.forEach((sauvegarde, index) => {
|
726 |
-
const date = new Date(sauvegarde.date);
|
727 |
-
const formattedDate = date.toLocaleDateString('fr-FR', {
|
728 |
-
day: 'numeric',
|
729 |
-
month: 'short',
|
730 |
-
year: 'numeric',
|
731 |
-
hour: '2-digit',
|
732 |
-
minute: '2-digit'
|
733 |
-
});
|
734 |
-
|
735 |
-
// Tronquer le titre si trop long pour l'affichage initial
|
736 |
-
const displayTitle = sauvegarde.titre.length > 60 ? sauvegarde.titre.substring(0, 57) + '...' : sauvegarde.titre;
|
737 |
-
|
738 |
-
const sauvegardeDiv = document.createElement('div');
|
739 |
-
// Utilise l'index original (avant tri) pour la suppression si n茅cessaire, ou l'index apr猫s tri si la suppression se base sur l'茅l茅ment affich茅.
|
740 |
-
// Ici on utilise l'index apr猫s tri car on supprime de la liste tri茅e affich茅e.
|
741 |
-
sauvegardeDiv.dataset.index = index; // Utiliser cet index pour la suppression
|
742 |
-
sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item hover:bg-blue-50 transition-all duration-200';
|
743 |
-
sauvegardeDiv.innerHTML = `
|
744 |
-
<div class="flex items-start cursor-pointer item-header"> <!-- Ajout curseur et classe pour le clic -->
|
745 |
-
<div class="bg-blue-100 rounded-lg p-2 mr-3 flex-shrink-0"> <!-- flex-shrink-0 -->
|
746 |
-
<i class="fas fa-file-alt text-blue-600"></i>
|
747 |
-
</div>
|
748 |
-
<div class="flex-grow overflow-hidden"> <!-- overflow-hidden -->
|
749 |
-
<h3 class="text-lg font-semibold text-gray-800 truncate" title="${sauvegarde.titre}">${displayTitle}</h3> <!-- truncate et title -->
|
750 |
-
<p class="text-sm text-gray-600 mb-2 flex items-center">
|
751 |
-
<i class="fas fa-clock text-xs mr-1 text-gray-400"></i>
|
752 |
-
${formattedDate}
|
753 |
-
</p>
|
754 |
-
</div>
|
755 |
-
<div class="flex space-x-2 ml-2 flex-shrink-0"> <!-- ml-2 et flex-shrink-0 -->
|
756 |
-
<button class="text-gray-400 hover:text-blue-600 focus:outline-none copy-btn p-1" title="Copier">
|
757 |
-
<i class="fas fa-copy"></i>
|
758 |
-
</button>
|
759 |
-
<button class="text-gray-400 hover:text-red-600 focus:outline-none delete-btn p-1" title="Supprimer">
|
760 |
-
<i class="fas fa-trash-alt"></i>
|
761 |
-
</button>
|
762 |
-
</div>
|
763 |
-
</div>
|
764 |
-
<div class="backup-content mt-4 text-sm text-gray-700 prose max-w-none border-t border-gray-100 pt-3">
|
765 |
-
<!-- Contenu charg茅 dynamiquement -->
|
766 |
-
</div>
|
767 |
-
`;
|
768 |
-
backupsList.appendChild(sauvegardeDiv);
|
769 |
-
|
770 |
-
// --- Event Listeners ---
|
771 |
-
const backupContentDiv = sauvegardeDiv.querySelector('.backup-content');
|
772 |
-
const headerDiv = sauvegardeDiv.querySelector('.item-header');
|
773 |
-
|
774 |
-
// Gestion de l'expansion/contraction du contenu au clic sur l'en-t锚te
|
775 |
-
headerDiv.addEventListener('click', (e) => {
|
776 |
-
// Basculer l'affichage du contenu actuel
|
777 |
-
const isExpanded = backupContentDiv.classList.contains('backup-content-expanded');
|
778 |
-
|
779 |
-
// Fermer tous les autres 茅l茅ments ouverts
|
780 |
-
document.querySelectorAll('.backup-content-expanded').forEach(content => {
|
781 |
-
if (content !== backupContentDiv) {
|
782 |
-
content.classList.remove('backup-content-expanded');
|
783 |
-
content.innerHTML = ''; // Vider le contenu des autres
|
784 |
-
}
|
785 |
-
});
|
786 |
-
|
787 |
-
if (!isExpanded) {
|
788 |
-
// Charger et afficher le contenu
|
789 |
-
backupContentDiv.innerHTML = marked.parse(sauvegarde.contenu);
|
790 |
-
backupContentDiv.classList.add('backup-content-expanded');
|
791 |
-
// Lancer MathJax seulement apr猫s que le contenu soit visible
|
792 |
-
setTimeout(() => {
|
793 |
-
MathJax.typesetPromise([backupContentDiv]).catch(function (err) {
|
794 |
-
console.error('MathJax typesetting error:', err);
|
795 |
-
});
|
796 |
-
}, 50); // petit d茅lai pour s'assurer que l'茅l茅ment est rendu
|
797 |
-
} else {
|
798 |
-
// Cacher le contenu
|
799 |
-
backupContentDiv.classList.remove('backup-content-expanded');
|
800 |
-
backupContentDiv.innerHTML = ''; // Vider le contenu quand on ferme
|
801 |
}
|
802 |
-
});
|
803 |
-
|
804 |
-
// Bouton de copie
|
805 |
-
const copyButton = sauvegardeDiv.querySelector('.copy-btn');
|
806 |
-
copyButton.addEventListener('click', (e) => {
|
807 |
-
e.stopPropagation(); // Emp锚che le clic de d茅clencher l'expansion
|
808 |
-
navigator.clipboard.writeText(sauvegarde.contenu).then(() => {
|
809 |
-
const icon = copyButton.querySelector('i');
|
810 |
-
icon.className = 'fas fa-check text-green-500'; // Ic么ne de succ猫s
|
811 |
-
setTimeout(() => {
|
812 |
-
icon.className = 'fas fa-copy'; // Retour 脿 l'ic么ne normale
|
813 |
-
}, 2000);
|
814 |
-
}).catch(err => {
|
815 |
-
console.error('Erreur de copie:', err);
|
816 |
-
displayNotification("Erreur lors de la copie", "error");
|
817 |
-
});
|
818 |
-
});
|
819 |
|
820 |
-
|
821 |
-
|
822 |
-
|
823 |
-
|
824 |
-
|
825 |
-
|
826 |
-
|
827 |
-
|
828 |
-
|
829 |
-
|
830 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
831 |
|
832 |
-
|
833 |
-
|
834 |
-
const originalIndex = originalSauvegardes.findIndex(s => s.date === itemToDelete.date && s.titre === itemToDelete.titre);
|
835 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
836 |
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
841 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
842 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
843 |
|
844 |
-
|
845 |
-
|
846 |
-
|
847 |
-
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
852 |
</div>
|
853 |
-
|
854 |
-
|
855 |
-
<p class="text-gray-600 mb-6 text-sm">脢tes-vous s没r de vouloir supprimer cette sauvegarde ?</p>
|
856 |
-
<div class="flex justify-end space-x-3">
|
857 |
-
<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>
|
858 |
-
<button class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 confirm-btn text-sm">Supprimer</button>
|
859 |
-
</div>
|
860 |
-
</div>
|
861 |
-
`;
|
862 |
-
document.body.appendChild(confirmationModal);
|
863 |
|
864 |
-
|
865 |
-
|
866 |
-
|
867 |
-
|
868 |
-
|
869 |
-
|
870 |
-
|
|
|
|
|
871 |
});
|
|
|
872 |
|
873 |
-
|
874 |
-
|
875 |
-
|
876 |
-
|
877 |
-
|
878 |
-
|
879 |
-
|
880 |
-
|
881 |
-
}
|
882 |
-
|
883 |
-
function closeModal(modalElement) {
|
884 |
-
modalElement.style.opacity = '0';
|
885 |
-
setTimeout(() => {
|
886 |
-
if (document.body.contains(modalElement)) {
|
887 |
-
document.body.removeChild(modalElement);
|
888 |
-
}
|
889 |
-
}, 300);
|
890 |
-
}
|
891 |
|
892 |
|
893 |
async function submitFrancaisForm() {
|
894 |
const form = document.getElementById('francais-form');
|
895 |
const output = document.getElementById('francais-output');
|
896 |
-
const sujetTextarea = document.getElementById('sujet-francais');
|
|
|
897 |
|
898 |
form.addEventListener('submit', async (e) => {
|
899 |
e.preventDefault();
|
900 |
|
901 |
-
// MODIFICATION: Validation c么t茅 client
|
902 |
const sujetValue = sujetTextarea.value.trim();
|
903 |
if (!sujetValue) {
|
904 |
output.innerHTML = `
|
@@ -909,53 +974,56 @@ function closeModal(modalElement) {
|
|
909 |
<p>Veuillez entrer un sujet avant de g茅n茅rer.</p>
|
910 |
</div>
|
911 |
</div>`;
|
912 |
-
sujetTextarea.focus();
|
913 |
-
sujetTextarea.classList.add('border-red-500');
|
914 |
-
setTimeout(() => sujetTextarea.classList.remove('border-red-500'), 3000);
|
915 |
-
return;
|
916 |
}
|
917 |
|
918 |
-
|
919 |
output.innerHTML = `
|
920 |
<div class="flex flex-col items-center justify-center py-8">
|
921 |
<div class="loader mb-4">
|
922 |
-
<div></div>
|
923 |
-
<div></div>
|
924 |
-
<div></div>
|
925 |
-
<div></div>
|
926 |
</div>
|
927 |
<p class="text-sm font-medium text-gray-600">G茅n茅ration en cours...</p>
|
|
|
928 |
</div>`;
|
929 |
|
930 |
const formData = new FormData(form);
|
|
|
|
|
|
|
931 |
try {
|
932 |
const response = await fetch('/api/francais', {
|
933 |
method: 'POST',
|
934 |
body: formData
|
935 |
});
|
936 |
|
937 |
-
const result = await response.json();
|
938 |
|
939 |
if (!response.ok) {
|
940 |
-
// Afficher l'erreur renvoy茅e par le serveur si disponible
|
941 |
throw new Error(result.output || `Erreur HTTP ${response.status}`);
|
942 |
}
|
943 |
|
944 |
-
|
945 |
output.style.opacity = '0';
|
946 |
-
|
947 |
-
|
|
|
|
|
|
|
|
|
948 |
output.style.opacity = '1';
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
|
955 |
-
|
956 |
-
|
957 |
-
|
958 |
-
}
|
|
|
959 |
} catch (error) {
|
960 |
console.error("Erreur lors de la soumission:", error);
|
961 |
output.innerHTML = `
|
@@ -973,13 +1041,12 @@ function closeModal(modalElement) {
|
|
973 |
async function submitEtudeTexteForm() {
|
974 |
const form = document.getElementById('etude-texte-form');
|
975 |
const output = document.getElementById('etude-texte-output');
|
976 |
-
const fileInput = document.getElementById('image-upload');
|
977 |
|
978 |
form.addEventListener('submit', async (e) => {
|
979 |
e.preventDefault();
|
980 |
|
981 |
-
|
982 |
-
if (fileInput.files.length === 0) {
|
983 |
output.innerHTML = `
|
984 |
<div class="alert-message alert-warning">
|
985 |
<i class="fas fa-exclamation-triangle"></i>
|
@@ -988,68 +1055,52 @@ function closeModal(modalElement) {
|
|
988 |
<p>Veuillez ajouter au moins une image pour l'analyse.</p>
|
989 |
</div>
|
990 |
</div>`;
|
991 |
-
return;
|
992 |
}
|
993 |
|
994 |
output.innerHTML = `
|
995 |
<div class="flex flex-col items-center justify-center py-8">
|
996 |
<div class="loader mb-4">
|
997 |
-
<div></div>
|
998 |
-
<div></div>
|
999 |
-
<div></div>
|
1000 |
-
<div></div>
|
1001 |
</div>
|
1002 |
<p class="text-sm font-medium text-gray-600">Analyse en cours...</p>
|
1003 |
<p class="text-xs text-gray-400 mt-2">Cela peut prendre un moment</p>
|
1004 |
</div>`;
|
1005 |
|
1006 |
const formData = new FormData();
|
1007 |
-
// Ajouter uniquement les fichiers valides de notre Map `uploadedFiles`
|
1008 |
-
if (uploadedFiles.size === 0) {
|
1009 |
-
// Double v茅rification (normalement impossible si fileInput.files > 0)
|
1010 |
-
output.innerHTML = `
|
1011 |
-
<div class="alert-message alert-warning">
|
1012 |
-
<i class="fas fa-exclamation-triangle"></i>
|
1013 |
-
<div>
|
1014 |
-
<p>Aucune image valide</p>
|
1015 |
-
<p>Veuillez v茅rifier les images s茅lectionn茅es.</p>
|
1016 |
-
</div>
|
1017 |
-
</div>`;
|
1018 |
-
return;
|
1019 |
-
}
|
1020 |
uploadedFiles.forEach((file) => {
|
1021 |
formData.append('images', file, file.name);
|
1022 |
});
|
1023 |
|
1024 |
-
|
1025 |
try {
|
1026 |
const response = await fetch('/api/etude-texte', {
|
1027 |
method: 'POST',
|
1028 |
-
body: formData
|
1029 |
});
|
1030 |
|
1031 |
-
const result = await response.json();
|
1032 |
|
1033 |
if (!response.ok) {
|
1034 |
-
// Afficher l'erreur renvoy茅e par le serveur si disponible
|
1035 |
throw new Error(result.output || `Erreur HTTP ${response.status}`);
|
1036 |
}
|
1037 |
|
1038 |
-
|
1039 |
output.style.opacity = '0';
|
1040 |
-
|
1041 |
-
|
|
|
|
|
|
|
|
|
1042 |
output.style.opacity = '1';
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
}, 100); // L茅ger d茅lai pour l'animation d'opacit茅
|
1053 |
} catch (error) {
|
1054 |
console.error("Erreur lors de l'analyse:", error);
|
1055 |
output.innerHTML = `
|
@@ -1067,29 +1118,27 @@ function closeModal(modalElement) {
|
|
1067 |
// Animation des cartes au d茅filement
|
1068 |
function animateOnScroll() {
|
1069 |
const cards = document.querySelectorAll('.card-hover');
|
1070 |
-
// Si IntersectionObserver n'est pas support茅, on affiche directement
|
1071 |
if (!('IntersectionObserver' in window)) {
|
1072 |
cards.forEach(card => card.style.opacity = '1');
|
1073 |
return;
|
1074 |
}
|
1075 |
-
|
1076 |
const observer = new IntersectionObserver((entries) => {
|
1077 |
-
entries.forEach((entry) => {
|
1078 |
if (entry.isIntersecting) {
|
1079 |
-
|
1080 |
-
|
1081 |
-
|
|
|
|
|
|
|
1082 |
}
|
1083 |
});
|
1084 |
-
}, {
|
1085 |
-
threshold: 0.1 // D茅clenche quand 10% est visible
|
1086 |
-
});
|
1087 |
|
1088 |
cards.forEach(card => {
|
1089 |
card.style.opacity = '0';
|
1090 |
card.style.transform = 'translateY(30px)';
|
1091 |
-
//
|
1092 |
-
// card.style.transition = 'opacity 0.6s ease-out, transform 0.6s ease-out';
|
1093 |
observer.observe(card);
|
1094 |
});
|
1095 |
}
|
@@ -1098,11 +1147,9 @@ function closeModal(modalElement) {
|
|
1098 |
function enhanceTextareaFocus() {
|
1099 |
const textarea = document.getElementById('sujet-francais');
|
1100 |
const counter = document.getElementById('character-count');
|
1101 |
-
|
1102 |
textarea.addEventListener('input', () => {
|
1103 |
const length = textarea.value.length;
|
1104 |
counter.textContent = `${length} caract猫re${length !== 1 ? 's' : ''}`;
|
1105 |
-
// Enl猫ve la bordure d'erreur si l'utilisateur commence 脿 taper
|
1106 |
textarea.classList.remove('border-red-500');
|
1107 |
});
|
1108 |
}
|
@@ -1112,43 +1159,33 @@ function closeModal(modalElement) {
|
|
1112 |
const radioGroups = document.querySelectorAll('input[type="radio"] + span');
|
1113 |
radioGroups.forEach(label => {
|
1114 |
const input = label.previousElementSibling;
|
1115 |
-
if (input.disabled) return;
|
1116 |
|
1117 |
label.addEventListener('mouseenter', () => {
|
1118 |
if (!input.checked) {
|
1119 |
-
|
|
|
1120 |
}
|
1121 |
});
|
1122 |
-
|
1123 |
label.addEventListener('mouseleave', () => {
|
1124 |
-
// Retirer les styles de survol seulement si non coch茅
|
1125 |
if (!input.checked) {
|
1126 |
-
|
1127 |
}
|
1128 |
});
|
1129 |
-
|
1130 |
-
// Assurer que les styles sont retir茅s si un autre bouton est coch茅
|
1131 |
input.addEventListener('change', () => {
|
1132 |
-
// Retrouver tous les labels du m锚me groupe
|
1133 |
const groupName = input.name;
|
1134 |
document.querySelectorAll(`input[name="${groupName}"] + span`).forEach(otherLabel => {
|
1135 |
if (otherLabel !== label) {
|
1136 |
-
otherLabel.classList.remove('border-blue-400', 'shadow-md', '-translate-y-0.5');
|
1137 |
-
// R茅tablir la bordure par d茅faut si besoin
|
1138 |
-
if (!otherLabel.previousElementSibling.checked) {
|
1139 |
-
otherLabel.classList.remove('border-primary'); // Si jamais elle 茅tait l脿
|
1140 |
-
}
|
1141 |
} else {
|
1142 |
-
|
1143 |
-
// et retirer les styles de hover qui pourraient rester
|
1144 |
-
label.classList.remove('shadow-md', '-translate-y-0.5');
|
1145 |
}
|
1146 |
});
|
1147 |
});
|
1148 |
});
|
1149 |
}
|
1150 |
|
1151 |
-
// Initialisation
|
1152 |
document.addEventListener('DOMContentLoaded', () => {
|
1153 |
initializeFileUpload();
|
1154 |
submitFrancaisForm();
|
@@ -1156,76 +1193,38 @@ function closeModal(modalElement) {
|
|
1156 |
animateOnScroll();
|
1157 |
enhanceTextareaFocus();
|
1158 |
enhanceRadioButtons();
|
1159 |
-
afficherSauvegardes();
|
1160 |
|
1161 |
-
//
|
1162 |
window.MathJax = {
|
1163 |
-
tex: {
|
1164 |
-
|
1165 |
-
|
1166 |
-
},
|
1167 |
-
svg: {
|
1168 |
-
fontCache: 'global'
|
1169 |
-
},
|
1170 |
-
options: {
|
1171 |
-
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'],
|
1172 |
-
ignoreHtmlClass: 'tex2jax_ignore',
|
1173 |
-
processHtmlClass: 'tex2jax_process'
|
1174 |
-
}
|
1175 |
};
|
1176 |
|
1177 |
-
|
1178 |
-
// Message de bienvenue (optionnel)
|
1179 |
const showWelcome = sessionStorage.getItem('welcomeShown') !== 'true';
|
1180 |
if (showWelcome) {
|
1181 |
const welcomeMessageContainer = document.createElement('div');
|
1182 |
welcomeMessageContainer.innerHTML = `
|
1183 |
<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]">
|
1184 |
<div class="flex items-start space-x-4">
|
1185 |
-
<div class="bg-gradient-to-r from-blue-500 to-blue-700 rounded-full p-3 mt-1 flex-shrink-0">
|
1186 |
-
|
1187 |
-
</div>
|
1188 |
-
<div>
|
1189 |
-
<p class="text-sm font-semibold text-gray-800">Bienvenue sur Mariam AI !</p>
|
1190 |
-
<p class="text-xs text-gray-500 mt-1">Votre assistant est pr锚t. Posez un sujet ou analysez un texte.</p>
|
1191 |
-
</div>
|
1192 |
</div>
|
1193 |
-
<button class="absolute top-2 right-2 text-gray-400 hover:text-gray-600 focus:outline-none close-welcome p-1">
|
1194 |
-
|
1195 |
-
</button>
|
1196 |
-
</div>
|
1197 |
-
`;
|
1198 |
document.body.appendChild(welcomeMessageContainer);
|
1199 |
const welcomeMessage = welcomeMessageContainer.firstElementChild;
|
1200 |
-
|
1201 |
-
|
1202 |
-
|
1203 |
-
|
1204 |
-
|
1205 |
-
const closeWelcomeButton = welcomeMessage.querySelector('.close-welcome');
|
1206 |
-
closeWelcomeButton.addEventListener('click', () => {
|
1207 |
-
closeWelcomeMessage(welcomeMessage);
|
1208 |
-
});
|
1209 |
-
|
1210 |
-
// Fermer automatiquement apr猫s un certain temps
|
1211 |
-
setTimeout(() => {
|
1212 |
-
if (document.body.contains(welcomeMessage)) {
|
1213 |
-
closeWelcomeMessage(welcomeMessage);
|
1214 |
-
}
|
1215 |
-
}, 7000); // 7 secondes
|
1216 |
-
|
1217 |
-
sessionStorage.setItem('welcomeShown', 'true'); // Marquer comme vu pour cette session
|
1218 |
}
|
1219 |
-
|
1220 |
function closeWelcomeMessage(element) {
|
1221 |
element.classList.add('opacity-0', 'translate-y-4');
|
1222 |
-
setTimeout(() => {
|
1223 |
-
if (element.parentElement) {
|
1224 |
-
element.parentElement.remove();
|
1225 |
-
}
|
1226 |
-
}, 500); // Attend la fin de la transition
|
1227 |
}
|
1228 |
-
|
1229 |
});
|
1230 |
</script>
|
1231 |
</body>
|
|
|
83 |
/* Styles pour les messages d'erreur/avertissement */
|
84 |
.alert-message {
|
85 |
display: flex;
|
86 |
+
align-items: flex-start; /* Align items start for better text wrapping */
|
87 |
+
/* space-x-3 equivalent using margin on icon */
|
88 |
padding: 1rem; /* p-4 */
|
89 |
border-radius: 0.5rem; /* rounded-lg */
|
90 |
+
border-width: 1px;
|
91 |
+
border-style: solid;
|
92 |
}
|
93 |
.alert-message i {
|
94 |
margin-right: 0.75rem; /* space-x-3 equivalent */
|
95 |
font-size: 1.25rem; /* text-xl */
|
96 |
+
margin-top: 0.125rem; /* slight top margin for better alignment */
|
97 |
}
|
98 |
.alert-message div p:first-child {
|
99 |
font-weight: 500; /* font-medium */
|
|
|
101 |
.alert-message div p:last-child {
|
102 |
font-size: 0.875rem; /* text-sm */
|
103 |
margin-top: 0.25rem; /* mt-1 */
|
104 |
+
line-height: 1.4; /* Improved line height for wrap */
|
105 |
}
|
106 |
|
107 |
.alert-warning {
|
108 |
+
color: #92400e; /* text-amber-700 */
|
109 |
background-color: #fffbeb; /* bg-amber-50 */
|
110 |
+
border-color: #fde68a /* border-amber-200 */
|
111 |
}
|
112 |
.alert-warning i {
|
113 |
color: var(--warning); /* text-amber-500 */
|
114 |
}
|
115 |
.alert-warning div p:last-child {
|
116 |
+
color: #b45309; /* text-amber-600 */
|
117 |
}
|
118 |
|
119 |
.alert-danger {
|
120 |
+
color: #991b1b; /* text-red-700 */
|
121 |
background-color: #fef2f2; /* bg-red-50 */
|
122 |
+
border-color: #fecaca; /* border-red-200 */
|
123 |
}
|
124 |
.alert-danger i {
|
125 |
color: var(--danger); /* text-red-500 */
|
126 |
}
|
127 |
.alert-danger div p:last-child {
|
128 |
+
color: #b91c1c; /* text-red-600 */
|
129 |
}
|
130 |
|
131 |
|
|
|
163 |
margin-top: 10px;
|
164 |
max-height: 0;
|
165 |
overflow: hidden;
|
166 |
+
transition: max-height 0.3s ease-out, opacity 0.3s ease-out, margin-top 0.3s ease-out, padding-top 0.3s ease-out; /* Added padding-top */
|
167 |
opacity: 0;
|
168 |
+
padding-top: 0; /* Start with no padding */
|
169 |
}
|
170 |
|
171 |
.backup-content-expanded {
|
172 |
display: block;
|
173 |
+
max-height: 3000px; /* Augment茅 pour les longs contenus */
|
174 |
opacity: 1;
|
175 |
+
margin-top: 1rem; /* mt-4 */
|
176 |
+
padding-top: 0.75rem; /* pt-3 */
|
177 |
}
|
178 |
|
179 |
+
|
180 |
/* Styles for multiple image upload */
|
181 |
.image-preview {
|
182 |
display: flex;
|
|
|
352 |
grid-template-columns: repeat(2, minmax(0, 1fr));
|
353 |
}
|
354 |
}
|
355 |
+
|
356 |
+
/* Style pour la checkbox DeepThink */
|
357 |
+
.deepthink-label {
|
358 |
+
display: flex;
|
359 |
+
align-items: center;
|
360 |
+
cursor: pointer;
|
361 |
+
font-size: 0.875rem; /* text-sm */
|
362 |
+
color: var(--secondary);
|
363 |
+
}
|
364 |
+
.deepthink-label input[type="checkbox"] {
|
365 |
+
appearance: none;
|
366 |
+
width: 1.25em;
|
367 |
+
height: 1.25em;
|
368 |
+
border: 2px solid #cbd5e0; /* border-gray-300 */
|
369 |
+
border-radius: 0.375rem; /* rounded-md */
|
370 |
+
margin-right: 0.5em;
|
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);
|
377 |
+
border-color: var(--primary);
|
378 |
+
}
|
379 |
+
.deepthink-label input[type="checkbox"]:checked::after {
|
380 |
+
content: '\f00c'; /* FontAwesome check icon */
|
381 |
+
font-family: 'Font Awesome 6 Free';
|
382 |
+
font-weight: 900;
|
383 |
+
color: white;
|
384 |
+
font-size: 0.8em;
|
385 |
+
position: absolute;
|
386 |
+
top: 50%;
|
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 {
|
399 |
+
visibility: hidden;
|
400 |
+
width: 220px;
|
401 |
+
background-color: #2d3748; /* gray-800 */
|
402 |
+
color: #fff;
|
403 |
+
text-align: center;
|
404 |
+
border-radius: 6px;
|
405 |
+
padding: 6px 10px;
|
406 |
+
position: absolute;
|
407 |
+
z-index: 10;
|
408 |
+
bottom: 135%;
|
409 |
+
left: 50%;
|
410 |
+
margin-left: -110px;
|
411 |
+
opacity: 0;
|
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: "";
|
418 |
+
position: absolute;
|
419 |
+
top: 100%;
|
420 |
+
left: 50%;
|
421 |
+
margin-left: -5px;
|
422 |
+
border-width: 5px;
|
423 |
+
border-style: solid;
|
424 |
+
border-color: #2d3748 transparent transparent transparent;
|
425 |
+
}
|
426 |
+
.deepthink-tooltip:hover .tooltip-text {
|
427 |
+
visibility: visible;
|
428 |
+
opacity: 1;
|
429 |
+
}
|
430 |
+
|
431 |
</style>
|
432 |
</head>
|
433 |
|
|
|
465 |
<div class="focus-ring">
|
466 |
<textarea id="sujet-francais" name="sujet" rows="4"
|
467 |
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:outline-none transition-all duration-200 resize-none"
|
468 |
+
placeholder="Entrez votre sujet ici..." required></textarea>
|
469 |
</div>
|
470 |
<div class="text-xs text-gray-400 text-right" id="character-count">0 caract猫res</div>
|
471 |
</div>
|
|
|
500 |
</span>
|
501 |
</label>
|
502 |
<label class="relative">
|
|
|
503 |
<input type="radio" name="choix" value="dissertation"
|
504 |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer">
|
505 |
<span class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400">
|
506 |
Dissertation
|
|
|
507 |
</span>
|
508 |
</label>
|
509 |
</div>
|
|
|
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">
|
|
|
555 |
</div>
|
556 |
</button>
|
557 |
</form>
|
558 |
+
<div id="francais-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[100px]">
|
559 |
<!-- Le contenu g茅n茅r茅 sera ins茅r茅 ici -->
|
560 |
</div>
|
561 |
</div>
|
|
|
577 |
</label>
|
578 |
<div class="border-3 border-dashed border-gray-300 rounded-xl p-8 text-center cursor-pointer hover:border-blue-400 transition-all duration-200 group"
|
579 |
id="drop-zone">
|
580 |
+
<input type="file" id="image-upload" name="images" accept="image/*" class="hidden" multiple>
|
581 |
<div class="space-y-4">
|
582 |
<div class="bg-blue-50 rounded-full w-16 h-16 flex items-center justify-center mx-auto transform transition-transform group-hover:scale-110 group-hover:rotate-6">
|
583 |
<i class="fas fa-cloud-upload-alt text-3xl text-blue-500"></i>
|
|
|
587 |
<p class="text-xs text-gray-400">PNG, JPG, WEBP jusqu'脿 10MB</p>
|
588 |
</div>
|
589 |
</div>
|
590 |
+
<div id="image-preview" class="image-preview"></div>
|
591 |
</div>
|
592 |
|
593 |
<button type="submit"
|
|
|
598 |
</div>
|
599 |
</button>
|
600 |
</form>
|
601 |
+
<div id="etude-texte-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none shadow-inner min-h-[100px]">
|
602 |
<!-- Le contenu analys茅 sera ins茅r茅 ici -->
|
603 |
</div>
|
604 |
</div>
|
|
|
734 |
previewElement.style.opacity = '0';
|
735 |
previewElement.style.transform = 'scale(0.9)';
|
736 |
setTimeout(() => {
|
737 |
+
if (imagePreview.contains(previewElement)) {
|
738 |
+
imagePreview.removeChild(previewElement);
|
739 |
+
}
|
740 |
}, 300);
|
741 |
}
|
742 |
}
|
|
|
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;
|
748 |
if (sauvegardes.length >= MAX_SAUVEGARDES) {
|
749 |
+
sauvegardes.shift();
|
750 |
}
|
751 |
sauvegardes.push({
|
752 |
+
titre: titre || "Sauvegarde sans titre",
|
753 |
contenu,
|
754 |
date: dateSauvegarde
|
755 |
});
|
|
|
757 |
localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes));
|
758 |
} catch (e) {
|
759 |
console.error("Erreur lors de la sauvegarde dans localStorage (peut-锚tre plein):", e);
|
|
|
760 |
displayNotification("Erreur de sauvegarde: Espace de stockage local plein.", "error");
|
761 |
+
return;
|
762 |
}
|
|
|
|
|
763 |
displayNotification("Sauvegard茅 avec succ猫s", "success");
|
764 |
+
afficherSauvegardes();
|
765 |
}
|
766 |
|
767 |
function displayNotification(message, type = 'success') {
|
768 |
const notification = document.createElement('div');
|
769 |
+
const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-700' : type === 'error' ? 'bg-red-50 border-red-200 text-red-700' : 'bg-yellow-50 border-yellow-200 text-yellow-700';
|
770 |
+
const iconClass = type === 'success' ? 'fa-check-circle text-green-500' : type === 'error' ? 'fa-exclamation-circle text-red-500' : 'fa-exclamation-triangle text-yellow-500';
|
771 |
|
772 |
+
|
773 |
+
notification.className = `fixed bottom-6 right-6 border ${bgColor} px-4 py-3 rounded-lg shadow-lg z-[100] flex items-center scale-in max-w-sm`; // z-index 茅lev茅, max-width
|
774 |
notification.innerHTML = `
|
775 |
+
<i class="fas ${iconClass} mr-3 text-lg flex-shrink-0"></i>
|
776 |
<span class="text-sm font-medium">${message}</span>
|
777 |
`;
|
778 |
document.body.appendChild(notification);
|
|
|
785 |
document.body.removeChild(notification);
|
786 |
}
|
787 |
}, 300);
|
788 |
+
}, type === 'error' ? 6000 : 3000); // Longer display for errors
|
789 |
}
|
790 |
|
791 |
|
|
|
797 |
}
|
798 |
|
799 |
|
800 |
+
function afficherSauvegardes() {
|
801 |
+
const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
|
802 |
+
sauvegardes.sort((a, b) => new Date(b.date) - new Date(a.date));
|
803 |
+
const backupsList = document.getElementById('backups-list');
|
804 |
+
backupsList.innerHTML = '';
|
805 |
+
|
806 |
+
if (sauvegardes.length === 0) {
|
807 |
+
backupsList.innerHTML = `
|
808 |
+
<div class="bg-blue-50 rounded-lg p-8 text-center">
|
809 |
+
<div class="bg-white rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4 shadow-sm">
|
810 |
+
<i class="fas fa-folder-open text-blue-300 text-2xl"></i>
|
811 |
+
</div>
|
812 |
+
<p class="text-gray-600">Aucune sauvegarde pour le moment.</p>
|
813 |
+
<p class="text-sm text-gray-500 mt-2">Vos r茅ponses g茅n茅r茅es appara卯tront ici.</p>
|
814 |
+
</div>
|
815 |
+
`;
|
816 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
817 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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');
|
831 |
+
sauvegardeDiv.dataset.index = index;
|
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');
|
861 |
+
const headerDiv = sauvegardeDiv.querySelector('.item-header');
|
|
|
862 |
|
863 |
+
headerDiv.addEventListener('click', (e) => {
|
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 |
|
885 |
+
const copyButton = sauvegardeDiv.querySelector('.copy-btn');
|
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');
|
901 |
+
deleteButton.addEventListener('click', (e) => {
|
902 |
+
e.stopPropagation();
|
903 |
+
const itemIndex = parseInt(sauvegardeDiv.dataset.index, 10);
|
904 |
+
const storedSauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]');
|
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 |
+
}
|
946 |
|
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 = `
|
|
|
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 = `
|
|
|
1041 |
async function submitEtudeTexteForm() {
|
1042 |
const form = document.getElementById('etude-texte-form');
|
1043 |
const output = document.getElementById('etude-texte-output');
|
1044 |
+
const fileInput = document.getElementById('image-upload');
|
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>
|
|
|
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 = `
|
|
|
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 |
}
|
|
|
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 |
}
|
|
|
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 |
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);
|
1220 |
+
welcomeMessage.querySelector('.close-welcome').addEventListener('click', () => closeWelcomeMessage(welcomeMessage));
|
1221 |
+
setTimeout(() => { if (document.body.contains(welcomeMessage)) closeWelcomeMessage(welcomeMessage); }, 7000);
|
1222 |
+
sessionStorage.setItem('welcomeShown', 'true');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1223 |
}
|
|
|
1224 |
function closeWelcomeMessage(element) {
|
1225 |
element.classList.add('opacity-0', 'translate-y-4');
|
1226 |
+
setTimeout(() => { if (element.parentElement) element.parentElement.remove(); }, 500);
|
|
|
|
|
|
|
|
|
1227 |
}
|
|
|
1228 |
});
|
1229 |
</script>
|
1230 |
</body>
|