Docfile commited on
Commit
02203c9
·
verified ·
1 Parent(s): ab7c7ab

Update templates/philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +840 -679
templates/philosophie.html CHANGED
@@ -1,702 +1,863 @@
1
  <!DOCTYPE html>
2
- <html lang="fr" class="scroll-smooth">
3
  <head>
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/select2@4.1.0-rc.0/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
- <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
17
- <style>
18
- /* Styles pour le Glow Up */
19
- :root {
20
- font-family: 'Inter', sans-serif;
21
- /* Variables pour des transitions plus fluides */
22
- --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
23
- --transition-fast: all 0.15s ease-out;
24
- }
25
-
26
- /* Optimisations pour le scroll fluide */
27
- html {
28
- scroll-behavior: smooth;
29
- -webkit-overflow-scrolling: touch;
30
- }
31
-
32
- body {
33
- /* Prévenir le reflow lors de la génération */
34
- will-change: scroll-position;
35
- /* Améliorer les performances sur mobile */
36
- -webkit-font-smoothing: antialiased;
37
- -moz-osx-font-smoothing: grayscale;
38
- }
39
-
40
- .collapsible {
41
- cursor: pointer;
42
- padding: 1rem;
43
- width: 100%;
44
- border: none;
45
- text-align: left;
46
- outline: none;
47
- transition: var(--transition-fast);
48
- /* Optimiser pour les interactions tactiles */
49
- touch-action: manipulation;
50
- -webkit-tap-highlight-color: transparent;
51
- }
52
-
53
- .collapsible:hover {
54
- background-color: #f9fafb;
55
- }
56
-
57
- .content {
58
- padding: 0 1rem;
59
- display: none;
60
- overflow: hidden;
61
- background-color: white;
62
- /* Transition plus fluide pour l'ouverture */
63
- transition: var(--transition-smooth);
64
- }
65
-
66
- /* Styles pour la lisibilité du Markdown généré */
67
- .prose {
68
- max-width: 100% !important;
69
- /* Optimiser le rendu du texte */
70
- text-rendering: optimizeLegibility;
71
- }
72
-
73
- #response .prose {
74
- color: #374151;
75
- word-wrap: break-word;
76
- overflow-wrap: break-word;
77
- /* Prévenir les débordements sur mobile */
78
- hyphens: auto;
79
- -webkit-hyphens: auto;
80
- -moz-hyphens: auto;
81
- }
82
-
83
- .prose p, .prose ul, .prose ol, .prose li {
84
- line-height: 1.75;
85
- }
86
-
87
- .prose h1, .prose h2, .prose h3 {
88
- margin-top: 1.5em;
89
- margin-bottom: 0.8em;
90
- line-height: 1.3;
91
- }
92
-
93
- /* Animation fadeIn optimisée */
94
- .animate-fadeIn {
95
- animation: fadeIn 0.4s ease-out forwards;
96
- /* Utiliser GPU pour l'animation */
97
- transform: translateZ(0);
98
- will-change: opacity, transform;
99
- }
100
-
101
- @keyframes fadeIn {
102
- from {
103
- opacity: 0;
104
- transform: translateY(15px) translateZ(0);
105
- }
106
- to {
107
- opacity: 1;
108
- transform: translateY(0) translateZ(0);
109
- }
110
- }
111
-
112
- /* Styles pour Select2 */
113
- .select2-container--default .select2-selection--single {
114
- border: 1px solid #d1d5db;
115
- border-radius: 0.75rem;
116
- height: auto;
117
- padding: 0.75rem 1rem;
118
- background-color: white;
119
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
120
- }
121
-
122
- .select2-container--default .select2-selection--single .select2-selection__rendered {
123
- color: #111827;
124
- line-height: inherit;
125
- padding-right: 1.5rem;
126
- }
127
-
128
- .select2-results__option .course-author {
129
- font-size: 0.875rem;
130
- color: #6b7280;
131
- display: block;
132
- margin-top: 0.1rem;
133
- }
134
-
135
- .select2-dropdown {
136
- border-radius: 0.75rem;
137
- border: 1px solid #d1d5db;
138
- box-shadow: 0 4px 6px -1px rgba(0,0,0,.1);
139
- }
140
-
141
- /* Styles pour l'aperçu de l'image */
142
- #image-preview {
143
- max-height: 200px;
144
- border-radius: 0.75rem;
145
- box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1);
146
- }
147
-
148
- /* Cacher le marqueur par défaut de <details> */
149
- summary {
150
- list-style: none;
151
- }
152
-
153
- summary::-webkit-details-marker {
154
- display: none;
155
- }
156
-
157
- /* Optimisations mobiles */
158
- @media (max-width: 640px) {
159
- /* Réduire les marges sur mobile pour plus d'espace */
160
- .max-w-3xl {
161
- max-width: 100%;
162
- margin-left: 0.5rem;
163
- margin-right: 0.5rem;
164
- }
165
-
166
- /* Optimiser l'affichage des sections de contenu */
167
- #response, #thinking-wrapper {
168
- /* Éviter les débordements horizontaux */
169
- overflow-x: hidden;
170
- }
171
-
172
- /* Améliorer la lisibilité du texte généré */
173
- .prose {
174
- font-size: 0.95rem;
175
- line-height: 1.6;
176
- }
177
-
178
- /* Réduire l'animation pour économiser la batterie */
179
- .animate-fadeIn {
180
- animation-duration: 0.2s;
181
- }
182
- }
183
-
184
- /* Conteneur de génération avec scroll optimisé */
185
- .generation-container {
186
- /* Conteneur stable pour éviter les recalculs de layout */
187
- contain: layout style;
188
- /* Optimiser les performances de scroll */
189
- transform: translateZ(0);
190
- }
191
-
192
- /* Améliorer les performances du textarea */
193
- textarea {
194
- /* Éviter les reflows pendant la saisie */
195
- resize: none;
196
- /* Optimiser sur mobile */
197
- -webkit-appearance: none;
198
- }
199
-
200
- /* Optimiser les boutons pour le tactile */
201
- button {
202
- touch-action: manipulation;
203
- -webkit-tap-highlight-color: transparent;
204
- transition: var(--transition-fast);
205
- }
206
-
207
- /* Scroll fluide pour les éléments avec beaucoup de contenu */
208
- .content-scrollable {
209
- -webkit-overflow-scrolling: touch;
210
- scroll-behavior: smooth;
211
- }
212
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  </head>
214
- <body class="bg-gray-50 text-gray-900">
215
- <!-- Navbar -->
216
- <nav class="bg-white/90 backdrop-blur-lg border-b border-gray-200 fixed w-full z-50">
217
- <div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
218
- <div class="flex justify-between items-center h-16">
219
- <div class="flex items-center space-x-3">
220
- <svg class="h-8 w-auto text-violet-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
221
- <path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.311a15.045 15.045 0 01-7.5 0C4.508 19.663 2.25 15.184 2.25 10.5 2.25 5.816 5.816 2.25 10.5 2.25c4.684 0 8.25 3.566 8.25 8.25 0 4.684-2.258 9.163-5.25 11.584z" />
222
- </svg>
223
- <div class="text-xl font-bold text-gray-800">Mariam AI</div>
224
- </div>
225
- <div class="flex items-center">
226
- <span class="inline-flex items-center px-3 py-1.5 rounded-full text-xs font-medium bg-violet-100 text-violet-800">
227
- Assistant Philosophique
228
- </span>
229
- </div>
230
- </div>
231
- </div>
232
- </nav>
233
-
234
- <!-- Main Content -->
235
- <main class="pt-24 pb-16 px-4 sm:px-6 lg:px-8">
236
- <div class="max-w-3xl mx-auto">
237
- <div class="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden">
238
- <div class="p-6 sm:p-8">
239
- <div class="text-center">
240
- <h1 class="text-3xl font-bold text-gray-900">Gen'Dissertation</h1>
241
- <p class="mt-2 text-md text-gray-600">Créez des dissertations et analyses pertinentes et structurées.</p>
242
- </div>
243
- </div>
244
-
245
- <div class="p-6 sm:p-8 border-t border-gray-200 space-y-6">
246
- <!-- Type Selection -->
247
- <div class="space-y-2">
248
- <label for="type-select" class="block text-sm font-medium text-gray-700">Type de travail</label>
249
- <select id="type-select" class="w-full rounded-xl border-gray-300 shadow-sm focus:border-violet-500 focus:ring-violet-500 bg-white py-3 px-4">
250
- <option value="1">Sujet Type 1</option>
251
- <option value="2">Sujet Type 2 (Citation)</option>
252
- <option value="3">Sujet Type 3</option>
253
- </select>
254
- </div>
255
-
256
- <!-- Conteneur pour les champs texte (visible par défaut) -->
257
- <div id="text-input-container">
258
- <!-- Course Selection -->
259
- <div class="space-y-2">
260
- <label for="course-select" class="block text-sm font-medium text-gray-700">Utiliser un cours comme contexte</label>
261
- <select id="course-select" class="w-full">
262
- <option value="">Choisir un cours...</option>
263
- </select>
264
- </div>
265
- <!-- Question Input -->
266
- <div class="space-y-2 mt-4">
267
- <label for="question" class="block text-sm font-medium text-gray-700">Sujet de dissertation</label>
268
- <textarea id="question" rows="4" class="w-full rounded-xl border-gray-300 shadow-sm focus:border-violet-500 focus:ring-violet-500 resize-none bg-white py-3 px-4" placeholder="Saisissez votre sujet de dissertation... ex: 'La liberté consiste-t-elle à faire tout ce que l'on veut ?'"></textarea>
269
- </div>
270
- </div>
271
-
272
- <!-- Conteneur pour l'upload d'image (caché par défaut) -->
273
- <div id="image-input-container" class="hidden">
274
- <div class="space-y-2">
275
- <label for="image-upload" class="block text-sm font-medium text-gray-700">Charger un document pour analyse</label>
276
- <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 cursor-pointer"/>
277
- <div class="mt-4 flex justify-center"><img id="image-preview" src="" alt="Aperçu de l'image" class="hidden"/></div>
278
- </div>
279
- </div>
280
-
281
- <!-- Submit Button -->
282
- <div class="pt-4">
283
- <button id="submit-btn" class="w-full flex items-center justify-center py-3.5 px-6 rounded-xl bg-violet-600 text-white font-semibold shadow-md shadow-violet-200 hover:bg-violet-700 transform hover:-translate-y-0.5 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-violet-500 focus:ring-offset-2">
284
- <svg class="h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm.75-13a.75.75 0 00-1.5 0v5c0 .414.336.75.75.75h4a.75.75 0 000-1.5h-3.25V5z" clip-rule="evenodd" /></svg>
285
- Générer la dissertation
286
- </button>
287
- </div>
288
- </div>
289
- </div>
290
-
291
- <!-- Thinking Process Section (Collapsible) -->
292
- <div id="thinking-wrapper" class="hidden mt-8 generation-container">
293
- <details id="thinking-container" class="bg-white border border-gray-200 rounded-xl shadow-sm">
294
- <summary class="flex justify-between items-center p-4 cursor-pointer">
295
- <h3 class="text-md font-semibold text-gray-700">Processus de pensée de l'IA</h3>
296
- <svg class="h-5 w-5 text-gray-500 transition-transform transform open:rotate-180" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
297
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
298
- </svg>
299
- </summary>
300
- <div id="thinking-process" class="p-4 border-t border-gray-200 text-sm text-gray-600 prose prose-sm max-w-none max-h-64 overflow-y-auto content-scrollable">
301
- <!-- Le contenu du processus de pensée sera injecté ici -->
302
- </div>
303
- </details>
304
- </div>
305
-
306
- <!-- Response Section -->
307
- <div id="response" class="hidden mt-6 generation-container">
308
- <div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6 sm:p-8 prose prose-violet max-w-none">
309
- <!-- Le contenu de la réponse sera injecté ici -->
310
- </div>
311
- </div>
312
-
313
- <!-- Action Buttons after generation -->
314
- <div id="action-buttons" class="hidden mt-6 grid grid-cols-1 sm:grid-cols-2 gap-4">
315
- <button id="copy-btn" class="w-full flex items-center justify-center py-3 px-6 rounded-xl bg-gray-100 text-gray-800 font-medium border border-gray-200 hover:bg-gray-200 transition-colors focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2">
316
- <svg class="h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" /></svg>
317
- Copier le texte
318
- </button>
319
- </div>
320
-
321
- <!-- Saved Dissertations Section -->
322
- <div class="mt-12">
323
- <h3 class="text-xl font-bold text-gray-800 mb-4">Historique</h3>
324
- <div id="dissertations-list" class="space-y-3">
325
- <!-- La liste des dissertations sauvegardées sera injectée ici -->
326
- </div>
327
- </div>
328
- </div>
329
- </main>
330
-
331
- <!-- Bouton flottant DeepThink -->
332
- <button id="deepthink-btn" class="fixed bottom-6 right-6 z-50 flex items-center bg-indigo-600 text-white px-5 py-3 rounded-full shadow-lg hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-transform hover:scale-105">
333
- <svg class="h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.898 20.562L16.25 22.5l-.648-1.938a3.375 3.375 0 00-2.672-2.672L11.25 18l1.938-.648a3.375 3.375 0 002.672-2.672L16.25 13.5l.648 1.938a3.375 3.375 0 002.672 2.672L21.75 18l-1.938.648a3.375 3.375 0 00-2.672 2.672z" /></svg>
334
- DeepThink
335
- </button>
336
-
337
- <script>
338
- $(document).ready(function() {
339
- // --- Initialisations ---
340
- $('#course-select').select2({
341
- placeholder: 'Optionnel : choisir un cours...',
342
- allowClear: true,
343
- templateResult: function (course) { if (!course.id) { return course.text; } return $(`<span>${course.text}</span><span class="course-author">Pr. ${$(course.element).data('author')}</span>`); },
344
- templateSelection: function (course) { return course.text; },
345
- });
346
- marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false });
347
- moment.locale('fr');
348
- const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true });
349
-
350
- // Variables pour optimiser le scroll
351
- let isScrolling = false;
352
- let scrollTimer = null;
353
- let lastScrollTime = 0;
354
 
355
- // Fonction de scroll optimisée avec throttling
356
- function smoothScrollToBottom(force = false) {
357
- const now = Date.now();
358
-
359
- // Throttling : éviter trop d'appels de scroll
360
- if (!force && now - lastScrollTime < 100) {
361
- return;
362
- }
363
-
364
- lastScrollTime = now;
365
-
366
- // Annuler le timer précédent s'il existe
367
- if (scrollTimer) {
368
- clearTimeout(scrollTimer);
369
- }
370
-
371
- // Utiliser requestAnimationFrame pour un scroll plus fluide
372
- if (!isScrolling) {
373
- isScrolling = true;
374
-
375
- requestAnimationFrame(() => {
376
- // Scroll fluide vers le bas avec une marge
377
- const targetScrollTop = Math.max(0, document.documentElement.scrollHeight - window.innerHeight - 50);
378
-
379
- // Utiliser la méthode native pour de meilleures performances
380
- window.scrollTo({
381
- top: targetScrollTop,
382
- behavior: 'smooth'
383
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
 
385
- // Marquer comme terminé après l'animation
386
- scrollTimer = setTimeout(() => {
387
- isScrolling = false;
388
- }, 200);
389
- });
390
- }
391
- }
392
-
393
- // --- Gestion de l'interface ---
394
- $('#type-select').change(function() {
395
- const type = $(this).val();
396
- // Cacher les sections de résultat lors du changement de type
397
- $('#thinking-wrapper, #response, #action-buttons').addClass('hidden');
398
-
399
- if (type === '3') { // Sujet Type 3 (anciennement Analyse d'image)
400
- $('#text-input-container').hide();
401
- $('#image-input-container').show().addClass('animate-fadeIn');
402
- $('#deepthink-btn').hide();
403
- $('#submit-btn').html('<svg class="h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" /></svg>Analyser le sujet');
404
- $('label[for="image-upload"]').text("Charger un document pour analyse (image, etc.)");
405
- } else { // Dissertation texte
406
- $('#text-input-container').show().addClass('animate-fadeIn');
407
- $('#image-input-container').hide();
408
- $('#deepthink-btn').show();
409
- $('#submit-btn').html('<svg class="h-5 w-5 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm.75-13a.75.75 0 00-1.5 0v5c0 .414.336.75.75.75h4a.75.75 0 000-1.5h-3.25V5z" clip-rule="evenodd" /></svg>Générer la dissertation');
410
- }
411
- }).trigger('change');
412
-
413
- $('#image-upload').change(function(e) {
414
- if (e.target.files && e.target.files[0]) {
415
- const reader = new FileReader();
416
- reader.onload = (event) => $('#image-preview').attr('src', event.target.result).removeClass('hidden').addClass('animate-fadeIn');
417
- reader.readAsDataURL(e.target.files[0]);
418
- }
419
- });
420
-
421
- // --- Logique de Génération en Streaming Optimisée ---
422
- async function handleStreamedGeneration(url, options) {
423
- Swal.fire({ title: 'Génération en cours...', html: 'Connexion au service IA...', allowOutsideClick: false, showConfirmButton: false, didOpen: () => Swal.showLoading() });
424
-
425
- $('#thinking-wrapper, #response, #action-buttons').addClass('hidden');
426
- $('#thinking-container').prop('open', false);
427
- const thinkingDiv = $('#thinking-process');
428
- const responseDiv = $('#response > div');
429
- thinkingDiv.html('');
430
- responseDiv.html('');
431
- let fullResponseText = '', fullThinkingText = '', firstChunkReceived = false;
432
-
433
- // Compteur pour optimiser les updates
434
- let chunkCount = 0;
435
- const updateFrequency = 3; // Mettre à jour tous les 3 chunks pour réduire les recalculs
436
-
437
- try {
438
- const response = await fetch(url, options);
439
- if (!response.ok) throw new Error(await response.text());
440
-
441
- const reader = response.body.getReader();
442
- const decoder = new TextDecoder();
443
- let buffer = '';
444
-
445
- while (true) {
446
- const { value, done } = await reader.read();
447
- if (!firstChunkReceived && (value || done)) {
448
- Swal.close();
449
- firstChunkReceived = true;
450
  }
451
- if (done) break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
 
453
- buffer += decoder.decode(value, { stream: true });
454
- const lines = buffer.split('\n');
455
- buffer = lines.pop();
456
 
457
- let shouldUpdate = false;
 
 
 
 
458
 
459
- for (const line of lines) {
460
- if (line.trim() === '') continue;
461
- try {
462
- const data = JSON.parse(line.trim());
463
- chunkCount++;
464
-
465
- if (data.type === 'thought') {
466
- if (!$('#thinking-wrapper').is(':visible')) {
467
- $('#thinking-wrapper').removeClass('hidden').addClass('animate-fadeIn');
468
- }
469
- fullThinkingText += data.content;
470
- shouldUpdate = true;
471
- } else if (data.type === 'answer') {
472
- if (!$('#response').is(':visible')) {
473
- $('#response').removeClass('hidden').addClass('animate-fadeIn');
474
- }
475
- fullResponseText += data.content;
476
- shouldUpdate = true;
477
- } else if (data.type === 'error') {
478
- throw new Error(data.content);
479
- }
480
- } catch (e) {
481
- console.error("Erreur JSON parse:", line, e);
482
- }
483
  }
484
-
485
- // Mettre à jour le DOM moins fréquemment pour de meilleures performances
486
- if (shouldUpdate && (chunkCount % updateFrequency === 0 || done)) {
487
- if (fullThinkingText) {
488
- thinkingDiv.html(marked.parse(fullThinkingText));
489
- }
490
- if (fullResponseText) {
491
- responseDiv.html(marked.parse(fullResponseText));
 
 
 
 
 
 
 
 
 
 
 
 
492
  }
493
-
494
- // Scroll optimisé vers le bas
495
- smoothScrollToBottom();
 
496
  }
497
  }
498
-
499
- // Mise à jour finale
500
- if (fullThinkingText) {
501
- thinkingDiv.html(marked.parse(fullThinkingText));
502
- }
503
- if (fullResponseText) {
504
- responseDiv.html(marked.parse(fullResponseText));
 
 
 
 
 
 
 
 
 
 
 
 
505
  }
506
 
507
- $('#action-buttons').removeClass('hidden').addClass('animate-fadeIn');
508
- Toast.fire({ icon: 'success', title: 'Génération terminée !' });
509
-
510
- let title;
511
- if ($('#type-select').val() === '3') {
512
- const fileName = $('#image-upload')[0].files[0]?.name || "Analyse de document";
513
- title = `Analyse (Sujet Type 3): ${fileName}`;
514
- } else {
515
- title = $('#question').val().trim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
  }
517
- saveDissertation(title, fullResponseText);
518
-
519
- // Scroll final pour s'assurer que tout est visible
520
- smoothScrollToBottom(true);
521
-
522
- } catch (error) {
523
- Swal.fire({ icon: 'error', title: 'Erreur', text: error.message || "Une erreur inconnue est survenue." });
524
- }
525
- }
526
-
527
- // --- Gestion des Clics ---
528
- $('#submit-btn, #deepthink-btn').click(function() {
529
- const type = $('#type-select').val();
530
- const isDeepThink = $(this).attr('id') === 'deepthink-btn';
531
-
532
- if (type === '3') {
533
- const imageFile = $('#image-upload')[0].files[0];
534
- if (!imageFile) { Swal.fire('Erreur', 'Veuillez sélectionner un document.', 'error'); return; }
535
- const formData = new FormData();
536
- formData.append('image', imageFile);
537
- handleStreamedGeneration('/stream_philo_image', { method: 'POST', body: formData });
538
- } else {
539
- const question = $('#question').val().trim();
540
- if (!question) { Swal.fire('Erreur', 'Veuillez saisir un sujet.', 'error'); return; }
541
- const data = { question, type, courseId: $('#course-select').val() || null };
542
- const url = isDeepThink ? '/stream_philo_deepthink' : '/stream_philo';
543
- handleStreamedGeneration(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
544
- }
545
- });
546
-
547
- // --- Fonctions de gestion de l'historique ---
548
- function loadCourses() {
549
- $.ajax({
550
- url: '/api/philosophy/courses',
551
- method: 'GET',
552
- }).done(function(courses) {
553
- const select = $('#course-select');
554
- courses.forEach(course => {
555
- const newOption = new Option(course.title, course.id, false, false);
556
- $(newOption).data('author', course.author);
557
- select.append(newOption);
558
- });
559
- select.trigger('change');
560
- }).fail(function() {
561
- Toast.fire({ icon: 'error', title: 'Erreur de chargement des cours' });
562
- });
563
- }
564
- loadCourses();
565
-
566
- function saveDissertation(title, content) {
567
- if (!title || !content) return;
568
- let saved = JSON.parse(localStorage.getItem('dissertations')) || [];
569
- saved.unshift({ title, content, timestamp: Date.now() });
570
- if(saved.length > 10) saved.pop();
571
- localStorage.setItem('dissertations', JSON.stringify(saved));
572
- updateSavedDissertationsList();
573
- }
574
-
575
- function deleteDissertation(index) {
576
- let saved = JSON.parse(localStorage.getItem('dissertations')) || [];
577
- saved.splice(index, 1);
578
- localStorage.setItem('dissertations', JSON.stringify(saved));
579
- updateSavedDissertationsList();
580
- Toast.fire({ icon: 'info', title: 'Dissertation supprimée' });
581
- }
582
-
583
- function updateSavedDissertationsList() {
584
- const list = $('#dissertations-list');
585
- list.empty();
586
- let saved = JSON.parse(localStorage.getItem('dissertations')) || [];
587
- if (saved.length === 0) {
588
- list.append('<p class="text-gray-500 text-center py-4">Aucune dissertation dans l\'historique.</p>');
589
- return;
590
- }
591
- saved.forEach((diss, index) => {
592
- const date = moment(diss.timestamp).fromNow();
593
- const title = diss.title.length > 60 ? diss.title.substring(0, 60) + '...' : diss.title;
594
- const item = $(`
595
- <div class="border border-gray-200 bg-white rounded-xl shadow-sm transition-shadow hover:shadow-md">
596
- <button class="collapsible rounded-t-xl w-full text-left">
597
- <div class="flex justify-between items-center w-full p-4">
598
- <span class="font-medium text-gray-800">${title}</span>
599
- <span class="text-gray-500 text-sm flex-shrink-0 ml-4">${date}</span>
600
  </div>
601
- </button>
602
- <div class="content prose max-w-none border-t border-gray-200">
603
- <div class="inner-content p-4 content-scrollable"></div>
604
- <div class="p-4 border-t border-gray-200 bg-gray-50 rounded-b-xl">
605
- <button class="delete-btn text-sm text-red-600 hover:text-red-800 font-medium" data-index="${index}">Supprimer</button>
606
- </div>
607
- </div>
608
- </div>
609
- `);
610
- item.find('.inner-content').html(marked.parse(diss.content));
611
- list.append(item);
612
- });
613
- }
614
-
615
- // Optimiser l'ouverture des dissertations avec une transition plus fluide
616
- $('#dissertations-list').on('click', '.collapsible', function() {
617
- const content = $(this).next('.content');
618
- const isVisible = content.is(':visible');
619
-
620
- // Animation plus fluide avec easing
621
- content.stop(true, true).slideToggle({
622
- duration: 300,
623
- easing: 'swing',
624
- start: function() {
625
- $(this).css('overflow', 'hidden');
626
- },
627
- complete: function() {
628
- $(this).css('overflow', isVisible ? 'hidden' : 'visible');
629
  }
630
- });
631
-
632
- $(this).toggleClass("active");
633
- });
634
-
635
- $('#dissertations-list').on('click', '.delete-btn', function(e) {
636
- e.stopPropagation(); // Empêcher le collapsible de se fermer
637
- const index = $(this).data('index');
638
- deleteDissertation(index);
639
- });
640
-
641
- $('#copy-btn').click(function() {
642
- const htmlToCopy = $('#response > div').html();
643
- // Pour copier le Markdown brut (plus fidèle)
644
- const textToCopy = new DOMParser().parseFromString(htmlToCopy, 'text/html').body.textContent || "";
645
- navigator.clipboard.writeText(textToCopy).then(() => {
646
- Toast.fire({ icon: 'success', title: 'Copié dans le presse-papiers!' });
647
- }).catch(() => {
648
- Toast.fire({ icon: 'error', title: 'Erreur de copie' });
649
- });
650
- });
651
 
652
- // Optimisation pour les événements de scroll sur mobile
653
- let ticking = false;
654
-
655
- function updateScrollPosition() {
656
- // Code pour gérer les changements de position de scroll si nécessaire
657
- ticking = false;
658
- }
659
-
660
- window.addEventListener('scroll', function() {
661
- if (!ticking) {
662
- requestAnimationFrame(updateScrollPosition);
663
- ticking = true;
664
- }
665
- }, { passive: true });
666
-
667
- // Optimisation du resize pour mobile
668
- let resizeTimer;
669
- window.addEventListener('resize', function() {
670
- clearTimeout(resizeTimer);
671
- resizeTimer = setTimeout(function() {
672
- // Recalculer les dimensions si nécessaire
673
- if (window.innerWidth <= 640) {
674
- // Optimisations spécifiques mobile
675
- $('body').addClass('mobile-optimized');
676
- } else {
677
- $('body').removeClass('mobile-optimized');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
678
  }
679
- }, 250);
680
- }, { passive: true });
681
-
682
- // Optimisation tactile pour mobile
683
- if ('ontouchstart' in window) {
684
- $('body').addClass('touch-device');
685
-
686
- // Améliorer les interactions tactiles
687
- $(document).on('touchstart', 'button, .collapsible', function() {
688
- $(this).addClass('touch-active');
689
- });
690
-
691
- $(document).on('touchend', 'button, .collapsible', function() {
692
- const element = $(this);
693
- setTimeout(() => element.removeClass('touch-active'), 150);
694
- });
695
- }
696
 
697
- // Init
698
- updateSavedDissertationsList();
699
- });
700
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  </body>
702
  </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="fr">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Générateur de Dissertations Philosophiques - Mariam AI</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Kalam:wght@400;700&display=swap" rel="stylesheet">
10
+ <style>
11
+ * {
12
+ margin: 0;
13
+ padding: 0;
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ :root {
18
+ --primary: #667eea;
19
+ --primary-dark: #5a6fd8;
20
+ --secondary: #764ba2;
21
+ --accent: #f093fb;
22
+ --background: #0f0f23;
23
+ --surface: #1a1a2e;
24
+ --surface-light: #16213e;
25
+ --text-primary: #ffffff;
26
+ --text-secondary: #a0a9c1;
27
+ --success: #10b981;
28
+ --error: #ef4444;
29
+ --warning: #f59e0b;
30
+ }
31
+
32
+ body {
33
+ font-family: 'Inter', sans-serif;
34
+ background: var(--background);
35
+ color: var(--text-primary);
36
+ line-height: 1.6;
37
+ overflow-x: hidden;
38
+ }
39
+
40
+ .background-animation {
41
+ position: fixed;
42
+ top: 0;
43
+ left: 0;
44
+ width: 100%;
45
+ height: 100%;
46
+ z-index: -1;
47
+ background: linear-gradient(-45deg, #667eea, #764ba2, #667eea, #764ba2);
48
+ background-size: 400% 400%;
49
+ animation: gradientShift 15s ease infinite;
50
+ opacity: 0.1;
51
+ }
52
+
53
+ @keyframes gradientShift {
54
+ 0% { background-position: 0% 50%; }
55
+ 50% { background-position: 100% 50%; }
56
+ 100% { background-position: 0% 50%; }
57
+ }
58
+
59
+ .container {
60
+ max-width: 1200px;
61
+ margin: 0 auto;
62
+ padding: 0 20px;
63
+ }
64
+
65
+ .header {
66
+ text-align: center;
67
+ padding: 60px 0 40px;
68
+ position: relative;
69
+ }
70
+
71
+ .header::before {
72
+ content: '';
73
+ position: absolute;
74
+ top: 0;
75
+ left: 50%;
76
+ transform: translateX(-50%);
77
+ width: 100px;
78
+ height: 4px;
79
+ background: linear-gradient(90deg, var(--primary), var(--accent));
80
+ border-radius: 2px;
81
+ }
82
+
83
+ .header h1 {
84
+ font-family: 'Kalam', cursive;
85
+ font-size: 3rem;
86
+ font-weight: 700;
87
+ background: linear-gradient(135deg, var(--primary), var(--accent));
88
+ -webkit-background-clip: text;
89
+ background-clip: text;
90
+ -webkit-text-fill-color: transparent;
91
+ margin-bottom: 16px;
92
+ animation: fadeInUp 1s ease;
93
+ }
94
+
95
+ .header p {
96
+ font-size: 1.2rem;
97
+ color: var(--text-secondary);
98
+ animation: fadeInUp 1s ease 0.2s both;
99
+ }
100
+
101
+ @keyframes fadeInUp {
102
+ from {
103
+ opacity: 0;
104
+ transform: translateY(30px);
105
+ }
106
+ to {
107
+ opacity: 1;
108
+ transform: translateY(0);
109
+ }
110
+ }
111
+
112
+ .main-content {
113
+ display: grid;
114
+ grid-template-columns: 1fr;
115
+ gap: 40px;
116
+ margin-bottom: 60px;
117
+ }
118
+
119
+ @media (min-width: 1024px) {
120
+ .main-content {
121
+ grid-template-columns: 400px 1fr;
122
+ gap: 60px;
123
+ }
124
+ }
125
+
126
+ .form-section {
127
+ background: var(--surface);
128
+ border-radius: 20px;
129
+ padding: 30px;
130
+ border: 1px solid rgba(102, 126, 234, 0.2);
131
+ backdrop-filter: blur(10px);
132
+ animation: fadeInLeft 1s ease 0.4s both;
133
+ }
134
+
135
+ @keyframes fadeInLeft {
136
+ from {
137
+ opacity: 0;
138
+ transform: translateX(-30px);
139
+ }
140
+ to {
141
+ opacity: 1;
142
+ transform: translateX(0);
143
+ }
144
+ }
145
+
146
+ .form-group {
147
+ margin-bottom: 24px;
148
+ }
149
+
150
+ .form-group label {
151
+ display: block;
152
+ margin-bottom: 8px;
153
+ font-weight: 600;
154
+ color: var(--text-primary);
155
+ font-size: 0.95rem;
156
+ }
157
+
158
+ .form-input, .form-select, .form-textarea {
159
+ width: 100%;
160
+ padding: 12px 16px;
161
+ background: var(--surface-light);
162
+ border: 2px solid rgba(102, 126, 234, 0.3);
163
+ border-radius: 12px;
164
+ color: var(--text-primary);
165
+ font-size: 16px;
166
+ transition: all 0.3s ease;
167
+ resize: vertical;
168
+ }
169
+
170
+ .form-input:focus, .form-select:focus, .form-textarea:focus {
171
+ outline: none;
172
+ border-color: var(--primary);
173
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
174
+ transform: translateY(-2px);
175
+ }
176
+
177
+ .form-textarea {
178
+ min-height: 120px;
179
+ }
180
+
181
+ .radio-group {
182
+ display: grid;
183
+ gap: 12px;
184
+ }
185
+
186
+ .radio-item {
187
+ position: relative;
188
+ cursor: pointer;
189
+ }
190
+
191
+ .radio-item input[type="radio"] {
192
+ display: none;
193
+ }
194
+
195
+ .radio-label {
196
+ display: block;
197
+ padding: 16px;
198
+ background: var(--surface-light);
199
+ border: 2px solid rgba(102, 126, 234, 0.3);
200
+ border-radius: 12px;
201
+ transition: all 0.3s ease;
202
+ position: relative;
203
+ padding-left: 48px;
204
+ }
205
+
206
+ .radio-label::before {
207
+ content: '';
208
+ position: absolute;
209
+ left: 16px;
210
+ top: 50%;
211
+ transform: translateY(-50%);
212
+ width: 20px;
213
+ height: 20px;
214
+ border: 2px solid rgba(102, 126, 234, 0.5);
215
+ border-radius: 50%;
216
+ transition: all 0.3s ease;
217
+ }
218
+
219
+ .radio-item input[type="radio"]:checked + .radio-label {
220
+ border-color: var(--primary);
221
+ background: rgba(102, 126, 234, 0.1);
222
+ }
223
+
224
+ .radio-item input[type="radio"]:checked + .radio-label::before {
225
+ border-color: var(--primary);
226
+ background: var(--primary);
227
+ box-shadow: inset 0 0 0 3px var(--surface-light);
228
+ }
229
+
230
+ .radio-label h4 {
231
+ color: var(--text-primary);
232
+ margin-bottom: 4px;
233
+ font-weight: 600;
234
+ }
235
+
236
+ .radio-label p {
237
+ color: var(--text-secondary);
238
+ font-size: 0.9rem;
239
+ }
240
+
241
+ .generate-btn {
242
+ width: 100%;
243
+ padding: 16px;
244
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
245
+ border: none;
246
+ border-radius: 12px;
247
+ color: white;
248
+ font-size: 1.1rem;
249
+ font-weight: 600;
250
+ cursor: pointer;
251
+ transition: all 0.3s ease;
252
+ position: relative;
253
+ overflow: hidden;
254
+ }
255
+
256
+ .generate-btn:hover {
257
+ transform: translateY(-2px);
258
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
259
+ }
260
+
261
+ .generate-btn:active {
262
+ transform: translateY(0);
263
+ }
264
+
265
+ .generate-btn:disabled {
266
+ opacity: 0.7;
267
+ cursor: not-allowed;
268
+ transform: none;
269
+ }
270
+
271
+ .btn-loading {
272
+ animation: pulse 2s ease-in-out infinite;
273
+ }
274
+
275
+ @keyframes pulse {
276
+ 0% { opacity: 1; }
277
+ 50% { opacity: 0.7; }
278
+ 100% { opacity: 1; }
279
+ }
280
+
281
+ .result-section {
282
+ background: var(--surface);
283
+ border-radius: 20px;
284
+ padding: 30px;
285
+ border: 1px solid rgba(102, 126, 234, 0.2);
286
+ backdrop-filter: blur(10px);
287
+ animation: fadeInRight 1s ease 0.6s both;
288
+ min-height: 400px;
289
+ display: flex;
290
+ flex-direction: column;
291
+ }
292
+
293
+ @keyframes fadeInRight {
294
+ from {
295
+ opacity: 0;
296
+ transform: translateX(30px);
297
+ }
298
+ to {
299
+ opacity: 1;
300
+ transform: translateX(0);
301
+ }
302
+ }
303
+
304
+ .result-header {
305
+ display: flex;
306
+ justify-content: space-between;
307
+ align-items: center;
308
+ margin-bottom: 24px;
309
+ padding-bottom: 16px;
310
+ border-bottom: 1px solid rgba(102, 126, 234, 0.2);
311
+ }
312
+
313
+ .result-title {
314
+ font-size: 1.5rem;
315
+ font-weight: 700;
316
+ color: var(--text-primary);
317
+ }
318
+
319
+ .download-btn {
320
+ padding: 10px 20px;
321
+ background: linear-gradient(135deg, var(--success), #059669);
322
+ border: none;
323
+ border-radius: 8px;
324
+ color: white;
325
+ font-weight: 600;
326
+ cursor: pointer;
327
+ transition: all 0.3s ease;
328
+ font-size: 0.9rem;
329
+ }
330
+
331
+ .download-btn:hover {
332
+ transform: translateY(-2px);
333
+ box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
334
+ }
335
+
336
+ .loading-state {
337
+ display: flex;
338
+ flex-direction: column;
339
+ align-items: center;
340
+ justify-content: center;
341
+ flex-grow: 1;
342
+ text-align: center;
343
+ }
344
+
345
+ .spinner {
346
+ width: 60px;
347
+ height: 60px;
348
+ border: 4px solid rgba(102, 126, 234, 0.3);
349
+ border-top: 4px solid var(--primary);
350
+ border-radius: 50%;
351
+ animation: spin 1s linear infinite;
352
+ margin-bottom: 20px;
353
+ }
354
+
355
+ @keyframes spin {
356
+ 0% { transform: rotate(0deg); }
357
+ 100% { transform: rotate(360deg); }
358
+ }
359
+
360
+ .loading-text {
361
+ font-size: 1.1rem;
362
+ color: var(--text-secondary);
363
+ margin-bottom: 8px;
364
+ }
365
+
366
+ .loading-subtext {
367
+ font-size: 0.9rem;
368
+ color: var(--text-secondary);
369
+ opacity: 0.7;
370
+ }
371
+
372
+ .empty-state {
373
+ display: flex;
374
+ flex-direction: column;
375
+ align-items: center;
376
+ justify-content: center;
377
+ flex-grow: 1;
378
+ text-align: center;
379
+ color: var(--text-secondary);
380
+ }
381
+
382
+ .empty-icon {
383
+ width: 80px;
384
+ height: 80px;
385
+ margin-bottom: 20px;
386
+ opacity: 0.5;
387
+ }
388
+
389
+ .dissertation-content {
390
+ flex-grow: 1;
391
+ }
392
+
393
+ .dissertation-paper {
394
+ background: #fdfaf4;
395
+ color: #1a2a4c;
396
+ padding: 40px;
397
+ border-radius: 12px;
398
+ font-family: 'Kalam', cursive;
399
+ font-size: 18px;
400
+ line-height: 1.8;
401
+ background-image: linear-gradient(transparent 97%, #d8e2ee 98%);
402
+ background-size: 100% 32px;
403
+ border-left: 4px solid #ffaaab;
404
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
405
+ }
406
+
407
+ .dissertation-title {
408
+ text-align: center;
409
+ font-size: 1.4rem;
410
+ font-weight: 700;
411
+ margin-bottom: 10px;
412
+ color: #1a2a4c;
413
+ }
414
+
415
+ .dissertation-prof {
416
+ text-align: center;
417
+ font-style: italic;
418
+ margin-bottom: 30px;
419
+ color: #4a6a9c;
420
+ }
421
+
422
+ .dissertation-section {
423
+ margin-bottom: 30px;
424
+ }
425
+
426
+ .section-title {
427
+ font-size: 1.2rem;
428
+ font-weight: 700;
429
+ text-transform: uppercase;
430
+ text-decoration: underline;
431
+ margin-bottom: 16px;
432
+ color: #1a2a4c;
433
+ }
434
+
435
+ .section-content {
436
+ text-align: justify;
437
+ }
438
+
439
+ .paragraph {
440
+ margin-bottom: 16px;
441
+ text-indent: 2em;
442
+ }
443
+
444
+ .partie-block {
445
+ margin-bottom: 24px;
446
+ }
447
+
448
+ .chapeau {
449
+ font-weight: 600;
450
+ margin-bottom: 12px;
451
+ }
452
+
453
+ .transition {
454
+ font-style: italic;
455
+ color: #4a6a9c;
456
+ margin-top: 16px;
457
+ }
458
+
459
+ .error-state {
460
+ background: rgba(239, 68, 68, 0.1);
461
+ border: 1px solid rgba(239, 68, 68, 0.3);
462
+ border-radius: 12px;
463
+ padding: 20px;
464
+ text-align: center;
465
+ color: var(--error);
466
+ }
467
+
468
+ .error-icon {
469
+ width: 48px;
470
+ height: 48px;
471
+ margin: 0 auto 16px;
472
+ }
473
+
474
+ .footer {
475
+ text-align: center;
476
+ padding: 40px 0;
477
+ color: var(--text-secondary);
478
+ font-size: 0.9rem;
479
+ }
480
+
481
+ @media (max-width: 768px) {
482
+ .container {
483
+ padding: 0 16px;
484
+ }
485
+
486
+ .header h1 {
487
+ font-size: 2.2rem;
488
+ }
489
+
490
+ .form-section, .result-section {
491
+ padding: 20px;
492
+ }
493
+
494
+ .main-content {
495
+ gap: 24px;
496
+ }
497
+ }
498
+
499
+ .notification {
500
+ position: fixed;
501
+ top: 20px;
502
+ right: 20px;
503
+ padding: 16px 20px;
504
+ border-radius: 12px;
505
+ color: white;
506
+ font-weight: 600;
507
+ z-index: 1000;
508
+ transform: translateX(400px);
509
+ opacity: 0;
510
+ transition: all 0.4s ease;
511
+ }
512
+
513
+ .notification.show {
514
+ transform: translateX(0);
515
+ opacity: 1;
516
+ }
517
+
518
+ .notification.success {
519
+ background: linear-gradient(135deg, var(--success), #059669);
520
+ }
521
+
522
+ .notification.error {
523
+ background: linear-gradient(135deg, var(--error), #dc2626);
524
+ }
525
+ </style>
526
  </head>
527
+ <body>
528
+ <div class="background-animation"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
 
530
+ <div class="container">
531
+ <header class="header">
532
+ <h1>Générateur de Dissertations</h1>
533
+ <p>Créez des dissertations philosophiques avec l'intelligence artificielle Mariam AI</p>
534
+ </header>
535
+
536
+ <main class="main-content">
537
+ <section class="form-section">
538
+ <form id="dissertationForm">
539
+ <div class="form-group">
540
+ <label for="courseSelect">Cours de référence (optionnel)</label>
541
+ <select id="courseSelect" class="form-select">
542
+ <option value="">Sélectionner un cours...</option>
543
+ </select>
544
+ </div>
545
+
546
+ <div class="form-group">
547
+ <label for="questionInput">Sujet de dissertation *</label>
548
+ <textarea
549
+ id="questionInput"
550
+ class="form-textarea"
551
+ placeholder="Exemple: L'art nous détourne-t-il de la réalité ?"
552
+ required
553
+ ></textarea>
554
+ </div>
555
+
556
+ <div class="form-group">
557
+ <label>Type de méthodologie</label>
558
+ <div class="radio-group">
559
+ <div class="radio-item">
560
+ <input type="radio" id="type1" name="methodType" value="type1" checked>
561
+ <label for="type1" class="radio-label">
562
+ <h4>Méthodologie Type 1</h4>
563
+ <p>Approche classique avec plan dialectique</p>
564
+ </label>
565
+ </div>
566
+ <div class="radio-item">
567
+ <input type="radio" id="type2" name="methodType" value="type2">
568
+ <label for="type2" class="radio-label">
569
+ <h4>Méthodologie Type 2</h4>
570
+ <p>Approche thématique avec développement progressif</p>
571
+ </label>
572
+ </div>
573
+ </div>
574
+ </div>
575
+
576
+ <button type="submit" class="generate-btn" id="generateBtn">
577
+ <span id="btnText">Générer la dissertation</span>
578
+ </button>
579
+ </form>
580
+ </section>
581
+
582
+ <section class="result-section">
583
+ <div class="result-header">
584
+ <h2 class="result-title">Résultat</h2>
585
+ <button class="download-btn" id="downloadBtn" style="display: none;">
586
+ 📄 Télécharger PDF
587
+ </button>
588
+ </div>
589
 
590
+ <div id="resultContent">
591
+ <div class="empty-state">
592
+ <svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
593
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
594
+ <polyline points="14,2 14,8 20,8"></polyline>
595
+ <line x1="16" y1="13" x2="8" y2="13"></line>
596
+ <line x1="16" y1="17" x2="8" y2="17"></line>
597
+ <polyline points="10,9 9,9 8,9"></polyline>
598
+ </svg>
599
+ <p>Votre dissertation apparaîtra ici</p>
600
+ </div>
601
+ </div>
602
+ </section>
603
+ </main>
604
+
605
+ <footer class="footer">
606
+ <p>&copy; 2024 Mariam AI - Générateur de Dissertations Philosophiques</p>
607
+ </footer>
608
+ </div>
609
+
610
+ <div id="notification" class="notification"></div>
611
+
612
+ <script>
613
+ class DissertationGenerator {
614
+ constructor() {
615
+ this.currentDissertation = null;
616
+ this.init();
617
+ }
618
+
619
+ init() {
620
+ this.loadCourses();
621
+ this.bindEvents();
622
+ }
623
+
624
+ async loadCourses() {
625
+ try {
626
+ const response = await fetch('/api/philosophy/courses');
627
+ const courses = await response.json();
628
+
629
+ const select = document.getElementById('courseSelect');
630
+ select.innerHTML = '<option value="">Sélectionner un cours...</option>';
631
+
632
+ courses.forEach(course => {
633
+ const option = document.createElement('option');
634
+ option.value = course.id;
635
+ option.textContent = course.title;
636
+ select.appendChild(option);
637
+ });
638
+ } catch (error) {
639
+ console.error('Erreur lors du chargement des cours:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  }
641
+ }
642
+
643
+ bindEvents() {
644
+ const form = document.getElementById('dissertationForm');
645
+ const downloadBtn = document.getElementById('downloadBtn');
646
+
647
+ form.addEventListener('submit', (e) => {
648
+ e.preventDefault();
649
+ this.generateDissertation();
650
+ });
651
+
652
+ downloadBtn.addEventListener('click', () => {
653
+ this.downloadPDF();
654
+ });
655
+ }
656
 
657
+ async generateDissertation() {
658
+ const form = document.getElementById('dissertationForm');
659
+ const formData = new FormData(form);
660
 
661
+ const data = {
662
+ question: document.getElementById('questionInput').value.trim(),
663
+ type: formData.get('methodType'),
664
+ courseId: document.getElementById('courseSelect').value || null
665
+ };
666
 
667
+ if (!data.question) {
668
+ this.showNotification('Veuillez saisir un sujet de dissertation', 'error');
669
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
670
  }
671
+
672
+ this.showLoading();
673
+
674
+ try {
675
+ const response = await fetch('/api/generate_dissertation', {
676
+ method: 'POST',
677
+ headers: {
678
+ 'Content-Type': 'application/json',
679
+ },
680
+ body: JSON.stringify(data)
681
+ });
682
+
683
+ const result = await response.json();
684
+
685
+ if (response.ok) {
686
+ this.currentDissertation = result;
687
+ this.displayDissertation(result);
688
+ this.showNotification('Dissertation générée avec succès !', 'success');
689
+ } else {
690
+ throw new Error(result.error || 'Erreur lors de la génération');
691
  }
692
+ } catch (error) {
693
+ console.error('Erreur:', error);
694
+ this.showError(error.message);
695
+ this.showNotification('Erreur lors de la génération', 'error');
696
  }
697
  }
698
+
699
+ showLoading() {
700
+ const resultContent = document.getElementById('resultContent');
701
+ const downloadBtn = document.getElementById('downloadBtn');
702
+ const generateBtn = document.getElementById('generateBtn');
703
+ const btnText = document.getElementById('btnText');
704
+
705
+ downloadBtn.style.display = 'none';
706
+ generateBtn.disabled = true;
707
+ generateBtn.classList.add('btn-loading');
708
+ btnText.textContent = 'Génération en cours...';
709
+
710
+ resultContent.innerHTML = `
711
+ <div class="loading-state">
712
+ <div class="spinner"></div>
713
+ <div class="loading-text">Génération de votre dissertation</div>
714
+ <div class="loading-subtext">Analyse du sujet et structuration des idées...</div>
715
+ </div>
716
+ `;
717
  }
718
 
719
+ displayDissertation(dissertation) {
720
+ const resultContent = document.getElementById('resultContent');
721
+ const downloadBtn = document.getElementById('downloadBtn');
722
+ const generateBtn = document.getElementById('generateBtn');
723
+ const btnText = document.getElementById('btnText');
724
+
725
+ // Réinitialiser le bouton
726
+ generateBtn.disabled = false;
727
+ generateBtn.classList.remove('btn-loading');
728
+ btnText.textContent = 'Générer une nouvelle dissertation';
729
+
730
+ // Afficher le bouton de téléchargement
731
+ downloadBtn.style.display = 'block';
732
+
733
+ let partiesHTML = '';
734
+ dissertation.parties.forEach((partie, index) => {
735
+ let argumentsHTML = '';
736
+ partie.arguments.forEach(arg => {
737
+ argumentsHTML += `<div class="paragraph">${arg.paragraphe_argumentatif}</div>`;
738
+ });
739
+
740
+ partiesHTML += `
741
+ <div class="partie-block">
742
+ <div class="paragraph chapeau">${partie.chapeau}</div>
743
+ ${argumentsHTML}
744
+ ${partie.transition ? `<div class="paragraph transition">${partie.transition}</div>` : ''}
745
+ </div>
746
+ `;
747
+ });
748
+
749
+ resultContent.innerHTML = `
750
+ <div class="dissertation-content">
751
+ <div class="dissertation-paper">
752
+ <div class="dissertation-title">Sujet : ${dissertation.sujet}</div>
753
+ <div class="dissertation-prof">Prof : ${dissertation.prof}</div>
754
+
755
+ <div class="dissertation-section">
756
+ <div class="section-title">Introduction</div>
757
+ <div class="section-content">
758
+ <div class="paragraph">${dissertation.introduction}</div>
759
+ </div>
760
+ </div>
761
+
762
+ <div class="dissertation-section">
763
+ <div class="section-title">Développement</div>
764
+ <div class="section-content">
765
+ ${partiesHTML}
766
+ </div>
767
+ </div>
768
+
769
+ <div class="dissertation-section">
770
+ <div class="section-title">Conclusion</div>
771
+ <div class="section-content">
772
+ <div class="paragraph">${dissertation.conclusion}</div>
773
+ </div>
774
+ </div>
775
+ </div>
776
+ </div>
777
+ `;
778
  }
779
+
780
+ showError(message) {
781
+ const resultContent = document.getElementById('resultContent');
782
+ const generateBtn = document.getElementById('generateBtn');
783
+ const btnText = document.getElementById('btnText');
784
+
785
+ generateBtn.disabled = false;
786
+ generateBtn.classList.remove('btn-loading');
787
+ btnText.textContent = 'Générer la dissertation';
788
+
789
+ resultContent.innerHTML = `
790
+ <div class="error-state">
791
+ <svg class="error-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
792
+ <circle cx="12" cy="12" r="10"></circle>
793
+ <line x1="15" y1="9" x2="9" y2="15"></line>
794
+ <line x1="9" y1="9" x2="15" y2="15"></line>
795
+ </svg>
796
+ <h3>Erreur de génération</h3>
797
+ <p>${message}</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
798
  </div>
799
+ `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
801
 
802
+ async downloadPDF() {
803
+ if (!this.currentDissertation) {
804
+ this.showNotification('Aucune dissertation à télécharger', 'error');
805
+ return;
806
+ }
807
+
808
+ try {
809
+ const response = await fetch('/api/generate_pdf', {
810
+ method: 'POST',
811
+ headers: {
812
+ 'Content-Type': 'application/json',
813
+ },
814
+ body: JSON.stringify(this.currentDissertation)
815
+ });
816
+
817
+ if (response.ok) {
818
+ const blob = await response.blob();
819
+ const url = window.URL.createObjectURL(blob);
820
+ const a = document.createElement('a');
821
+ a.href = url;
822
+
823
+ // Créer un nom de fichier sécurisé
824
+ const safeFilename = this.currentDissertation.sujet
825
+ .substring(0, 50)
826
+ .replace(/[^a-zA-Z0-9\s\-_]/g, '')
827
+ .trim();
828
+
829
+ a.download = `${safeFilename || 'dissertation'}.pdf`;
830
+ document.body.appendChild(a);
831
+ a.click();
832
+ document.body.removeChild(a);
833
+ window.URL.revokeObjectURL(url);
834
+
835
+ this.showNotification('PDF téléchargé avec succès !', 'success');
836
+ } else {
837
+ throw new Error('Erreur lors de la génération du PDF');
838
+ }
839
+ } catch (error) {
840
+ console.error('Erreur téléchargement PDF:', error);
841
+ this.showNotification('Erreur lors du téléchargement PDF', 'error');
842
+ }
843
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
844
 
845
+ showNotification(message, type = 'success') {
846
+ const notification = document.getElementById('notification');
847
+ notification.textContent = message;
848
+ notification.className = `notification ${type}`;
849
+ notification.classList.add('show');
850
+
851
+ setTimeout(() => {
852
+ notification.classList.remove('show');
853
+ }, 4000);
854
+ }
855
+ }
856
+
857
+ // Initialiser l'application
858
+ document.addEventListener('DOMContentLoaded', () => {
859
+ new DissertationGenerator();
860
+ });
861
+ </script>
862
  </body>
863
  </html>