Docfile commited on
Commit
a424765
·
verified ·
1 Parent(s): 90e991c

Upload philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +365 -108
templates/philosophie.html CHANGED
@@ -265,13 +265,13 @@
265
 
266
  <!-- Course Selection -->
267
  <div class="space-y-3">
268
- <label class="block text-sm font-medium text-gray-700">Sélection du cours</label>
269
  <select id="course-select" class="w-full">
270
- <option value="">Choisir un cours...</option>
271
  </select>
272
- <div class="course-meta hidden">
273
  <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-4 border border-gray-100">
274
- <div class="flex justify-between items-center">
275
  <span id="course-author" class="flex items-center space-x-2">
276
  <svg class="h-5 w-5 text-violet-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
277
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
@@ -345,11 +345,22 @@
345
  </div>
346
  </div>
347
 
 
 
 
 
 
 
 
 
 
 
348
  <script>
349
  $(document).ready(function() {
350
  // Initialisation de Select2
351
  $('#course-select').select2({
352
- placeholder: 'Choisir un cours...',
 
353
  templateResult: function (course) {
354
  if (!course.id) {
355
  return course.text;
@@ -361,6 +372,7 @@
361
  `);
362
  },
363
  templateSelection: function (course) {
 
364
  return course.text; // Seul le titre est affiché dans la sélection
365
  },
366
 
@@ -401,6 +413,7 @@
401
  }
402
  addButtonAnimation('submit-btn');
403
  addButtonAnimation('copy-btn');
 
404
 
405
  // Gestion du changement de type avec affichage personnalisé
406
  $('#type-select').change(function() {
@@ -442,6 +455,8 @@
442
  $(newOption).data('author', course.author); // Ajouter l'auteur comme data
443
  select.append(newOption);
444
  });
 
 
445
  })
446
 
447
  .fail(function() {
@@ -455,30 +470,35 @@
455
  // Gestion du changement de cours avec animations
456
  $('#course-select').on('change', function() {
457
  const courseId = $(this).val();
 
458
 
459
  if (courseId) {
460
  $.ajax({
461
  url: `/api/philosophy/courses/${courseId}`,
462
  method: 'GET',
463
  beforeSend: function() {
464
- $('.course-meta').addClass('animate-pulse');
 
 
 
465
  },
466
  success: function(course) {
467
- $('.course-meta').removeClass('hidden animate-pulse')
468
- .addClass('animate-fadeIn');
469
  $('#course-author span').text(`Pr. ${course.author}`);
470
  $('#course-date span').text(new Date(course.updated_at).toLocaleDateString('fr-FR', {
471
  day: 'numeric',
472
  month: 'long',
473
  year: 'numeric' }));
 
 
474
 
475
- // Afficher une notification de succès
476
- Toast.fire({
477
  icon: 'success',
478
- title: 'Cours chargé avec succès'
479
- });
480
  },
481
  error: function() {
 
482
  Toast.fire({
483
  icon: 'error',
484
  title: 'Erreur',
@@ -487,11 +507,18 @@
487
  }
488
  });
489
  } else {
490
- $('.course-meta').addClass('animate-fadeOut').on('animationend', function() {
491
- $(this).addClass('hidden').removeClass('animate-fadeOut');
492
- });
 
 
 
 
 
 
 
 
493
  }
494
-
495
  });
496
 
497
  // Gestion de la soumission avec conversion en Markdown et sauvegarde
@@ -499,7 +526,13 @@
499
  const question = $('#question').val().trim();
500
 
501
  if (!question) {
502
- // Gestion de l'erreur si la question est vide (inchangée)
 
 
 
 
 
 
503
  return;
504
  }
505
 
@@ -534,7 +567,7 @@
534
  };
535
 
536
  $.ajax({
537
- url: '/submit_philo',
538
  method: 'POST',
539
  contentType: 'application/json',
540
  data: JSON.stringify(data),
@@ -556,11 +589,16 @@
556
  timer: 2000
557
  });
558
  },
559
- error: function() {
 
 
 
 
 
560
  Swal.fire({
561
  icon: 'error',
562
  title: 'Erreur de génération',
563
- text: 'Une erreur est survenue lors de la génération de votre dissertation.',
564
  customClass: {
565
  popup: 'rounded-2xl',
566
  confirmButton: 'bg-violet-600 hover:bg-violet-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200'
@@ -570,9 +608,153 @@
570
  });
571
  });
572
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
573
 
574
  function saveDissertation(title, content) {
575
  let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
 
 
 
 
 
576
  savedDissertations.push({ title, content, timestamp: Date.now() });
577
  localStorage.setItem('dissertations', JSON.stringify(savedDissertations));
578
  updateSavedDissertationsList();
@@ -580,15 +762,30 @@
580
  }
581
 
582
  function deleteDissertation(index) {
583
- let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
584
- savedDissertations.splice(index, 1);
585
- localStorage.setItem('dissertations', JSON.stringify(savedDissertations));
586
- updateSavedDissertationsList();
587
- Toast.fire({
588
- icon: 'success',
589
- title: 'Dissertation supprimée avec succès'
590
- });
591
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  }
593
 
594
  // Fonction pour afficher les dissertations sauvegardées (avec sections repliables et suppression)
@@ -597,40 +794,69 @@
597
  dissertationsList.empty(); // Vider la liste actuelle
598
 
599
  let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
 
 
 
600
 
601
  if (savedDissertations.length === 0) {
602
- dissertationsList.append('<p class="text-gray-500">Aucune dissertation sauvegardée.</p>');
603
  return;
604
  }
605
 
606
 
607
-
608
  savedDissertations.forEach((diss, index) => {
609
- const date = moment(diss.timestamp).format('LLL');
610
-
611
- const collapsible = $(`<button class="collapsible rounded-xl border border-gray-100 flex justify-between w-full"><span>${diss.title}</span><span class="text-gray-500 text-sm">${date}</span></button>`);
612
- const deleteButton = $('<button class="text-red-500 hover:text-red-700 ml-2">Supprimer</button>');
613
- const content = $('<div class="content prose prose-violet max-w-none p-4"></div>').html(marked.parse(diss.content)); // Convertir en HTML
614
-
615
- collapsible.append(deleteButton); // Ajouter le bouton supprimer
616
- dissertationsList.append(collapsible, content);
617
-
618
- // Gestionnaire d'événement pour chaque section repliable
619
- collapsible.click(function(event) {
620
- // Empêcher la propagation de l'événement click sur le bouton "Supprimer"
621
- if (event.target === deleteButton[0]) {
622
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
 
625
- content.slideToggle("fast");
626
- collapsible.toggleClass("active");
 
 
627
  });
628
 
629
- deleteButton.click(function() {
630
- // Supprimer la dissertation correspondante
631
- deleteDissertation(index);
632
-
633
- });
634
  });
635
  }
636
 
@@ -647,27 +873,40 @@
647
  const temp = document.createElement('div');
648
  temp.innerHTML = responseDiv.innerHTML;
649
 
650
- // Fonction récursive pour extraire le texte en préservant les sauts de ligne
651
  function extractText(node) {
652
  let text = '';
653
  node.childNodes.forEach(child => {
654
- if (child.nodeType === 3) { // Nœud texte
655
- text += child.textContent;
656
- } else if (child.nodeType === 1) { // Élément
657
- // Ajouter des sauts de ligne pour les éléments de bloc
658
- if (window.getComputedStyle(child).display === 'block') {
659
- text += '\n';
 
 
660
  }
661
- text += extractText(child);
662
- if (window.getComputedStyle(child).display === 'block') {
 
 
 
 
 
 
 
 
 
 
663
  text += '\n';
664
  }
665
- }
666
  });
667
- return text;
 
668
  }
669
 
670
- textToCopy = extractText(temp).trim();
671
 
672
  // Animation et copie
673
  $(this).addClass('scale-95 bg-violet-100');
@@ -676,20 +915,21 @@
676
  navigator.clipboard.writeText(textToCopy)
677
  .then(() => {
678
  $(this).removeClass('scale-95 bg-violet-100')
679
- .addClass('bg-green-50 text-green-700');
680
 
681
  setTimeout(() => {
682
- $(this).removeClass('bg-green-50 text-green-700');
683
- }, 1000);
684
 
685
  Toast.fire({
686
  icon: 'success',
687
  title: 'Copié avec succès',
688
- text: 'Le contenu a été copié dans votre presse-papiers',
689
  timer: 2000
690
  });
691
  })
692
  .catch((err) => {
 
693
  // Fallback pour les appareils mobiles qui ne supportent pas l'API Clipboard
694
  try {
695
  // Créer un élément textarea temporaire
@@ -698,73 +938,90 @@
698
  textarea.style.position = 'fixed'; // Évite le défilement
699
  textarea.style.opacity = '0';
700
  document.body.appendChild(textarea);
701
-
702
  // Sélectionner et copier le texte
703
  textarea.select();
 
 
 
 
 
 
 
 
 
704
  document.execCommand('copy');
705
-
706
  // Nettoyer
707
  document.body.removeChild(textarea);
708
-
709
  // Feedback positif
710
  $(this).removeClass('scale-95 bg-violet-100')
711
- .addClass('bg-green-50 text-green-700');
712
 
713
  setTimeout(() => {
714
- $(this).removeClass('bg-green-50 text-green-700');
715
- }, 1000);
716
 
717
  Toast.fire({
718
  icon: 'success',
719
- title: 'Copié avec succès',
720
  timer: 2000
721
  });
722
  } catch (fallbackErr) {
 
723
  // Si même le fallback échoue
724
  $(this).removeClass('scale-95 bg-violet-100')
725
- .addClass('bg-red-50 text-red-700');
726
 
727
  setTimeout(() => {
728
- $(this).removeClass('bg-red-50 text-red-700');
729
- }, 1000);
730
 
731
  Toast.fire({
732
  icon: 'error',
733
  title: 'Erreur de copie',
734
- text: 'Impossible de copier le contenu',
735
  timer: 3000
736
  });
737
  }
738
  });
739
  });
740
 
741
- // Ajout des styles d'animation personnalisés
742
- const style = document.createElement('style');
743
- style.textContent = `
744
- @keyframes fadeIn {
745
- from { opacity: 0; transform: translateY(10px); }
746
- to { opacity: 1; transform: translateY(0); }
747
- }
748
- @keyframes slideUp {
749
- from { opacity: 0; transform: translateY(20px); }
750
- to { opacity: 1; transform: translateY(0); }
751
- }
752
- @keyframes shake {
753
- 0%, 100% { transform: translateX(0); }
754
- 25% { transform: translateX(-5px); }
755
- 75% { transform: translateX(5px); }
756
- }
757
- .animate-fadeIn {
758
- animation: fadeIn 0.5s ease-out forwards;
759
- }
760
- .animate-slideUp {
761
- animation: slideUp 0.5s ease-out forwards;
762
- }
763
- .animate-shake {
764
- animation: shake 0.5s ease-in-out;
765
- }
766
- `;
767
- document.head.appendChild(style);
 
 
 
 
 
 
 
768
 
769
  // Met à jour le label initial avec la valeur par défaut du select
770
  const initialSelectValue = $('#type-select').val();
@@ -776,8 +1033,8 @@
776
  initialLabelText = `Type ${initialSelectValue} - ${initialSelectedText}`;
777
  }
778
  $('#current-type-label').text(initialLabelText);
779
- });
780
- </script>
781
 
782
  </body>
783
  </html>
 
265
 
266
  <!-- Course Selection -->
267
  <div class="space-y-3">
268
+ <label class="block text-sm font-medium text-gray-700">Sélection du cours (Optionnel)</label>
269
  <select id="course-select" class="w-full">
270
+ <option value="">Choisir un cours pour contextualiser...</option>
271
  </select>
272
+ <div class="course-meta hidden mt-2">
273
  <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-4 border border-gray-100">
274
+ <div class="flex justify-between items-center text-sm">
275
  <span id="course-author" class="flex items-center space-x-2">
276
  <svg class="h-5 w-5 text-violet-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
277
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
 
345
  </div>
346
  </div>
347
 
348
+ <!-- DeepThink Floating Action Button -->
349
+ <button id="deepthink-btn"
350
+ title="Utiliser DeepThink (1 fois/jour)"
351
+ class="fixed bottom-6 right-6 z-50 bg-gradient-to-br from-purple-600 to-blue-500 hover:from-purple-700 hover:to-blue-600 text-white p-4 rounded-full shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 disabled:hover:shadow-lg">
352
+ <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
353
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z" />
354
+ </svg>
355
+ <span class="sr-only">DeepThink</span> <!-- Screen reader text -->
356
+ </button>
357
+
358
  <script>
359
  $(document).ready(function() {
360
  // Initialisation de Select2
361
  $('#course-select').select2({
362
+ placeholder: 'Choisir un cours pour contextualiser...',
363
+ allowClear: true, // Permet de déselectionner
364
  templateResult: function (course) {
365
  if (!course.id) {
366
  return course.text;
 
372
  `);
373
  },
374
  templateSelection: function (course) {
375
+ if (!course.id) { return course.text; } // Placeholder text
376
  return course.text; // Seul le titre est affiché dans la sélection
377
  },
378
 
 
413
  }
414
  addButtonAnimation('submit-btn');
415
  addButtonAnimation('copy-btn');
416
+ addButtonAnimation('deepthink-btn'); // Add animation to deepthink button
417
 
418
  // Gestion du changement de type avec affichage personnalisé
419
  $('#type-select').change(function() {
 
455
  $(newOption).data('author', course.author); // Ajouter l'auteur comme data
456
  select.append(newOption);
457
  });
458
+ // Important: Trigger update after adding options for Select2
459
+ select.trigger('change.select2');
460
  })
461
 
462
  .fail(function() {
 
470
  // Gestion du changement de cours avec animations
471
  $('#course-select').on('change', function() {
472
  const courseId = $(this).val();
473
+ const $courseMeta = $('.course-meta'); // Cache the element
474
 
475
  if (courseId) {
476
  $.ajax({
477
  url: `/api/philosophy/courses/${courseId}`,
478
  method: 'GET',
479
  beforeSend: function() {
480
+ $courseMeta.addClass('animate-pulse').removeClass('hidden'); // Show pulsing placeholder
481
+ // Clear previous data immediately for better UX
482
+ $('#course-author span').text('');
483
+ $('#course-date span').text('');
484
  },
485
  success: function(course) {
 
 
486
  $('#course-author span').text(`Pr. ${course.author}`);
487
  $('#course-date span').text(new Date(course.updated_at).toLocaleDateString('fr-FR', {
488
  day: 'numeric',
489
  month: 'long',
490
  year: 'numeric' }));
491
+ // Fade in content after data is loaded
492
+ $courseMeta.removeClass('animate-pulse').addClass('animate-fadeIn');
493
 
494
+ // Afficher une notification de succès discrète
495
+ /*Toast.fire({
496
  icon: 'success',
497
+ title: 'Cours chargé'
498
+ });*/
499
  },
500
  error: function() {
501
+ $courseMeta.addClass('hidden').removeClass('animate-pulse animate-fadeIn'); // Hide on error
502
  Toast.fire({
503
  icon: 'error',
504
  title: 'Erreur',
 
507
  }
508
  });
509
  } else {
510
+ // Fade out if a course was previously selected, then hide
511
+ if (!$courseMeta.hasClass('hidden')) {
512
+ $courseMeta.addClass('animate-fadeOut').on('animationend', function() {
513
+ $(this).addClass('hidden').removeClass('animate-fadeOut animate-fadeIn');
514
+ // Clear data when deselected
515
+ $('#course-author span').text('');
516
+ $('#course-date span').text('');
517
+ // Unbind animationend to prevent multiple executions
518
+ $(this).off('animationend');
519
+ });
520
+ }
521
  }
 
522
  });
523
 
524
  // Gestion de la soumission avec conversion en Markdown et sauvegarde
 
526
  const question = $('#question').val().trim();
527
 
528
  if (!question) {
529
+ Toast.fire({
530
+ icon: 'warning',
531
+ title: 'Sujet Requis',
532
+ text: 'Veuillez saisir un sujet de dissertation.'
533
+ });
534
+ $('#question').addClass('animate-shake border-red-500').focus();
535
+ setTimeout(() => $('#question').removeClass('animate-shake border-red-500'), 600);
536
  return;
537
  }
538
 
 
567
  };
568
 
569
  $.ajax({
570
+ url: '/submit_philo', // Standard endpoint
571
  method: 'POST',
572
  contentType: 'application/json',
573
  data: JSON.stringify(data),
 
589
  timer: 2000
590
  });
591
  },
592
+ error: function(jqXHR) {
593
+ Swal.close();
594
+ let errorMsg = 'Une erreur est survenue lors de la génération de votre dissertation.';
595
+ if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
596
+ errorMsg = jqXHR.responseJSON.error;
597
+ }
598
  Swal.fire({
599
  icon: 'error',
600
  title: 'Erreur de génération',
601
+ text: errorMsg,
602
  customClass: {
603
  popup: 'rounded-2xl',
604
  confirmButton: 'bg-violet-600 hover:bg-violet-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200'
 
608
  });
609
  });
610
 
611
+ // --- START: DeepThink Functionality ---
612
+ const DEEPTHINK_COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
613
+ const DEEPTHINK_STORAGE_KEY = 'lastDeepThinkUsage';
614
+ const $deepThinkBtn = $('#deepthink-btn');
615
+
616
+ function checkDeepThinkCooldown() {
617
+ const lastUsage = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
618
+ if (!lastUsage) {
619
+ $deepThinkBtn.prop('disabled', false).attr('title', 'Utiliser DeepThink (1 fois/jour)');
620
+ return true; // Available
621
+ }
622
+
623
+ const lastUsageTime = parseInt(lastUsage, 10);
624
+ const now = Date.now();
625
+ const timeElapsed = now - lastUsageTime;
626
+
627
+ if (timeElapsed >= DEEPTHINK_COOLDOWN_MS) {
628
+ $deepThinkBtn.prop('disabled', false).attr('title', 'Utiliser DeepThink (1 fois/jour)');
629
+ return true; // Available
630
+ } else {
631
+ const remainingTime = DEEPTHINK_COOLDOWN_MS - timeElapsed;
632
+ const hours = Math.floor(remainingTime / (60 * 60 * 1000));
633
+ const minutes = Math.floor((remainingTime % (60 * 60 * 1000)) / (60 * 1000));
634
+ $deepThinkBtn.prop('disabled', true).attr('title', `DeepThink disponible dans ${hours}h ${minutes}m`);
635
+ return false; // Not available
636
+ }
637
+ }
638
+
639
+ // Check cooldown on page load
640
+ checkDeepThinkCooldown();
641
+ // Periodically check cooldown (e.g., every minute) in case the tab stays open
642
+ setInterval(checkDeepThinkCooldown, 60000);
643
+
644
+
645
+ // Click handler for DeepThink button
646
+ $deepThinkBtn.click(function() {
647
+ if (!checkDeepThinkCooldown()) {
648
+ Toast.fire({
649
+ icon: 'warning',
650
+ title: 'DeepThink Non Disponible',
651
+ text: $deepThinkBtn.attr('title') // Show remaining time from title
652
+ });
653
+ return;
654
+ }
655
+
656
+ const question = $('#question').val().trim();
657
+
658
+ if (!question) {
659
+ Toast.fire({
660
+ icon: 'warning',
661
+ title: 'Sujet Requis',
662
+ text: 'Veuillez saisir un sujet avant d\'utiliser DeepThink.'
663
+ });
664
+ $('#question').addClass('animate-shake border-red-500').focus();
665
+ setTimeout(() => $('#question').removeClass('animate-shake border-red-500'), 600);
666
+ return;
667
+ }
668
+
669
+ // Use the same sophisticated loading animation, adjusted for DeepThink
670
+ Swal.fire({
671
+ title: 'Génération DeepThink en cours',
672
+ html: `
673
+ <div class="space-y-4">
674
+ <div class="flex justify-center">
675
+ <div class="w-16 h-16 relative">
676
+ <div class="absolute inset-0 rounded-full border-4 border-purple-200 animate-ping"></div>
677
+ <div class="absolute inset-0 rounded-full border-4 border-purple-500 animate-pulse"></div>
678
+ </div>
679
+ </div>
680
+ <div class="text-gray-600">
681
+ <p class="animate-pulse">Analyse philosophique approfondie...</p>
682
+ <p class="text-sm mt-2 text-gray-500">Cela peut prendre un peu plus de temps</p>
683
+ </div>
684
+ </div>
685
+ `,
686
+ allowOutsideClick: false,
687
+ showConfirmButton: false,
688
+ customClass: {
689
+ popup: 'rounded-2xl'
690
+ }
691
+ });
692
+
693
+ const data = {
694
+ question: question,
695
+ type: $('#type-select').val(),
696
+ courseId: $('#course-select').val() || null
697
+ };
698
+
699
+ $.ajax({
700
+ url: '/submit_philo_deepthink', // Call the NEW endpoint
701
+ method: 'POST',
702
+ contentType: 'application/json',
703
+ data: JSON.stringify(data),
704
+ success: function(data) {
705
+ Swal.close();
706
+
707
+ const htmlContent = marked.parse(data.response);
708
+ // Display the dissertation (replaces previous content)
709
+ $('#response > div').html(htmlContent);
710
+ $('#response').removeClass('hidden').addClass('animate-fadeIn');
711
+ $('#copy-btn').removeClass('hidden').addClass('animate-slideUp');
712
+
713
+ // Save the dissertation locally (optional, but consistent)
714
+ saveDissertation(question + " (DeepThink)", data.response);
715
+
716
+ // RECORD the usage timestamp
717
+ localStorage.setItem(DEEPTHINK_STORAGE_KEY, Date.now().toString());
718
+ // Update button state immediately
719
+ checkDeepThinkCooldown();
720
+
721
+ Toast.fire({
722
+ icon: 'success',
723
+ title: 'DeepThink terminé!',
724
+ text: 'Dissertation générée et sauvegardée.',
725
+ timer: 2500
726
+ });
727
+ },
728
+ error: function(jqXHR) {
729
+ Swal.close(); // Close loading indicator on error too
730
+ let errorMsg = 'Une erreur est survenue lors de la génération DeepThink.';
731
+ if (jqXHR.responseJSON && jqXHR.responseJSON.error) {
732
+ errorMsg = jqXHR.responseJSON.error;
733
+ }
734
+ Swal.fire({
735
+ icon: 'error',
736
+ title: 'Erreur DeepThink',
737
+ text: errorMsg,
738
+ customClass: {
739
+ popup: 'rounded-2xl',
740
+ confirmButton: 'bg-violet-600 hover:bg-violet-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200'
741
+ }
742
+ });
743
+ // Do NOT update cooldown here, allow user to retry if it was a server error
744
+ }
745
+ });
746
+ });
747
+
748
+ // --- END: DeepThink Functionality ---
749
+
750
 
751
  function saveDissertation(title, content) {
752
  let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
753
+ // Limit the number of saved dissertations to avoid excessive storage use
754
+ const MAX_SAVED = 20;
755
+ if (savedDissertations.length >= MAX_SAVED) {
756
+ savedDissertations.shift(); // Remove the oldest one
757
+ }
758
  savedDissertations.push({ title, content, timestamp: Date.now() });
759
  localStorage.setItem('dissertations', JSON.stringify(savedDissertations));
760
  updateSavedDissertationsList();
 
762
  }
763
 
764
  function deleteDissertation(index) {
765
+ Swal.fire({
766
+ title: 'Êtes-vous sûr?',
767
+ text: "Cette action est irréversible!",
768
+ icon: 'warning',
769
+ showCancelButton: true,
770
+ confirmButtonColor: '#d33',
771
+ cancelButtonColor: '#3085d6',
772
+ confirmButtonText: 'Oui, supprimer!',
773
+ cancelButtonText: 'Annuler',
774
+ customClass: {
775
+ popup: 'rounded-2xl',
776
+ }
777
+ }).then((result) => {
778
+ if (result.isConfirmed) {
779
+ let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
780
+ savedDissertations.splice(index, 1);
781
+ localStorage.setItem('dissertations', JSON.stringify(savedDissertations));
782
+ updateSavedDissertationsList();
783
+ Toast.fire({
784
+ icon: 'success',
785
+ title: 'Dissertation supprimée'
786
+ });
787
+ }
788
+ })
789
  }
790
 
791
  // Fonction pour afficher les dissertations sauvegardées (avec sections repliables et suppression)
 
794
  dissertationsList.empty(); // Vider la liste actuelle
795
 
796
  let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
797
+ // Sort by timestamp descending (newest first)
798
+ savedDissertations.sort((a, b) => b.timestamp - a.timestamp);
799
+
800
 
801
  if (savedDissertations.length === 0) {
802
+ dissertationsList.append('<p class="text-center text-gray-500 italic py-4">Aucune dissertation sauvegardée pour le moment.</p>');
803
  return;
804
  }
805
 
806
 
 
807
  savedDissertations.forEach((diss, index) => {
808
+ const date = moment(diss.timestamp).format('LLL'); // Format: 5 juin 2024 15:53
809
+ const uniqueId = `diss-${index}`; // Unique ID for content div
810
+
811
+ const $collapsible = $(`
812
+ <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden">
813
+ <button class="collapsible bg-gray-50 hover:bg-gray-100 transition-colors duration-150 p-4 flex justify-between items-center w-full text-left focus:outline-none focus:ring-2 focus:ring-violet-300">
814
+ <span class="font-medium text-gray-800 flex-1 mr-4 truncate" title="${diss.title}">${diss.title}</span>
815
+ <div class="flex items-center space-x-3 flex-shrink-0">
816
+ <span class="text-gray-500 text-xs">${date}</span>
817
+ <button class="delete-dissertation text-red-500 hover:text-red-700 p-1 rounded hover:bg-red-100 transition-colors duration-150" data-index="${index}" title="Supprimer">
818
+ <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
819
+ </button>
820
+ <svg class="h-5 w-5 text-gray-400 transform transition-transform duration-200 chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
821
+ </div>
822
+ </button>
823
+ </div>
824
+ `);
825
+
826
+ // Add content div separately to control its visibility
827
+ const $content = $(`<div id="${uniqueId}" class="content prose prose-violet max-w-none p-6 border-t border-gray-200"></div>`).html(marked.parse(diss.content));
828
+
829
+ // Append collapsible header first, then the initially hidden content
830
+ dissertationsList.append($collapsible.append($content));
831
+
832
+
833
+ // Event handler for the collapsible button (excluding the delete button)
834
+ $collapsible.find('.collapsible').click(function(event) {
835
+ // Prevent toggling if the delete button was clicked
836
+ if ($(event.target).closest('.delete-dissertation').length) {
837
+ return;
838
  }
839
+ const $thisContent = $(this).siblings('.content');
840
+ const $chevron = $(this).find('.chevron');
841
+
842
+ $thisContent.slideToggle("fast", function() {
843
+ // Add/remove 'active' class and rotate chevron after animation completes
844
+ if ($thisContent.is(':visible')) {
845
+ $collapsible.addClass('active');
846
+ $chevron.addClass('rotate-180');
847
+ } else {
848
+ $collapsible.removeClass('active');
849
+ $chevron.removeClass('rotate-180');
850
+ }
851
+ });
852
+ });
853
 
854
+ // Event handler for the delete button
855
+ $collapsible.find('.delete-dissertation').click(function() {
856
+ const indexToDelete = $(this).data('index');
857
+ deleteDissertation(indexToDelete);
858
  });
859
 
 
 
 
 
 
860
  });
861
  }
862
 
 
873
  const temp = document.createElement('div');
874
  temp.innerHTML = responseDiv.innerHTML;
875
 
876
+ // Fonction récursive améliorée pour extraire le texte avec un meilleur formatage
877
  function extractText(node) {
878
  let text = '';
879
  node.childNodes.forEach(child => {
880
+ if (child.nodeType === Node.TEXT_NODE) { // Nœud texte
881
+ text += child.textContent;
882
+ } else if (child.nodeType === Node.ELEMENT_NODE) { // Élément
883
+ const tagName = child.tagName.toLowerCase();
884
+ // Ajouter des sauts de ligne avant/après les éléments de bloc, sauf s'il y en a déjà
885
+ const isBlock = window.getComputedStyle(child).display === 'block';
886
+ if (isBlock && !text.endsWith('\n\n') && text.length > 0) {
887
+ text += '\n'; // Un saut de ligne avant le bloc
888
  }
889
+
890
+ if (tagName === 'br') {
891
+ text += '\n';
892
+ } else if (tagName === 'li') {
893
+ text += '* '; // Marqueur pour les listes
894
+ text += extractText(child);
895
+ text += '\n';
896
+ } else {
897
+ text += extractText(child);
898
+ }
899
+
900
+ if (isBlock && !text.endsWith('\n') && tagName !== 'li') { // Ajouter un saut de ligne après le bloc si nécessaire
901
  text += '\n';
902
  }
903
+ }
904
  });
905
+ // Nettoyer les espaces multiples et les sauts de ligne excessifs
906
+ return text.replace(/\n{3,}/g, '\n\n').trim();
907
  }
908
 
909
+ textToCopy = extractText(temp);
910
 
911
  // Animation et copie
912
  $(this).addClass('scale-95 bg-violet-100');
 
915
  navigator.clipboard.writeText(textToCopy)
916
  .then(() => {
917
  $(this).removeClass('scale-95 bg-violet-100')
918
+ .addClass('bg-green-50 text-green-700 border-green-200');
919
 
920
  setTimeout(() => {
921
+ $(this).removeClass('bg-green-50 text-green-700 border-green-200');
922
+ }, 1200);
923
 
924
  Toast.fire({
925
  icon: 'success',
926
  title: 'Copié avec succès',
927
+ //text: 'Le contenu a été copié dans votre presse-papiers',
928
  timer: 2000
929
  });
930
  })
931
  .catch((err) => {
932
+ console.error("Clipboard API failed: ", err);
933
  // Fallback pour les appareils mobiles qui ne supportent pas l'API Clipboard
934
  try {
935
  // Créer un élément textarea temporaire
 
938
  textarea.style.position = 'fixed'; // Évite le défilement
939
  textarea.style.opacity = '0';
940
  document.body.appendChild(textarea);
941
+
942
  // Sélectionner et copier le texte
943
  textarea.select();
944
+ // Pour iOS
945
+ if (navigator.userAgent.match(/ipad|iphone/i)) {
946
+ const range = document.createRange();
947
+ range.selectNodeContents(textarea);
948
+ const selection = window.getSelection();
949
+ selection.removeAllRanges();
950
+ selection.addRange(range);
951
+ textarea.setSelectionRange(0, 999999);
952
+ }
953
  document.execCommand('copy');
954
+
955
  // Nettoyer
956
  document.body.removeChild(textarea);
957
+
958
  // Feedback positif
959
  $(this).removeClass('scale-95 bg-violet-100')
960
+ .addClass('bg-green-50 text-green-700 border-green-200');
961
 
962
  setTimeout(() => {
963
+ $(this).removeClass('bg-green-50 text-green-700 border-green-200');
964
+ }, 1200);
965
 
966
  Toast.fire({
967
  icon: 'success',
968
+ title: 'Copié avec succès (Fallback)',
969
  timer: 2000
970
  });
971
  } catch (fallbackErr) {
972
+ console.error("Fallback copy failed: ", fallbackErr);
973
  // Si même le fallback échoue
974
  $(this).removeClass('scale-95 bg-violet-100')
975
+ .addClass('bg-red-50 text-red-700 border-red-200');
976
 
977
  setTimeout(() => {
978
+ $(this).removeClass('bg-red-50 text-red-700 border-red-200');
979
+ }, 1200);
980
 
981
  Toast.fire({
982
  icon: 'error',
983
  title: 'Erreur de copie',
984
+ text: 'Impossible de copier le contenu automatiquement.',
985
  timer: 3000
986
  });
987
  }
988
  });
989
  });
990
 
991
+ // Ajout des styles d'animation personnalisés (Déjà présents mais on s'assure qu'ils y sont)
992
+ const styleCheck = document.getElementById('custom-animations-style');
993
+ if (!styleCheck) {
994
+ const style = document.createElement('style');
995
+ style.id = 'custom-animations-style'; // Add an ID to prevent duplication
996
+ style.textContent = `
997
+ @keyframes fadeIn {
998
+ from { opacity: 0; transform: translateY(10px); }
999
+ to { opacity: 1; transform: translateY(0); }
1000
+ }
1001
+ @keyframes fadeOut {
1002
+ from { opacity: 1; }
1003
+ to { opacity: 0; }
1004
+ }
1005
+ @keyframes slideUp {
1006
+ from { opacity: 0; transform: translateY(20px); }
1007
+ to { opacity: 1; transform: translateY(0); }
1008
+ }
1009
+ @keyframes shake {
1010
+ 0%, 100% { transform: translateX(0); }
1011
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); }
1012
+ 20%, 40%, 60%, 80% { transform: translateX(3px); }
1013
+ }
1014
+ .animate-fadeIn { animation: fadeIn 0.5s ease-out forwards; }
1015
+ .animate-fadeOut { animation: fadeOut 0.3s ease-out forwards; }
1016
+ .animate-slideUp { animation: slideUp 0.5s ease-out forwards; }
1017
+ .animate-shake { animation: shake 0.5s ease-in-out; }
1018
+ .rotate-180 { transform: rotate(180deg); }
1019
+ .chevron { transition: transform 0.2s ease-in-out; } /* Smooth chevron rotation */
1020
+
1021
+ `;
1022
+ document.head.appendChild(style);
1023
+ }
1024
+
1025
 
1026
  // Met à jour le label initial avec la valeur par défaut du select
1027
  const initialSelectValue = $('#type-select').val();
 
1033
  initialLabelText = `Type ${initialSelectValue} - ${initialSelectedText}`;
1034
  }
1035
  $('#current-type-label').text(initialLabelText);
1036
+ }); // --- FIN de $(document).ready ---
1037
+ </script>
1038
 
1039
  </body>
1040
  </html>