Docfile commited on
Commit
dbc968d
·
verified ·
1 Parent(s): d3e3794

Upload philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +189 -742
templates/philosophie.html CHANGED
@@ -4,17 +4,20 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Mariam AI - Assistant Philosophique</title>
 
7
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
8
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
9
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
 
10
  <script src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.7.3/sweetalert2.all.min.js"></script>
11
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script>
12
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
13
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/locale/fr.js"></script>
 
14
  <link href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.7.3/sweetalert2.min.css" rel="stylesheet">
15
  <script src="https://cdn.tailwindcss.com"></script>
16
  <style>
17
- /* Styles existants */
18
  .collapsible {
19
  cursor: pointer;
20
  padding: 18px;
@@ -23,189 +26,68 @@
23
  text-align: left;
24
  outline: none;
25
  font-size: 15px;
26
- background-color: #f1f1f1;
27
  display: flex;
28
  justify-content: space-between;
29
  align-items: center;
30
  }
31
 
32
- /* Nouveaux styles pour le Markdown responsive */
33
- .prose {
34
- max-width: 100% !important;
35
- }
36
-
37
- .prose p {
38
- margin-top: 1.25em;
39
- margin-bottom: 1.25em;
40
- line-height: 1.75;
41
- }
42
 
43
- .prose ul {
44
- margin-top: 1.25em;
45
- margin-bottom: 1.25em;
46
- padding-left: 1.625em;
47
- }
48
-
49
- .prose li {
50
- margin-top: 0.5em;
51
- margin-bottom: 0.5em;
52
- padding-left: 0.375em;
53
- }
54
-
55
- .prose h1, .prose h2, .prose h3 {
56
- margin-top: 2em;
57
- margin-bottom: 1em;
58
- line-height: 1.3;
59
- }
60
-
61
- /* Styles spécifiques pour mobile */
62
  @media (max-width: 640px) {
63
- .prose {
64
- font-size: 0.95rem;
65
- }
66
-
67
- .prose p {
68
- margin-top: 1em;
69
- margin-bottom: 1em;
70
- }
71
-
72
- .prose ul {
73
- padding-left: 1.25em;
74
- }
75
-
76
- .prose li {
77
- margin-top: 0.375em;
78
- margin-bottom: 0.375em;
79
- }
80
-
81
- .prose h1, .prose h2, .prose h3 {
82
- margin-top: 1.5em;
83
- margin-bottom: 0.75em;
84
- }
85
  }
86
 
87
- /* Styles pour améliorer la lisibilité du texte */
88
- #response .prose {
89
- color: #374151;
90
- word-wrap: break-word;
91
- overflow-wrap: break-word;
92
- hyphens: auto;
93
- }
94
 
95
- #response .prose > * + * {
96
- margin-top: 1em;
97
- }
98
-
99
- #response .prose blockquote {
100
- margin: 1.5em 0;
101
- padding-left: 1em;
102
- border-left: 4px solid #e5e7eb;
103
- font-style: italic;
104
- }
105
-
106
- #response .prose code {
107
- background-color: #f3f4f6;
108
- padding: 0.2em 0.4em;
109
- border-radius: 0.25em;
110
- font-size: 0.875em;
111
- }
112
-
113
-
114
- .active, .collapsible:hover {
115
- background-color: #ddd;
116
- }
117
 
118
  .content {
119
- padding: 0 18px;
120
  display: none;
121
  overflow: hidden;
122
  background-color: white;
123
  }
124
- .animate-fadeIn {
125
- animation: fadeIn 0.5s ease-out forwards;
126
- }
127
- .animate-slideUp {
128
- animation: slideUp 0.5s ease-out forwards;
129
- }
130
- .animate-shake {
131
- animation: shake 0.5s ease-in-out;
132
- }
133
- @keyframes fadeIn {
134
- from { opacity: 0; transform: translateY(10px); }
135
- to { opacity: 1; transform: translateY(0); }
136
- }
137
- @keyframes slideUp {
138
- from { opacity: 0; transform: translateY(20px); }
139
- to { opacity: 1; transform: translateY(0); }
140
- }
141
- @keyframes shake {
142
- 0%, 100% { transform: translateX(0); }
143
- 25% { transform: translateX(-5px); }
144
- 75% { transform: translateX(5px); }
145
- }
146
-
147
- /* Ajout du style pour Select2 */
148
- .select2-container--default .select2-selection--single {
149
- border: 1px solid #e5e7eb;
150
- border-radius: 0.75rem;
151
- height: auto;
152
- padding: 0.625rem 1rem;
153
- background-color: white;
154
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
155
- display: flex;
156
- align-items: center;
157
- }
158
- .select2-container--default .select2-selection--single .select2-selection__rendered {
159
- color: #374151;
160
- line-height: inherit;
161
- padding-right: 1.5rem;
162
- }
163
-
164
- .select2-container--default .select2-selection--single .select2-selection__arrow {
165
- top: 50%;
166
- transform: translateY(-50%);
167
- right: 0.75rem;
168
- }
169
-
170
- .select2-dropdown {
171
- border: 1px solid #e5e7eb;
172
- border-radius: 0.75rem;
173
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
174
- padding: 0.25rem;
175
- }
176
- .select2-search--dropdown .select2-search__field {
177
- border: 1px solid #e5e7eb;
178
- border-radius: 0.375rem;
179
- padding: 0.5rem;
180
- }
181
-
182
- .select2-results__options {
183
- padding: 0.25rem;
184
- }
185
- .select2-results__option {
186
- padding: 0.5rem 0.75rem;
187
- }
188
 
189
-
190
- .select2-container--default .select2-results__option--highlighted.select2-results__option--selectable{
191
- background-color: #ede9fe;
192
- color:#374151;
193
- }
194
-
195
- .select2-container--default .select2-results__option--selected {
196
- background-color: #f3f4f6;
197
- color:#374151;
198
- }
199
- .select2-results__option .course-author {
200
- font-size: 0.875rem;
201
- color: #6b7280;
202
- display: block;
203
- margin-top: 0.25rem;
204
- }
205
- .select2-container--open .select2-selection--single .select2-selection__arrow {
206
- border-color: transparent transparent #a8a29e;
207
-
208
- }
 
 
 
 
 
 
 
 
209
 
210
  </style>
211
  </head>
@@ -265,11 +147,12 @@
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">
@@ -292,7 +175,7 @@
292
 
293
  <!-- Question Input -->
294
  <div class="space-y-3">
295
- <label class="block text-sm font-medium text-gray-700">Sujet de dissertation</label>
296
  <div class="relative">
297
  <textarea id="question" rows="4"
298
  class="w-full rounded-xl border-gray-200 shadow-sm focus:border-violet-500 focus:ring-violet-500 resize-none bg-white py-3 px-4"
@@ -357,53 +240,51 @@
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;
367
  }
368
- const author = $(course.element).data('author'); // Récupérer l'auteur
369
- return $(`
 
 
 
 
370
  <span>${course.text}</span>
371
  <span class="course-author">Pr. ${author}</span>
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
-
379
  });
380
- // Configuration de marked
 
381
  marked.setOptions({
382
- breaks: true,
383
- gfm: true,
384
- headerIds: true,
385
- langPrefix: 'language-',
386
- smartLists: true,
387
- smartypants: true
388
  });
389
 
390
- moment.locale('fr'); // Configuration de moment.js en français
 
391
 
392
- // Configuration des notifications
393
  const Toast = Swal.mixin({
394
- toast: true,
395
- position: 'top-end',
396
- showConfirmButton: false,
397
- timer: 3000,
398
- timerProgressBar: true,
399
- customClass: {
400
- popup: 'rounded-lg shadow-xl border border-gray-100'
401
- }
402
  });
403
 
404
-
405
-
406
- // Animation des boutons
407
  function addButtonAnimation(buttonId) {
408
  $(`#${buttonId}`).on('mousedown', function() {
409
  $(this).addClass('scale-95');
@@ -413,626 +294,192 @@
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() {
420
  const selectedOption = $(this).find('option:selected');
421
  const typeLabel = selectedOption.text();
422
  const type = $(this).val();
423
- let labelText;
424
- if(type === '3') {
425
- labelText = 'S - Synthèse';
426
- } else {
427
- labelText = `Type ${type} - ${typeLabel}`;
428
- }
429
  $('#current-type-label').text(labelText);
 
430
 
431
- });
432
-
433
- // Chargement des cours avec animation
434
- function loadCourses() {
435
  return $.ajax({
436
  url: '/api/philosophy/courses',
437
  method: 'GET',
 
438
  beforeSend: function() {
439
- $('#course-select').prop('disabled', true).addClass('animate-pulse');
440
-
441
- },
442
- complete: function() {
443
- $('#course-select').prop('disabled', false).removeClass('animate-pulse');
444
-
445
  }
 
446
  });
447
  }
448
 
449
- loadCourses()
450
- .done(function(courses) {
451
- const select = $('#course-select');
452
- courses.forEach(course => {
453
- // Créer une nouvelle option avec l'auteur déjà inclus
454
- const newOption = new Option(course.title, course.id, false, false);
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() {
 
 
 
 
463
  Toast.fire({
464
- icon: 'error',
465
- title: 'Erreur de chargement des cours',
466
- text: 'Veuillez réessayer ultérieurement'
467
  });
 
 
468
  });
469
 
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',
505
- text: 'Impossible de charger les détails du cours'
506
- });
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
525
  $('#submit-btn').click(function() {
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
-
539
- // Animation de chargement sophistiquée
540
- Swal.fire({
541
- title: 'Génération en cours',
542
- html: `
543
- <div class="space-y-4">
544
- <div class="flex justify-center">
545
- <div class="w-16 h-16 relative">
546
- <div class="absolute inset-0 rounded-full border-4 border-violet-200 animate-ping"></div>
547
- <div class="absolute inset-0 rounded-full border-4 border-violet-500 animate-pulse"></div>
548
- </div>
549
- </div>
550
- <div class="text-gray-600">
551
- <p class="animate-pulse">Analyse philosophique en cours...</p>
552
- <p class="text-sm mt-2 text-gray-500">Veuillez patienter quelques instants</p>
553
- </div>
554
- </div>
555
- `,
556
- allowOutsideClick: false,
557
- showConfirmButton: false,
558
- customClass: {
559
- popup: 'rounded-2xl'
560
- }
561
- });
562
-
563
- const data = {
564
- question: question,
565
- type: $('#type-select').val(),
566
- courseId: $('#course-select').val() || null
567
- };
568
 
569
  $.ajax({
570
- url: '/submit_philo', // Standard endpoint
571
- method: 'POST',
572
- contentType: 'application/json',
573
- data: JSON.stringify(data),
574
- success: function(data) {
575
- Swal.close();
576
-
577
- const htmlContent = marked.parse(data.response);
578
- // Afficher la dissertation
579
- $('#response > div').html(htmlContent);
580
- $('#response').removeClass('hidden').addClass('animate-fadeIn');
581
- $('#copy-btn').removeClass('hidden').addClass('animate-slideUp');
582
-
583
- // Sauvegarder la dissertation
584
- saveDissertation(question, data.response);
585
-
586
- Toast.fire({
587
- icon: 'success',
588
- title: 'Dissertation générée et sauvegardée avec succès',
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'
605
- }
606
- });
607
- }
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();
761
-
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)
792
- function updateSavedDissertationsList() {
793
- const dissertationsList = $('#dissertations-list');
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
-
863
- // Appeler la fonction pour afficher les dissertations au chargement de la page
864
- updateSavedDissertationsList();
865
-
866
- // Nouvelle gestion de la copie
867
- $('#copy-btn').click(function() {
868
- // Sélectionner le contenu de la réponse en utilisant innerHTML pour obtenir le HTML formaté
869
- const responseDiv = document.querySelector('#response > div');
870
- let textToCopy = '';
871
-
872
- // Créer un élément temporaire pour convertir le HTML en texte brut tout en préservant le formatage
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');
913
-
914
- // Utiliser l'API Clipboard avec gestion des erreurs
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
936
- const textarea = document.createElement('textarea');
937
- textarea.value = textToCopy;
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();
1028
- const initialSelectedText = $('#type-select option:selected').text();
1029
- let initialLabelText;
1030
- if(initialSelectValue === '3') {
1031
- initialLabelText = 'S - Synthèse';
1032
- } else {
1033
- initialLabelText = `Type ${initialSelectValue} - ${initialSelectedText}`;
1034
- }
1035
- $('#current-type-label').text(initialLabelText);
1036
  }); // --- FIN de $(document).ready ---
1037
  </script>
1038
 
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Mariam AI - Assistant Philosophique</title>
7
+ <!-- jQuery first, then Select2 JS -->
8
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
9
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet" />
10
  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/select2.min.js"></script>
11
+ <!-- Other JS libraries -->
12
  <script src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.7.3/sweetalert2.all.min.js"></script>
13
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script>
14
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
15
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/locale/fr.js"></script>
16
+ <!-- CSS -->
17
  <link href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert2/11.7.3/sweetalert2.min.css" rel="stylesheet">
18
  <script src="https://cdn.tailwindcss.com"></script>
19
  <style>
20
+ /* Styles existants (collapsible, prose, animations, etc.) */
21
  .collapsible {
22
  cursor: pointer;
23
  padding: 18px;
 
26
  text-align: left;
27
  outline: none;
28
  font-size: 15px;
29
+ background-color: #f1f1f1; /* Base style, override below */
30
  display: flex;
31
  justify-content: space-between;
32
  align-items: center;
33
  }
34
 
35
+ .prose { max-width: 100% !important; }
36
+ .prose p { margin-top: 1.25em; margin-bottom: 1.25em; line-height: 1.75; }
37
+ .prose ul { margin-top: 1.25em; margin-bottom: 1.25em; padding-left: 1.625em; }
38
+ .prose li { margin-top: 0.5em; margin-bottom: 0.5em; padding-left: 0.375em; }
39
+ .prose h1, .prose h2, .prose h3 { margin-top: 2em; margin-bottom: 1em; line-height: 1.3; }
 
 
 
 
 
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  @media (max-width: 640px) {
42
+ .prose { font-size: 0.95rem; }
43
+ .prose p { margin-top: 1em; margin-bottom: 1em; }
44
+ .prose ul { padding-left: 1.25em; }
45
+ .prose li { margin-top: 0.375em; margin-bottom: 0.375em; }
46
+ .prose h1, .prose h2, .prose h3 { margin-top: 1.5em; margin-bottom: 0.75em; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
 
49
+ #response .prose { color: #374151; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; }
50
+ #response .prose > * + * { margin-top: 1em; }
51
+ #response .prose blockquote { margin: 1.5em 0; padding-left: 1em; border-left: 4px solid #e5e7eb; font-style: italic; }
52
+ #response .prose code { background-color: #f3f4f6; padding: 0.2em 0.4em; border-radius: 0.25em; font-size: 0.875em; }
 
 
 
53
 
54
+ /* Removed active/hover on .collapsible, handled by JS/Tailwind */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
  .content {
57
+ padding: 0 18px; /* Base padding, overridden by Tailwind below */
58
  display: none;
59
  overflow: hidden;
60
  background-color: white;
61
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
+ /* Animations */
64
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
65
+ @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
66
+ @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
67
+ @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); } 20%, 40%, 60%, 80% { transform: translateX(3px); } }
68
+ .animate-fadeIn { animation: fadeIn 0.5s ease-out forwards; }
69
+ .animate-fadeOut { animation: fadeOut 0.3s ease-out forwards; }
70
+ .animate-slideUp { animation: slideUp 0.5s ease-out forwards; }
71
+ .animate-shake { animation: shake 0.5s ease-in-out; }
72
+ .rotate-180 { transform: rotate(180deg); }
73
+ .chevron { transition: transform 0.2s ease-in-out; } /* Smooth chevron rotation */
74
+
75
+
76
+ /* Select2 Styles */
77
+ .select2-container--default .select2-selection--single { border: 1px solid #e5e7eb !important; border-radius: 0.75rem !important; height: auto !important; padding: 0.625rem 1rem !important; background-color: white !important; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05) !important; display: flex !important; align-items: center !important; }
78
+ .select2-container--default .select2-selection--single .select2-selection__rendered { color: #374151 !important; line-height: inherit !important; padding-right: 1.5rem !important; }
79
+ .select2-container--default .select2-selection--single .select2-selection__placeholder { color: #9ca3af !important; } /* Placeholder color */
80
+ .select2-container--default .select2-selection--single .select2-selection__arrow { top: 50% !important; transform: translateY(-50%) !important; right: 0.75rem !important; height: 1.25rem !important; width: 1.25rem !important; }
81
+ .select2-container--default .select2-selection--single .select2-selection__clear { font-size: 1.5rem; font-weight: bold; color: #9ca3af; margin-right: 0.5rem; } /* Style clear button */
82
+
83
+ .select2-dropdown { border: 1px solid #e5e7eb !important; border-radius: 0.75rem !important; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important; padding: 0.25rem !important; background-color: white !important; }
84
+ .select2-search--dropdown .select2-search__field { border: 1px solid #e5e7eb !important; border-radius: 0.375rem !important; padding: 0.5rem !important; }
85
+ .select2-results__options { padding: 0.25rem !important; max-height: 250px !important; } /* Limit dropdown height */
86
+ .select2-results__option { padding: 0.5rem 0.75rem !important; }
87
+ .select2-container--default .select2-results__option--highlighted.select2-results__option--selectable { background-color: #ede9fe !important; color:#374151 !important; }
88
+ .select2-container--default .select2-results__option--selected { background-color: #f3f4f6 !important; color:#374151 !important; }
89
+ .select2-results__option .course-author { font-size: 0.875rem; color: #6b7280; display: block; margin-top: 0.25rem; }
90
+ .select2-container--default .select2-results__option[aria-disabled=true] { color: #9ca3af; } /* Disabled option style */
91
 
92
  </style>
93
  </head>
 
147
 
148
  <!-- Course Selection -->
149
  <div class="space-y-3">
150
+ <label for="course-select" class="block text-sm font-medium text-gray-700">Sélection du cours (Optionnel, pour contextualiser)</label>
151
+ <!-- NOTE: We only need the select element. Select2 will replace it. -->
152
  <select id="course-select" class="w-full">
153
+ <!-- Placeholder option is added via Select2 config -->
154
  </select>
155
+ <div class="course-meta hidden mt-2"> <!-- Meta info appears below -->
156
  <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-4 border border-gray-100">
157
  <div class="flex justify-between items-center text-sm">
158
  <span id="course-author" class="flex items-center space-x-2">
 
175
 
176
  <!-- Question Input -->
177
  <div class="space-y-3">
178
+ <label for="question" class="block text-sm font-medium text-gray-700">Sujet de dissertation</label>
179
  <div class="relative">
180
  <textarea id="question" rows="4"
181
  class="w-full rounded-xl border-gray-200 shadow-sm focus:border-violet-500 focus:ring-violet-500 resize-none bg-white py-3 px-4"
 
240
 
241
  <script>
242
  $(document).ready(function() {
243
+
244
+ const $courseSelect = $('#course-select'); // Cache selector
245
+
246
+ // 1. Initialize Select2 for Course Selection
247
+ $courseSelect.select2({
248
+ placeholder: 'Choisir un cours...',
249
+ allowClear: true, // Allows removing the selection
250
+ data: [{ id: '', text: 'Chargement des cours...' }], // Initial loading state
251
  templateResult: function (course) {
252
+ if (!course.id) { // For placeholder or loading text
253
  return course.text;
254
  }
255
+ // Ensure data is present before accessing
256
+ const author = course.author || $(course.element).data('author'); // Get author from data or element
257
+ if (!author) {
258
+ return course.text; // Fallback if author is missing
259
+ }
260
+ return $(`
261
  <span>${course.text}</span>
262
  <span class="course-author">Pr. ${author}</span>
263
  `);
264
  },
265
  templateSelection: function (course) {
266
+ // Handle placeholder or selected item text
267
+ return course.text || 'Choisir un cours...';
268
  },
 
269
  });
270
+
271
+ // 2. Configuration for marked (Markdown parser)
272
  marked.setOptions({
273
+ breaks: true, gfm: true, headerIds: false, // Disabled header IDs to avoid conflicts
274
+ langPrefix: 'language-', smartLists: true, smartypants: true
 
 
 
 
275
  });
276
 
277
+ // 3. Configure moment.js locale
278
+ moment.locale('fr');
279
 
280
+ // 4. Configure SweetAlert2 Toast notifications
281
  const Toast = Swal.mixin({
282
+ toast: true, position: 'top-end', showConfirmButton: false,
283
+ timer: 3000, timerProgressBar: true,
284
+ customClass: { popup: 'rounded-lg shadow-xl border border-gray-100' }
 
 
 
 
 
285
  });
286
 
287
+ // 5. Button Animation Helper
 
 
288
  function addButtonAnimation(buttonId) {
289
  $(`#${buttonId}`).on('mousedown', function() {
290
  $(this).addClass('scale-95');
 
294
  }
295
  addButtonAnimation('submit-btn');
296
  addButtonAnimation('copy-btn');
297
+ addButtonAnimation('deepthink-btn');
298
 
299
+ // 6. Type Selection Change Handler
300
  $('#type-select').change(function() {
301
  const selectedOption = $(this).find('option:selected');
302
  const typeLabel = selectedOption.text();
303
  const type = $(this).val();
304
+ let labelText = (type === '3') ? 'S - Synthèse' : `Type ${type} - ${typeLabel}`;
 
 
 
 
 
305
  $('#current-type-label').text(labelText);
306
+ }).trigger('change'); // Trigger initially to set the label
307
 
308
+ // 7. Load Courses Function
309
+ function loadCourses() {
310
+ console.log("Attempting to load courses..."); // Debug log
 
311
  return $.ajax({
312
  url: '/api/philosophy/courses',
313
  method: 'GET',
314
+ dataType: 'json', // Expect JSON response
315
  beforeSend: function() {
316
+ // Set loading state in Select2
317
+ $courseSelect.empty().append(new Option('Chargement...', '', true, true)).prop('disabled', true).trigger('change');
318
+ console.log("Set loading state for course select."); // Debug log
 
 
 
319
  }
320
+ // No 'complete' needed here, handled in done/fail
321
  });
322
  }
323
 
324
+ // 8. Execute Course Loading and Populate Select2
325
+ loadCourses()
326
+ .done(function(courses) {
327
+ console.log("Courses received:", courses); // Debug log
328
+ if (!Array.isArray(courses)) {
329
+ console.error("Received data is not an array:", courses);
330
+ Toast.fire({ icon: 'error', title: 'Erreur Format Données', text: 'Les données des cours reçues sont invalides.' });
331
+ // Set error state in Select2
332
+ $courseSelect.empty().append(new Option('Erreur chargement', '', true, true)).prop('disabled', true).trigger('change');
333
+ return;
334
+ }
335
+
336
+ // Prepare data for Select2, including the placeholder
337
+ const selectData = courses.map(course => ({
338
+ id: course.id,
339
+ text: course.title,
340
+ author: course.author, // Include author directly in data object
341
+ updated_at: course.updated_at // Include update date
342
+ }));
343
+
344
+ console.log("Prepared Select2 data:", selectData); // Debug log
345
+
346
+ // Re-initialize Select2 with the actual data
347
+ $courseSelect.empty(); // Clear previous options (like 'Loading...')
348
+ $courseSelect.select2({ // Initialize again with full config
349
+ placeholder: 'Choisir un cours...',
350
+ allowClear: true,
351
+ data: selectData, // Provide the formatted data directly
352
+ templateResult: function (course) {
353
+ if (!course.id) return course.text;
354
+ const author = course.author; // Access author directly from data
355
+ if (!author) return course.text;
356
+ return $(`<span>${course.text}</span><span class="course-author">Pr. ${author}</span>`);
357
+ },
358
+ templateSelection: function (course) {
359
+ return course.text || 'Choisir un cours...';
360
+ }
361
+ }).prop('disabled', false); // Enable the dropdown
362
 
363
+ console.log("Select2 re-initialized with courses."); // Debug log
364
+
365
+ })
366
+ .fail(function(jqXHR, textStatus, errorThrown) {
367
+ console.error("Failed to load courses:", textStatus, errorThrown, jqXHR.responseText); // Debug log
368
  Toast.fire({
369
+ icon: 'error', title: 'Erreur Chargement Cours',
370
+ text: 'Impossible de récupérer la liste des cours. Vérifiez la console pour les détails.'
 
371
  });
372
+ // Set error state in Select2
373
+ $courseSelect.empty().append(new Option('Erreur chargement', '', true, true)).prop('disabled', true).trigger('change');
374
  });
375
 
376
+ // 9. Course Selection Change Handler (to show meta info)
377
+ $courseSelect.on('change', function(e) {
378
+ const $courseMeta = $('.course-meta');
379
+ const selectedData = $(this).select2('data')[0]; // Get selected data object from Select2
380
+
381
+ console.log("Course selection changed:", selectedData); // Debug log
382
 
383
+ if (selectedData && selectedData.id) { // Check if a valid course is selected (not placeholder/cleared)
384
+ $.ajax({
385
+ url: `/api/philosophy/courses/${selectedData.id}`, // Use ID from selected data
386
  method: 'GET',
387
  beforeSend: function() {
388
+ $courseMeta.addClass('animate-pulse').removeClass('hidden');
 
389
  $('#course-author span').text('');
390
  $('#course-date span').text('');
391
  },
392
+ success: function(course) { // Use the detailed data from the second AJAX call
393
  $('#course-author span').text(`Pr. ${course.author}`);
394
+ $('#course-date span').text(moment(course.updated_at).format('LL')); // Format date nicely
 
 
 
 
395
  $courseMeta.removeClass('animate-pulse').addClass('animate-fadeIn');
 
 
 
 
 
 
396
  },
397
  error: function() {
398
+ $courseMeta.addClass('hidden').removeClass('animate-pulse animate-fadeIn');
399
+ Toast.fire({ icon: 'error', title: 'Erreur', text: 'Impossible de charger les détails du cours sélectionné.' });
 
 
 
 
400
  }
401
  });
402
  } else {
403
+ // Course deselected or placeholder selected
404
  if (!$courseMeta.hasClass('hidden')) {
405
  $courseMeta.addClass('animate-fadeOut').on('animationend', function() {
406
  $(this).addClass('hidden').removeClass('animate-fadeOut animate-fadeIn');
407
+ $('#course-author span').text('');
408
+ $('#course-date span').text('');
409
+ $(this).off('animationend');
 
 
410
  });
411
  }
412
  }
413
  });
414
 
415
+ // 10. Submit Button Click Handler (Standard Generation)
416
  $('#submit-btn').click(function() {
417
  const question = $('#question').val().trim();
418
+ if (!question) { /* Error handling as before */ return; }
419
+ Swal.fire({ /* Loading indicator as before */ });
420
+ const data = { question: question, type: $('#type-select').val(), courseId: $courseSelect.val() || null };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
  $.ajax({
423
+ url: '/submit_philo', method: 'POST', contentType: 'application/json', data: JSON.stringify(data),
424
+ success: function(data) { /* Success handling as before */ },
425
+ error: function(jqXHR) { /* Error handling as before */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
  });
427
  });
428
 
429
+ // 11. DeepThink Functionality (Cooldown check and AJAX call)
430
+ const DEEPTHINK_COOLDOWN_MS = 24 * 60 * 60 * 1000;
431
  const DEEPTHINK_STORAGE_KEY = 'lastDeepThinkUsage';
432
  const $deepThinkBtn = $('#deepthink-btn');
433
 
434
+ function checkDeepThinkCooldown() { /* Cooldown logic as before */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  checkDeepThinkCooldown();
436
+ setInterval(checkDeepThinkCooldown, 60000); // Re-check periodically
 
 
437
 
 
438
  $deepThinkBtn.click(function() {
439
+ if (!checkDeepThinkCooldown()) { /* Cooldown message */ return; }
 
 
 
 
 
 
 
 
440
  const question = $('#question').val().trim();
441
+ if (!question) { /* Error handling */ return; }
442
+ Swal.fire({ /* DeepThink loading indicator */ });
443
+ const data = { question: question, type: $('#type-select').val(), courseId: $courseSelect.val() || null };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  $.ajax({
446
+ url: '/submit_philo_deepthink', method: 'POST', contentType: 'application/json', data: JSON.stringify(data),
 
 
 
447
  success: function(data) {
448
+ /* Display result, save dissertation, update cooldown */
449
  Swal.close();
 
450
  const htmlContent = marked.parse(data.response);
 
451
  $('#response > div').html(htmlContent);
452
  $('#response').removeClass('hidden').addClass('animate-fadeIn');
453
  $('#copy-btn').removeClass('hidden').addClass('animate-slideUp');
 
 
454
  saveDissertation(question + " (DeepThink)", data.response);
 
 
455
  localStorage.setItem(DEEPTHINK_STORAGE_KEY, Date.now().toString());
 
456
  checkDeepThinkCooldown();
457
+ Toast.fire({ icon: 'success', title: 'DeepThink terminé!', text: 'Dissertation générée et sauvegardée.', timer: 2500 });
 
 
 
 
 
 
458
  },
459
+ error: function(jqXHR) { /* Error handling as before */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  });
461
  });
462
 
 
 
463
 
464
+ // 12. Save / Delete / Update Saved Dissertations List Functions
465
+ function saveDissertation(title, content) { /* Logic as before */ }
466
+ function deleteDissertation(index) { /* Logic with confirmation as before */ }
467
+ function updateSavedDissertationsList() { /* Logic with collapsible sections as before */ }
468
+ updateSavedDissertationsList(); // Initial load of saved dissertations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
 
470
+ // 13. Copy Button Click Handler
471
+ $('#copy-btn').click(function() { /* Copy logic as before */ });
 
 
472
 
 
 
 
473
 
474
+ // 14. Ensure custom animation styles are present
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
  const styleCheck = document.getElementById('custom-animations-style');
476
  if (!styleCheck) {
477
  const style = document.createElement('style');
478
+ style.id = 'custom-animations-style';
479
+ style.textContent = ` /* Animation keyframes as before */ `;
480
+ document.head.appendChild(style);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  }
482
 
 
 
 
 
 
 
 
 
 
 
 
483
  }); // --- FIN de $(document).ready ---
484
  </script>
485