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

Update templates/philosophie.html

Browse files
Files changed (1) hide show
  1. templates/philosophie.html +426 -468
templates/philosophie.html CHANGED
@@ -18,162 +18,25 @@
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,6 +45,7 @@
182
  text-align: left;
183
  outline: none;
184
  transition: var(--transition-fast);
 
185
  touch-action: manipulation;
186
  -webkit-tap-highlight-color: transparent;
187
  }
@@ -195,11 +59,14 @@
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,6 +74,7 @@
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,8 +90,10 @@
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,7 +108,105 @@
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,12 +235,14 @@
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
  }
@@ -281,43 +251,62 @@
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;
@@ -366,7 +355,7 @@
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,7 +371,7 @@
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>
@@ -401,42 +390,33 @@
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,6 +426,7 @@
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,10 +434,11 @@
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,6 +450,7 @@
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,263 +468,261 @@ $(document).ready(function() {
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,64 +739,19 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
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';
765
-
766
- if (type === '3') {
767
- const imageFile = $('#image-upload')[0].files[0];
768
- if (!imageFile) {
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 = '';
@@ -827,16 +763,16 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
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
  }
@@ -846,50 +782,65 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
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,11 +891,12 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
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',
@@ -960,13 +912,14 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
960
  });
961
 
962
  $('#dissertations-list').on('click', '.delete-btn', function(e) {
963
- e.stopPropagation();
964
  const index = $(this).data('index');
965
  deleteDissertation(index);
966
  });
967
 
968
  $('#copy-btn').click(function() {
969
  const htmlToCopy = $('#response > div').html();
 
970
  const textToCopy = new DOMParser().parseFromString(htmlToCopy, 'text/html').body.textContent || "";
971
  navigator.clipboard.writeText(textToCopy).then(() => {
972
  Toast.fire({ icon: 'success', title: 'Copié dans le presse-papiers!' });
@@ -975,10 +928,11 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
975
  });
976
  });
977
 
978
- // Optimisations mobiles
979
  let ticking = false;
980
 
981
  function updateScrollPosition() {
 
982
  ticking = false;
983
  }
984
 
@@ -989,11 +943,14 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
989
  }
990
  }, { passive: true });
991
 
 
992
  let resizeTimer;
993
  window.addEventListener('resize', function() {
994
  clearTimeout(resizeTimer);
995
  resizeTimer = setTimeout(function() {
 
996
  if (window.innerWidth <= 640) {
 
997
  $('body').addClass('mobile-optimized');
998
  } else {
999
  $('body').removeClass('mobile-optimized');
@@ -1001,9 +958,11 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
1001
  }, 250);
1002
  }, { passive: true });
1003
 
 
1004
  if ('ontouchstart' in window) {
1005
  $('body').addClass('touch-device');
1006
 
 
1007
  $(document).on('touchstart', 'button, .collapsible', function() {
1008
  $(this).addClass('touch-active');
1009
  });
@@ -1014,8 +973,7 @@ La liberté ne peut se réduire à la simple capacité de faire tout ce que l'on
1014
  });
1015
  }
1016
 
1017
- // Initialisation
1018
- loadCourses();
1019
  updateSavedDissertationsList();
1020
  });
1021
  </script>
 
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
  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
  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
  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
  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
  transform: translateY(0) translateZ(0);
109
  }
110
  }
111
+
112
+ /* Styles pour le loader élégant */
113
+ .elegant-loader {
114
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
115
+ border-radius: 1rem;
116
+ padding: 2rem;
117
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
118
+ backdrop-filter: blur(10px);
119
+ border: 1px solid rgba(255, 255, 255, 0.2);
120
+ }
121
+
122
+ .loader-content {
123
+ text-align: center;
124
+ color: white;
125
+ }
126
+
127
+ .progress-container {
128
+ background: rgba(255, 255, 255, 0.2);
129
+ border-radius: 50px;
130
+ padding: 4px;
131
+ margin: 1.5rem 0;
132
+ backdrop-filter: blur(10px);
133
+ }
134
+
135
+ .progress-bar {
136
+ background: linear-gradient(90deg, #60a5fa, #34d399, #fbbf24);
137
+ background-size: 200% 100%;
138
+ border-radius: 50px;
139
+ height: 12px;
140
+ width: 0%;
141
+ transition: width 0.3s ease;
142
+ animation: progressGradient 2s ease-in-out infinite;
143
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
144
+ }
145
+
146
+ @keyframes progressGradient {
147
+ 0%, 100% { background-position: 0% 50%; }
148
+ 50% { background-position: 100% 50%; }
149
+ }
150
+
151
+ .loader-icon {
152
+ display: inline-block;
153
+ animation: float 3s ease-in-out infinite;
154
+ font-size: 3rem;
155
+ margin-bottom: 1rem;
156
+ }
157
+
158
+ @keyframes float {
159
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
160
+ 50% { transform: translateY(-10px) rotate(5deg); }
161
+ }
162
+
163
+ .thinking-particles {
164
+ display: flex;
165
+ justify-content: center;
166
+ gap: 0.5rem;
167
+ margin-top: 1rem;
168
+ }
169
+
170
+ .particle {
171
+ width: 8px;
172
+ height: 8px;
173
+ background: rgba(255, 255, 255, 0.8);
174
+ border-radius: 50%;
175
+ animation: particle-bounce 1.4s ease-in-out infinite both;
176
+ }
177
+
178
+ .particle:nth-child(1) { animation-delay: -0.32s; }
179
+ .particle:nth-child(2) { animation-delay: -0.16s; }
180
+ .particle:nth-child(3) { animation-delay: 0s; }
181
+
182
+ @keyframes particle-bounce {
183
+ 0%, 80%, 100% { transform: scale(0.8) translateY(0); opacity: 0.7; }
184
+ 40% { transform: scale(1.2) translateY(-10px); opacity: 1; }
185
+ }
186
+
187
+ /* Effet de pluie pour l'affichage du texte */
188
+ .rain-text {
189
+ opacity: 0;
190
+ transform: translateY(-20px);
191
+ animation: rainDrop 0.8s ease-out forwards;
192
+ }
193
+
194
+ @keyframes rainDrop {
195
+ 0% {
196
+ opacity: 0;
197
+ transform: translateY(-20px) scale(0.95);
198
+ }
199
+ 50% {
200
+ opacity: 0.7;
201
+ transform: translateY(5px) scale(1.02);
202
+ }
203
+ 100% {
204
+ opacity: 1;
205
+ transform: translateY(0) scale(1);
206
+ }
207
+ }
208
+
209
+ /* Styles pour Select2 */
210
  .select2-container--default .select2-selection--single {
211
  border: 1px solid #d1d5db;
212
  border-radius: 0.75rem;
 
235
  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1);
236
  }
237
 
238
+ /* Styles pour l'aperçu de l'image */
239
  #image-preview {
240
  max-height: 200px;
241
  border-radius: 0.75rem;
242
  box-shadow: 0 4px 6px -1px rgba(0,0,0,.1), 0 2px 4px -2px rgba(0,0,0,.1);
243
  }
244
 
245
+ /* Cacher le marqueur par défaut de <details> */
246
  summary {
247
  list-style: none;
248
  }
 
251
  display: none;
252
  }
253
 
254
+ /* Optimisations mobiles */
255
  @media (max-width: 640px) {
256
+ /* Réduire les marges sur mobile pour plus d'espace */
257
  .max-w-3xl {
258
  max-width: 100%;
259
  margin-left: 0.5rem;
260
  margin-right: 0.5rem;
261
  }
262
 
263
+ /* Optimiser l'affichage des sections de contenu */
264
  #response, #thinking-wrapper {
265
+ /* Éviter les débordements horizontaux */
266
  overflow-x: hidden;
267
  }
268
 
269
+ /* Améliorer la lisibilité du texte généré */
270
  .prose {
271
  font-size: 0.95rem;
272
  line-height: 1.6;
273
  }
274
 
275
+ /* Réduire l'animation pour économiser la batterie */
276
  .animate-fadeIn {
277
  animation-duration: 0.2s;
278
  }
279
+
280
+ .elegant-loader {
281
+ padding: 1.5rem;
282
+ margin: 0.5rem;
283
+ }
284
  }
285
 
286
+ /* Conteneur de génération avec scroll optimisé */
287
  .generation-container {
288
+ /* Conteneur stable pour éviter les recalculs de layout */
289
  contain: layout style;
290
+ /* Optimiser les performances de scroll */
291
  transform: translateZ(0);
292
  }
293
 
294
+ /* Améliorer les performances du textarea */
295
  textarea {
296
+ /* Éviter les reflows pendant la saisie */
297
  resize: none;
298
+ /* Optimiser sur mobile */
299
  -webkit-appearance: none;
300
  }
301
 
302
+ /* Optimiser les boutons pour le tactile */
303
  button {
304
  touch-action: manipulation;
305
  -webkit-tap-highlight-color: transparent;
306
  transition: var(--transition-fast);
307
  }
308
 
309
+ /* Scroll fluide pour les éléments avec beaucoup de contenu */
310
  .content-scrollable {
311
  -webkit-overflow-scrolling: touch;
312
  scroll-behavior: smooth;
 
355
  </select>
356
  </div>
357
 
358
+ <!-- Conteneur pour les champs texte (visible par défaut) -->
359
  <div id="text-input-container">
360
  <!-- Course Selection -->
361
  <div class="space-y-2">
 
371
  </div>
372
  </div>
373
 
374
+ <!-- Conteneur pour l'upload d'image (caché par défaut) -->
375
  <div id="image-input-container" class="hidden">
376
  <div class="space-y-2">
377
  <label for="image-upload" class="block text-sm font-medium text-gray-700">Charger un document pour analyse</label>
 
390
  </div>
391
  </div>
392
 
393
+ <!-- Elegant Loading Section -->
394
+ <div id="loading-section" class="hidden mt-8">
395
+ <div class="elegant-loader">
396
+ <div class="loader-content">
397
+ <div class="loader-icon">🧠</div>
398
+ <h3 class="text-xl font-bold mb-2">Réflexion en cours...</h3>
399
+ <p class="text-white/80 mb-4" id="loading-status">Analyse du sujet</p>
400
+
401
+ <div class="progress-container">
402
+ <div class="progress-bar" id="progress-bar"></div>
403
  </div>
404
 
405
+ <div class="flex justify-between text-sm text-white/70 mb-4">
406
+ <span>Progression</span>
407
+ <span id="progress-text">0%</span>
 
 
 
 
 
408
  </div>
409
 
410
+ <div class="thinking-particles">
411
+ <div class="particle"></div>
412
+ <div class="particle"></div>
413
+ <div class="particle"></div>
 
 
 
 
 
414
  </div>
415
  </div>
416
  </div>
417
  </div>
418
 
419
+ <!-- Thinking Process Section (Collapsible) -->
420
  <div id="thinking-wrapper" class="hidden mt-8 generation-container">
421
  <details id="thinking-container" class="bg-white border border-gray-200 rounded-xl shadow-sm">
422
  <summary class="flex justify-between items-center p-4 cursor-pointer">
 
426
  </svg>
427
  </summary>
428
  <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">
429
+ <!-- Le contenu du processus de pensée sera injecté ici -->
430
  </div>
431
  </details>
432
  </div>
 
434
  <!-- Response Section -->
435
  <div id="response" class="hidden mt-6 generation-container">
436
  <div class="bg-white border border-gray-200 rounded-xl shadow-sm p-6 sm:p-8 prose prose-violet max-w-none">
437
+ <!-- Le contenu de la réponse sera injecté ici -->
438
  </div>
439
  </div>
440
 
441
+ <!-- Action Buttons after generation -->
442
  <div id="action-buttons" class="hidden mt-6 grid grid-cols-1 sm:grid-cols-2 gap-4">
443
  <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">
444
  <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>
 
450
  <div class="mt-12">
451
  <h3 class="text-xl font-bold text-gray-800 mb-4">Historique</h3>
452
  <div id="dissertations-list" class="space-y-3">
453
+ <!-- La liste des dissertations sauvegardées sera injectée ici -->
454
  </div>
455
  </div>
456
  </div>
 
468
  $('#course-select').select2({
469
  placeholder: 'Optionnel : choisir un cours...',
470
  allowClear: true,
471
+ templateResult: function (course) { if (!course.id) { return course.text; } return $(`<span>${course.text}</span><span class="course-author">Pr. ${$(course.element).data('author')}</span>`); },
 
 
 
472
  templateSelection: function (course) { return course.text; },
473
  });
474
  marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false });
475
  moment.locale('fr');
476
  const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 3000, timerProgressBar: true });
477
 
478
+ // Variables pour le système de chargement
 
479
  let progressInterval;
480
+ let currentProgress = 0;
481
+ let storedThinkingContent = '';
482
+ let storedResponseContent = '';
483
+
484
+ // Messages de statut pour le loader
485
  const loadingMessages = [
486
+ "Analyse du sujet...",
487
+ "Structuration des idées...",
488
+ "Recherche de références...",
489
+ "Développement de l'argumentation...",
490
+ "Formulation des conclusions...",
491
+ "Finalisation de la réponse..."
 
492
  ];
493
 
494
+ // Fonction pour démarrer le loader élégant
495
+ function startElegantLoader() {
496
+ // Cacher les sections précédentes
497
+ $('#thinking-wrapper, #response, #action-buttons').addClass('hidden');
498
 
499
+ // Afficher le loader
500
  $('#loading-section').removeClass('hidden').addClass('animate-fadeIn');
501
+
502
+ // Reset du progrès
503
+ currentProgress = 0;
504
  $('#progress-bar').css('width', '0%');
505
+ $('#progress-text').text('0%');
506
 
507
+ // Animation du progrès avec messages dynamiques
 
 
 
 
 
 
 
 
508
  progressInterval = setInterval(() => {
509
+ // Incrément intelligent du progrès
510
+ if (currentProgress < 30) {
511
+ currentProgress += Math.random() * 8 + 2; // 2-10% par étape
512
+ } else if (currentProgress < 70) {
513
+ currentProgress += Math.random() * 5 + 1; // 1-6% par étape
514
+ } else if (currentProgress < 95) {
515
+ currentProgress += Math.random() * 2 + 0.5; // 0.5-2.5% par étape
516
+ } else {
517
+ currentProgress = Math.min(currentProgress + 0.1, 99); // Très lent vers la fin
518
  }
519
+
520
+ // Mise à jour de l'interface
521
+ $('#progress-bar').css('width', currentProgress + '%');
522
+ $('#progress-text').text(Math.floor(currentProgress) + '%');
523
+
524
+ // Changement des messages de statut
525
+ const messageIndex = Math.floor(currentProgress / 16.67); // 6 messages répartis sur 100%
526
+ if (messageIndex < loadingMessages.length) {
527
+ $('#loading-status').text(loadingMessages[messageIndex]);
528
+ }
529
+
530
+ }, 200); // Mise à jour toutes les 200ms
 
531
  }
532
 
533
+ // Fonction pour terminer le loader et afficher le contenu
534
+ function finishLoaderAndDisplay() {
535
+ // Terminer le progrès à 100%
536
  currentProgress = 100;
537
+ $('#progress-bar').css('width', '100%');
538
+ $('#progress-text').text('100%');
539
+ $('#loading-status').text('Génération terminée !');
540
 
541
+ clearInterval(progressInterval);
542
+
543
+ // Attendre un peu puis masquer le loader et afficher le contenu
544
  setTimeout(() => {
545
+ $('#loading-section').addClass('hidden');
546
+
547
+ // Afficher le processus de pensée s'il existe
548
+ if (storedThinkingContent) {
549
+ $('#thinking-wrapper').removeClass('hidden').addClass('animate-fadeIn');
550
+ displayContentWithRainEffect($('#thinking-process'), marked.parse(storedThinkingContent));
551
+ }
552
+
553
+ // Afficher la réponse avec effet de pluie
554
+ if (storedResponseContent) {
555
+ $('#response').removeClass('hidden').addClass('animate-fadeIn');
556
+ displayContentWithRainEffect($('#response > div'), marked.parse(storedResponseContent));
557
+ }
558
+
559
+ }, 800); // Délai pour laisser le temps de voir 100%
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  }
561
 
562
+ // Fonction pour afficher le contenu avec effet de pluie (goutte par goutte)
563
+ function displayContentWithRainEffect(container, htmlContent) {
 
564
  container.empty();
565
 
566
+ // Créer un div temporaire pour parser le HTML
 
 
 
567
  const tempDiv = $('<div>').html(htmlContent);
568
 
569
+ // Extraire tous les éléments et textes
570
+ const elements = [];
571
+
572
+ function extractElements(node) {
573
+ $(node).contents().each(function() {
574
+ if (this.nodeType === Node.TEXT_NODE && this.textContent.trim()) {
575
+ // Diviser le texte en mots
576
+ const words = this.textContent.split(/(\s+)/);
577
+ words.forEach(word => {
578
+ if (word.trim()) {
579
+ elements.push({
580
+ type: 'text',
581
+ content: word,
582
+ parent: $(this).parent()[0] ? $(this).parent()[0].tagName.toLowerCase() : 'span'
583
+ });
584
+ } else if (word) {
585
+ elements.push({
586
+ type: 'space',
587
+ content: word,
588
+ parent: $(this).parent()[0] ? $(this).parent()[0].tagName.toLowerCase() : 'span'
589
+ });
590
+ }
591
+ });
592
+ } else if (this.nodeType === Node.ELEMENT_NODE) {
593
+ // Pour les éléments HTML complets (images, liens, etc.)
594
+ if (['img', 'br', 'hr'].includes(this.tagName.toLowerCase())) {
595
+ elements.push({
596
+ type: 'element',
597
+ content: this.outerHTML,
598
+ parent: null
599
+ });
600
+ } else {
601
+ extractElements(this);
602
  }
603
  }
604
+ });
605
+ }
606
+
607
+ extractElements(tempDiv);
608
+
609
+ // Variables pour la reconstruction
610
+ let currentElement = null;
611
+ let elementIndex = 0;
612
+
613
+ // Fonction récursive pour afficher les éléments avec délai
614
+ function showNextElement() {
615
+ if (elementIndex >= elements.length) {
616
+ // Afficher les boutons d'action
617
+ $('#action-buttons').removeClass('hidden').addClass('animate-fadeIn');
618
+ Toast.fire({ icon: 'success', title: 'Génération terminée !' });
619
+ return;
620
+ }
621
+
622
+ const item = elements[elementIndex];
623
+
624
+ // Créer ou continuer l'élément parent
625
+ if (!currentElement || currentElement.prop('tagName').toLowerCase() !== item.parent) {
626
+ if (currentElement) {
627
+ container.append(currentElement);
628
+ }
629
+ currentElement = $(`<${item.parent}></${item.parent}>`);
630
+ }
631
+
632
+ // Créer un span pour l'animation
633
+ let span;
634
+ if (item.type === 'element') {
635
+ span = $(item.content);
636
+ } else {
637
+ span = $('<span>').text(item.content).addClass('rain-text');
638
+ }
639
+
640
+ currentElement.append(span);
641
+
642
+ // Animation de l'élément
643
+ setTimeout(() => {
644
+ span.removeClass('rain-text');
645
 
646
+ // Scroll fluide
647
+ const containerOffset = container.offset();
648
+ const windowHeight = $(window).height();
649
+ if (containerOffset && containerOffset.top > windowHeight * 0.7) {
650
+ $('html, body').animate({
651
+ scrollTop: containerOffset.top - windowHeight * 0.3
652
+ }, 300);
653
+ }
 
 
 
 
654
 
655
+ elementIndex++;
656
+ showNextElement();
657
+ }, 50 + Math.random() * 100); // Délai variable pour un effet plus naturel
 
 
658
  }
659
 
660
+ // Démarrer l'affichage
661
+ showNextElement();
 
 
 
 
 
 
 
662
 
663
+ // S'assurer que le dernier élément est ajouté
664
  setTimeout(() => {
665
+ if (currentElement) {
666
+ container.append(currentElement);
667
+ }
668
+ }, elements.length * 80);
669
  }
670
 
671
+ // Variables pour optimiser le scroll
672
+ let isScrolling = false;
673
+ let scrollTimer = null;
674
+ let lastScrollTime = 0;
675
+
676
+ // Fonction de scroll optimisée avec throttling
677
+ function smoothScrollToBottom(force = false) {
678
+ const now = Date.now();
679
+
680
+ // Throttling : éviter trop d'appels de scroll
681
+ if (!force && now - lastScrollTime < 100) {
682
+ return;
683
+ }
684
+
685
+ lastScrollTime = now;
686
+
687
+ // Annuler le timer précédent s'il existe
688
+ if (scrollTimer) {
689
+ clearTimeout(scrollTimer);
690
+ }
691
+
692
+ // Utiliser requestAnimationFrame pour un scroll plus fluide
693
+ if (!isScrolling) {
694
+ isScrolling = true;
695
+
696
+ requestAnimationFrame(() => {
697
+ // Scroll fluide vers le bas avec une marge
698
+ const targetScrollTop = Math.max(0, document.documentElement.scrollHeight - window.innerHeight - 50);
699
+
700
+ // Utiliser la méthode native pour de meilleures performances
701
+ window.scrollTo({
702
+ top: targetScrollTop,
703
+ behavior: 'smooth'
704
+ });
705
+
706
+ // Marquer comme terminé après l'animation
707
+ scrollTimer = setTimeout(() => {
708
+ isScrolling = false;
709
+ }, 200);
710
+ });
711
+ }
712
  }
713
 
714
  // --- Gestion de l'interface ---
715
  $('#type-select').change(function() {
716
  const type = $(this).val();
717
  // Cacher les sections de résultat lors du changement de type
718
+ $('#thinking-wrapper, #response, #action-buttons, #loading-section').addClass('hidden');
719
 
720
+ if (type === '3') { // Sujet Type 3 (anciennement Analyse d'image)
721
  $('#text-input-container').hide();
722
  $('#image-input-container').show().addClass('animate-fadeIn');
723
  $('#deepthink-btn').hide();
724
  $('#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');
725
+ $('label[for="image-upload"]').text("Charger un document pour analyse (image, etc.)");
726
  } else { // Dissertation texte
727
  $('#text-input-container').show().addClass('animate-fadeIn');
728
  $('#image-input-container').hide();
 
739
  }
740
  });
741
 
742
+ // --- Logique de Génération avec Nouveau Système de Loading ---
743
+ async function handleStreamedGeneration(url, options) {
744
+ // Démarrer le loader élégant
745
+ startElegantLoader();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
746
 
747
+ // Réinitialiser le contenu stocké
748
+ storedThinkingContent = '';
749
+ storedResponseContent = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
 
 
 
 
 
 
 
751
  try {
752
  const response = await fetch(url, options);
753
  if (!response.ok) throw new Error(await response.text());
754
 
 
 
 
755
  const reader = response.body.getReader();
756
  const decoder = new TextDecoder();
757
  let buffer = '';
 
763
  buffer += decoder.decode(value, { stream: true });
764
  const lines = buffer.split('\n');
765
  buffer = lines.pop();
766
+
767
  for (const line of lines) {
768
  if (line.trim() === '') continue;
769
  try {
770
  const data = JSON.parse(line.trim());
771
 
772
  if (data.type === 'thought') {
773
+ storedThinkingContent += data.content;
774
  } else if (data.type === 'answer') {
775
+ storedResponseContent += data.content;
776
  } else if (data.type === 'error') {
777
  throw new Error(data.content);
778
  }
 
782
  }
783
  }
784
 
785
+ // Une fois le streaming terminé, finaliser le loader et afficher le contenu
786
+ finishLoaderAndDisplay();
787
+
788
+ // Sauvegarder la dissertation
789
+ let title;
790
+ if ($('#type-select').val() === '3') {
791
+ const fileName = $('#image-upload')[0].files[0]?.name || "Analyse de document";
792
+ title = `Analyse (Sujet Type 3): ${fileName}`;
793
+ } else {
794
+ title = $('#question').val().trim();
795
+ }
796
+ saveDissertation(title, storedResponseContent);
797
 
798
  } catch (error) {
799
+ clearInterval(progressInterval);
800
  $('#loading-section').addClass('hidden');
801
  Swal.fire({ icon: 'error', title: 'Erreur', text: error.message || "Une erreur inconnue est survenue." });
802
  }
803
  }
804
 
805
+ // --- Gestion des Clics ---
806
+ $('#submit-btn, #deepthink-btn').click(function() {
807
+ const type = $('#type-select').val();
808
+ const isDeepThink = $(this).attr('id') === 'deepthink-btn';
809
+
810
+ if (type === '3') {
811
+ const imageFile = $('#image-upload')[0].files[0];
812
+ if (!imageFile) { Swal.fire('Erreur', 'Veuillez sélectionner un document.', 'error'); return; }
813
+ const formData = new FormData();
814
+ formData.append('image', imageFile);
815
+ handleStreamedGeneration('/stream_philo_image', { method: 'POST', body: formData });
816
+ } else {
817
+ const question = $('#question').val().trim();
818
+ if (!question) { Swal.fire('Erreur', 'Veuillez saisir un sujet.', 'error'); return; }
819
+ const data = { question, type, courseId: $('#course-select').val() || null };
820
+ const url = isDeepThink ? '/stream_philo_deepthink' : '/stream_philo';
821
+ handleStreamedGeneration(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) });
822
+ }
823
+ });
824
+
825
  // --- Fonctions de gestion de l'historique ---
826
  function loadCourses() {
827
+ const courses = [
828
+ { id: 1, title: "Introduction à la philosophie", author: "Martin" },
829
+ { id: 2, title: "Éthique et morale", author: "Dubois" },
830
+ { id: 3, title: "Métaphysique moderne", author: "Bernard" },
831
+ { id: 4, title: "Philosophie politique", author: "Rousseau" },
832
+ { id: 5, title: "Esthétique et art", author: "Delacroix" }
833
  ];
834
 
835
  const select = $('#course-select');
836
+ courses.forEach(course => {
837
  const newOption = new Option(course.title, course.id, false, false);
838
  $(newOption).data('author', course.author);
839
  select.append(newOption);
840
  });
841
  select.trigger('change');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
842
  }
843
+ loadCourses();
844
 
845
  function saveDissertation(title, content) {
846
  if (!title || !content) return;
 
891
  });
892
  }
893
 
894
+ // Optimiser l'ouverture des dissertations avec une transition plus fluide
895
  $('#dissertations-list').on('click', '.collapsible', function() {
896
  const content = $(this).next('.content');
897
  const isVisible = content.is(':visible');
898
 
899
+ // Animation plus fluide avec easing
900
  content.stop(true, true).slideToggle({
901
  duration: 300,
902
  easing: 'swing',
 
912
  });
913
 
914
  $('#dissertations-list').on('click', '.delete-btn', function(e) {
915
+ e.stopPropagation(); // Empêcher le collapsible de se fermer
916
  const index = $(this).data('index');
917
  deleteDissertation(index);
918
  });
919
 
920
  $('#copy-btn').click(function() {
921
  const htmlToCopy = $('#response > div').html();
922
+ // Pour copier le Markdown brut (plus fidèle)
923
  const textToCopy = new DOMParser().parseFromString(htmlToCopy, 'text/html').body.textContent || "";
924
  navigator.clipboard.writeText(textToCopy).then(() => {
925
  Toast.fire({ icon: 'success', title: 'Copié dans le presse-papiers!' });
 
928
  });
929
  });
930
 
931
+ // Optimisation pour les événements de scroll sur mobile
932
  let ticking = false;
933
 
934
  function updateScrollPosition() {
935
+ // Code pour gérer les changements de position de scroll si nécessaire
936
  ticking = false;
937
  }
938
 
 
943
  }
944
  }, { passive: true });
945
 
946
+ // Optimisation du resize pour mobile
947
  let resizeTimer;
948
  window.addEventListener('resize', function() {
949
  clearTimeout(resizeTimer);
950
  resizeTimer = setTimeout(function() {
951
+ // Recalculer les dimensions si nécessaire
952
  if (window.innerWidth <= 640) {
953
+ // Optimisations spécifiques mobile
954
  $('body').addClass('mobile-optimized');
955
  } else {
956
  $('body').removeClass('mobile-optimized');
 
958
  }, 250);
959
  }, { passive: true });
960
 
961
+ // Optimisation tactile pour mobile
962
  if ('ontouchstart' in window) {
963
  $('body').addClass('touch-device');
964
 
965
+ // Améliorer les interactions tactiles
966
  $(document).on('touchstart', 'button, .collapsible', function() {
967
  $(this).addClass('touch-active');
968
  });
 
973
  });
974
  }
975
 
976
+ // Init
 
977
  updateSavedDissertationsList();
978
  });
979
  </script>