Docfile commited on
Commit
8c0e8fe
·
verified ·
1 Parent(s): b4f195b

Upload svt.html

Browse files
Files changed (1) hide show
  1. templates/svt.html +101 -145
templates/svt.html CHANGED
@@ -9,16 +9,20 @@
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
10
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
  <style>
12
- /* --- Styles (conservés et légèrement ajustés) --- */
13
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
14
-
15
  body {
16
  font-family: 'Poppins', sans-serif;
17
  background: linear-gradient(135deg, #f6f8fc 0%, #e9f0f7 100%);
18
  }
19
 
20
- .animate-fade-in { animation: fadeIn 0.5s ease-in; }
21
- .animate-slide-up { animation: slideUp 0.5s ease-out; }
 
 
 
 
 
22
 
23
  @keyframes fadeIn {
24
  from { opacity: 0; }
@@ -36,11 +40,21 @@
36
  border: 1px solid rgba(255, 255, 255, 0.2);
37
  }
38
 
39
- .hover-scale { transition: transform 0.2s; }
40
- .hover-scale:hover { transform: scale(1.02); }
 
 
 
 
 
 
 
 
 
41
 
42
- .image-preview { transition: all 0.3s ease; }
43
- .image-preview:hover .image-overlay { opacity: 1; }
 
44
 
45
  .image-overlay {
46
  opacity: 0;
@@ -59,48 +73,52 @@
59
  .preview-container {
60
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
61
  }
62
- .image-preview img { height: 28 object-cover;}
 
 
63
  #historyContainer .grid {
64
  grid-template-columns: repeat(1, minmax(0, 1fr));
65
  }
66
  }
67
 
68
- #response {
69
- white-space: pre-wrap;
70
- word-wrap: break-word;
71
- overflow-wrap: break-word;
72
- }
73
-
74
- /* Styles pour le contenu markdown */
75
- #response p {
76
- margin-bottom: 1em;
77
- line-height: 1.6;
78
- }
79
-
80
- #response ul, #response ol {
81
- padding-left: 1.5em;
82
- margin-bottom: 1em;
83
- }
84
-
85
- #response li {
86
- margin-bottom: 0.5em;
87
- }
88
-
89
- @media (max-width: 640px) {
90
- #response {
91
- font-size: 0.95rem;
92
- padding: 1rem;
93
- }
94
-
95
- #response p, #response li {
96
- line-height: 1.7;
97
- }
98
-
99
- #response ul, #response ol {
100
- padding-left: 1.2em;
101
- }
102
- }
103
- /* --- Fin des Styles --- */
 
 
104
  </style>
105
  </head>
106
  <body class="min-h-screen flex items-center justify-center p-4 md:p-8">
@@ -138,10 +156,12 @@
138
  </div>
139
 
140
  <!-- Conteneur des prévisualisations -->
141
- <div id="previewContainer" class="preview-container mt-4"></div>
 
 
142
  </div>
143
 
144
- <button id="submitBtn" onclick="submitQuestion()" class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white py-4 rounded-xl hover-scale transition-all duration-300 font-semibold text-lg flex items-center justify-center gap-2">
145
  <i class="fas fa-paper-plane"></i>
146
  Analyser
147
  </button>
@@ -163,18 +183,22 @@
163
  </div>
164
  </div>
165
 
 
166
  <div class="space-y-8 animate-slide-up">
167
  <h2 class="text-2xl font-bold text-blue-900 mb-4">Historique des Réponses</h2>
168
  <button onclick="clearLocalStorage()" class="bg-red-500 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
169
  Effacer l'historique
170
  </button>
171
- <div id="historyContainer"></div>
 
 
172
  </div>
173
  </div>
174
 
 
 
175
  <script>
176
  let uploadedFiles = [];
177
- let submitInProgress = false; // Flag pour empêcher les soumissions multiples
178
 
179
  function handleImageUpload(event) {
180
  const files = event.target.files;
@@ -182,17 +206,6 @@
182
 
183
  for (let i = 0; i < files.length; i++) {
184
  const file = files[i];
185
- // Validation de la taille du fichier (exemple: 5MB)
186
- if (file.size > 50 * 1024 * 1024) {
187
- Swal.fire({
188
- icon: 'error',
189
- title: 'Fichier trop volumineux',
190
- text: `Le fichier "${file.name}" est trop volumineux. La taille maximale est de 5MB.`,
191
- confirmButtonColor: '#2563eb'
192
- });
193
- continue; // Passe au fichier suivant
194
- }
195
-
196
  uploadedFiles.push(file);
197
 
198
  const reader = new FileReader();
@@ -237,7 +250,6 @@
237
  }
238
  }
239
 
240
-
241
  function previewImage(src) {
242
  Swal.fire({
243
  imageUrl: src,
@@ -268,10 +280,6 @@
268
  <i class="fas fa-crop-alt text-purple-500 mt-1"></i>
269
  <p>Rognez vos images pour ne garder que l'essentiel du sujet.</p>
270
  </div>
271
- <div class="flex items-start gap-3">
272
- <i class="fas fa-exclamation-triangle text-yellow-500 mt-1"></i>
273
- <p><b>Important:</b> Ne soumettez qu'une seule fois votre requête. Des soumissions multiples peuvent entraîner des erreurs.</p>
274
- </div>
275
  </div>
276
  `,
277
  icon: 'info',
@@ -305,8 +313,6 @@
305
  }
306
 
307
  async function submitQuestion() {
308
- if (submitInProgress) return; // Empêche les soumissions multiples
309
-
310
  if (uploadedFiles.length === 0) {
311
  Swal.fire({
312
  icon: 'error',
@@ -321,13 +327,7 @@
321
  const loader = document.getElementById('loader');
322
  const responseDiv = document.getElementById('response');
323
  const copyResponseContainer = document.getElementById('copyResponseContainer');
324
- const submitBtn = document.getElementById('submitBtn');
325
-
326
 
327
- // Désactive le bouton et affiche le loader
328
- submitInProgress = true;
329
- submitBtn.disabled = true;
330
- submitBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> Analyse...`;
331
  loader.classList.remove('hidden');
332
  responseDiv.innerHTML = '';
333
  copyResponseContainer.classList.add('hidden');
@@ -339,29 +339,27 @@
339
  formData.append('images', uploadedFiles[i]);
340
  }
341
 
342
- try {
343
- const response = await fetch('/svt_submit', {
344
- method: 'POST',
345
- body: formData
346
- });
347
-
348
- if (!response.ok) {
349
- // Gère les erreurs HTTP (ex: 404, 500, etc.)
350
- throw new Error(`Erreur HTTP: ${response.status} ${response.statusText}`);
351
- }
352
-
353
- const data = await response.json();
354
 
355
- if (data.error) {
356
- // Gère les erreurs spécifiques de l'application (ex: format d'image invalide)
357
- throw new Error(data.error);
358
- }
359
 
 
 
 
 
 
 
 
360
  const htmlContent = marked.parse(data.response);
361
  responseDiv.innerHTML = htmlContent;
362
  responseDiv.classList.add('animate-fade-in');
363
  copyResponseContainer.classList.remove('hidden');
364
 
 
365
  const imagesData = await Promise.all(uploadedFiles.map(file => {
366
  return new Promise((resolve) => {
367
  const reader = new FileReader();
@@ -372,34 +370,10 @@
372
 
373
  saveResponseToLocalStorage(option, imagesData, data.response);
374
  displayHistory();
375
-
376
- } catch (error) {
377
- console.error("Erreur lors de la soumission:", error);
378
- responseDiv.innerHTML = `
379
- <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
380
- <p class="text-red-700">Une erreur s'est produite: ${error.message}</p>
381
- </div>
382
- `;
383
- Swal.fire({
384
- icon: 'error',
385
- title: 'Erreur',
386
- text: `Une erreur s'est produite lors de l'analyse. Veuillez réessayer plus tard. Détails: ${error.message}`,
387
- confirmButtonColor: '#2563eb'
388
- });
389
- } finally {
390
- // Réactive le bouton et cache le loader
391
- submitInProgress = false;
392
- submitBtn.disabled = false;
393
- submitBtn.innerHTML = `<i class="fas fa-paper-plane"></i> Analyser`;
394
- loader.classList.add('hidden');
395
- // Reset uploaded files after successful or failed submission.
396
- uploadedFiles = [];
397
- document.getElementById('previewContainer').innerHTML = '';
398
- document.getElementById("imageUpload").value = null;
399
  }
400
  }
401
 
402
-
403
  function saveResponseToLocalStorage(option, images, response) {
404
  const timestamp = new Date().toISOString();
405
  const data = { option, images, response, timestamp };
@@ -419,35 +393,17 @@
419
  }
420
 
421
  function clearLocalStorage() {
422
- Swal.fire({
423
- title: 'Êtes-vous sûr(e) ?',
424
- text: "Cette action effacera tout l'historique. Elle est irréversible.",
425
- icon: 'warning',
426
- showCancelButton: true,
427
- confirmButtonColor: '#d33',
428
- cancelButtonColor: '#3085d6',
429
- confirmButtonText: 'Oui, effacer!',
430
- cancelButtonText: 'Annuler'
431
- }).then((result) => {
432
- if (result.isConfirmed) {
433
- const keysToRemove = [];
434
- for (let i = 0; i < localStorage.length; i++) {
435
- const key = localStorage.key(i);
436
- if (key.startsWith('svt_response_')) {
437
- keysToRemove.push(key);
438
- }
439
- }
440
- for (const key of keysToRemove) {
441
- localStorage.removeItem(key);
442
- }
443
- displayHistory();
444
- Swal.fire(
445
- 'Effacé!',
446
- 'Votre historique a été effacé.',
447
- 'success'
448
- )
449
  }
450
- });
 
 
 
 
451
  }
452
 
453
  function displayHistory() {
@@ -461,7 +417,7 @@
461
  }
462
 
463
  const responseList = document.createElement('ul');
464
- responseList.className = 'grid gap-4 md:grid-cols-2';
465
 
466
  responses.forEach(response => {
467
  const listItem = document.createElement('li');
@@ -477,7 +433,7 @@
477
  response.images.forEach(imageData => {
478
  const img = document.createElement('img');
479
  img.src = imageData;
480
- img.className = 'h-12 w-12 object-cover rounded-md cursor-pointer';
481
  img.onclick = () => previewImage(imageData);
482
  previewContainer.appendChild(img);
483
  });
@@ -504,7 +460,7 @@
504
  historyContainer.appendChild(responseList);
505
  }
506
 
507
- displayHistory(); // Affiche l'historique au chargement initial
508
  </script>
509
  </body>
510
  </html>
 
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
10
  <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
11
  <style>
 
12
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
13
+
14
  body {
15
  font-family: 'Poppins', sans-serif;
16
  background: linear-gradient(135deg, #f6f8fc 0%, #e9f0f7 100%);
17
  }
18
 
19
+ .animate-fade-in {
20
+ animation: fadeIn 0.5s ease-in;
21
+ }
22
+
23
+ .animate-slide-up {
24
+ animation: slideUp 0.5s ease-out;
25
+ }
26
 
27
  @keyframes fadeIn {
28
  from { opacity: 0; }
 
40
  border: 1px solid rgba(255, 255, 255, 0.2);
41
  }
42
 
43
+ .hover-scale {
44
+ transition: transform 0.2s;
45
+ }
46
+
47
+ .hover-scale:hover {
48
+ transform: scale(1.02);
49
+ }
50
+
51
+ .image-preview {
52
+ transition: all 0.3s ease;
53
+ }
54
 
55
+ .image-preview:hover .image-overlay {
56
+ opacity: 1;
57
+ }
58
 
59
  .image-overlay {
60
  opacity: 0;
 
73
  .preview-container {
74
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
75
  }
76
+ .image-preview img {
77
+ height: 32 object-cover;
78
+ }
79
  #historyContainer .grid {
80
  grid-template-columns: repeat(1, minmax(0, 1fr));
81
  }
82
  }
83
 
84
+ #response {
85
+ white-space: pre-wrap; /* Préserve les espaces et les sauts de ligne */
86
+ word-wrap: break-word; /* Permet le retour à la ligne des mots longs */
87
+ overflow-wrap: break-word; /* Assure la césure des mots très longs */
88
+ }
89
+
90
+ /* Styles pour le contenu markdown */
91
+ #response p {
92
+ margin-bottom: 1em;
93
+ line-height: 1.6;
94
+ }
95
+
96
+ #response ul, #response ol {
97
+ padding-left: 1.5em;
98
+ margin-bottom: 1em;
99
+ }
100
+
101
+ #response li {
102
+ margin-bottom: 0.5em;
103
+ }
104
+
105
+ /* Ajustements responsives pour mobile */
106
+ @media (max-width: 640px) {
107
+ #response {
108
+ font-size: 0.95rem;
109
+ padding: 1rem;
110
+ }
111
+
112
+ #response p, #response li {
113
+ line-height: 1.7;
114
+ }
115
+
116
+ #response ul, #response ol {
117
+ padding-left: 1.2em;
118
+ }
119
+ }
120
+
121
+
122
  </style>
123
  </head>
124
  <body class="min-h-screen flex items-center justify-center p-4 md:p-8">
 
156
  </div>
157
 
158
  <!-- Conteneur des prévisualisations -->
159
+ <div id="previewContainer" class="preview-container mt-4">
160
+ <!-- Les prévisualisations seront ajoutées ici -->
161
+ </div>
162
  </div>
163
 
164
+ <button onclick="submitQuestion()" class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white py-4 rounded-xl hover-scale transition-all duration-300 font-semibold text-lg flex items-center justify-center gap-2">
165
  <i class="fas fa-paper-plane"></i>
166
  Analyser
167
  </button>
 
183
  </div>
184
  </div>
185
 
186
+ <!-- Historique des réponses déplacé ici -->
187
  <div class="space-y-8 animate-slide-up">
188
  <h2 class="text-2xl font-bold text-blue-900 mb-4">Historique des Réponses</h2>
189
  <button onclick="clearLocalStorage()" class="bg-red-500 hover:bg-red-700 text-white px-4 py-2 rounded-lg">
190
  Effacer l'historique
191
  </button>
192
+ <div id="historyContainer">
193
+ <!-- L'historique des réponses sera affiché ici -->
194
+ </div>
195
  </div>
196
  </div>
197
 
198
+
199
+
200
  <script>
201
  let uploadedFiles = [];
 
202
 
203
  function handleImageUpload(event) {
204
  const files = event.target.files;
 
206
 
207
  for (let i = 0; i < files.length; i++) {
208
  const file = files[i];
 
 
 
 
 
 
 
 
 
 
 
209
  uploadedFiles.push(file);
210
 
211
  const reader = new FileReader();
 
250
  }
251
  }
252
 
 
253
  function previewImage(src) {
254
  Swal.fire({
255
  imageUrl: src,
 
280
  <i class="fas fa-crop-alt text-purple-500 mt-1"></i>
281
  <p>Rognez vos images pour ne garder que l'essentiel du sujet.</p>
282
  </div>
 
 
 
 
283
  </div>
284
  `,
285
  icon: 'info',
 
313
  }
314
 
315
  async function submitQuestion() {
 
 
316
  if (uploadedFiles.length === 0) {
317
  Swal.fire({
318
  icon: 'error',
 
327
  const loader = document.getElementById('loader');
328
  const responseDiv = document.getElementById('response');
329
  const copyResponseContainer = document.getElementById('copyResponseContainer');
 
 
330
 
 
 
 
 
331
  loader.classList.remove('hidden');
332
  responseDiv.innerHTML = '';
333
  copyResponseContainer.classList.add('hidden');
 
339
  formData.append('images', uploadedFiles[i]);
340
  }
341
 
342
+ const response = await fetch('/svt_submit', {
343
+ method: 'POST',
344
+ body: formData
345
+ });
 
 
 
 
 
 
 
 
346
 
347
+ const data = await response.json();
348
+ loader.classList.add('hidden');
 
 
349
 
350
+ if (data.error) {
351
+ responseDiv.innerHTML = `
352
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
353
+ <p class="text-red-700">Erreur : ${data.error}</p>
354
+ </div>
355
+ `;
356
+ } else {
357
  const htmlContent = marked.parse(data.response);
358
  responseDiv.innerHTML = htmlContent;
359
  responseDiv.classList.add('animate-fade-in');
360
  copyResponseContainer.classList.remove('hidden');
361
 
362
+ // Convertir les images en base64 avant de les sauvegarder
363
  const imagesData = await Promise.all(uploadedFiles.map(file => {
364
  return new Promise((resolve) => {
365
  const reader = new FileReader();
 
370
 
371
  saveResponseToLocalStorage(option, imagesData, data.response);
372
  displayHistory();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  }
374
  }
375
 
376
+ // Fonctions pour la gestion du localStorage
377
  function saveResponseToLocalStorage(option, images, response) {
378
  const timestamp = new Date().toISOString();
379
  const data = { option, images, response, timestamp };
 
393
  }
394
 
395
  function clearLocalStorage() {
396
+ const keysToRemove = [];
397
+ for (let i = 0; i < localStorage.length; i++) {
398
+ const key = localStorage.key(i);
399
+ if (key.startsWith('svt_response_')) {
400
+ keysToRemove.push(key);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
401
  }
402
+ }
403
+ for (const key of keysToRemove) {
404
+ localStorage.removeItem(key);
405
+ }
406
+ displayHistory();
407
  }
408
 
409
  function displayHistory() {
 
417
  }
418
 
419
  const responseList = document.createElement('ul');
420
+ responseList.className = 'grid gap-4 md:grid-cols-2';
421
 
422
  responses.forEach(response => {
423
  const listItem = document.createElement('li');
 
433
  response.images.forEach(imageData => {
434
  const img = document.createElement('img');
435
  img.src = imageData;
436
+ img.className = 'h-12 w-12 object-cover rounded-md cursor-pointer';
437
  img.onclick = () => previewImage(imageData);
438
  previewContainer.appendChild(img);
439
  });
 
460
  historyContainer.appendChild(responseList);
461
  }
462
 
463
+ displayHistory();
464
  </script>
465
  </body>
466
  </html>