Docfile commited on
Commit
810c4e4
·
verified ·
1 Parent(s): 5524fc2

Update templates/philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +203 -69
templates/philosophie.html CHANGED
@@ -14,24 +14,48 @@
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
- /* ... (tous les styles de votre fichier original restent ici) ... */
18
  .collapsible { cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px; background-color: #f1f1f1; display: flex; justify-content: space-between; align-items: center; }
 
 
 
 
19
  .prose { max-width: 100% !important; }
20
  .prose p { margin-top: 1.25em; margin-bottom: 1.25em; line-height: 1.75; }
21
- .prose ul { margin-top: 1.25em; margin-bottom: 1.25em; padding-left: 1.625em; }
 
 
22
  #response .prose { color: #374151; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; }
23
- .content { padding: 0 18px; display: none; overflow: hidden; background-color: white; }
 
24
  .animate-fadeIn { animation: fadeIn 0.5s ease-out forwards; }
25
  @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
 
 
26
  .select2-container--default .select2-selection--single { border: 1px solid #e5e7eb; border-radius: 0.75rem; height: auto; padding: 0.625rem 1rem; background-color: white; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); display: flex; align-items: center; }
27
  .select2-container--default .select2-selection--single .select2-selection__rendered { color: #374151; line-height: inherit; padding-right: 1.5rem; }
 
 
 
28
  #image-preview { max-height: 200px; border-radius: 0.75rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1); }
29
  </style>
30
  </head>
31
  <body class="bg-gradient-to-br from-violet-50 to-indigo-50 min-h-screen">
32
  <!-- Navbar -->
33
  <nav class="bg-white/80 backdrop-blur-md border-b border-gray-200 fixed w-full z-50">
34
- <!-- ... (contenu de la navbar inchangé) ... -->
 
 
 
 
 
 
 
 
 
 
 
 
35
  </nav>
36
 
37
  <!-- Main Content -->
@@ -53,14 +77,22 @@
53
  </select>
54
  </div>
55
 
56
- <!-- Conteneur pour les champs de texte -->
57
  <div id="text-input-container">
58
  <!-- Course Selection -->
59
  <div class="space-y-3">
60
- <label class="block text-sm font-medium text-gray-700">Appuyer l'analyse sur un cours (Optionnel)</label>
61
  <select id="course-select" class="w-full">
62
- <option value="">Ne pas utiliser de cours</option>
63
  </select>
 
 
 
 
 
 
 
 
64
  </div>
65
  <!-- Question Input -->
66
  <div class="space-y-3 mt-8">
@@ -69,35 +101,41 @@
69
  </div>
70
  </div>
71
 
72
- <!-- Conteneur pour l'upload d'image -->
73
  <div id="image-input-container" class="hidden">
74
  <div class="space-y-3">
75
  <label for="image-upload" class="block text-sm font-medium text-gray-700">Charger une image pour analyse</label>
76
  <input type="file" id="image-upload" accept="image/jpeg, image/png, image/webp" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-violet-50 file:text-violet-700 hover:file:bg-violet-100"/>
77
- <div class="mt-4 flex justify-center">
78
- <img id="image-preview" src="" alt="Aperçu de l'image" class="hidden"/>
79
- </div>
80
  </div>
81
  </div>
82
 
83
  <!-- Submit Button -->
84
  <button id="submit-btn" class="w-full py-4 px-6 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 text-white font-medium shadow-lg shadow-violet-200 hover:shadow-xl hover:shadow-violet-300 transform hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:ring-offset-2">
85
- Générer
86
  </button>
87
 
 
 
 
 
 
 
88
  <!-- Response Section -->
89
  <div id="response" class="hidden mt-8 prose prose-violet max-w-none">
90
- <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-6 border border-gray-100">
91
- <!-- La réponse en streaming sera insérée ici -->
92
- </div>
93
  </div>
94
 
95
  <!-- Copy Button -->
96
- <button id="copy-btn" class="hidden w-full py-3 ...">Copier</button>
 
 
97
 
98
  <!-- Saved Dissertations Section -->
99
  <div id="saved-dissertations" class="mt-8">
100
- <!-- ... (section inchangée) ... -->
 
101
  </div>
102
  </div>
103
  </div>
@@ -112,8 +150,12 @@
112
  <script>
113
  $(document).ready(function() {
114
  // --- Initialisations ---
115
- $('#course-select').select2({ /* ... options select2 ... */ });
116
- marked.setOptions({ breaks: true, gfm: true });
 
 
 
 
117
  moment.locale('fr');
118
  const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true });
119
 
@@ -124,66 +166,80 @@ $(document).ready(function() {
124
  $('#text-input-container').hide();
125
  $('#image-input-container').show();
126
  $('#deepthink-btn').hide();
127
- $('#submit-btn span').text("Analyser l'image");
128
  } else { // Dissertation texte
129
  $('#text-input-container').show();
130
  $('#image-input-container').hide();
131
  $('#deepthink-btn').show();
132
- $('#submit-btn span').text("Générer la dissertation");
133
  }
134
  }).trigger('change');
135
 
136
  $('#image-upload').change(function(e) {
137
  if (e.target.files && e.target.files[0]) {
138
  const reader = new FileReader();
139
- reader.onload = function(event) {
140
- $('#image-preview').attr('src', event.target.result).removeClass('hidden');
141
- }
142
  reader.readAsDataURL(e.target.files[0]);
143
  }
144
  });
145
 
146
- // --- Chargement des cours (inchangé) ---
147
- // ...
148
-
149
  // --- Logique de Génération en Streaming ---
150
  async function handleStreamedGeneration(url, options) {
151
- Swal.fire({ title: 'Génération en cours...', html: 'Veuillez patienter...', allowOutsideClick: false, showConfirmButton: false, didOpen: () => { Swal.showLoading() }});
152
 
 
 
153
  const responseDiv = $('#response > div');
 
154
  responseDiv.html('');
155
- $('#response').removeClass('hidden');
156
- $('#copy-btn').addClass('hidden');
157
- let fullResponseText = '';
158
 
159
  try {
160
  const response = await fetch(url, options);
161
-
162
- if (!response.ok) {
163
- const errorText = await response.text();
164
- throw new Error(errorText || `Erreur HTTP: ${response.status}`);
165
- }
166
 
167
  const reader = response.body.getReader();
168
  const decoder = new TextDecoder();
 
169
 
170
  while (true) {
171
  const { value, done } = await reader.read();
 
 
 
 
172
  if (done) break;
173
-
174
- const chunk = decoder.decode(value, { stream: true });
175
- fullResponseText += chunk;
176
- responseDiv.html(marked.parse(fullResponseText));
177
- window.scrollTo(0, document.body.scrollHeight);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  }
179
 
180
- Swal.close();
181
  $('#copy-btn').removeClass('hidden');
182
  Toast.fire({ icon: 'success', title: 'Génération terminée !' });
183
- saveDissertation($('#question').val().trim() || "Analyse d'image", fullResponseText);
 
184
 
185
  } catch (error) {
186
- console.error('Erreur de streaming:', error);
187
  Swal.fire({ icon: 'error', title: 'Erreur', text: error.message });
188
  }
189
  }
@@ -193,41 +249,119 @@ $(document).ready(function() {
193
  const type = $('#type-select').val();
194
  const isDeepThink = $(this).attr('id') === 'deepthink-btn';
195
 
196
- if (type === '3') { // Analyse d'image
197
  const imageFile = $('#image-upload')[0].files[0];
198
- if (!imageFile) {
199
- Swal.fire('Erreur', 'Veuillez sélectionner une image.', 'error');
200
- return;
201
- }
202
  const formData = new FormData();
203
  formData.append('image', imageFile);
204
  handleStreamedGeneration('/stream_philo_image', { method: 'POST', body: formData });
205
-
206
- } else { // Dissertation texte
207
  const question = $('#question').val().trim();
208
- if (!question) {
209
- Swal.fire('Erreur', 'Veuillez saisir un sujet.', 'error');
210
- return;
211
- }
212
- const data = {
213
- question: question,
214
- type: type,
215
- courseId: $('#course-select').val() || null
216
- };
217
  const url = isDeepThink ? '/stream_philo_deepthink' : '/stream_philo';
218
- handleStreamedGeneration(url, {
219
- method: 'POST',
220
- headers: { 'Content-Type': 'application/json' },
221
- body: JSON.stringify(data)
 
 
 
 
 
 
 
 
 
 
 
222
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  });
225
 
226
- // --- Fonctions de sauvegarde et copie (inchangées) ---
227
- function saveDissertation(title, content) { /* ... */ }
228
- function deleteDissertation(index) { /* ... */ }
229
- function updateSavedDissertationsList() { /* ... */ }
230
- $('#copy-btn').click(function() { /* ... */ });
 
 
 
231
 
232
  updateSavedDissertationsList();
233
  });
 
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 de votre fichier original `philosophie (7).html` */
18
  .collapsible { cursor: pointer; padding: 18px; width: 100%; border: none; text-align: left; outline: none; font-size: 15px; background-color: #f1f1f1; display: flex; justify-content: space-between; align-items: center; }
19
+ .active, .collapsible:hover { background-color: #ddd; }
20
+ .content { padding: 0 18px; display: none; overflow: hidden; background-color: white; }
21
+
22
+ /* Styles pour la responsivité et la lisibilité du Markdown généré */
23
  .prose { max-width: 100% !important; }
24
  .prose p { margin-top: 1.25em; margin-bottom: 1.25em; line-height: 1.75; }
25
+ .prose ul, .prose ol { margin-top: 1.25em; margin-bottom: 1.25em; padding-left: 1.625em; }
26
+ .prose li { margin-top: 0.5em; margin-bottom: 0.5em; padding-left: 0.375em; }
27
+ .prose h1, .prose h2, .prose h3 { margin-top: 2em; margin-bottom: 1em; line-height: 1.3; }
28
  #response .prose { color: #374151; word-wrap: break-word; overflow-wrap: break-word; hyphens: auto; }
29
+
30
+ /* Styles d'animation */
31
  .animate-fadeIn { animation: fadeIn 0.5s ease-out forwards; }
32
  @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
33
+
34
+ /* Styles pour Select2 */
35
  .select2-container--default .select2-selection--single { border: 1px solid #e5e7eb; border-radius: 0.75rem; height: auto; padding: 0.625rem 1rem; background-color: white; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); display: flex; align-items: center; }
36
  .select2-container--default .select2-selection--single .select2-selection__rendered { color: #374151; line-height: inherit; padding-right: 1.5rem; }
37
+ .select2-results__option .course-author { font-size: 0.875rem; color: #6b7280; display: block; margin-top: 0.25rem; }
38
+
39
+ /* Styles pour l'aperçu de l'image */
40
  #image-preview { max-height: 200px; border-radius: 0.75rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1); }
41
  </style>
42
  </head>
43
  <body class="bg-gradient-to-br from-violet-50 to-indigo-50 min-h-screen">
44
  <!-- Navbar -->
45
  <nav class="bg-white/80 backdrop-blur-md border-b border-gray-200 fixed w-full z-50">
46
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
47
+ <div class="flex justify-between items-center h-16">
48
+ <div class="flex items-center">
49
+ <div class="text-2xl font-bold bg-gradient-to-r from-violet-600 to-indigo-600 text-transparent bg-clip-text">Mariam AI</div>
50
+ </div>
51
+ <div class="flex items-center space-x-4">
52
+ <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-violet-100 text-violet-800">
53
+ <span class="w-2 h-2 bg-violet-400 rounded-full animate-pulse mr-2"></span>
54
+ Assistant Philosophique
55
+ </span>
56
+ </div>
57
+ </div>
58
+ </div>
59
  </nav>
60
 
61
  <!-- Main Content -->
 
77
  </select>
78
  </div>
79
 
80
+ <!-- Conteneur pour les champs texte (visible par défaut) -->
81
  <div id="text-input-container">
82
  <!-- Course Selection -->
83
  <div class="space-y-3">
84
+ <label class="block text-sm font-medium text-gray-700">Sélection du cours</label>
85
  <select id="course-select" class="w-full">
86
+ <option value="">Choisir un cours...</option>
87
  </select>
88
+ <div class="course-meta hidden mt-4">
89
+ <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-4 border border-gray-100">
90
+ <div class="flex justify-between items-center">
91
+ <span id="course-author" class="flex items-center space-x-2"><svg class="h-5 w-5 text-violet-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg><span class="text-gray-600"></span></span>
92
+ <span id="course-date" class="flex items-center space-x-2"><svg class="h-5 w-5 text-violet-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg><span class="text-gray-600"></span></span>
93
+ </div>
94
+ </div>
95
+ </div>
96
  </div>
97
  <!-- Question Input -->
98
  <div class="space-y-3 mt-8">
 
101
  </div>
102
  </div>
103
 
104
+ <!-- Conteneur pour l'upload d'image (caché par défaut) -->
105
  <div id="image-input-container" class="hidden">
106
  <div class="space-y-3">
107
  <label for="image-upload" class="block text-sm font-medium text-gray-700">Charger une image pour analyse</label>
108
  <input type="file" id="image-upload" accept="image/jpeg, image/png, image/webp" class="block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-violet-50 file:text-violet-700 hover:file:bg-violet-100"/>
109
+ <div class="mt-4 flex justify-center"><img id="image-preview" src="" alt="Aperçu de l'image" class="hidden"/></div>
 
 
110
  </div>
111
  </div>
112
 
113
  <!-- Submit Button -->
114
  <button id="submit-btn" class="w-full py-4 px-6 rounded-xl bg-gradient-to-r from-violet-600 to-indigo-600 text-white font-medium shadow-lg shadow-violet-200 hover:shadow-xl hover:shadow-violet-300 transform hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:ring-offset-2">
115
+ Générer la dissertation
116
  </button>
117
 
118
+ <!-- Thinking Process Section -->
119
+ <div id="thinking-container" class="hidden mt-8">
120
+ <h3 class="text-md font-semibold text-gray-600 mb-2 border-b pb-2">Processus de pensée de l'IA :</h3>
121
+ <div id="thinking-process" class="text-sm text-gray-500 bg-gray-50 p-4 rounded-lg prose prose-sm max-w-none"></div>
122
+ </div>
123
+
124
  <!-- Response Section -->
125
  <div id="response" class="hidden mt-8 prose prose-violet max-w-none">
126
+ <h3 class="text-lg font-bold text-gray-800 mb-2">Réponse :</h3>
127
+ <div class="bg-gradient-to-r from-gray-50 to-white rounded-xl p-6 border border-gray-100"></div>
 
128
  </div>
129
 
130
  <!-- Copy Button -->
131
+ <button id="copy-btn" class="hidden mt-8 w-full py-3 px-6 rounded-xl bg-gray-50 text-gray-700 font-medium border border-gray-200 hover:bg-gray-100 transform hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2">
132
+ Copier la dissertation
133
+ </button>
134
 
135
  <!-- Saved Dissertations Section -->
136
  <div id="saved-dissertations" class="mt-8">
137
+ <h3 class="text-lg font-medium text-gray-700 mb-4">Dissertations Sauvegardées</h3>
138
+ <div id="dissertations-list" class="space-y-4"></div>
139
  </div>
140
  </div>
141
  </div>
 
150
  <script>
151
  $(document).ready(function() {
152
  // --- Initialisations ---
153
+ $('#course-select').select2({
154
+ placeholder: 'Choisir un cours...',
155
+ templateResult: function (course) { if (!course.id) { return course.text; } return $(`<span>${course.text}</span><span class="course-author">Pr. ${$(course.element).data('author')}</span>`); },
156
+ templateSelection: function (course) { return course.text; },
157
+ });
158
+ marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false });
159
  moment.locale('fr');
160
  const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true });
161
 
 
166
  $('#text-input-container').hide();
167
  $('#image-input-container').show();
168
  $('#deepthink-btn').hide();
169
+ $('#submit-btn').text("Analyser l'image");
170
  } else { // Dissertation texte
171
  $('#text-input-container').show();
172
  $('#image-input-container').hide();
173
  $('#deepthink-btn').show();
174
+ $('#submit-btn').text("Générer la dissertation");
175
  }
176
  }).trigger('change');
177
 
178
  $('#image-upload').change(function(e) {
179
  if (e.target.files && e.target.files[0]) {
180
  const reader = new FileReader();
181
+ reader.onload = (event) => $('#image-preview').attr('src', event.target.result).removeClass('hidden');
 
 
182
  reader.readAsDataURL(e.target.files[0]);
183
  }
184
  });
185
 
 
 
 
186
  // --- Logique de Génération en Streaming ---
187
  async function handleStreamedGeneration(url, options) {
188
+ Swal.fire({ title: 'Génération en cours...', html: 'Connexion au service IA...', allowOutsideClick: false, showConfirmButton: false, didOpen: () => Swal.showLoading() });
189
 
190
+ $('#thinking-container, #response, #copy-btn').addClass('hidden');
191
+ const thinkingDiv = $('#thinking-process');
192
  const responseDiv = $('#response > div');
193
+ thinkingDiv.html('');
194
  responseDiv.html('');
195
+ let fullResponseText = '', fullThinkingText = '', firstChunkReceived = false;
 
 
196
 
197
  try {
198
  const response = await fetch(url, options);
199
+ if (!response.ok) throw new Error(await response.text());
 
 
 
 
200
 
201
  const reader = response.body.getReader();
202
  const decoder = new TextDecoder();
203
+ let buffer = '';
204
 
205
  while (true) {
206
  const { value, done } = await reader.read();
207
+ if (!firstChunkReceived && (value || done)) {
208
+ Swal.close();
209
+ firstChunkReceived = true;
210
+ }
211
  if (done) break;
212
+
213
+ buffer += decoder.decode(value, { stream: true });
214
+ const lines = buffer.split('\n');
215
+ buffer = lines.pop();
216
+
217
+ for (const line of lines) {
218
+ if (line.trim() === '') continue;
219
+ try {
220
+ const data = JSON.parse(line);
221
+ if (data.type === 'thought') {
222
+ $('#thinking-container').removeClass('hidden');
223
+ fullThinkingText += data.content;
224
+ thinkingDiv.html(marked.parse(fullThinkingText));
225
+ } else if (data.type === 'answer') {
226
+ $('#response').removeClass('hidden');
227
+ fullResponseText += data.content;
228
+ responseDiv.html(marked.parse(fullResponseText));
229
+ } else if (data.type === 'error') {
230
+ throw new Error(data.content);
231
+ }
232
+ } catch (e) { console.error("Erreur JSON parse:", line); }
233
+ }
234
+ window.scrollTo(0, document.body.scrollHeight);
235
  }
236
 
 
237
  $('#copy-btn').removeClass('hidden');
238
  Toast.fire({ icon: 'success', title: 'Génération terminée !' });
239
+ const title = $('#type-select').val() === '3' ? "Analyse d'image" : $('#question').val().trim();
240
+ saveDissertation(title, fullResponseText);
241
 
242
  } catch (error) {
 
243
  Swal.fire({ icon: 'error', title: 'Erreur', text: error.message });
244
  }
245
  }
 
249
  const type = $('#type-select').val();
250
  const isDeepThink = $(this).attr('id') === 'deepthink-btn';
251
 
252
+ if (type === '3') {
253
  const imageFile = $('#image-upload')[0].files[0];
254
+ if (!imageFile) { Swal.fire('Erreur', 'Veuillez sélectionner une image.', 'error'); return; }
 
 
 
255
  const formData = new FormData();
256
  formData.append('image', imageFile);
257
  handleStreamedGeneration('/stream_philo_image', { method: 'POST', body: formData });
258
+ } else {
 
259
  const question = $('#question').val().trim();
260
+ if (!question) { Swal.fire('Erreur', 'Veuillez saisir un sujet.', 'error'); return; }
261
+ const data = { question, type, courseId: $('#course-select').val() || null };
 
 
 
 
 
 
 
262
  const url = isDeepThink ? '/stream_philo_deepthink' : '/stream_philo';
263
+ handleStreamedGeneration(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
264
+ }
265
+ });
266
+
267
+ // --- Fonctions existantes ---
268
+ function loadCourses() {
269
+ $.ajax({
270
+ url: '/api/philosophy/courses',
271
+ method: 'GET',
272
+ }).done(function(courses) {
273
+ const select = $('#course-select');
274
+ courses.forEach(course => {
275
+ const newOption = new Option(course.title, course.id, false, false);
276
+ $(newOption).data('author', course.author);
277
+ select.append(newOption);
278
  });
279
+ select.trigger('change');
280
+ }).fail(function() {
281
+ Toast.fire({ icon: 'error', title: 'Erreur de chargement des cours' });
282
+ });
283
+ }
284
+ loadCourses();
285
+
286
+ $('#course-select').on('change', function() {
287
+ const courseId = $(this).val();
288
+ if (courseId) {
289
+ $.ajax({
290
+ url: `/api/philosophy/courses/${courseId}`,
291
+ method: 'GET',
292
+ success: function(course) {
293
+ $('.course-meta').removeClass('hidden').addClass('animate-fadeIn');
294
+ $('#course-author span').text(`Pr. ${course.author}`);
295
+ $('#course-date span').text(new Date(course.updated_at).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' }));
296
+ }
297
+ });
298
+ } else {
299
+ $('.course-meta').addClass('hidden');
300
+ }
301
+ });
302
+
303
+ function saveDissertation(title, content) {
304
+ let saved = JSON.parse(localStorage.getItem('dissertations')) || [];
305
+ saved.unshift({ title, content, timestamp: Date.now() });
306
+ if(saved.length > 10) saved.pop(); // Limite à 10 sauvegardes
307
+ localStorage.setItem('dissertations', JSON.stringify(saved));
308
+ updateSavedDissertationsList();
309
+ }
310
+
311
+ function deleteDissertation(index) {
312
+ let saved = JSON.parse(localStorage.getItem('dissertations')) || [];
313
+ saved.splice(index, 1);
314
+ localStorage.setItem('dissertations', JSON.stringify(saved));
315
+ updateSavedDissertationsList();
316
+ Toast.fire({ icon: 'info', title: 'Dissertation supprimée' });
317
+ }
318
+
319
+ function updateSavedDissertationsList() {
320
+ const list = $('#dissertations-list');
321
+ list.empty();
322
+ let saved = JSON.parse(localStorage.getItem('dissertations')) || [];
323
+ if (saved.length === 0) {
324
+ list.append('<p class="text-gray-500">Aucune dissertation sauvegardée.</p>');
325
+ return;
326
  }
327
+ saved.forEach((diss, index) => {
328
+ const date = moment(diss.timestamp).format('LLL');
329
+ const title = diss.title.length > 50 ? diss.title.substring(0, 50) + '...' : diss.title;
330
+ const item = $(`
331
+ <div class="border border-gray-100 rounded-xl">
332
+ <button class="collapsible rounded-t-xl flex justify-between w-full p-4">
333
+ <span class="font-medium text-gray-800">${title}</span>
334
+ <span class="text-gray-500 text-sm">${date}</span>
335
+ </button>
336
+ <div class="content prose max-w-none p-4 border-t border-gray-200">
337
+ <div class="inner-content"></div>
338
+ <button class="delete-btn mt-4 text-sm text-red-600 hover:text-red-800" data-index="${index}">Supprimer</button>
339
+ </div>
340
+ </div>
341
+ `);
342
+ item.find('.inner-content').html(marked.parse(diss.content));
343
+ list.append(item);
344
+ });
345
+ }
346
+
347
+ $('#dissertations-list').on('click', '.collapsible', function() {
348
+ $(this).next('.content').slideToggle("fast");
349
+ $(this).parent().toggleClass("active");
350
+ });
351
+
352
+ $('#dissertations-list').on('click', '.delete-btn', function() {
353
+ const index = $(this).data('index');
354
+ deleteDissertation(index);
355
  });
356
 
357
+ $('#copy-btn').click(function() {
358
+ const textToCopy = $('#response > div').text();
359
+ navigator.clipboard.writeText(textToCopy).then(() => {
360
+ Toast.fire({ icon: 'success', title: 'Copié dans le presse-papiers!' });
361
+ }).catch(() => {
362
+ Toast.fire({ icon: 'error', title: 'Erreur de copie' });
363
+ });
364
+ });
365
 
366
  updateSavedDissertationsList();
367
  });