Docfile commited on
Commit
1731ca9
·
verified ·
1 Parent(s): d300041

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +318 -109
templates/index.html CHANGED
@@ -43,16 +43,28 @@
43
  background-color: #EEF2FF;
44
  }
45
 
46
- /* Styles améliorés pour le tableau avec scroll horizontal */
 
 
 
 
 
 
 
 
 
 
 
 
47
  .markdown-content {
48
  overflow-x: auto;
49
- padding-bottom: 1rem; /* Espace pour la scrollbar */
50
  }
51
 
52
  .markdown-content table {
53
  border-collapse: collapse;
54
  min-width: 100%;
55
- width: max-content; /* Permet au tableau de dépasser si nécessaire */
56
  margin: 1rem 0;
57
  }
58
 
@@ -61,8 +73,8 @@
61
  border: 1px solid #e5e7eb;
62
  padding: 0.75rem;
63
  text-align: left;
64
- white-space: nowrap; /* Empêche le retour à la ligne */
65
- min-width: 150px; /* Largeur minimum pour chaque cellule */
66
  }
67
 
68
  .markdown-content th {
@@ -73,64 +85,48 @@
73
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
74
  }
75
 
76
- /* Style pour la scrollbar horizontale */
77
- .markdown-content::-webkit-scrollbar {
78
  height: 8px;
 
79
  }
80
 
81
- .markdown-content::-webkit-scrollbar-track {
82
  background: #f1f1f1;
83
  border-radius: 4px;
84
  }
85
 
86
- .markdown-content::-webkit-scrollbar-thumb {
87
  background: #6366F1;
88
  border-radius: 4px;
89
  }
90
 
91
- .markdown-content::-webkit-scrollbar-thumb:hover {
92
  background: #4F46E5;
93
  }
94
 
95
- /* Styles pour SweetAlert2 */
96
- .swal2-popup {
97
- width: 90% !important;
98
- max-width: 1200px !important;
 
 
99
  }
100
 
101
- .swal2-html-container {
102
- max-height: 80vh !important;
103
- overflow-y: auto !important;
104
- }
105
-
106
- /* Indicateur de défilement */
107
- .scroll-indicator {
108
- position: absolute;
109
- bottom: 20px;
110
- right: 20px;
111
- background: rgba(99, 102, 241, 0.9);
112
- color: white;
113
- padding: 8px 12px;
114
- border-radius: 4px;
115
- font-size: 0.875rem;
116
- opacity: 0;
117
- transition: opacity 0.3s ease;
118
- pointer-events: none;
119
- }
120
-
121
- .scroll-indicator.visible {
122
- opacity: 1;
123
  }
124
  </style>
125
  </head>
126
  <body class="bg-gray-50">
127
- <!-- [Le reste du HTML reste identique jusqu'au script] -->
128
  <div class="min-h-screen">
129
  <!-- Header -->
130
  <header class="bg-white shadow-sm">
131
  <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
132
  <h1 class="text-3xl font-bold text-indigo-600">Mariam AI</h1>
133
- <p class="mt-1 text-sm text-gray-500">Assistant pour commentaire composé.</p>
134
  </div>
135
  </header>
136
 
@@ -139,24 +135,46 @@
139
  <!-- Upload Section -->
140
  <div class="bg-white rounded-lg shadow p-6 mb-8">
141
  <form id="uploadForm" class="space-y-6">
142
- <div class="upload-zone rounded-lg p-8 text-center">
143
- <input type="file" id="imageInput" accept="image/*" required
144
- class="hidden" onchange="updateFileName()">
145
- <label for="imageInput" class="cursor-pointer">
146
- <div class="space-y-2">
147
- <svg class="mx-auto h-12 w-12 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
148
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
149
- d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
150
- </svg>
151
- <div id="fileName" class="text-sm text-gray-500">
152
- Cliquez ou glissez une image ici
 
 
 
 
 
 
 
 
 
 
153
  </div>
154
  </div>
155
  </label>
156
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
157
  <div class="flex justify-center">
158
- <button type="submit"
159
- class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200">
160
  Analyser l'image
161
  </button>
162
  </div>
@@ -173,18 +191,21 @@
173
 
174
  <!-- Results Section -->
175
  <div id="results" class="space-y-8 hidden">
176
- <!-- Tableau Button -->
177
- <div class="flex justify-center">
178
  <button id="showTableau"
179
  class="px-6 py-3 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition-colors duration-200">
180
  Voir le tableau d'analyse
181
  </button>
 
 
 
 
182
  </div>
183
 
184
  <!-- Dissertation -->
185
  <div class="bg-white rounded-lg shadow p-6">
186
  <h2 class="text-2xl font-bold text-gray-900 mb-4">Dissertation</h2>
187
- <div id="dissertationResult" class="prose max-w-none markdown-content">
188
  </div>
189
  </div>
190
  </div>
@@ -193,68 +214,99 @@
193
 
194
  <script>
195
  let tableauContent = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- function updateFileName() {
198
- const input = document.getElementById('imageInput');
199
- const fileName = document.getElementById('fileName');
200
- if (input.files.length > 0) {
201
- fileName.textContent = input.files[0].name;
 
 
 
 
 
 
 
 
 
 
202
  } else {
203
- fileName.textContent = 'Cliquez ou glissez une image ici';
 
 
 
 
204
  }
205
  }
206
 
207
- function showScrollIndicator(container) {
208
- if (container.scrollWidth > container.clientWidth) {
209
- const indicator = document.createElement('div');
210
- indicator.className = 'scroll-indicator visible';
211
- indicator.textContent = '← Faites défiler →';
212
- container.parentElement.appendChild(indicator);
213
-
214
- setTimeout(() => {
215
- indicator.classList.remove('visible');
216
- }, 3000);
217
-
218
- container.addEventListener('scroll', () => {
219
- if (container.scrollLeft > 0) {
220
- indicator.classList.add('visible');
221
- } else {
222
- indicator.classList.remove('visible');
223
- }
224
-
225
- // Cache l'indicateur après 1 seconde de scroll
226
- clearTimeout(container.scrollTimeout);
227
- container.scrollTimeout = setTimeout(() => {
228
- indicator.classList.remove('visible');
229
- }, 1000);
230
- });
231
  }
232
  }
233
 
234
- document.getElementById('showTableau').addEventListener('click', () => {
235
- Swal.fire({
236
- title: 'Tableau d\'analyse',
237
- html: marked.parse(tableauContent),
238
- width: '90%',
239
- customClass: {
240
- htmlContainer: 'markdown-content'
241
- },
242
- didRender: () => {
243
- const container = document.querySelector('.markdown-content');
244
- showScrollIndicator(container);
245
-
246
- // Ajout des ombres de défilement
247
- container.addEventListener('scroll', () => {
248
- const maxScroll = container.scrollWidth - container.clientWidth;
249
- container.style.boxShadow = container.scrollLeft > 0 ?
250
- 'inset 10px 0 5px -5px rgba(0,0,0,0.1)' : '';
251
- container.style.boxShadow += container.scrollLeft < maxScroll ?
252
- ', inset -10px 0 5px -5px rgba(0,0,0,0.1)' : '';
253
- });
254
- }
255
- });
256
  });
257
 
 
258
  document.getElementById('uploadForm').addEventListener('submit', async (e) => {
259
  e.preventDefault();
260
 
@@ -263,15 +315,21 @@
263
  const dissertationResult = document.getElementById('dissertationResult');
264
 
265
  const formData = new FormData();
266
- formData.append('image', document.getElementById('imageInput').files[0]);
267
 
268
  loading.classList.remove('hidden');
269
  results.classList.add('hidden');
 
270
 
271
  try {
272
  const response = await fetch('/analyze', {
273
  method: 'POST',
274
- body: formData
 
 
 
 
 
275
  });
276
 
277
  const data = await response.json();
@@ -285,7 +343,7 @@
285
  Swal.fire({
286
  icon: 'error',
287
  title: 'Erreur',
288
- text: data.error
289
  });
290
  }
291
  } catch (error) {
@@ -296,8 +354,159 @@
296
  });
297
  } finally {
298
  loading.classList.add('hidden');
 
 
 
299
  }
300
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
301
  </script>
302
  </body>
303
  </html>
 
43
  background-color: #EEF2FF;
44
  }
45
 
46
+ .upload-zone.drag-over {
47
+ background-color: #EEF2FF;
48
+ border-color: #4F46E5;
49
+ transform: scale(1.02);
50
+ }
51
+
52
+ .preview-image {
53
+ max-height: 300px;
54
+ width: auto;
55
+ object-fit: contain;
56
+ }
57
+
58
+ /* Styles pour le tableau avec scroll horizontal */
59
  .markdown-content {
60
  overflow-x: auto;
61
+ padding-bottom: 1rem;
62
  }
63
 
64
  .markdown-content table {
65
  border-collapse: collapse;
66
  min-width: 100%;
67
+ width: max-content;
68
  margin: 1rem 0;
69
  }
70
 
 
73
  border: 1px solid #e5e7eb;
74
  padding: 0.75rem;
75
  text-align: left;
76
+ white-space: nowrap;
77
+ min-width: 150px;
78
  }
79
 
80
  .markdown-content th {
 
85
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
86
  }
87
 
88
+ /* Styles pour la scrollbar */
89
+ .custom-scrollbar::-webkit-scrollbar {
90
  height: 8px;
91
+ width: 8px;
92
  }
93
 
94
+ .custom-scrollbar::-webkit-scrollbar-track {
95
  background: #f1f1f1;
96
  border-radius: 4px;
97
  }
98
 
99
+ .custom-scrollbar::-webkit-scrollbar-thumb {
100
  background: #6366F1;
101
  border-radius: 4px;
102
  }
103
 
104
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
105
  background: #4F46E5;
106
  }
107
 
108
+ /* Progress bar */
109
+ .progress-bar {
110
+ height: 4px;
111
+ background-color: #E5E7EB;
112
+ border-radius: 2px;
113
+ overflow: hidden;
114
  }
115
 
116
+ .progress-value {
117
+ height: 100%;
118
+ background-color: #6366F1;
119
+ transition: width 0.3s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
  </style>
122
  </head>
123
  <body class="bg-gray-50">
 
124
  <div class="min-h-screen">
125
  <!-- Header -->
126
  <header class="bg-white shadow-sm">
127
  <div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8">
128
  <h1 class="text-3xl font-bold text-indigo-600">Mariam AI</h1>
129
+ <p class="mt-1 text-sm text-gray-500">Assistant pour commentaire composé</p>
130
  </div>
131
  </header>
132
 
 
135
  <!-- Upload Section -->
136
  <div class="bg-white rounded-lg shadow p-6 mb-8">
137
  <form id="uploadForm" class="space-y-6">
138
+ <div class="upload-zone rounded-lg p-8 text-center relative">
139
+ <input type="file" id="imageInput" accept="image/*" required class="hidden">
140
+ <label for="imageInput" class="cursor-pointer block">
141
+ <div id="dropZone" class="space-y-4">
142
+ <!-- Icon container -->
143
+ <div id="uploadIcon" class="transition-all duration-200">
144
+ <svg class="mx-auto h-12 w-12 text-indigo-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
145
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
146
+ d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"/>
147
+ </svg>
148
+ <p id="fileName" class="mt-2 text-sm text-gray-500">
149
+ Cliquez ou glissez une image ici
150
+ </p>
151
+ </div>
152
+
153
+ <!-- Preview container -->
154
+ <div id="previewContainer" class="hidden flex flex-col items-center space-y-4">
155
+ <img id="imagePreview" class="preview-image rounded-lg shadow-md" src="" alt="Prévisualisation">
156
+ <button type="button" id="removeImage" class="text-sm text-red-500 hover:text-red-700">
157
+ Supprimer l'image
158
+ </button>
159
  </div>
160
  </div>
161
  </label>
162
  </div>
163
+
164
+ <!-- Progress bar -->
165
+ <div id="progressContainer" class="hidden">
166
+ <div class="flex justify-between text-sm text-gray-600 mb-1">
167
+ <span>Progression</span>
168
+ <span id="progressText">0%</span>
169
+ </div>
170
+ <div class="progress-bar">
171
+ <div id="progressValue" class="progress-value" style="width: 0%"></div>
172
+ </div>
173
+ </div>
174
+
175
  <div class="flex justify-center">
176
+ <button type="submit" id="analyzeButton" disabled
177
+ class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-gray-400 cursor-not-allowed transition-colors duration-200">
178
  Analyser l'image
179
  </button>
180
  </div>
 
191
 
192
  <!-- Results Section -->
193
  <div id="results" class="space-y-8 hidden">
194
+ <div class="flex justify-center space-x-4">
 
195
  <button id="showTableau"
196
  class="px-6 py-3 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition-colors duration-200">
197
  Voir le tableau d'analyse
198
  </button>
199
+ <button id="downloadPDF"
200
+ class="px-6 py-3 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors duration-200">
201
+ Télécharger en PDF
202
+ </button>
203
  </div>
204
 
205
  <!-- Dissertation -->
206
  <div class="bg-white rounded-lg shadow p-6">
207
  <h2 class="text-2xl font-bold text-gray-900 mb-4">Dissertation</h2>
208
+ <div id="dissertationResult" class="prose max-w-none markdown-content custom-scrollbar">
209
  </div>
210
  </div>
211
  </div>
 
214
 
215
  <script>
216
  let tableauContent = '';
217
+ const dropZone = document.getElementById('dropZone');
218
+ const imageInput = document.getElementById('imageInput');
219
+ const fileName = document.getElementById('fileName');
220
+ const uploadIcon = document.getElementById('uploadIcon');
221
+ const previewContainer = document.getElementById('previewContainer');
222
+ const imagePreview = document.getElementById('imagePreview');
223
+ const removeImage = document.getElementById('removeImage');
224
+ const analyzeButton = document.getElementById('analyzeButton');
225
+ const progressContainer = document.getElementById('progressContainer');
226
+ const progressValue = document.getElementById('progressValue');
227
+ const progressText = document.getElementById('progressText');
228
+
229
+ // Fonctions de drag & drop
230
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
231
+ dropZone.addEventListener(eventName, preventDefaults, false);
232
+ document.body.addEventListener(eventName, preventDefaults, false);
233
+ });
234
+
235
+ ['dragenter', 'dragover'].forEach(eventName => {
236
+ dropZone.addEventListener(eventName, highlight, false);
237
+ });
238
+
239
+ ['dragleave', 'drop'].forEach(eventName => {
240
+ dropZone.addEventListener(eventName, unhighlight, false);
241
+ });
242
+
243
+ dropZone.addEventListener('drop', handleDrop, false);
244
+
245
+ function preventDefaults(e) {
246
+ e.preventDefault();
247
+ e.stopPropagation();
248
+ }
249
 
250
+ function highlight(e) {
251
+ dropZone.classList.add('drag-over');
252
+ }
253
+
254
+ function unhighlight(e) {
255
+ dropZone.classList.remove('drag-over');
256
+ }
257
+
258
+ function handleDrop(e) {
259
+ const dt = e.dataTransfer;
260
+ const files = dt.files;
261
+
262
+ if (files.length > 0 && files[0].type.startsWith('image/')) {
263
+ imageInput.files = files;
264
+ updateImagePreview(files[0]);
265
  } else {
266
+ Swal.fire({
267
+ icon: 'error',
268
+ title: 'Type de fichier non valide',
269
+ text: 'Veuillez sélectionner une image'
270
+ });
271
  }
272
  }
273
 
274
+ // Gestion de la prévisualisation
275
+ imageInput.addEventListener('change', (e) => {
276
+ if (e.target.files.length > 0) {
277
+ updateImagePreview(e.target.files[0]);
278
+ }
279
+ });
280
+
281
+ function updateImagePreview(file) {
282
+ if (file.type.startsWith('image/')) {
283
+ const reader = new FileReader();
284
+ reader.onload = (e) => {
285
+ imagePreview.src = e.target.result;
286
+ uploadIcon.classList.add('hidden');
287
+ previewContainer.classList.remove('hidden');
288
+ analyzeButton.disabled = false;
289
+ analyzeButton.classList.remove('bg
290
+
291
+ -gray-400', 'cursor-not-allowed');
292
+ analyzeButton.classList.add('bg-indigo-600', 'hover:bg-indigo-700');
293
+ };
294
+ reader.readAsDataURL(file);
 
 
 
295
  }
296
  }
297
 
298
+ removeImage.addEventListener('click', (e) => {
299
+ e.preventDefault();
300
+ imageInput.value = '';
301
+ imagePreview.src = '';
302
+ uploadIcon.classList.remove('hidden');
303
+ previewContainer.classList.add('hidden');
304
+ analyzeButton.disabled = true;
305
+ analyzeButton.classList.remove('bg-indigo-600', 'hover:bg-indigo-700');
306
+ analyzeButton.classList.add('bg-gray-400', 'cursor-not-allowed');
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  });
308
 
309
+ // Gestion de l'upload et de l'analyse
310
  document.getElementById('uploadForm').addEventListener('submit', async (e) => {
311
  e.preventDefault();
312
 
 
315
  const dissertationResult = document.getElementById('dissertationResult');
316
 
317
  const formData = new FormData();
318
+ formData.append('image', imageInput.files[0]);
319
 
320
  loading.classList.remove('hidden');
321
  results.classList.add('hidden');
322
+ progressContainer.classList.remove('hidden');
323
 
324
  try {
325
  const response = await fetch('/analyze', {
326
  method: 'POST',
327
+ body: formData,
328
+ onUploadProgress: (progressEvent) => {
329
+ const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
330
+ progressValue.style.width = `${percentCompleted}%`;
331
+ progressText.textContent = `${percentCompleted}%`;
332
+ }
333
  });
334
 
335
  const data = await response.json();
 
343
  Swal.fire({
344
  icon: 'error',
345
  title: 'Erreur',
346
+ text: data.error || 'Une erreur est survenue lors de l\'analyse'
347
  });
348
  }
349
  } catch (error) {
 
354
  });
355
  } finally {
356
  loading.classList.add('hidden');
357
+ progressContainer.classList.add('hidden');
358
+ progressValue.style.width = '0%';
359
+ progressText.textContent = '0%';
360
  }
361
  });
362
+
363
+ // Gestion de l'affichage du tableau
364
+ document.getElementById('showTableau').addEventListener('click', () => {
365
+ Swal.fire({
366
+ title: 'Tableau d\'analyse',
367
+ html: marked.parse(tableauContent),
368
+ width: '90%',
369
+ customClass: {
370
+ htmlContainer: 'markdown-content custom-scrollbar'
371
+ },
372
+ didRender: () => {
373
+ const container = document.querySelector('.markdown-content');
374
+ if (container.scrollWidth > container.clientWidth) {
375
+ // Ajouter des indicateurs de défilement
376
+ const scrollIndicator = document.createElement('div');
377
+ scrollIndicator.className = 'fixed bottom-4 right-4 bg-indigo-600 text-white px-3 py-2 rounded-md shadow-lg opacity-0 transition-opacity duration-300';
378
+ scrollIndicator.textContent = '← Faites défiler →';
379
+ document.body.appendChild(scrollIndicator);
380
+
381
+ // Gérer la visibilité de l'indicateur de défilement
382
+ let scrollTimeout;
383
+ container.addEventListener('scroll', () => {
384
+ scrollIndicator.style.opacity = '1';
385
+ clearTimeout(scrollTimeout);
386
+ scrollTimeout = setTimeout(() => {
387
+ scrollIndicator.style.opacity = '0';
388
+ }, 1500);
389
+ });
390
+
391
+ // Ajouter des ombres de défilement
392
+ function updateScrollShadows() {
393
+ const maxScroll = container.scrollWidth - container.clientWidth;
394
+ const leftShadow = container.scrollLeft > 0 ?
395
+ 'inset 10px 0 5px -5px rgba(0,0,0,0.1)' : '';
396
+ const rightShadow = container.scrollLeft < maxScroll ?
397
+ 'inset -10px 0 5px -5px rgba(0,0,0,0.1)' : '';
398
+
399
+ container.style.boxShadow = [leftShadow, rightShadow]
400
+ .filter(Boolean)
401
+ .join(', ');
402
+ }
403
+
404
+ container.addEventListener('scroll', updateScrollShadows);
405
+ updateScrollShadows(); // Initial call
406
+ }
407
+ }
408
+ });
409
+ });
410
+
411
+ // Gestion du téléchargement PDF
412
+ document.getElementById('downloadPDF').addEventListener('click', async () => {
413
+ try {
414
+ const loadingText = document.createElement('div');
415
+ loadingText.className = 'fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg shadow-xl z-50';
416
+ loadingText.innerHTML = `
417
+ <div class="flex flex-col items-center space-y-4">
418
+ <span class="loader"></span>
419
+ <p class="text-gray-600">Génération du PDF en cours...</p>
420
+ </div>
421
+ `;
422
+ document.body.appendChild(loadingText);
423
+
424
+ // Préparer les données pour le PDF
425
+ const pdfData = {
426
+ dissertation: marked.parse(document.getElementById('dissertationResult').innerHTML),
427
+ tableau: tableauContent,
428
+ imageData: imagePreview.src
429
+ };
430
+
431
+ // Appel à l'API pour générer le PDF
432
+ const response = await fetch('/generate-pdf', {
433
+ method: 'POST',
434
+ headers: {
435
+ 'Content-Type': 'application/json',
436
+ },
437
+ body: JSON.stringify(pdfData)
438
+ });
439
+
440
+ if (!response.ok) throw new Error('Erreur lors de la génération du PDF');
441
+
442
+ // Télécharger le PDF
443
+ const blob = await response.blob();
444
+ const url = window.URL.createObjectURL(blob);
445
+ const a = document.createElement('a');
446
+ a.href = url;
447
+ a.download = 'analyse-mariam-ai.pdf';
448
+ document.body.appendChild(a);
449
+ a.click();
450
+ document.body.removeChild(a);
451
+ window.URL.revokeObjectURL(url);
452
+
453
+ Swal.fire({
454
+ icon: 'success',
455
+ title: 'PDF généré avec succès',
456
+ text: 'Le téléchargement devrait commencer automatiquement'
457
+ });
458
+ } catch (error) {
459
+ Swal.fire({
460
+ icon: 'error',
461
+ title: 'Erreur',
462
+ text: 'Une erreur est survenue lors de la génération du PDF'
463
+ });
464
+ } finally {
465
+ const loadingText = document.querySelector('.fixed.top-1/2');
466
+ if (loadingText) document.body.removeChild(loadingText);
467
+ }
468
+ });
469
+
470
+ // Amélioration de l'accessibilité
471
+ const setupAccessibility = () => {
472
+ // Ajouter des attributs ARIA
473
+ const dropZone = document.querySelector('.upload-zone');
474
+ dropZone.setAttribute('role', 'button');
475
+ dropZone.setAttribute('aria-label', 'Zone de dépôt d\'image. Cliquez ou glissez une image ici.');
476
+
477
+ // Gestion du clavier
478
+ dropZone.addEventListener('keydown', (e) => {
479
+ if (e.key === 'Enter' || e.key === ' ') {
480
+ e.preventDefault();
481
+ imageInput.click();
482
+ }
483
+ });
484
+
485
+ // Messages d'aide pour les lecteurs d'écran
486
+ const addAriaLive = () => {
487
+ const liveRegion = document.createElement('div');
488
+ liveRegion.setAttribute('aria-live', 'polite');
489
+ liveRegion.setAttribute('aria-atomic', 'true');
490
+ liveRegion.className = 'sr-only';
491
+ document.body.appendChild(liveRegion);
492
+
493
+ return (message) => {
494
+ liveRegion.textContent = message;
495
+ };
496
+ };
497
+
498
+ const announce = addAriaLive();
499
+
500
+ // Annoncer les événements importants
501
+ imageInput.addEventListener('change', () => {
502
+ if (imageInput.files.length > 0) {
503
+ announce('Image sélectionnée : ' + imageInput.files[0].name);
504
+ }
505
+ });
506
+ };
507
+
508
+ // Initialisation de l'accessibilité au chargement
509
+ document.addEventListener('DOMContentLoaded', setupAccessibility);
510
  </script>
511
  </body>
512
  </html>