Docfile commited on
Commit
9154be4
·
verified ·
1 Parent(s): 4bd2c6a

Upload svt.html

Browse files
Files changed (1) hide show
  1. templates/svt.html +145 -101
templates/svt.html CHANGED
@@ -9,20 +9,16 @@
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,21 +36,11 @@
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,52 +59,48 @@
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,12 +138,10 @@
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,22 +163,18 @@
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,6 +182,17 @@
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,6 +237,7 @@
250
  }
251
  }
252
 
 
253
  function previewImage(src) {
254
  Swal.fire({
255
  imageUrl: src,
@@ -280,6 +268,10 @@
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,6 +305,8 @@
313
  }
314
 
315
  async function submitQuestion() {
 
 
316
  if (uploadedFiles.length === 0) {
317
  Swal.fire({
318
  icon: 'error',
@@ -327,7 +321,13 @@
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,27 +339,29 @@
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,10 +372,34 @@
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,17 +419,35 @@
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,7 +461,7 @@
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,7 +477,7 @@
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,7 +504,7 @@
460
  historyContainer.appendChild(responseList);
461
  }
462
 
463
- displayHistory();
464
  </script>
465
  </body>
466
  </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
+ /* --- 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
  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
  .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
  </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
  </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
 
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
  }
238
  }
239
 
240
+
241
  function previewImage(src) {
242
  Swal.fire({
243
  imageUrl: src,
 
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
  }
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
  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
  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
 
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
  }
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
  }
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
  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
  historyContainer.appendChild(responseList);
505
  }
506
 
507
+ displayHistory(); // Affiche l'historique au chargement initial
508
  </script>
509
  </body>
510
  </html>