Docfile commited on
Commit
033435c
·
verified ·
1 Parent(s): 5b23458

Update templates/philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +494 -492
templates/philosophie.html CHANGED
@@ -18,25 +18,162 @@
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;
@@ -45,7 +182,6 @@
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
  }
@@ -59,14 +195,11 @@
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
 
@@ -74,7 +207,6 @@
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;
@@ -90,10 +222,8 @@
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
  }
@@ -108,30 +238,7 @@
108
  transform: translateY(0) translateZ(0);
109
  }
110
  }
111
-
112
- /* Animation de typing pour le streaming fondu */
113
- @keyframes typing {
114
- from { width: 0; }
115
- to { width: 100%; }
116
- }
117
-
118
- @keyframes fadeInChar {
119
- from {
120
- opacity: 0;
121
- transform: translateY(10px);
122
- }
123
- to {
124
- opacity: 1;
125
- transform: translateY(0);
126
- }
127
- }
128
-
129
- .typing-char {
130
- display: inline-block;
131
- animation: fadeInChar 0.3s ease-out forwards;
132
- }
133
 
134
- /* Styles pour Select2 */
135
  .select2-container--default .select2-selection--single {
136
  border: 1px solid #d1d5db;
137
  border-radius: 0.75rem;
@@ -160,14 +267,12 @@
160
  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1);
161
  }
162
 
163
- /* Styles pour l'aperçu de l'image */
164
  #image-preview {
165
  max-height: 200px;
166
  border-radius: 0.75rem;
167
  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1);
168
  }
169
 
170
- /* Cacher le marqueur par défaut de <details> */
171
  summary {
172
  list-style: none;
173
  }
@@ -175,192 +280,44 @@
175
  summary::-webkit-details-marker {
176
  display: none;
177
  }
178
-
179
- /* Styles pour le loader premium */
180
- .loader-overlay {
181
- position: fixed;
182
- top: 0;
183
- left: 0;
184
- width: 100%;
185
- height: 100%;
186
- background: rgba(0, 0, 0, 0.8);
187
- backdrop-filter: blur(10px);
188
- z-index: 9999;
189
- display: flex;
190
- align-items: center;
191
- justify-content: center;
192
- opacity: 0;
193
- transition: opacity 0.3s ease-out;
194
- }
195
-
196
- .loader-overlay.show {
197
- opacity: 1;
198
- }
199
-
200
- .loader-container {
201
- background: white;
202
- border-radius: 20px;
203
- padding: 2rem;
204
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
205
- text-align: center;
206
- max-width: 400px;
207
- width: 90%;
208
- transform: scale(0.9);
209
- transition: transform 0.3s ease-out;
210
- }
211
-
212
- .loader-overlay.show .loader-container {
213
- transform: scale(1);
214
- }
215
-
216
- .progress-circle {
217
- width: 120px;
218
- height: 120px;
219
- margin: 0 auto 1.5rem;
220
- position: relative;
221
- }
222
-
223
- .progress-ring {
224
- transform: rotate(-90deg);
225
- width: 100%;
226
- height: 100%;
227
- }
228
-
229
- .progress-ring-circle {
230
- stroke: #e5e7eb;
231
- stroke-width: 8;
232
- fill: transparent;
233
- r: 52;
234
- cx: 60;
235
- cy: 60;
236
- }
237
-
238
- .progress-ring-progress {
239
- stroke: #8b5cf6;
240
- stroke-width: 8;
241
- fill: transparent;
242
- r: 52;
243
- cx: 60;
244
- cy: 60;
245
- stroke-dasharray: 326.73;
246
- stroke-dashoffset: 326.73;
247
- transition: stroke-dashoffset 0.3s ease-out;
248
- stroke-linecap: round;
249
- }
250
-
251
- .progress-percentage {
252
- position: absolute;
253
- top: 50%;
254
- left: 50%;
255
- transform: translate(-50%, -50%);
256
- font-size: 1.5rem;
257
- font-weight: bold;
258
- color: #8b5cf6;
259
- }
260
-
261
- .loader-brain {
262
- width: 40px;
263
- height: 40px;
264
- margin: 0 auto 1rem;
265
- animation: pulse 2s infinite;
266
- }
267
-
268
- @keyframes pulse {
269
- 0%, 100% { transform: scale(1); opacity: 1; }
270
- 50% { transform: scale(1.1); opacity: 0.8; }
271
- }
272
-
273
- .loader-dots {
274
- display: flex;
275
- justify-content: center;
276
- gap: 0.5rem;
277
- margin-top: 1rem;
278
- }
279
-
280
- .loader-dot {
281
- width: 8px;
282
- height: 8px;
283
- background: #8b5cf6;
284
- border-radius: 50%;
285
- animation: loadingDots 1.4s infinite ease-in-out both;
286
- }
287
-
288
- .loader-dot:nth-child(1) { animation-delay: -0.32s; }
289
- .loader-dot:nth-child(2) { animation-delay: -0.16s; }
290
- .loader-dot:nth-child(3) { animation-delay: 0s; }
291
-
292
- @keyframes loadingDots {
293
- 0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }
294
- 40% { transform: scale(1); opacity: 1; }
295
- }
296
-
297
- .status-messages {
298
- min-height: 1.5rem;
299
- color: #6b7280;
300
- font-size: 0.9rem;
301
- margin-top: 1rem;
302
- }
303
 
304
- /* Optimisations mobiles */
305
  @media (max-width: 640px) {
306
- /* Réduire les marges sur mobile pour plus d'espace */
307
  .max-w-3xl {
308
  max-width: 100%;
309
  margin-left: 0.5rem;
310
  margin-right: 0.5rem;
311
  }
312
 
313
- /* Optimiser l'affichage des sections de contenu */
314
  #response, #thinking-wrapper {
315
- /* Éviter les débordements horizontaux */
316
  overflow-x: hidden;
317
  }
318
 
319
- /* Améliorer la lisibilité du texte généré */
320
  .prose {
321
  font-size: 0.95rem;
322
  line-height: 1.6;
323
  }
324
 
325
- /* Réduire l'animation pour économiser la batterie */
326
  .animate-fadeIn {
327
  animation-duration: 0.2s;
328
  }
329
-
330
- .loader-container {
331
- padding: 1.5rem;
332
- }
333
-
334
- .progress-circle {
335
- width: 100px;
336
- height: 100px;
337
- }
338
  }
339
 
340
- /* Conteneur de génération avec scroll optimisé */
341
  .generation-container {
342
- /* Conteneur stable pour éviter les recalculs de layout */
343
  contain: layout style;
344
- /* Optimiser les performances de scroll */
345
  transform: translateZ(0);
346
  }
347
 
348
- /* Améliorer les performances du textarea */
349
  textarea {
350
- /* Éviter les reflows pendant la saisie */
351
  resize: none;
352
- /* Optimiser sur mobile */
353
  -webkit-appearance: none;
354
  }
355
 
356
- /* Optimiser les boutons pour le tactile */
357
  button {
358
  touch-action: manipulation;
359
  -webkit-tap-highlight-color: transparent;
360
  transition: var(--transition-fast);
361
  }
362
 
363
- /* Scroll fluide pour les éléments avec beaucoup de contenu */
364
  .content-scrollable {
365
  -webkit-overflow-scrolling: touch;
366
  scroll-behavior: smooth;
@@ -368,34 +325,6 @@
368
  </style>
369
  </head>
370
  <body class="bg-gray-50 text-gray-900">
371
- <!-- Loader Overlay -->
372
- <div id="loader-overlay" class="loader-overlay">
373
- <div class="loader-container">
374
- <div class="loader-brain">
375
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-full h-full text-violet-600">
376
- <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" />
377
- </svg>
378
- </div>
379
-
380
- <div class="progress-circle">
381
- <svg class="progress-ring">
382
- <circle class="progress-ring-circle"></circle>
383
- <circle class="progress-ring-progress" id="progress-ring"></circle>
384
- </svg>
385
- <div class="progress-percentage" id="progress-percentage">0%</div>
386
- </div>
387
-
388
- <h3 class="text-xl font-semibold text-gray-800 mb-2">Génération en cours...</h3>
389
- <div class="status-messages" id="status-message">Initialisation de l'IA philosophique...</div>
390
-
391
- <div class="loader-dots">
392
- <div class="loader-dot"></div>
393
- <div class="loader-dot"></div>
394
- <div class="loader-dot"></div>
395
- </div>
396
- </div>
397
- </div>
398
-
399
  <!-- Navbar -->
400
  <nav class="bg-white/90 backdrop-blur-lg border-b border-gray-200 fixed w-full z-50">
401
  <div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
@@ -437,7 +366,7 @@
437
  </select>
438
  </div>
439
 
440
- <!-- Conteneur pour les champs texte (visible par défaut) -->
441
  <div id="text-input-container">
442
  <!-- Course Selection -->
443
  <div class="space-y-2">
@@ -453,7 +382,7 @@
453
  </div>
454
  </div>
455
 
456
- <!-- Conteneur pour l'upload d'image (caché par défaut) -->
457
  <div id="image-input-container" class="hidden">
458
  <div class="space-y-2">
459
  <label for="image-upload" class="block text-sm font-medium text-gray-700">Charger un document pour analyse</label>
@@ -471,8 +400,43 @@
471
  </div>
472
  </div>
473
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
 
475
- <!-- Thinking Process Section (Collapsible) -->
476
  <div id="thinking-wrapper" class="hidden mt-8 generation-container">
477
  <details id="thinking-container" class="bg-white border border-gray-200 rounded-xl shadow-sm">
478
  <summary class="flex justify-between items-center p-4 cursor-pointer">
@@ -482,7 +446,6 @@
482
  </svg>
483
  </summary>
484
  <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">
485
- <!-- Le contenu du processus de pensée sera injecté ici -->
486
  </div>
487
  </details>
488
  </div>
@@ -490,11 +453,10 @@
490
  <!-- Response Section -->
491
  <div id="response" class="hidden mt-6 generation-container">
492
  <div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6 sm:p-8 prose prose-violet max-w-none">
493
- <!-- Le contenu de la réponse sera injecté ici -->
494
  </div>
495
  </div>
496
 
497
- <!-- Action Buttons after generation -->
498
  <div id="action-buttons" class="hidden mt-6 grid grid-cols-1 sm:grid-cols-2 gap-4">
499
  <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">
500
  <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>
@@ -506,7 +468,6 @@
506
  <div class="mt-12">
507
  <h3 class="text-xl font-bold text-gray-800 mb-4">Historique</h3>
508
  <div id="dissertations-list" class="space-y-3">
509
- <!-- La liste des dissertations sauvegardées sera injectée ici -->
510
  </div>
511
  </div>
512
  </div>
@@ -524,182 +485,263 @@ $(document).ready(function() {
524
  $('#course-select').select2({
525
  placeholder: 'Optionnel : choisir un cours...',
526
  allowClear: true,
527
- templateResult: function (course) { if (!course.id) { return course.text; } return $(`<span>${course.text}</span><span class="course-author">Pr. ${$(course.element).data('author')}</span>`); },
 
 
 
528
  templateSelection: function (course) { return course.text; },
529
  });
530
  marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false });
531
  moment.locale('fr');
532
  const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true });
533
 
534
- // --- Gestion du Loader Premium ---
535
- const statusMessages = [
536
- "Initialisation de l'IA philosophique...",
537
- "Analyse du sujet en cours...",
538
- "Recherche de concepts philosophiques...",
539
- "Construction de l'argumentation...",
540
- "Structuration de la dissertation...",
541
- "Rédaction de l'introduction...",
542
- "Développement des parties principales...",
543
- "Formulation de la conclusion...",
544
- "Révision et finalisation..."
545
- ];
546
-
547
  let currentProgress = 0;
548
  let progressInterval;
549
- let messageInterval;
 
 
 
 
 
 
 
 
 
 
 
550
 
551
- function showLoader() {
552
- $('#loader-overlay').addClass('show');
553
  currentProgress = 0;
554
- updateProgress();
555
 
556
- // Progression automatique
 
 
 
 
 
 
 
 
 
 
 
 
557
  progressInterval = setInterval(() => {
558
  if (currentProgress < 95) {
559
- currentProgress += Math.random() * 3 + 1;
560
- updateProgress();
 
 
 
 
 
561
  }
562
- }, 200);
563
 
564
- // Messages de statut
565
- let messageIndex = 0;
566
- $('#status-message').text(statusMessages[messageIndex]);
567
- messageInterval = setInterval(() => {
568
- messageIndex = (messageIndex + 1) % statusMessages.length;
569
- $('#status-message').fadeOut(200, function() {
570
- $(this).text(statusMessages[messageIndex]).fadeIn(200);
571
- });
572
- }, 2000);
573
  }
574
 
575
- function hideLoader() {
576
- currentProgress = 100;
577
- updateProgress();
 
 
 
578
  clearInterval(progressInterval);
579
- clearInterval(messageInterval);
580
-
581
- $('#status-message').fadeOut(200, function() {
582
- $(this).text('Génération terminée !').fadeIn(200);
583
- });
584
 
585
  setTimeout(() => {
586
- $('#loader-overlay').removeClass('show');
587
- }, 1000);
 
 
588
  }
589
 
590
- function updateProgress() {
591
- const circle = $('#progress-ring')[0];
592
- const circumference = 2 * Math.PI * 52;
593
- const offset = circumference - (currentProgress / 100) * circumference;
594
-
595
- circle.style.strokeDashoffset = offset;
596
- $('#progress-percentage').text(Math.round(currentProgress) + '%');
597
  }
598
 
599
- // --- Fonction de streaming fondu pour l'affichage ---
600
- function typewriterEffect(element, text, speed = 30) {
601
- return new Promise((resolve) => {
602
- let i = 0;
603
- element.html('');
604
-
605
- function typeChar() {
606
- if (i < text.length) {
607
- const char = text.charAt(i);
608
- const span = $(`<span class="typing-char" style="animation-delay: ${i * 10}ms">${char}</span>`);
609
- element.append(span);
610
- i++;
611
- setTimeout(typeChar, speed);
612
- } else {
613
- resolve();
614
- }
615
- }
616
-
617
- typeChar();
618
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  }
620
 
621
- // Fonction alternative pour un rendu plus fluide par mots
622
- function smoothStreamText(element, text, wordsPerSecond = 8) {
623
- return new Promise((resolve) => {
624
- const words = text.split(' ');
625
- let wordIndex = 0;
626
- element.html('');
627
-
628
- function addWord() {
629
- if (wordIndex < words.length) {
630
- const word = words[wordIndex];
631
- const span = $(`<span class="typing-char">${word} </span>`);
632
- element.append(span);
633
- wordIndex++;
634
-
635
- // Scroll fluide pendant l'affichage
636
- smoothScrollToBottom();
637
-
638
- setTimeout(addWord, 1000 / wordsPerSecond);
639
  } else {
640
- resolve();
641
  }
642
- }
643
-
644
- addWord();
645
  });
646
  }
647
 
648
- // Variables pour optimiser le scroll
649
- let isScrolling = false;
650
- let scrollTimer = null;
651
- let lastScrollTime = 0;
652
-
653
- // Fonction de scroll optimisée avec throttling
654
- function smoothScrollToBottom(force = false) {
655
- const now = Date.now();
656
 
657
- // Throttling : éviter trop d'appels de scroll
658
- if (!force && now - lastScrollTime < 100) {
659
- return;
660
- }
661
 
662
- lastScrollTime = now;
 
663
 
664
- // Annuler le timer précédent s'il existe
665
- if (scrollTimer) {
666
- clearTimeout(scrollTimer);
667
- }
668
-
669
- // Utiliser requestAnimationFrame pour un scroll plus fluide
670
- if (!isScrolling) {
671
- isScrolling = true;
672
-
673
- requestAnimationFrame(() => {
674
- // Scroll fluide vers le bas avec une marge
675
- const targetScrollTop = Math.max(0, document.documentElement.scrollHeight - window.innerHeight - 50);
 
 
 
 
 
 
 
 
 
 
676
 
677
- // Utiliser la méthode native pour de meilleures performances
678
- window.scrollTo({
679
- top: targetScrollTop,
680
- behavior: 'smooth'
 
 
 
 
 
681
  });
682
 
683
- // Marquer comme terminé après l'animation
684
- scrollTimer = setTimeout(() => {
685
- isScrolling = false;
686
- }, 200);
687
- });
 
 
688
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
689
  }
690
 
691
  // --- Gestion de l'interface ---
692
  $('#type-select').change(function() {
693
  const type = $(this).val();
694
  // Cacher les sections de résultat lors du changement de type
695
- $('#thinking-wrapper, #response, #action-buttons').addClass('hidden');
696
 
697
  if (type === '3') { // Sujet Type 3
698
  $('#text-input-container').hide();
699
  $('#image-input-container').show().addClass('animate-fadeIn');
700
  $('#deepthink-btn').hide();
701
  $('#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');
702
- $('label[for="image-upload"]').text("Charger un document pour analyse (image, etc.)");
703
  } else { // Dissertation texte
704
  $('#text-input-container').show().addClass('animate-fadeIn');
705
  $('#image-input-container').hide();
@@ -716,129 +758,7 @@ $(document).ready(function() {
716
  }
717
  });
718
 
719
- // --- Simulation de génération (remplace l'ancien streaming) ---
720
- async function simulateGeneration(data, isDeepThink = false) {
721
- showLoader();
722
-
723
- // Cacher les sections de résultat
724
- $('#thinking-wrapper, #response, #action-buttons').addClass('hidden');
725
- $('#thinking-container').prop('open', false);
726
- const thinkingDiv = $('#thinking-process');
727
- const responseDiv = $('#response > div');
728
- thinkingDiv.html('');
729
- responseDiv.html('');
730
-
731
- try {
732
- // Simulation du temps de traitement (3-6 secondes)
733
- const processingTime = Math.random() * 3000 + 3000;
734
-
735
- await new Promise(resolve => setTimeout(resolve, processingTime));
736
-
737
- // Texte simulé pour la démonstration
738
- const sampleThinking = `
739
- ## Analyse du sujet
740
-
741
- Je vais commencer par analyser les termes clés de ce sujet philosophique.
742
-
743
- **Concepts centraux :**
744
- - La notion de liberté
745
- - L'idée de contrainte
746
- - La dimension morale et politique
747
-
748
- **Problématique :** Ce sujet interroge la nature même de la liberté humaine et ses limites.
749
-
750
- **Plan envisagé :**
751
- 1. La liberté comme absence de contrainte
752
- 2. Les limites nécessaires à la liberté
753
- 3. La liberté authentique et la responsabilité
754
- `;
755
-
756
- const sampleResponse = `
757
- # La liberté consiste-t-elle à faire tout ce que l'on veut ?
758
-
759
- ## Introduction
760
-
761
- La liberté apparaît spontanément comme la capacité de faire ce que l'on désire, sans entrave ni contrainte. Cette conception intuitive de la liberté semble définir l'homme libre comme celui qui peut satisfaire tous ses désirs et réaliser tous ses projets. Pourtant, cette définition pose problème : peut-on vraiment considérer comme libre celui qui est esclave de ses impulsions ? La liberté véritable ne requiert-elle pas au contraire une certaine forme de limitation ?
762
-
763
- **Problématique :** La liberté se résume-t-elle à l'absence de contraintes extérieures, ou implique-t-elle une maîtrise de soi qui suppose des limites ?
764
-
765
- ## I. La liberté comme absence de contrainte
766
-
767
- ### A. La conception spontanée de la liberté
768
-
769
- La liberté se présente d'abord négativement comme l'absence d'obstacles à nos actions. Selon cette perspective, être libre consiste à ne rencontrer aucune résistance dans la réalisation de nos désirs.
770
-
771
- ### B. La liberté politique et juridique
772
-
773
- Dans le domaine politique, la liberté se définit effectivement par l'absence de contraintes arbitraires. Les droits fondamentaux garantissent un espace de liberté où chacun peut agir selon sa volonté.
774
-
775
- ### C. Les limites de cette conception
776
-
777
- Cependant, cette vision purement négative de la liberté conduit à des paradoxes. Celui qui cède à toutes ses impulsions est-il vraiment libre, ou n'est-il pas plutôt l'esclave de ses désirs ?
778
-
779
- ## II. La nécessité des limites pour la liberté authentique
780
-
781
- ### A. La liberté et la raison
782
-
783
- Pour les philosophes comme Kant, la vraie liberté ne consiste pas à suivre ses inclinations, mais à agir selon la raison et le devoir moral. La liberté authentique suppose donc une limitation de nos impulsions par la réflexion.
784
-
785
- ### B. La liberté sociale
786
-
787
- Rousseau montre que la liberté en société ne peut exister sans lois. Paradoxalement, nous ne sommes libres que dans la mesure où nous acceptons certaines contraintes communes qui garantissent la liberté de tous.
788
-
789
- ### C. La liberté comme conquête de soi
790
-
791
- La liberté véritable implique une victoire sur nos déterminations naturelles et sociales. Elle se conquiert par l'éducation, la réflexion et l'effort moral.
792
-
793
- ## Conclusion
794
-
795
- La liberté ne saurait se réduire à la simple capacité de faire tout ce que l'on veut. Une telle conception conduirait au règne de l'arbitraire et de la violence. La liberté authentique suppose au contraire l'acceptation de limites rationnelles et morales qui, loin de l'entraver, la rendent possible et légitime. Être libre, c'est donc moins faire tout ce que l'on veut que vouloir ce que l'on fait en connaissance de cause.
796
-
797
- La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, réflexion et responsabilité morale.
798
- `;
799
-
800
- hideLoader();
801
-
802
- // Affichage avec effet de streaming fondu
803
- setTimeout(async () => {
804
- // Afficher la section de pensée
805
- $('#thinking-wrapper').removeClass('hidden').addClass('animate-fadeIn');
806
- await smoothStreamText(thinkingDiv, sampleThinking, 12);
807
-
808
- // Petit délai puis afficher la réponse
809
- setTimeout(async () => {
810
- $('#response').removeClass('hidden').addClass('animate-fadeIn');
811
-
812
- // Convertir en HTML et afficher avec streaming
813
- const htmlContent = marked.parse(sampleResponse);
814
- await smoothStreamText(responseDiv, htmlContent, 8);
815
-
816
- // Afficher les boutons d'action
817
- $('#action-buttons').removeClass('hidden').addClass('animate-fadeIn');
818
- Toast.fire({ icon: 'success', title: 'Génération terminée !' });
819
-
820
- // Sauvegarder
821
- let title;
822
- if ($('#type-select').val() === '3') {
823
- const fileName = $('#image-upload')[0].files[0]?.name || "Analyse de document";
824
- title = `Analyse (Sujet Type 3): ${fileName}`;
825
- } else {
826
- title = $('#question').val().trim();
827
- }
828
- saveDissertation(title, sampleResponse);
829
-
830
- smoothScrollToBottom(true);
831
- }, 1000);
832
-
833
- }, 500);
834
-
835
- } catch (error) {
836
- hideLoader();
837
- Swal.fire({ icon: 'error', title: 'Erreur', text: error.message || "Une erreur inconnue est survenue." });
838
- }
839
- }
840
-
841
- // --- Gestion des Clics ---
842
  $('#submit-btn, #deepthink-btn').click(function() {
843
  const type = $('#type-select').val();
844
  const isDeepThink = $(this).attr('id') === 'deepthink-btn';
@@ -849,43 +769,127 @@ La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, r
849
  Swal.fire('Erreur', 'Veuillez sélectionner un document.', 'error');
850
  return;
851
  }
852
- const data = { type: 'image', file: imageFile };
853
- simulateGeneration(data);
854
  } else {
855
  const question = $('#question').val().trim();
856
  if (!question) {
857
  Swal.fire('Erreur', 'Veuillez saisir un sujet.', 'error');
858
  return;
859
  }
860
- const data = {
861
- question,
862
- type,
863
- courseId: $('#course-select').val() || null,
864
- isDeepThink
865
- };
866
- simulateGeneration(data, isDeepThink);
867
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
868
  });
869
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  // --- Fonctions de gestion de l'historique ---
871
  function loadCourses() {
872
- // Simulation des cours pour la démo
873
- const mockCourses = [
874
- { id: '1', title: 'Philosophie Morale', author: 'Kant' },
875
- { id: '2', title: 'Philosophie Politique', author: 'Rousseau' },
876
- { id: '3', title: 'Métaphysique', author: 'Descartes' },
877
- { id: '4', title: 'Éthique', author: 'Spinoza' }
878
  ];
879
 
880
  const select = $('#course-select');
881
- mockCourses.forEach(course => {
882
  const newOption = new Option(course.title, course.id, false, false);
883
  $(newOption).data('author', course.author);
884
  select.append(newOption);
885
  });
886
  select.trigger('change');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  }
888
- loadCourses();
889
 
890
  function saveDissertation(title, content) {
891
  if (!title || !content) return;
@@ -936,12 +940,11 @@ La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, r
936
  });
937
  }
938
 
939
- // Optimiser l'ouverture des dissertations avec une transition plus fluide
940
  $('#dissertations-list').on('click', '.collapsible', function() {
941
  const content = $(this).next('.content');
942
  const isVisible = content.is(':visible');
943
 
944
- // Animation plus fluide avec easing
945
  content.stop(true, true).slideToggle({
946
  duration: 300,
947
  easing: 'swing',
@@ -972,7 +975,7 @@ La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, r
972
  });
973
  });
974
 
975
- // Optimisation pour les événements de scroll sur mobile
976
  let ticking = false;
977
 
978
  function updateScrollPosition() {
@@ -986,7 +989,6 @@ La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, r
986
  }
987
  }, { passive: true });
988
 
989
- // Optimisation du resize pour mobile
990
  let resizeTimer;
991
  window.addEventListener('resize', function() {
992
  clearTimeout(resizeTimer);
@@ -999,7 +1001,6 @@ La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, r
999
  }, 250);
1000
  }, { passive: true });
1001
 
1002
- // Optimisation tactile pour mobile
1003
  if ('ontouchstart' in window) {
1004
  $('body').addClass('touch-device');
1005
 
@@ -1013,7 +1014,8 @@ La liberté se révèle ainsi comme un idéal exigeant qui suppose éducation, r
1013
  });
1014
  }
1015
 
1016
- // Init
 
1017
  updateSavedDissertationsList();
1018
  });
1019
  </script>
 
18
  /* Styles pour le Glow Up */
19
  :root {
20
  font-family: 'Inter', sans-serif;
 
21
  --transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
22
  --transition-fast: all 0.15s ease-out;
23
+ --violet-gradient: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
24
  }
25
 
 
26
  html {
27
  scroll-behavior: smooth;
28
  -webkit-overflow-scrolling: touch;
29
  }
30
 
31
  body {
 
32
  will-change: scroll-position;
 
33
  -webkit-font-smoothing: antialiased;
34
  -moz-osx-font-smoothing: grayscale;
35
  }
36
+
37
+ /* Loader magnifique avec animations */
38
+ .progress-container {
39
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
40
+ border: 2px solid #e2e8f0;
41
+ position: relative;
42
+ overflow: hidden;
43
+ }
44
+
45
+ .progress-container::before {
46
+ content: '';
47
+ position: absolute;
48
+ top: 0;
49
+ left: -100%;
50
+ width: 100%;
51
+ height: 100%;
52
+ background: linear-gradient(90deg, transparent, rgba(139, 92, 246, 0.1), transparent);
53
+ animation: shimmer 2s infinite;
54
+ }
55
+
56
+ @keyframes shimmer {
57
+ 0% { left: -100%; }
58
+ 100% { left: 100%; }
59
+ }
60
+
61
+ .progress-bar {
62
+ background: var(--violet-gradient);
63
+ height: 100%;
64
+ transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
65
+ position: relative;
66
+ overflow: hidden;
67
+ box-shadow: 0 0 20px rgba(139, 92, 246, 0.3);
68
+ }
69
+
70
+ .progress-bar::after {
71
+ content: '';
72
+ position: absolute;
73
+ top: 0;
74
+ left: 0;
75
+ right: 0;
76
+ bottom: 0;
77
+ background: linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.3) 50%, transparent 70%);
78
+ animation: progressGlow 2s ease-in-out infinite;
79
+ }
80
+
81
+ @keyframes progressGlow {
82
+ 0%, 100% { transform: translateX(-100%) skewX(-15deg); }
83
+ 50% { transform: translateX(200%) skewX(-15deg); }
84
+ }
85
+
86
+ .loading-dots {
87
+ display: inline-flex;
88
+ align-items: center;
89
+ }
90
+
91
+ .loading-dots span {
92
+ width: 8px;
93
+ height: 8px;
94
+ border-radius: 50%;
95
+ background: #8b5cf6;
96
+ margin: 0 2px;
97
+ animation: loadingDots 1.4s infinite ease-in-out both;
98
+ }
99
+
100
+ .loading-dots span:nth-child(1) { animation-delay: -0.32s; }
101
+ .loading-dots span:nth-child(2) { animation-delay: -0.16s; }
102
+
103
+ @keyframes loadingDots {
104
+ 0%, 80%, 100% {
105
+ transform: scale(0.8);
106
+ opacity: 0.5;
107
+ }
108
+ 40% {
109
+ transform: scale(1.2);
110
+ opacity: 1;
111
+ }
112
+ }
113
+
114
+ /* Animation de fondu pour le streaming */
115
+ .streaming-text {
116
+ opacity: 0;
117
+ animation: fadeInUp 0.6s ease-out forwards;
118
+ }
119
+
120
+ @keyframes fadeInUp {
121
+ from {
122
+ opacity: 0;
123
+ transform: translateY(20px);
124
+ }
125
+ to {
126
+ opacity: 1;
127
+ transform: translateY(0);
128
+ }
129
+ }
130
+
131
+ .word-by-word {
132
+ display: inline;
133
+ opacity: 0;
134
+ animation: wordFadeIn 0.3s ease-out forwards;
135
+ }
136
+
137
+ @keyframes wordFadeIn {
138
+ from {
139
+ opacity: 0;
140
+ transform: translateY(10px) scale(0.95);
141
+ }
142
+ to {
143
+ opacity: 1;
144
+ transform: translateY(0) scale(1);
145
+ }
146
+ }
147
+
148
+ /* Pulse effect pour le contenu en cours de génération */
149
+ .generating-pulse {
150
+ animation: pulseGlow 2s ease-in-out infinite;
151
+ }
152
+
153
+ @keyframes pulseGlow {
154
+ 0%, 100% {
155
+ box-shadow: 0 0 20px rgba(139, 92, 246, 0.1);
156
+ transform: scale(1);
157
+ }
158
+ 50% {
159
+ box-shadow: 0 0 30px rgba(139, 92, 246, 0.2);
160
+ transform: scale(1.01);
161
+ }
162
+ }
163
+
164
+ /* Styles pour le texte en cours de streaming */
165
+ .streaming-cursor::after {
166
+ content: '|';
167
+ animation: cursorBlink 1s infinite;
168
+ color: #8b5cf6;
169
+ margin-left: 2px;
170
+ }
171
+
172
+ @keyframes cursorBlink {
173
+ 0%, 50% { opacity: 1; }
174
+ 51%, 100% { opacity: 0; }
175
+ }
176
+
177
  .collapsible {
178
  cursor: pointer;
179
  padding: 1rem;
 
182
  text-align: left;
183
  outline: none;
184
  transition: var(--transition-fast);
 
185
  touch-action: manipulation;
186
  -webkit-tap-highlight-color: transparent;
187
  }
 
195
  display: none;
196
  overflow: hidden;
197
  background-color: white;
 
198
  transition: var(--transition-smooth);
199
  }
200
 
 
201
  .prose {
202
  max-width: 100% !important;
 
203
  text-rendering: optimizeLegibility;
204
  }
205
 
 
207
  color: #374151;
208
  word-wrap: break-word;
209
  overflow-wrap: break-word;
 
210
  hyphens: auto;
211
  -webkit-hyphens: auto;
212
  -moz-hyphens: auto;
 
222
  line-height: 1.3;
223
  }
224
 
 
225
  .animate-fadeIn {
226
  animation: fadeIn 0.4s ease-out forwards;
 
227
  transform: translateZ(0);
228
  will-change: opacity, transform;
229
  }
 
238
  transform: translateY(0) translateZ(0);
239
  }
240
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
 
 
242
  .select2-container--default .select2-selection--single {
243
  border: 1px solid #d1d5db;
244
  border-radius: 0.75rem;
 
267
  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1);
268
  }
269
 
 
270
  #image-preview {
271
  max-height: 200px;
272
  border-radius: 0.75rem;
273
  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1);
274
  }
275
 
 
276
  summary {
277
  list-style: none;
278
  }
 
280
  summary::-webkit-details-marker {
281
  display: none;
282
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
 
284
  @media (max-width: 640px) {
 
285
  .max-w-3xl {
286
  max-width: 100%;
287
  margin-left: 0.5rem;
288
  margin-right: 0.5rem;
289
  }
290
 
 
291
  #response, #thinking-wrapper {
 
292
  overflow-x: hidden;
293
  }
294
 
 
295
  .prose {
296
  font-size: 0.95rem;
297
  line-height: 1.6;
298
  }
299
 
 
300
  .animate-fadeIn {
301
  animation-duration: 0.2s;
302
  }
 
 
 
 
 
 
 
 
 
303
  }
304
 
 
305
  .generation-container {
 
306
  contain: layout style;
 
307
  transform: translateZ(0);
308
  }
309
 
 
310
  textarea {
 
311
  resize: none;
 
312
  -webkit-appearance: none;
313
  }
314
 
 
315
  button {
316
  touch-action: manipulation;
317
  -webkit-tap-highlight-color: transparent;
318
  transition: var(--transition-fast);
319
  }
320
 
 
321
  .content-scrollable {
322
  -webkit-overflow-scrolling: touch;
323
  scroll-behavior: smooth;
 
325
  </style>
326
  </head>
327
  <body class="bg-gray-50 text-gray-900">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  <!-- Navbar -->
329
  <nav class="bg-white/90 backdrop-blur-lg border-b border-gray-200 fixed w-full z-50">
330
  <div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8">
 
366
  </select>
367
  </div>
368
 
369
+ <!-- Conteneur pour les champs texte -->
370
  <div id="text-input-container">
371
  <!-- Course Selection -->
372
  <div class="space-y-2">
 
382
  </div>
383
  </div>
384
 
385
+ <!-- Conteneur pour l'upload d'image -->
386
  <div id="image-input-container" class="hidden">
387
  <div class="space-y-2">
388
  <label for="image-upload" class="block text-sm font-medium text-gray-700">Charger un document pour analyse</label>
 
400
  </div>
401
  </div>
402
  </div>
403
+
404
+ <!-- Loading Section -->
405
+ <div id="loading-section" class="hidden mt-8 generation-container">
406
+ <div class="bg-white border border-gray-200 rounded-xl shadow-lg p-8 generating-pulse">
407
+ <div class="text-center space-y-6">
408
+ <div class="mx-auto w-16 h-16 relative">
409
+ <svg class="w-16 h-16 text-violet-600 animate-spin" fill="none" viewBox="0 0 24 24">
410
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
411
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
412
+ </svg>
413
+ </div>
414
+
415
+ <div class="space-y-3">
416
+ <h3 class="text-xl font-semibold text-gray-800">Génération en cours</h3>
417
+ <p id="loading-message" class="text-gray-600">Initialisation de l'IA philosophique</p>
418
+ <div class="loading-dots">
419
+ <span></span>
420
+ <span></span>
421
+ <span></span>
422
+ </div>
423
+ </div>
424
+
425
+ <!-- Barre de progression -->
426
+ <div class="space-y-2">
427
+ <div class="flex justify-between text-sm text-gray-600">
428
+ <span>Progression</span>
429
+ <span id="progress-percentage">0%</span>
430
+ </div>
431
+ <div class="progress-container w-full h-3 rounded-full">
432
+ <div id="progress-bar" class="progress-bar w-0 rounded-full"></div>
433
+ </div>
434
+ </div>
435
+ </div>
436
+ </div>
437
+ </div>
438
 
439
+ <!-- Thinking Process Section -->
440
  <div id="thinking-wrapper" class="hidden mt-8 generation-container">
441
  <details id="thinking-container" class="bg-white border border-gray-200 rounded-xl shadow-sm">
442
  <summary class="flex justify-between items-center p-4 cursor-pointer">
 
446
  </svg>
447
  </summary>
448
  <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">
 
449
  </div>
450
  </details>
451
  </div>
 
453
  <!-- Response Section -->
454
  <div id="response" class="hidden mt-6 generation-container">
455
  <div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6 sm:p-8 prose prose-violet max-w-none">
 
456
  </div>
457
  </div>
458
 
459
+ <!-- Action Buttons -->
460
  <div id="action-buttons" class="hidden mt-6 grid grid-cols-1 sm:grid-cols-2 gap-4">
461
  <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">
462
  <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>
 
468
  <div class="mt-12">
469
  <h3 class="text-xl font-bold text-gray-800 mb-4">Historique</h3>
470
  <div id="dissertations-list" class="space-y-3">
 
471
  </div>
472
  </div>
473
  </div>
 
485
  $('#course-select').select2({
486
  placeholder: 'Optionnel : choisir un cours...',
487
  allowClear: true,
488
+ templateResult: function (course) {
489
+ if (!course.id) { return course.text; }
490
+ return $(`<span>${course.text}</span><span class="course-author">Pr. ${$(course.element).data('author')}</span>`);
491
+ },
492
  templateSelection: function (course) { return course.text; },
493
  });
494
  marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false });
495
  moment.locale('fr');
496
  const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true });
497
 
498
+ // Variables pour le loader et streaming
 
 
 
 
 
 
 
 
 
 
 
 
499
  let currentProgress = 0;
500
  let progressInterval;
501
+ let streamingTimeout;
502
+
503
+ // Messages de chargement rotatifs
504
+ const loadingMessages = [
505
+ "Initialisation de l'IA philosophique",
506
+ "Analyse du sujet proposé",
507
+ "Recherche des concepts clés",
508
+ "Construction de l'argumentation",
509
+ "Structuration de la dissertation",
510
+ "Vérification de la cohérence",
511
+ "Finalisation de la réponse"
512
+ ];
513
 
514
+ // Fonction pour simuler le progress loader
515
+ function startProgressLoader() {
516
  currentProgress = 0;
517
+ let messageIndex = 0;
518
 
519
+ $('#loading-section').removeClass('hidden').addClass('animate-fadeIn');
520
+ $('#progress-bar').css('width', '0%');
521
+ $('#progress-percentage').text('0%');
522
+
523
+ // Animation du message
524
+ const messageInterval = setInterval(() => {
525
+ $('#loading-message').fadeOut(300, function() {
526
+ $(this).text(loadingMessages[messageIndex]).fadeIn(300);
527
+ messageIndex = (messageIndex + 1) % loadingMessages.length;
528
+ });
529
+ }, 2000);
530
+
531
+ // Animation de la barre de progression
532
  progressInterval = setInterval(() => {
533
  if (currentProgress < 95) {
534
+ // Progression plus rapide au début, plus lente vers la fin
535
+ const increment = currentProgress < 30 ? Math.random() * 8 + 3 :
536
+ currentProgress < 60 ? Math.random() * 4 + 2 :
537
+ Math.random() * 2 + 0.5;
538
+
539
+ currentProgress = Math.min(currentProgress + increment, 95);
540
+ updateProgressBar(currentProgress);
541
  }
542
+ }, 300);
543
 
544
+ // Nettoyer après 30 secondes max
545
+ setTimeout(() => {
546
+ clearInterval(messageInterval);
547
+ clearInterval(progressInterval);
548
+ completeProgress();
549
+ }, 30000);
 
 
 
550
  }
551
 
552
+ function updateProgressBar(progress) {
553
+ $('#progress-bar').css('width', progress + '%');
554
+ $('#progress-percentage').text(Math.round(progress) + '%');
555
+ }
556
+
557
+ function completeProgress() {
558
  clearInterval(progressInterval);
559
+ currentProgress = 100;
560
+ updateProgressBar(100);
 
 
 
561
 
562
  setTimeout(() => {
563
+ $('#loading-section').fadeOut(500, function() {
564
+ startStreamingDisplay();
565
+ });
566
+ }, 800);
567
  }
568
 
569
+ // Fonction pour démarrer l'affichage en streaming avec effet de fondu
570
+ function startStreamingDisplay() {
571
+ // Simuler des données reçues (remplacer par vraies données de l'API)
572
+ simulateStreamingData();
 
 
 
573
  }
574
 
575
+ // Simulation du streaming avec de vraies données (remplacer par l'appel API réel)
576
+ function simulateStreamingData() {
577
+ // Exemple de données - remplacer par les vraies données de votre API
578
+ const sampleThinking = `## Analyse du sujet
579
+
580
+ Le sujet "La liberté consiste-t-elle à faire tout ce que l'on veut ?" interroge la nature même de la liberté. Il faut distinguer entre liberté et licence, entre pouvoir et droit.
581
+
582
+ ## Plan envisagé
583
+ 1. La liberté comme absence de contraintes
584
+ 2. Les limites nécessaires à la liberté
585
+ 3. La liberté authentique comme autodétermination`;
586
+
587
+ const sampleResponse = `# La liberté consiste-t-elle à faire tout ce que l'on veut ?
588
+
589
+ ## Introduction
590
+
591
+ La question de la liberté traverse toute l'histoire de la philosophie. À première vue, être libre semble signifier pouvoir agir selon ses désirs, sans contrainte. Cependant, cette conception spontanée de la liberté pose problème : peut-on vraiment identifier liberté et licence ? La liberté véritable ne suppose-t-elle pas au contraire certaines limites ?
592
+
593
+ ## I. La liberté comme absence de contraintes
594
+
595
+ ### A. La conception commune de la liberté
596
+
597
+ Dans l'usage courant, être libre c'est faire ce que l'on veut, quand on le veut. Cette conception négative de la liberté la définit par l'absence d'obstacles extérieurs à nos actions.
598
+
599
+ ### B. La liberté naturelle selon Hobbes
600
+
601
+ Thomas Hobbes définit la liberté comme "l'absence d'obstacles extérieurs". Dans l'état de nature, chaque individu a le droit à tout, y compris au corps d'autrui.
602
+
603
+ ## II. Les limites nécessaires à la liberté
604
+
605
+ ### A. La liberté des uns s'arrête où commence celle des autres
606
+
607
+ John Stuart Mill, dans "De la liberté", établit le principe de non-nuisance : ma liberté ne doit pas porter atteinte à celle d'autrui.
608
+
609
+ ### B. Les contraintes comme conditions de la liberté
610
+
611
+ Paradoxalement, certaines contraintes peuvent être libératrices. Les règles du jeu au tennis ne limitent pas le joueur, elles rendent le jeu possible.
612
+
613
+ ## III. La liberté authentique comme autodétermination
614
+
615
+ ### A. La liberté selon Kant
616
+
617
+ Pour Kant, la vraie liberté n'est pas de suivre ses inclinations, mais d'agir selon des principes rationnels que l'on s'est donnés à soi-même.
618
+
619
+ ### B. L'autonomie comme essence de la liberté
620
+
621
+ Être libre, c'est être autonome, c'est-à-dire se donner ses propres lois. Cela implique une maîtrise de soi et une réflexion sur ses actes.
622
+
623
+ ## Conclusion
624
+
625
+ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on veut. Une telle conception conduirait au chaos et à la négation même de la liberté. La véritable liberté réside dans l'autodétermination rationnelle, dans la capacité à agir selon des principes que l'on s'est librement choisis. Ainsi, les contraintes ne s'opposent pas nécessairement à la liberté ; elles peuvent même la rendre possible.`;
626
+
627
+ // Démarrer le streaming avec effet de fondu
628
+ startWordByWordStreaming(sampleThinking, sampleResponse);
629
  }
630
 
631
+ // Fonction pour l'affichage mot par mot avec effet de fondu
632
+ function startWordByWordStreaming(thinkingText, responseText) {
633
+ // Afficher d'abord la section de pensée
634
+ $('#thinking-wrapper').removeClass('hidden').addClass('animate-fadeIn');
635
+ streamTextWordByWord('#thinking-process', thinkingText, 50, () => {
636
+ // Une fois la pensée terminée, afficher la réponse
637
+ $('#response').removeClass('hidden').addClass('animate-fadeIn');
638
+ streamTextWordByWord('#response > div', responseText, 30, () => {
639
+ // Une fois terminé, afficher les boutons d'action
640
+ $('#action-buttons').removeClass('hidden').addClass('animate-fadeIn');
641
+ Toast.fire({ icon: 'success', title: 'Génération terminée !' });
642
+
643
+ // Sauvegarder la dissertation
644
+ let title;
645
+ if ($('#type-select').val() === '3') {
646
+ const fileName = $('#image-upload')[0].files[0]?.name || "Analyse de document";
647
+ title = `Analyse (Sujet Type 3): ${fileName}`;
 
648
  } else {
649
+ title = $('#question').val().trim();
650
  }
651
+ saveDissertation(title, responseText);
652
+ });
 
653
  });
654
  }
655
 
656
+ // Fonction pour streamer le texte mot par mot
657
+ function streamTextWordByWord(selector, text, delay = 50, callback) {
658
+ const container = $(selector);
659
+ container.empty();
 
 
 
 
660
 
661
+ // Convertir le markdown en HTML
662
+ const htmlContent = marked.parse(text);
 
 
663
 
664
+ // Créer un élément temporaire pour extraire le texte
665
+ const tempDiv = $('<div>').html(htmlContent);
666
 
667
+ // Fonction récursive pour traiter chaque nœud
668
+ function processNode(node, targetContainer) {
669
+ if (node.nodeType === Node.TEXT_NODE) {
670
+ // Nœud texte - streamer mot par mot
671
+ const words = node.textContent.split(/(\s+)/);
672
+ let wordIndex = 0;
673
+
674
+ function addNextWord() {
675
+ if (wordIndex < words.length) {
676
+ const word = words[wordIndex];
677
+ const span = $('<span>').addClass('word-by-word').text(word);
678
+ span.css('animation-delay', '0s');
679
+ targetContainer.append(span);
680
+
681
+ setTimeout(() => {
682
+ wordIndex++;
683
+ addNextWord();
684
+ }, delay);
685
+ } else if (callback && node === tempDiv.get(0).lastChild) {
686
+ setTimeout(callback, 500);
687
+ }
688
+ }
689
 
690
+ addNextWord();
691
+
692
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
693
+ // Nœud élément - recréer l'élément et traiter ses enfants
694
+ const newElement = $(`<${node.tagName.toLowerCase()}>`);
695
+
696
+ // Copier les attributs
697
+ Array.from(node.attributes).forEach(attr => {
698
+ newElement.attr(attr.name, attr.value);
699
  });
700
 
701
+ targetContainer.append(newElement);
702
+
703
+ // Traiter les enfants
704
+ Array.from(node.childNodes).forEach(child => {
705
+ processNode(child, newElement);
706
+ });
707
+ }
708
  }
709
+
710
+ // Démarrer le traitement
711
+ Array.from(tempDiv.get(0).childNodes).forEach(child => {
712
+ processNode(child, container);
713
+ });
714
+
715
+ // Auto-scroll pendant le streaming
716
+ const scrollInterval = setInterval(() => {
717
+ smoothScrollToBottom();
718
+ }, 200);
719
+
720
+ setTimeout(() => {
721
+ clearInterval(scrollInterval);
722
+ }, (text.split(' ').length * delay) + 2000);
723
+ }
724
+
725
+ // Fonction de scroll optimisée
726
+ function smoothScrollToBottom() {
727
+ const targetScrollTop = Math.max(0, document.documentElement.scrollHeight - window.innerHeight - 50);
728
+ window.scrollTo({
729
+ top: targetScrollTop,
730
+ behavior: 'smooth'
731
+ });
732
  }
733
 
734
  // --- Gestion de l'interface ---
735
  $('#type-select').change(function() {
736
  const type = $(this).val();
737
  // Cacher les sections de résultat lors du changement de type
738
+ $('#loading-section, #thinking-wrapper, #response, #action-buttons').addClass('hidden');
739
 
740
  if (type === '3') { // Sujet Type 3
741
  $('#text-input-container').hide();
742
  $('#image-input-container').show().addClass('animate-fadeIn');
743
  $('#deepthink-btn').hide();
744
  $('#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');
 
745
  } else { // Dissertation texte
746
  $('#text-input-container').show().addClass('animate-fadeIn');
747
  $('#image-input-container').hide();
 
758
  }
759
  });
760
 
761
+ // --- Gestion des clics pour démarrer la génération ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
  $('#submit-btn, #deepthink-btn').click(function() {
763
  const type = $('#type-select').val();
764
  const isDeepThink = $(this).attr('id') === 'deepthink-btn';
 
769
  Swal.fire('Erreur', 'Veuillez sélectionner un document.', 'error');
770
  return;
771
  }
 
 
772
  } else {
773
  const question = $('#question').val().trim();
774
  if (!question) {
775
  Swal.fire('Erreur', 'Veuillez saisir un sujet.', 'error');
776
  return;
777
  }
 
 
 
 
 
 
 
778
  }
779
+
780
+ // Cacher les sections précédentes et démarrer le loader
781
+ $('#thinking-wrapper, #response, #action-buttons').addClass('hidden');
782
+ startProgressLoader();
783
+
784
+ // Simuler une génération (remplacer par l'appel API réel)
785
+ setTimeout(() => {
786
+ completeProgress();
787
+ }, Math.random() * 3000 + 2000); // Entre 2 et 5 secondes
788
+
789
+ /*
790
+ // Code pour l'appel API réel (décommenter et adapter) :
791
+ const data = type === '3' ?
792
+ new FormData().append('image', $('#image-upload')[0].files[0]) :
793
+ { question: $('#question').val().trim(), type, courseId: $('#course-select').val() || null };
794
+
795
+ const url = type === '3' ? '/stream_philo_image' :
796
+ isDeepThink ? '/stream_philo_deepthink' : '/stream_philo';
797
+
798
+ handleRealStreamedGeneration(url, {
799
+ method: 'POST',
800
+ headers: type !== '3' ? { 'Content-Type': 'application/json' } : undefined,
801
+ body: type === '3' ? data : JSON.stringify(data)
802
+ });
803
+ */
804
  });
805
 
806
+ // Fonction pour les vrais appels API streamés (à utiliser quand l'API est prête)
807
+ async function handleRealStreamedGeneration(url, options) {
808
+ startProgressLoader();
809
+
810
+ let fullResponseText = '', fullThinkingText = '';
811
+
812
+ try {
813
+ const response = await fetch(url, options);
814
+ if (!response.ok) throw new Error(await response.text());
815
+
816
+ // Compléter la barre de progression
817
+ completeProgress();
818
+
819
+ const reader = response.body.getReader();
820
+ const decoder = new TextDecoder();
821
+ let buffer = '';
822
+
823
+ while (true) {
824
+ const { value, done } = await reader.read();
825
+ if (done) break;
826
+
827
+ buffer += decoder.decode(value, { stream: true });
828
+ const lines = buffer.split('\n');
829
+ buffer = lines.pop();
830
+
831
+ for (const line of lines) {
832
+ if (line.trim() === '') continue;
833
+ try {
834
+ const data = JSON.parse(line.trim());
835
+
836
+ if (data.type === 'thought') {
837
+ fullThinkingText += data.content;
838
+ } else if (data.type === 'answer') {
839
+ fullResponseText += data.content;
840
+ } else if (data.type === 'error') {
841
+ throw new Error(data.content);
842
+ }
843
+ } catch (e) {
844
+ console.error("Erreur JSON parse:", line, e);
845
+ }
846
+ }
847
+ }
848
+
849
+ // Démarrer l'affichage streaming avec les vraies données
850
+ startWordByWordStreaming(fullThinkingText, fullResponseText);
851
+
852
+ } catch (error) {
853
+ $('#loading-section').addClass('hidden');
854
+ Swal.fire({ icon: 'error', title: 'Erreur', text: error.message || "Une erreur inconnue est survenue." });
855
+ }
856
+ }
857
+
858
  // --- Fonctions de gestion de l'historique ---
859
  function loadCourses() {
860
+ // Simuler le chargement des cours
861
+ const sampleCourses = [
862
+ { id: '1', title: 'Introduction à la philosophie politique', author: 'Martin' },
863
+ { id: '2', title: 'Éthique et morale', author: 'Dubois' },
864
+ { id: '3', title: 'Métaphysique contemporaine', author: 'Laurent' }
 
865
  ];
866
 
867
  const select = $('#course-select');
868
+ sampleCourses.forEach(course => {
869
  const newOption = new Option(course.title, course.id, false, false);
870
  $(newOption).data('author', course.author);
871
  select.append(newOption);
872
  });
873
  select.trigger('change');
874
+
875
+ /*
876
+ // Code pour l'appel API réel :
877
+ $.ajax({
878
+ url: '/api/philosophy/courses',
879
+ method: 'GET',
880
+ }).done(function(courses) {
881
+ const select = $('#course-select');
882
+ courses.forEach(course => {
883
+ const newOption = new Option(course.title, course.id, false, false);
884
+ $(newOption).data('author', course.author);
885
+ select.append(newOption);
886
+ });
887
+ select.trigger('change');
888
+ }).fail(function() {
889
+ Toast.fire({ icon: 'error', title: 'Erreur de chargement des cours' });
890
+ });
891
+ */
892
  }
 
893
 
894
  function saveDissertation(title, content) {
895
  if (!title || !content) return;
 
940
  });
941
  }
942
 
943
+ // Gestion des interactions avec l'historique
944
  $('#dissertations-list').on('click', '.collapsible', function() {
945
  const content = $(this).next('.content');
946
  const isVisible = content.is(':visible');
947
 
 
948
  content.stop(true, true).slideToggle({
949
  duration: 300,
950
  easing: 'swing',
 
975
  });
976
  });
977
 
978
+ // Optimisations mobiles
979
  let ticking = false;
980
 
981
  function updateScrollPosition() {
 
989
  }
990
  }, { passive: true });
991
 
 
992
  let resizeTimer;
993
  window.addEventListener('resize', function() {
994
  clearTimeout(resizeTimer);
 
1001
  }, 250);
1002
  }, { passive: true });
1003
 
 
1004
  if ('ontouchstart' in window) {
1005
  $('body').addClass('touch-device');
1006
 
 
1014
  });
1015
  }
1016
 
1017
+ // Initialisation
1018
+ loadCourses();
1019
  updateSavedDissertationsList();
1020
  });
1021
  </script>