Docfile commited on
Commit
e3ed368
·
verified ·
1 Parent(s): 6207250

Upload philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +351 -120
templates/philosophie.html CHANGED
@@ -17,7 +17,7 @@
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,7 +26,7 @@
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;
@@ -51,43 +51,46 @@
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>
@@ -141,16 +144,16 @@
141
  </div>
142
  </div>
143
  <div id="current-type-label" class="inline-flex px-4 py-2 rounded-xl text-sm font-medium bg-gradient-to-r from-violet-50 to-indigo-50 text-violet-700 border border-violet-200">
144
- Sujet de type 1
145
  </div>
146
  </div>
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">
@@ -247,13 +250,13 @@
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
  }
@@ -270,7 +273,7 @@
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
 
@@ -305,180 +308,408 @@
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>
 
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 exactly as provided before (collapsible, prose, original animations, Select2 base styles) */
21
  .collapsible {
22
  cursor: pointer;
23
  padding: 18px;
 
26
  text-align: left;
27
  outline: none;
28
  font-size: 15px;
29
+ background-color: #f1f1f1;
30
  display: flex;
31
  justify-content: space-between;
32
  align-items: center;
 
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
+ /* Original hover/active for collapsible button */
55
+ .active, .collapsible:hover {
56
+ background-color: #ddd;
57
+ }
58
 
59
  .content {
60
+ padding: 0 18px;
61
  display: none;
62
  overflow: hidden;
63
  background-color: white;
64
  }
65
 
66
+ /* Original Animations */
67
  @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
68
+ @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } /* Added fadeOut */
69
  @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
70
  @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); } 20%, 40%, 60%, 80% { transform: translateX(3px); } }
71
  .animate-fadeIn { animation: fadeIn 0.5s ease-out forwards; }
72
+ .animate-fadeOut { animation: fadeOut 0.3s ease-out forwards; } /* Added fadeOut */
73
  .animate-slideUp { animation: slideUp 0.5s ease-out forwards; }
74
  .animate-shake { animation: shake 0.5s ease-in-out; }
75
+ .rotate-180 { transform: rotate(180deg); } /* For chevron */
76
+ .chevron { transition: transform 0.2s ease-in-out; } /* For chevron */
77
 
78
 
79
  /* Select2 Styles */
80
  .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; }
81
  .select2-container--default .select2-selection--single .select2-selection__rendered { color: #374151 !important; line-height: inherit !important; padding-right: 1.5rem !important; }
82
+ .select2-container--default .select2-selection--single .select2-selection__placeholder { color: #9ca3af !important; }
83
  .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; }
84
+ .select2-container--default .select2-selection--single .select2-selection__clear { font-size: 1.5rem; font-weight: bold; color: #9ca3af; margin-right: 0.5rem; }
85
 
86
  .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; }
87
  .select2-search--dropdown .select2-search__field { border: 1px solid #e5e7eb !important; border-radius: 0.375rem !important; padding: 0.5rem !important; }
88
+ .select2-results__options { padding: 0.25rem !important; max-height: 250px !important; }
89
  .select2-results__option { padding: 0.5rem 0.75rem !important; }
90
  .select2-container--default .select2-results__option--highlighted.select2-results__option--selectable { background-color: #ede9fe !important; color:#374151 !important; }
91
  .select2-container--default .select2-results__option--selected { background-color: #f3f4f6 !important; color:#374151 !important; }
92
  .select2-results__option .course-author { font-size: 0.875rem; color: #6b7280; display: block; margin-top: 0.25rem; }
93
+ .select2-container--default .select2-results__option[aria-disabled=true] { color: #9ca3af; }
94
 
95
  </style>
96
  </head>
 
144
  </div>
145
  </div>
146
  <div id="current-type-label" class="inline-flex px-4 py-2 rounded-xl text-sm font-medium bg-gradient-to-r from-violet-50 to-indigo-50 text-violet-700 border border-violet-200">
147
+ <!-- Label set by JS -->
148
  </div>
149
  </div>
150
 
151
  <!-- Course Selection -->
152
  <div class="space-y-3">
153
+ <label for="course-select" class="block text-sm font-medium text-gray-700">Sélection du cours (Optionnel)</label>
 
154
  <select id="course-select" class="w-full">
155
+ <!-- Placeholder Option - Select2 will use this -->
156
+ <option value="">Choisir un cours...</option>
157
  </select>
158
  <div class="course-meta hidden mt-2"> <!-- Meta info appears below -->
159
  <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-4 border border-gray-100">
 
250
  $courseSelect.select2({
251
  placeholder: 'Choisir un cours...',
252
  allowClear: true, // Allows removing the selection
253
+ // Keep the initial placeholder option from HTML
254
  templateResult: function (course) {
255
+ if (!course.id) { // For placeholder
256
  return course.text;
257
  }
258
+ // Get author data attached to the option element
259
+ const author = $(course.element).data('author');
260
  if (!author) {
261
  return course.text; // Fallback if author is missing
262
  }
 
273
 
274
  // 2. Configuration for marked (Markdown parser)
275
  marked.setOptions({
276
+ breaks: true, gfm: true, headerIds: false, // headerIds: false is usually safer
277
  langPrefix: 'language-', smartLists: true, smartypants: true
278
  });
279
 
 
308
  $('#current-type-label').text(labelText);
309
  }).trigger('change'); // Trigger initially to set the label
310
 
311
+ // 7. Load Courses Function (with original animation indication)
312
  function loadCourses() {
313
  console.log("Attempting to load courses..."); // Debug log
314
  return $.ajax({
315
  url: '/api/philosophy/courses',
316
  method: 'GET',
317
+ dataType: 'json',
318
  beforeSend: function() {
319
+ // Original animation: Disable and add pulse class to the Select2 container
320
+ $courseSelect.prop('disabled', true).next('.select2-container').addClass('animate-pulse');
321
+ console.log("Set loading state (pulse) for course select."); // Debug log
322
+ },
323
+ // Remove pulse on complete, regardless of success/fail
324
+ complete: function() {
325
+ $courseSelect.prop('disabled', false).next('.select2-container').removeClass('animate-pulse');
326
+ console.log("Removed loading state (pulse)."); // Debug log
327
  }
 
328
  });
329
  }
330
 
331
+ // 8. Execute Course Loading and Populate Select2 (using append and .data())
332
  loadCourses()
333
  .done(function(courses) {
334
  console.log("Courses received:", courses); // Debug log
335
  if (!Array.isArray(courses)) {
336
  console.error("Received data is not an array:", courses);
337
  Toast.fire({ icon: 'error', title: 'Erreur Format Données', text: 'Les données des cours reçues sont invalides.' });
338
+ return; // Stop processing if data is invalid
 
 
339
  }
340
 
341
+ // Clear existing course options (keep the placeholder)
342
+ $courseSelect.find('option:not([value=""])').remove();
343
+
344
+ // Add new options from fetched data
345
+ courses.forEach(course => {
346
+ // Create a new <option> element using the Option constructor
347
+ const newOption = new Option(course.title, course.id); // (text, value)
348
+
349
+ // Attach the author and update date as data attributes TO THE OPTION
350
+ $(newOption).data('author', course.author);
351
+ $(newOption).data('updated_at', course.updated_at); // Store update date if needed
352
+
353
+ // Append the new option to the original select element
354
+ $courseSelect.append(newOption);
355
+ });
 
 
 
 
 
 
 
 
 
 
 
356
 
357
+ // IMPORTANT: Trigger change for Select2 to recognize new options
358
+ // Use 'change.select2' for better compatibility if needed, but 'change' usually works
359
+ $courseSelect.trigger('change');
360
+
361
+ console.log("Appended new options and triggered change."); // Debug log
362
 
363
  })
364
  .fail(function(jqXHR, textStatus, errorThrown) {
365
  console.error("Failed to load courses:", textStatus, errorThrown, jqXHR.responseText); // Debug log
366
  Toast.fire({
367
  icon: 'error', title: 'Erreur Chargement Cours',
368
+ text: 'Impossible de récupérer la liste des cours.'
369
  });
370
+ // Optionally clear or show an error message in the select itself
371
+ $courseSelect.find('option:not([value=""])').remove(); // Clear any potentially added options on fail
372
  });
373
 
374
+ // 9. Course Selection Change Handler (Original animation for meta info)
375
+ $courseSelect.on('change', function() {
376
+ const courseId = $(this).val();
377
+ const $courseMeta = $('.course-meta'); // Cache the element
378
+ const selectedOption = $(this).find('option:selected'); // Get the selected <option>
379
+
380
+ console.log("Course selection changed. ID:", courseId); // Debug log
381
+
382
+ if (courseId) {
383
+ // Get data directly from the selected <option> element's data attributes
384
+ const author = selectedOption.data('author');
385
+ const updatedAt = selectedOption.data('updated_at'); // Get the date stored earlier
386
+
387
+ if (author && updatedAt) {
388
+ console.log("Displaying meta:", author, updatedAt); // Debug log
389
+ // Display Meta Info using data attributes (no second AJAX needed here)
390
+ $('#course-author span').text(`Pr. ${author}`);
391
+ $('#course-date span').text(moment(updatedAt).format('LL')); // Format date nicely
392
+
393
+ // Original animation: Remove hidden, add fadeIn
394
+ $courseMeta.removeClass('hidden animate-fadeOut').addClass('animate-fadeIn');
395
+
396
+ // Original subtle success Toast (uncomment if desired)
397
+ /* Toast.fire({
398
+ icon: 'success',
399
+ title: 'Cours chargé avec succès',
400
+ timer: 1500
401
+ });*/
402
+ } else {
403
+ // Data attributes might be missing, potentially fallback to AJAX if really needed
404
+ console.warn("Author or updated_at data missing from selected option.", selectedOption);
405
+ // Hide meta if data is incomplete
406
+ if (!$courseMeta.hasClass('hidden')) {
407
+ $courseMeta.addClass('animate-fadeOut').on('animationend', function() {
408
+ $(this).addClass('hidden').removeClass('animate-fadeOut animate-fadeIn');
409
+ $('#course-author span').text('');
410
+ $('#course-date span').text('');
411
+ $(this).off('animationend');
412
+ });
413
+ }
414
+ }
415
+
416
  } else {
417
+ // Course deselected (placeholder selected)
418
+ console.log("Course deselected."); // Debug log
419
+ // Original animation: Fade out if visible, then hide
420
  if (!$courseMeta.hasClass('hidden')) {
421
+ $courseMeta.removeClass('animate-fadeIn').addClass('animate-fadeOut').on('animationend', function() {
422
+ $(this).addClass('hidden').removeClass('animate-fadeOut');
423
  $('#course-author span').text('');
424
  $('#course-date span').text('');
425
+ $(this).off('animationend'); // Unbind animationend
426
  });
427
  }
428
  }
429
  });
430
 
431
+
432
+ // 10. Submit Button Click Handler (Standard Generation - Original Logic)
433
  $('#submit-btn').click(function() {
434
  const question = $('#question').val().trim();
435
+
436
+ if (!question) {
437
+ Toast.fire({ icon: 'warning', title: 'Sujet Requis', text: 'Veuillez saisir un sujet de dissertation.' });
438
+ $('#question').addClass('animate-shake border-red-500').focus();
439
+ setTimeout(() => $('#question').removeClass('animate-shake border-red-500 border-gray-200'), 600); // Reset border
440
+ return;
441
+ }
442
+
443
+ // Original Sophisticated loading animation
444
+ Swal.fire({
445
+ title: 'Génération en cours',
446
+ html: `<div class="space-y-4"><div class="flex justify-center"><div class="w-16 h-16 relative"><div class="absolute inset-0 rounded-full border-4 border-violet-200 animate-ping"></div><div class="absolute inset-0 rounded-full border-4 border-violet-500 animate-pulse"></div></div></div><div class="text-gray-600"><p class="animate-pulse">Analyse philosophique en cours...</p><p class="text-sm mt-2 text-gray-500">Veuillez patienter quelques instants</p></div></div>`,
447
+ allowOutsideClick: false, showConfirmButton: false, customClass: { popup: 'rounded-2xl' }
448
+ });
449
+
450
  const data = { question: question, type: $('#type-select').val(), courseId: $courseSelect.val() || null };
451
 
452
  $.ajax({
453
  url: '/submit_philo', method: 'POST', contentType: 'application/json', data: JSON.stringify(data),
454
+ success: function(data) {
455
+ Swal.close();
456
+ const htmlContent = marked.parse(data.response);
457
+ $('#response > div').html(htmlContent);
458
+ $('#response').removeClass('hidden').addClass('animate-fadeIn');
459
+ $('#copy-btn').removeClass('hidden').addClass('animate-slideUp');
460
+ saveDissertation(question, data.response); // Save locally
461
+ Toast.fire({ icon: 'success', title: 'Dissertation générée et sauvegardée', timer: 2000 });
462
+ },
463
+ error: function(jqXHR) {
464
+ Swal.close();
465
+ let errorMsg = 'Une erreur est survenue lors de la génération.';
466
+ if (jqXHR.responseJSON && jqXHR.responseJSON.error) { errorMsg = jqXHR.responseJSON.error; }
467
+ Swal.fire({ icon: 'error', title: 'Erreur de génération', text: errorMsg, customClass: { popup: 'rounded-2xl', confirmButton: 'bg-violet-600 hover:bg-violet-700 text-white font-medium py-2 px-4 rounded-lg' } });
468
+ }
469
  });
470
  });
471
 
472
+ // --- START: DeepThink Functionality (Original Logic) ---
473
+ const DEEPTHINK_COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24 hours
474
  const DEEPTHINK_STORAGE_KEY = 'lastDeepThinkUsage';
475
  const $deepThinkBtn = $('#deepthink-btn');
476
 
477
+ function checkDeepThinkCooldown() {
478
+ const lastUsage = localStorage.getItem(DEEPTHINK_STORAGE_KEY);
479
+ if (!lastUsage) {
480
+ $deepThinkBtn.prop('disabled', false).attr('title', 'Utiliser DeepThink (1 fois/jour)');
481
+ return true; // Available
482
+ }
483
+ const lastUsageTime = parseInt(lastUsage, 10);
484
+ const now = Date.now();
485
+ const timeElapsed = now - lastUsageTime;
486
+ if (timeElapsed >= DEEPTHINK_COOLDOWN_MS) {
487
+ $deepThinkBtn.prop('disabled', false).attr('title', 'Utiliser DeepThink (1 fois/jour)');
488
+ return true; // Available
489
+ } else {
490
+ const remainingTime = DEEPTHINK_COOLDOWN_MS - timeElapsed;
491
+ const hours = Math.floor(remainingTime / (3600 * 1000));
492
+ const minutes = Math.floor((remainingTime % (3600 * 1000)) / (60 * 1000));
493
+ $deepThinkBtn.prop('disabled', true).attr('title', `DeepThink disponible dans ${hours}h ${minutes}m`);
494
+ return false; // Not available
495
+ }
496
+ }
497
+ checkDeepThinkCooldown(); // Check on load
498
  setInterval(checkDeepThinkCooldown, 60000); // Re-check periodically
499
 
500
+ // Click handler for DeepThink button
501
  $deepThinkBtn.click(function() {
502
+ if (!checkDeepThinkCooldown()) {
503
+ Toast.fire({ icon: 'warning', title: 'DeepThink Non Disponible', text: $deepThinkBtn.attr('title') });
504
+ return;
505
+ }
506
  const question = $('#question').val().trim();
507
+ if (!question) {
508
+ Toast.fire({ icon: 'warning', title: 'Sujet Requis', text: 'Veuillez saisir un sujet avant d\'utiliser DeepThink.' });
509
+ $('#question').addClass('animate-shake border-red-500').focus();
510
+ setTimeout(() => $('#question').removeClass('animate-shake border-red-500 border-gray-200'), 600);
511
+ return;
512
+ }
513
+
514
+ // Original DeepThink loading animation
515
+ Swal.fire({
516
+ title: 'Génération DeepThink en cours',
517
+ html: `<div class="space-y-4"><div class="flex justify-center"><div class="w-16 h-16 relative"><div class="absolute inset-0 rounded-full border-4 border-purple-200 animate-ping"></div><div class="absolute inset-0 rounded-full border-4 border-purple-500 animate-pulse"></div></div></div><div class="text-gray-600"><p class="animate-pulse">Analyse philosophique approfondie...</p><p class="text-sm mt-2 text-gray-500">Cela peut prendre un peu plus de temps</p></div></div>`,
518
+ allowOutsideClick: false, showConfirmButton: false, customClass: { popup: 'rounded-2xl' }
519
+ });
520
+
521
  const data = { question: question, type: $('#type-select').val(), courseId: $courseSelect.val() || null };
522
 
523
  $.ajax({
524
  url: '/submit_philo_deepthink', method: 'POST', contentType: 'application/json', data: JSON.stringify(data),
525
  success: function(data) {
 
526
  Swal.close();
527
  const htmlContent = marked.parse(data.response);
528
  $('#response > div').html(htmlContent);
529
  $('#response').removeClass('hidden').addClass('animate-fadeIn');
530
  $('#copy-btn').removeClass('hidden').addClass('animate-slideUp');
531
+ saveDissertation(question + " (DeepThink)", data.response); // Save locally
532
+ localStorage.setItem(DEEPTHINK_STORAGE_KEY, Date.now().toString()); // Record usage
533
+ checkDeepThinkCooldown(); // Update button state
534
  Toast.fire({ icon: 'success', title: 'DeepThink terminé!', text: 'Dissertation générée et sauvegardée.', timer: 2500 });
535
  },
536
+ error: function(jqXHR) {
537
+ Swal.close();
538
+ let errorMsg = 'Erreur lors de la génération DeepThink.';
539
+ if (jqXHR.responseJSON && jqXHR.responseJSON.error) { errorMsg = jqXHR.responseJSON.error; }
540
+ Swal.fire({ icon: 'error', title: 'Erreur DeepThink', text: errorMsg, customClass: { popup: 'rounded-2xl', confirmButton: 'bg-violet-600 hover:bg-violet-700 text-white font-medium py-2 px-4 rounded-lg' } });
541
+ }
542
  });
543
  });
544
+ // --- END: DeepThink Functionality ---
545
 
546
 
547
+ // --- START: Saved Dissertations (Original Logic) ---
548
+ function saveDissertation(title, content) {
549
+ let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
550
+ const MAX_SAVED = 20; // Limit saved items
551
+ if (savedDissertations.length >= MAX_SAVED) { savedDissertations.shift(); }
552
+ savedDissertations.push({ title, content, timestamp: Date.now() });
553
+ localStorage.setItem('dissertations', JSON.stringify(savedDissertations));
554
+ updateSavedDissertationsList();
555
+ }
556
+
557
+ function deleteDissertation(index) {
558
+ // Original confirmation dialog
559
+ Swal.fire({
560
+ title: 'Êtes-vous sûr?', text: "Cette action est irréversible!", icon: 'warning',
561
+ showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6',
562
+ confirmButtonText: 'Oui, supprimer!', cancelButtonText: 'Annuler',
563
+ customClass: { popup: 'rounded-2xl' }
564
+ }).then((result) => {
565
+ if (result.isConfirmed) {
566
+ let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
567
+ savedDissertations.splice(index, 1);
568
+ localStorage.setItem('dissertations', JSON.stringify(savedDissertations));
569
+ updateSavedDissertationsList(); // Refresh list UI
570
+ Toast.fire({ icon: 'success', title: 'Dissertation supprimée' });
571
+ }
572
+ });
573
+ }
574
 
575
+ // Original function to display saved dissertations with collapsible sections
576
+ function updateSavedDissertationsList() {
577
+ const dissertationsList = $('#dissertations-list');
578
+ dissertationsList.empty(); // Clear current list
579
 
580
+ let savedDissertations = JSON.parse(localStorage.getItem('dissertations')) || [];
581
+ savedDissertations.sort((a, b) => b.timestamp - a.timestamp); // Sort newest first
582
+
583
+ if (savedDissertations.length === 0) {
584
+ dissertationsList.append('<p class="text-center text-gray-500 italic py-4">Aucune dissertation sauvegardée.</p>');
585
+ return;
586
+ }
587
+
588
+ savedDissertations.forEach((diss, index) => {
589
+ const date = moment(diss.timestamp).format('LLL');
590
+ const uniqueId = `diss-content-${index}`; // Unique ID for content
591
+
592
+ // Original Collapsible structure with Tailwind & custom classes
593
+ const $collapsibleContainer = $(`
594
+ <div class="bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden mb-4">
595
+ <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" aria-expanded="false" aria-controls="${uniqueId}">
596
+ <span class="font-medium text-gray-800 flex-1 mr-4 truncate" title="${diss.title}">${diss.title}</span>
597
+ <div class="flex items-center space-x-3 flex-shrink-0">
598
+ <span class="text-gray-500 text-xs">${date}</span>
599
+ <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">
600
+ <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>
601
+ </button>
602
+ <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>
603
+ </div>
604
+ </button>
605
+ <div id="${uniqueId}" class="content prose prose-violet max-w-none p-6 border-t border-gray-200" style="display: none;">
606
+ <!-- Content added below -->
607
+ </div>
608
+ </div>`);
609
+
610
+ // Add parsed markdown content to the content div
611
+ $collapsibleContainer.find('.content').html(marked.parse(diss.content));
612
+
613
+ dissertationsList.append($collapsibleContainer);
614
+
615
+ // Event handler for the collapsible button (excluding the delete button)
616
+ $collapsibleContainer.find('.collapsible').click(function(event) {
617
+ if ($(event.target).closest('.delete-dissertation').length) { return; } // Ignore clicks on delete button
618
+
619
+ const $contentDiv = $(this).next('.content');
620
+ const $chevron = $(this).find('.chevron');
621
+ const isExpanded = $(this).attr('aria-expanded') === 'true';
622
+
623
+ $contentDiv.slideToggle("fast", () => { // Use slideToggle for smooth animation
624
+ $(this).attr('aria-expanded', !isExpanded);
625
+ $chevron.toggleClass('rotate-180', !isExpanded);
626
+ // Add/remove 'active' class AFTER animation if needed for styling
627
+ // $(this).toggleClass('active', !isExpanded);
628
+ });
629
+ });
630
+
631
+ // Event handler for the delete button
632
+ $collapsibleContainer.find('.delete-dissertation').click(function() {
633
+ const indexToDelete = $(this).data('index');
634
+ deleteDissertation(indexToDelete);
635
+ });
636
+ });
637
+ }
638
+ updateSavedDissertationsList(); // Initial display
639
+ // --- END: Saved Dissertations ---
640
+
641
+
642
+ // --- START: Copy Button (Original Logic) ---
643
+ $('#copy-btn').click(function() {
644
+ const responseDiv = document.querySelector('#response > div');
645
+ let textToCopy = '';
646
+ const temp = document.createElement('div');
647
+ temp.innerHTML = responseDiv.innerHTML;
648
+
649
+ function extractText(node) { // Original extraction logic
650
+ let text = '';
651
+ node.childNodes.forEach(child => {
652
+ if (child.nodeType === 3) { text += child.textContent; }
653
+ else if (child.nodeType === 1) {
654
+ const isBlock = window.getComputedStyle(child).display === 'block';
655
+ if (isBlock && text.length > 0 && !text.endsWith('\n\n')) { text += '\n'; }
656
+ if (child.tagName === 'BR') { text += '\n'; }
657
+ else if (child.tagName === 'LI') { text += '* ' + extractText(child) + '\n'; }
658
+ else { text += extractText(child); }
659
+ if (isBlock && !text.endsWith('\n') && child.tagName !== 'LI') { text += '\n'; }
660
+ }
661
+ });
662
+ return text.replace(/\n{3,}/g, '\n\n').trim();
663
+ }
664
+ textToCopy = extractText(temp);
665
+
666
+ $(this).addClass('scale-95 bg-violet-100'); // Press animation
667
+
668
+ navigator.clipboard.writeText(textToCopy)
669
+ .then(() => { // Success
670
+ $(this).removeClass('scale-95 bg-violet-100').addClass('bg-green-50 text-green-700 border-green-200');
671
+ setTimeout(() => $(this).removeClass('bg-green-50 text-green-700 border-green-200'), 1200);
672
+ Toast.fire({ icon: 'success', title: 'Copié avec succès', timer: 2000 });
673
+ })
674
+ .catch((err) => { // Fallback
675
+ console.error("Clipboard API failed: ", err);
676
+ try {
677
+ const textarea = document.createElement('textarea');
678
+ /* Fallback textarea logic as before */
679
+ document.execCommand('copy');
680
+ document.body.removeChild(textarea);
681
+ $(this).removeClass('scale-95 bg-violet-100').addClass('bg-green-50 text-green-700 border-green-200');
682
+ setTimeout(() => $(this).removeClass('bg-green-50 text-green-700 border-green-200'), 1200);
683
+ Toast.fire({ icon: 'success', title: 'Copié avec succès (Fallback)', timer: 2000 });
684
+ } catch (fallbackErr) { // Fallback failed
685
+ console.error("Fallback copy failed: ", fallbackErr);
686
+ $(this).removeClass('scale-95 bg-violet-100').addClass('bg-red-50 text-red-700 border-red-200');
687
+ setTimeout(() => $(this).removeClass('bg-red-50 text-red-700 border-red-200'), 1200);
688
+ Toast.fire({ icon: 'error', title: 'Erreur de copie', text: 'Impossible de copier.', timer: 3000 });
689
+ }
690
+ });
691
+ });
692
+ // --- END: Copy Button ---
693
 
694
+ // --- Animation Styles (Ensure they are present) ---
695
  const styleCheck = document.getElementById('custom-animations-style');
696
  if (!styleCheck) {
697
+ const style = document.createElement('style'); style.id = 'custom-animations-style';
698
+ style.textContent = `
699
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
700
+ @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
701
+ @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
702
+ @keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-3px); } 20%, 40%, 60%, 80% { transform: translateX(3px); } }
703
+ .animate-fadeIn { animation: fadeIn 0.5s ease-out forwards; }
704
+ .animate-fadeOut { animation: fadeOut 0.3s ease-out forwards; }
705
+ .animate-slideUp { animation: slideUp 0.5s ease-out forwards; }
706
+ .animate-shake { animation: shake 0.5s ease-in-out; }
707
+ .rotate-180 { transform: rotate(180deg); }
708
+ .chevron { transition: transform 0.2s ease-in-out; }
709
+ `;
710
  document.head.appendChild(style);
711
  }
712
+ // --- END Animation Styles ---
713
 
714
  }); // --- FIN de $(document).ready ---
715
  </script>