Docfile commited on
Commit
e2f6838
·
verified ·
1 Parent(s): af4cdb5

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +383 -190
templates/index.html CHANGED
@@ -9,34 +9,51 @@
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
  <style>
11
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
12
-
13
  body {
14
  font-family: 'Poppins', sans-serif;
15
  background-color: #f8fafc;
16
  }
17
-
18
  .drop-zone {
19
  border: 2px dashed #4f46e5;
20
  transition: all 0.3s ease;
21
  }
22
-
23
  .drop-zone:hover, .drop-zone.active {
24
  border-color: #4338ca;
25
  background-color: #eef2ff;
26
  }
27
-
28
  pre {
29
  white-space: pre-wrap;
30
  word-wrap: break-word;
31
- max-height: 500px;
32
  overflow-y: auto;
 
 
 
33
  }
34
-
35
  .pdf-container {
36
- height: 80vh;
37
- border: 1px solid #e5e7eb;
38
- border-radius: 0.5rem;
39
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
  </style>
42
  </head>
@@ -64,39 +81,55 @@
64
  <!-- Main Content -->
65
  <main class="container mx-auto px-4 py-8">
66
  <!-- Status Alert -->
67
- <div x-show="systemStatus.show" :class="systemStatus.type === 'error' ? 'bg-red-100 border-red-500 text-red-800' : 'bg-green-100 border-green-500 text-green-800'"
68
- class="mb-6 p-4 border-l-4 rounded-md flex items-start">
69
- <div class="flex-shrink-0 mr-3">
70
- <i :class="systemStatus.type === 'error' ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'" class="text-xl"></i>
 
 
 
 
 
 
 
 
 
 
 
71
  </div>
72
  <div>
73
  <p class="font-medium" x-text="systemStatus.title"></p>
74
- <p class="text-sm mt-1" x-text="systemStatus.message"></p>
 
75
  </div>
 
 
 
76
  </div>
77
 
78
  <!-- Workflow Steps -->
79
- <div class="flex flex-wrap mb-8">
 
80
  <div class="w-full lg:w-1/3 px-2 mb-4">
81
- <div class="bg-white rounded-lg shadow-md p-6 h-full">
82
  <div class="flex items-center mb-4">
83
- <div class="bg-indigo-100 text-indigo-700 rounded-full w-8 h-8 flex items-center justify-center mr-3">
84
  <i class="fas fa-upload"></i>
85
  </div>
86
  <h2 class="text-xl font-semibold">1. Télécharger une image</h2>
87
  </div>
88
- <p class="text-gray-600 mb-4">Importez une photo d'un exercice de mathématiques, physique ou chimie.</p>
89
-
90
  <!-- Upload Area -->
91
- <div class="mt-4">
92
- <div
93
- @dragover.prevent="dragOver = true"
94
- @dragleave.prevent="dragOver = false"
95
  @drop.prevent="handleDrop($event)"
96
  :class="{'active': dragOver}"
97
  class="drop-zone h-52 rounded-lg flex flex-col items-center justify-center text-center p-4 cursor-pointer"
98
  @click="document.getElementById('fileInput').click()">
99
-
100
  <template x-if="!imagePreview">
101
  <div>
102
  <i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i>
@@ -105,74 +138,91 @@
105
  <p class="text-gray-500 text-sm mt-2">(PNG, JPG, JPEG)</p>
106
  </div>
107
  </template>
108
-
109
  <template x-if="imagePreview">
110
- <img :src="imagePreview" alt="Aperçu" class="h-full object-contain rounded"/>
111
  </template>
112
  </div>
113
-
114
  <input type="file" id="fileInput" class="hidden" accept="image/png, image/jpeg, image/jpg" @change="handleFileSelect($event)">
115
  </div>
 
 
 
116
  </div>
117
  </div>
118
-
 
119
  <div class="w-full lg:w-1/3 px-2 mb-4">
120
- <div class="bg-white rounded-lg shadow-md p-6 h-full">
121
  <div class="flex items-center mb-4">
122
- <div class="bg-indigo-100 text-indigo-700 rounded-full w-8 h-8 flex items-center justify-center mr-3">
123
  <i class="fas fa-robot"></i>
124
  </div>
125
  <h2 class="text-xl font-semibold">2. Analyse IA</h2>
126
  </div>
127
- <p class="text-gray-600 mb-4">Notre IA analysera l'exercice et génèrera une solution détaillée en LaTeX.</p>
128
-
129
- <div class="mt-4">
130
- <button @click="processImage()"
131
- :disabled="!imageFile || processing"
132
- :class="{'bg-indigo-300 cursor-not-allowed': !imageFile || processing, 'bg-indigo-600 hover:bg-indigo-700': imageFile && !processing}"
 
 
 
133
  class="w-full py-3 rounded-md text-white font-medium flex items-center justify-center transition duration-300">
134
  <template x-if="processing">
135
  <i class="fas fa-spinner fa-spin mr-2"></i>
136
  </template>
137
  <span x-text="processing ? 'Traitement en cours...' : 'Analyser et Résoudre'"></span>
138
  </button>
139
-
 
 
 
140
  <div x-show="processingStage" class="mt-4">
141
  <div class="bg-indigo-50 rounded-md p-3">
142
  <div class="flex items-center">
143
  <i class="fas fa-sync fa-spin text-indigo-600 mr-2"></i>
144
- <span class="text-indigo-800 font-medium" x-text="processingStage"></span>
145
  </div>
146
- <div class="w-full bg-indigo-200 rounded-full h-2.5 mt-2">
147
- <div class="bg-indigo-600 h-2.5 rounded-full" :style="`width: ${processingProgress}%`"></div>
148
  </div>
149
  </div>
150
  </div>
151
  </div>
152
  </div>
153
  </div>
154
-
 
155
  <div class="w-full lg:w-1/3 px-2 mb-4">
156
- <div class="bg-white rounded-lg shadow-md p-6 h-full">
157
  <div class="flex items-center mb-4">
158
- <div class="bg-indigo-100 text-indigo-700 rounded-full w-8 h-8 flex items-center justify-center mr-3">
159
  <i class="fas fa-file-pdf"></i>
160
  </div>
161
  <h2 class="text-xl font-semibold">3. Résultat PDF</h2>
162
  </div>
163
  <p class="text-gray-600 mb-4">La solution sera compilée en un PDF professionnel prêt à télécharger.</p>
164
-
165
- <div class="mt-4">
166
- <button @click="downloadPDF()"
167
- :disabled="!pdfData"
168
  :class="{'bg-gray-300 cursor-not-allowed': !pdfData, 'bg-green-600 hover:bg-green-700': pdfData}"
169
  class="w-full py-3 rounded-md text-white font-medium flex items-center justify-center transition duration-300">
170
  <i class="fas fa-download mr-2"></i>
171
  <span>Télécharger le PDF</span>
172
  </button>
173
-
174
- <p x-show="!pdfData" class="text-sm text-gray-500 text-center mt-3">
175
- Le PDF sera disponible après l'analyse
 
 
 
 
 
 
176
  </p>
177
  </div>
178
  </div>
@@ -182,137 +232,217 @@
182
  <!-- Results Section -->
183
  <div x-show="showResults" class="mt-8">
184
  <div class="bg-white rounded-lg shadow-md overflow-hidden">
185
- <div class="bg-indigo-700 text-white px-6 py-4">
186
- <h2 class="text-xl font-semibold">Résultats de l'Analyse</h2>
187
  </div>
188
-
189
  <div class="flex flex-wrap">
190
  <!-- Tabs -->
191
- <div class="w-full px-4 py-4 bg-indigo-50 border-b border-indigo-100">
192
- <div class="flex flex-wrap -mx-2">
193
- <div class="px-2">
194
- <button @click="activeTab = 'pdf'"
195
- :class="{'bg-indigo-600 text-white': activeTab === 'pdf', 'bg-white text-indigo-700': activeTab !== 'pdf'}"
196
- class="px-4 py-2 rounded-md font-medium transition duration-300">
197
- <i class="fas fa-file-pdf mr-2"></i>PDF
 
 
198
  </button>
199
  </div>
200
- <div class="px-2">
201
- <button @click="activeTab = 'latex'"
202
- :class="{'bg-indigo-600 text-white': activeTab === 'latex', 'bg-white text-indigo-700': activeTab !== 'latex'}"
203
- class="px-4 py-2 rounded-md font-medium transition duration-300">
204
  <i class="fas fa-code mr-2"></i>Code LaTeX
205
  </button>
206
  </div>
207
- <div class="px-2" x-show="thinkingProcess">
208
- <button @click="activeTab = 'thinking'"
209
- :class="{'bg-indigo-600 text-white': activeTab === 'thinking', 'bg-white text-indigo-700': activeTab !== 'thinking'}"
210
- class="px-4 py-2 rounded-md font-medium transition duration-300">
211
- <i class="fas fa-brain mr-2"></i>Processus de Réflexion
 
 
 
 
 
 
 
 
 
 
212
  </button>
213
  </div>
214
  </div>
215
  </div>
216
-
217
  <!-- Content Panels -->
218
  <div class="w-full p-6">
219
  <!-- PDF Preview -->
220
- <div x-show="activeTab === 'pdf'" class="pdf-container">
221
- <iframe x-show="pdfData" :src="'data:application/pdf;base64,' + pdfData" width="100%" height="100%" class="border-0"></iframe>
222
- <div x-show="!pdfData" class="h-full flex items-center justify-center">
223
- <div class="text-center text-gray-500">
224
- <i class="fas fa-file-pdf text-4xl mb-3"></i>
225
- <p>Aucun PDF disponible pour le moment</p>
 
 
 
 
 
 
 
226
  </div>
227
  </div>
228
  </div>
229
-
230
  <!-- LaTeX Code -->
231
- <div x-show="activeTab === 'latex'" class="bg-gray-50 rounded-md">
232
- <div class="bg-gray-100 px-4 py-2 border-b border-gray-200 flex justify-between items-center">
233
- <span class="font-medium">Code LaTeX généré</span>
234
- <button @click="copyToClipboard(latexCode)" class="text-indigo-600 hover:text-indigo-800 transition duration-300">
235
  <i class="fas fa-copy mr-1"></i>Copier
236
  </button>
237
  </div>
238
- <pre class="p-4 text-sm"><code x-text="latexCode || 'Aucun code LaTeX disponible'"></code></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  </div>
240
-
241
  <!-- Thinking Process -->
242
- <div x-show="activeTab === 'thinking'" class="bg-gray-50 rounded-md">
243
- <div class="bg-gray-100 px-4 py-2 border-b border-gray-200">
244
- <span class="font-medium">Processus de réflexion de l'IA</span>
245
  </div>
246
- <pre class="p-4 text-sm"><code x-text="thinkingProcess || 'Aucune information sur le processus de réflexion disponible'"></code></pre>
247
  </div>
248
  </div>
249
  </div>
250
  </div>
251
  </div>
252
-
253
  <!-- Footer -->
254
  <footer class="mt-12 text-center text-gray-600 text-sm">
255
  <p>Mariam AI © 2025 - Solution d'automatisation pour les sciences</p>
256
- <p class="mt-1">Version expérimentale - Nécessite une installation LaTeX locale</p>
 
257
  </footer>
258
  </main>
259
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  <script>
261
  function app() {
262
  return {
263
- // État de l'application
264
- systemStatus: {
265
- show: false,
266
- type: 'info',
267
- title: '',
268
- message: ''
269
- },
270
  dragOver: false,
271
  imageFile: null,
272
  imagePreview: null,
 
 
273
  processing: false,
274
  processingStage: null,
275
  processingProgress: 0,
 
 
276
  showResults: false,
277
- activeTab: 'pdf',
278
-
279
- // Données générées
280
  latexCode: null,
281
  thinkingProcess: null,
282
- pdfData: null,
283
- compilationLog: null,
284
-
 
 
 
285
  // Méthodes
286
  checkLatexInstallation() {
287
  fetch('/check-latex')
288
  .then(response => response.json())
289
  .then(data => {
 
290
  if (!data.success) {
291
- this.showStatus('error', 'Configuration requise', data.message);
 
 
 
 
 
 
 
 
 
 
 
292
  }
293
  })
294
  .catch(error => {
295
  console.error('Erreur lors de la vérification LaTeX:', error);
296
- this.showStatus('error', 'Erreur système', 'Impossible de vérifier l\'installation LaTeX.');
 
 
297
  });
298
  },
299
-
300
- showStatus(type, title, message) {
301
- this.systemStatus = {
302
- show: true,
303
- type,
304
- title,
305
- message
306
- };
307
-
308
- // Auto-hide after 10 seconds for success messages
309
- if (type === 'success') {
310
  setTimeout(() => {
311
- this.systemStatus.show = false;
312
- }, 10000);
 
 
 
313
  }
314
  },
315
-
 
 
 
 
 
316
  handleDrop(event) {
317
  this.dragOver = false;
318
  const files = event.dataTransfer.files;
@@ -320,131 +450,194 @@
320
  this.processFile(files[0]);
321
  }
322
  },
323
-
324
  handleFileSelect(event) {
325
  const file = event.target.files[0];
326
  if (file) {
327
  this.processFile(file);
328
  }
 
 
329
  },
330
-
331
  processFile(file) {
332
- // Vérifier si c'est une image
333
- if (!file.type.match('image.*')) {
334
  this.showStatus('error', 'Format incorrect', 'Veuillez sélectionner une image (PNG, JPG, JPEG)');
335
  return;
336
  }
337
-
338
- // Stocker le fichier et créer un aperçu
 
 
 
 
 
339
  this.imageFile = file;
340
  const reader = new FileReader();
341
  reader.onload = (e) => {
342
  this.imagePreview = e.target.result;
343
- this.showStatus('success', 'Image téléchargée', 'Votre image a été correctement importée. Vous pouvez maintenant lancer l\'analyse.');
 
 
 
 
344
  };
345
  reader.readAsDataURL(file);
346
-
347
- // Reset results
348
  this.resetResults();
 
349
  },
350
-
 
 
 
 
 
 
 
 
351
  resetResults() {
352
  this.showResults = false;
353
  this.latexCode = null;
354
  this.thinkingProcess = null;
355
  this.pdfData = null;
356
  this.compilationLog = null;
357
- this.activeTab = 'pdf';
358
  },
359
-
360
  processImage() {
361
- if (!this.imageFile || this.processing) return;
362
-
363
  this.processing = true;
364
- this.processingStage = "Initialisation...";
365
- this.processingProgress = 5;
366
-
367
- // Préparer les données du formulaire
368
  const formData = new FormData();
369
  formData.append('image', this.imageFile);
370
-
371
- // Envoyer la demande
372
- setTimeout(() => this.updateProcessingStatus("Analyse de l'image par l'IA...", 20), 1000);
373
-
374
  fetch('/process-image', {
375
  method: 'POST',
376
  body: formData
377
  })
378
- .then(response => response.json())
 
 
 
 
 
 
 
 
 
 
 
 
379
  .then(data => {
380
- this.updateProcessingStatus("Finalisation des résultats...", 90);
381
-
382
- setTimeout(() => {
383
- this.processing = false;
384
- this.processingStage = null;
385
-
386
- if (data.success) {
387
- this.latexCode = data.latex;
388
- this.thinkingProcess = data.thinking;
389
- this.pdfData = data.pdf_base64;
390
- this.showResults = true;
391
- this.showStatus('success', 'Analyse réussie', 'La solution a été générée avec succès. Vous pouvez maintenant télécharger le PDF.');
392
- } else {
393
- this.showStatus('error', 'Échec de l\'analyse', data.message);
394
- if (data.latex) {
395
- this.latexCode = data.latex;
396
- this.thinkingProcess = data.thinking;
397
- this.compilationLog = data.compilation_log;
398
- this.showResults = true;
399
- this.activeTab = 'latex'; // Afficher l'onglet LaTeX en cas d'erreur PDF
400
- }
401
- }
402
- }, 500);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  })
404
  .catch(error => {
405
  console.error('Erreur lors du traitement de l\'image:', error);
406
  this.processing = false;
407
  this.processingStage = null;
408
- this.showStatus('error', 'Erreur système', 'Une erreur inattendue est survenue lors du traitement de l\'image.');
 
 
 
409
  });
410
  },
411
-
412
  updateProcessingStatus(message, progress) {
413
  this.processingStage = message;
414
  this.processingProgress = progress;
415
  },
416
-
417
  downloadPDF() {
418
- if (!this.pdfData) return;
419
-
420
- // Créer un blob à partir du base64
421
- const byteCharacters = atob(this.pdfData);
422
- const byteNumbers = new Array(byteCharacters.length);
423
- for (let i = 0; i < byteCharacters.length; i++) {
424
- byteNumbers[i] = byteCharacters.charCodeAt(i);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
425
  }
426
- const byteArray = new Uint8Array(byteNumbers);
427
- const blob = new Blob([byteArray], {type: 'application/pdf'});
428
-
429
- // Créer un lien et déclencher le téléchargement
430
- const link = document.createElement('a');
431
- link.href = URL.createObjectURL(blob);
432
- link.download = 'solution_mariam_ai.pdf';
433
- document.body.appendChild(link);
434
- link.click();
435
- document.body.removeChild(link);
436
  },
437
-
438
  copyToClipboard(text) {
439
  if (!text) return;
440
-
441
  navigator.clipboard.writeText(text)
442
  .then(() => {
443
- this.showStatus('success', 'Copié', 'Le code LaTeX a été copié dans le presse-papiers.');
444
  })
445
  .catch(err => {
446
  console.error('Erreur lors de la copie:', err);
447
- this.showStatus('error', 'Échec de la copie', 'Impossible de copier le code dans le presse-papiers.');
448
  });
449
  }
450
  };
 
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
  <style>
11
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
12
+
13
  body {
14
  font-family: 'Poppins', sans-serif;
15
  background-color: #f8fafc;
16
  }
17
+
18
  .drop-zone {
19
  border: 2px dashed #4f46e5;
20
  transition: all 0.3s ease;
21
  }
22
+
23
  .drop-zone:hover, .drop-zone.active {
24
  border-color: #4338ca;
25
  background-color: #eef2ff;
26
  }
27
+
28
  pre {
29
  white-space: pre-wrap;
30
  word-wrap: break-word;
31
+ max-height: 500px; /* Hauteur max pour les blocs de code/log */
32
  overflow-y: auto;
33
+ background-color: #f9fafb; /* Fond légèrement différent pour pre */
34
+ border: 1px solid #e5e7eb;
35
+ border-radius: 0.375rem; /* rounded-md */
36
  }
37
+
38
  .pdf-container {
39
+ height: 80vh; /* Hauteur fixe pour le conteneur PDF */
40
+ border: 1px solid #e5e7eb; /* gray-200 */
41
+ border-radius: 0.5rem; /* rounded-lg */
42
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-md */
43
+ overflow: hidden; /* Pour s'assurer que l'iframe reste dedans */
44
+ }
45
+
46
+ /* Style pour le log de compilation lorsqu'il y a une erreur */
47
+ .compilation-log-error {
48
+ border-left: 4px solid #ef4444; /* red-500 */
49
+ background-color: #fee2e2; /* red-100 */
50
+ padding: 1rem; /* p-4 */
51
+ margin-top: 1rem; /* mt-4 */
52
+ }
53
+ .compilation-log-error pre {
54
+ background-color: #fff; /* Fond blanc pour le log lui-même */
55
+ border-color: #fecaca; /* red-200 */
56
+ color: #374151; /* gray-700 */
57
  }
58
  </style>
59
  </head>
 
81
  <!-- Main Content -->
82
  <main class="container mx-auto px-4 py-8">
83
  <!-- Status Alert -->
84
+ <div x-show="systemStatus.show"
85
+ :class="{
86
+ 'bg-red-100 border-red-500 text-red-800': systemStatus.type === 'error',
87
+ 'bg-green-100 border-green-500 text-green-800': systemStatus.type === 'success',
88
+ 'bg-yellow-100 border-yellow-500 text-yellow-800': systemStatus.type === 'warning',
89
+ 'bg-blue-100 border-blue-500 text-blue-800': systemStatus.type === 'info'
90
+ }"
91
+ class="mb-6 p-4 border-l-4 rounded-md flex items-start shadow">
92
+ <div class="flex-shrink-0 mr-3 mt-1">
93
+ <i :class="{
94
+ 'fas fa-exclamation-circle text-red-500': systemStatus.type === 'error',
95
+ 'fas fa-check-circle text-green-500': systemStatus.type === 'success',
96
+ 'fas fa-exclamation-triangle text-yellow-500': systemStatus.type === 'warning',
97
+ 'fas fa-info-circle text-blue-500': systemStatus.type === 'info'
98
+ }" class="text-xl"></i>
99
  </div>
100
  <div>
101
  <p class="font-medium" x-text="systemStatus.title"></p>
102
+ <!-- Utiliser v-html pour permettre des sauts de ligne simples si besoin -->
103
+ <p class="text-sm mt-1" x-html="systemStatus.message.replace(/\n/g, '<br>')"></p>
104
  </div>
105
+ <button @click="systemStatus.show = false" class="ml-auto pl-3 flex-shrink-0">
106
+ <i class="fas fa-times text-gray-500 hover:text-gray-700"></i>
107
+ </button>
108
  </div>
109
 
110
  <!-- Workflow Steps -->
111
+ <div class="flex flex-wrap -mx-2 mb-8">
112
+ <!-- Colonne 1: Upload -->
113
  <div class="w-full lg:w-1/3 px-2 mb-4">
114
+ <div class="bg-white rounded-lg shadow-md p-6 h-full flex flex-col">
115
  <div class="flex items-center mb-4">
116
+ <div class="bg-indigo-100 text-indigo-700 rounded-full w-8 h-8 flex items-center justify-center mr-3 flex-shrink-0">
117
  <i class="fas fa-upload"></i>
118
  </div>
119
  <h2 class="text-xl font-semibold">1. Télécharger une image</h2>
120
  </div>
121
+ <p class="text-gray-600 mb-4">Importez une photo d'un exercice de mathématiques.</p>
122
+
123
  <!-- Upload Area -->
124
+ <div class="mt-4 flex-grow">
125
+ <div
126
+ @dragover.prevent="dragOver = true"
127
+ @dragleave.prevent="dragOver = false"
128
  @drop.prevent="handleDrop($event)"
129
  :class="{'active': dragOver}"
130
  class="drop-zone h-52 rounded-lg flex flex-col items-center justify-center text-center p-4 cursor-pointer"
131
  @click="document.getElementById('fileInput').click()">
132
+
133
  <template x-if="!imagePreview">
134
  <div>
135
  <i class="fas fa-cloud-upload-alt text-4xl text-indigo-500 mb-3"></i>
 
138
  <p class="text-gray-500 text-sm mt-2">(PNG, JPG, JPEG)</p>
139
  </div>
140
  </template>
141
+
142
  <template x-if="imagePreview">
143
+ <img :src="imagePreview" alt="Aperçu" class="max-h-full object-contain rounded"/>
144
  </template>
145
  </div>
146
+
147
  <input type="file" id="fileInput" class="hidden" accept="image/png, image/jpeg, image/jpg" @change="handleFileSelect($event)">
148
  </div>
149
+ <button x-show="imagePreview" @click="clearUpload()" class="mt-4 w-full text-sm text-red-600 hover:text-red-800 transition duration-300">
150
+ <i class="fas fa-times mr-1"></i> Changer l'image
151
+ </button>
152
  </div>
153
  </div>
154
+
155
+ <!-- Colonne 2: Analyse -->
156
  <div class="w-full lg:w-1/3 px-2 mb-4">
157
+ <div class="bg-white rounded-lg shadow-md p-6 h-full flex flex-col">
158
  <div class="flex items-center mb-4">
159
+ <div class="bg-indigo-100 text-indigo-700 rounded-full w-8 h-8 flex items-center justify-center mr-3 flex-shrink-0">
160
  <i class="fas fa-robot"></i>
161
  </div>
162
  <h2 class="text-xl font-semibold">2. Analyse IA</h2>
163
  </div>
164
+ <p class="text-gray-600 mb-4">L'IA analysera l'exercice et génèrera une solution détaillée en LaTeX.</p>
165
+
166
+ <div class="mt-4 flex-grow flex flex-col justify-center">
167
+ <button @click="processImage()"
168
+ :disabled="!imageFile || processing || latexCheckFailed"
169
+ :class="{
170
+ 'bg-indigo-300 cursor-not-allowed': !imageFile || processing || latexCheckFailed,
171
+ 'bg-indigo-600 hover:bg-indigo-700': imageFile && !processing && !latexCheckFailed
172
+ }"
173
  class="w-full py-3 rounded-md text-white font-medium flex items-center justify-center transition duration-300">
174
  <template x-if="processing">
175
  <i class="fas fa-spinner fa-spin mr-2"></i>
176
  </template>
177
  <span x-text="processing ? 'Traitement en cours...' : 'Analyser et Résoudre'"></span>
178
  </button>
179
+ <p x-show="latexCheckFailed" class="text-xs text-red-600 text-center mt-2">
180
+ Impossible de continuer. Veuillez vérifier le message d'erreur système ci-dessus concernant l'installation LaTeX.
181
+ </p>
182
+
183
  <div x-show="processingStage" class="mt-4">
184
  <div class="bg-indigo-50 rounded-md p-3">
185
  <div class="flex items-center">
186
  <i class="fas fa-sync fa-spin text-indigo-600 mr-2"></i>
187
+ <span class="text-indigo-800 font-medium text-sm" x-text="processingStage"></span>
188
  </div>
189
+ <div class="w-full bg-indigo-200 rounded-full h-1.5 mt-2 overflow-hidden">
190
+ <div class="bg-indigo-600 h-1.5 rounded-full transition-all duration-300 ease-linear" :style="`width: ${processingProgress}%`"></div>
191
  </div>
192
  </div>
193
  </div>
194
  </div>
195
  </div>
196
  </div>
197
+
198
+ <!-- Colonne 3: Résultat -->
199
  <div class="w-full lg:w-1/3 px-2 mb-4">
200
+ <div class="bg-white rounded-lg shadow-md p-6 h-full flex flex-col">
201
  <div class="flex items-center mb-4">
202
+ <div class="bg-indigo-100 text-indigo-700 rounded-full w-8 h-8 flex items-center justify-center mr-3 flex-shrink-0">
203
  <i class="fas fa-file-pdf"></i>
204
  </div>
205
  <h2 class="text-xl font-semibold">3. Résultat PDF</h2>
206
  </div>
207
  <p class="text-gray-600 mb-4">La solution sera compilée en un PDF professionnel prêt à télécharger.</p>
208
+
209
+ <div class="mt-4 flex-grow flex flex-col justify-center">
210
+ <button @click="downloadPDF()"
211
+ :disabled="!pdfData"
212
  :class="{'bg-gray-300 cursor-not-allowed': !pdfData, 'bg-green-600 hover:bg-green-700': pdfData}"
213
  class="w-full py-3 rounded-md text-white font-medium flex items-center justify-center transition duration-300">
214
  <i class="fas fa-download mr-2"></i>
215
  <span>Télécharger le PDF</span>
216
  </button>
217
+
218
+ <p x-show="!pdfData && !processing && showResults" class="text-sm text-red-600 text-center mt-3">
219
+ Échec de la génération du PDF. Vérifiez les logs de compilation.
220
+ </p>
221
+ <p x-show="!pdfData && !processing && !showResults" class="text-sm text-gray-500 text-center mt-3">
222
+ Le PDF sera disponible après une analyse réussie.
223
+ </p>
224
+ <p x-show="!pdfData && processing" class="text-sm text-gray-500 text-center mt-3">
225
+ Génération du PDF en cours...
226
  </p>
227
  </div>
228
  </div>
 
232
  <!-- Results Section -->
233
  <div x-show="showResults" class="mt-8">
234
  <div class="bg-white rounded-lg shadow-md overflow-hidden">
235
+ <div class="bg-gray-100 border-b border-gray-200 px-6 py-4">
236
+ <h2 class="text-xl font-semibold text-gray-800">Résultats de l'Analyse</h2>
237
  </div>
238
+
239
  <div class="flex flex-wrap">
240
  <!-- Tabs -->
241
+ <div class="w-full px-4 py-3 bg-indigo-50 border-b border-indigo-100">
242
+ <div class="flex flex-wrap -mx-1">
243
+ <div class="px-1 mb-1">
244
+ <button @click="activeTab = 'pdf'"
245
+ :class="{'bg-indigo-600 text-white shadow-sm': activeTab === 'pdf', 'bg-white text-indigo-700 hover:bg-indigo-100': activeTab !== 'pdf'}"
246
+ class="px-4 py-2 rounded-md font-medium text-sm transition duration-200 flex items-center">
247
+ <i class="fas fa-file-pdf mr-2"></i>Aperçu PDF
248
+ <span x-show="!pdfData" class="ml-2 text-xs bg-red-500 text-white rounded-full px-1.5 py-0.5">Erreur</span>
249
+ <span x-show="pdfData" class="ml-2 text-xs bg-green-500 text-white rounded-full px-1.5 py-0.5">OK</span>
250
  </button>
251
  </div>
252
+ <div class="px-1 mb-1">
253
+ <button @click="activeTab = 'latex'"
254
+ :class="{'bg-indigo-600 text-white shadow-sm': activeTab === 'latex', 'bg-white text-indigo-700 hover:bg-indigo-100': activeTab !== 'latex'}"
255
+ class="px-4 py-2 rounded-md font-medium text-sm transition duration-200 flex items-center">
256
  <i class="fas fa-code mr-2"></i>Code LaTeX
257
  </button>
258
  </div>
259
+ <!-- NOUVEL ONGLET POUR LOGS DE COMPILATION -->
260
+ <div class="px-1 mb-1" x-show="compilationLog">
261
+ <button @click="activeTab = 'log'"
262
+ :class="{'bg-indigo-600 text-white shadow-sm': activeTab === 'log', 'bg-white text-indigo-700 hover:bg-indigo-100': activeTab !== 'log'}"
263
+ class="px-4 py-2 rounded-md font-medium text-sm transition duration-200 flex items-center">
264
+ <i class="fas fa-terminal mr-2"></i>Log Compilation
265
+ <span x-show="!pdfData" class="ml-2 text-xs bg-red-500 text-white rounded-full px-1.5 py-0.5"><i class="fas fa-exclamation-triangle"></i></span>
266
+ <span x-show="pdfData" class="ml-2 text-xs bg-green-500 text-white rounded-full px-1.5 py-0.5"><i class="fas fa-check"></i></span>
267
+ </button>
268
+ </div>
269
+ <div class="px-1 mb-1" x-show="thinkingProcess">
270
+ <button @click="activeTab = 'thinking'"
271
+ :class="{'bg-indigo-600 text-white shadow-sm': activeTab === 'thinking', 'bg-white text-indigo-700 hover:bg-indigo-100': activeTab !== 'thinking'}"
272
+ class="px-4 py-2 rounded-md font-medium text-sm transition duration-200 flex items-center">
273
+ <i class="fas fa-brain mr-2"></i>Processus IA
274
  </button>
275
  </div>
276
  </div>
277
  </div>
278
+
279
  <!-- Content Panels -->
280
  <div class="w-full p-6">
281
  <!-- PDF Preview -->
282
+ <div x-show="activeTab === 'pdf'">
283
+ <div class="pdf-container bg-gray-100 flex items-center justify-center">
284
+ <iframe x-show="pdfData" :src="'data:application/pdf;base64,' + pdfData" width="100%" height="100%" class="border-0">
285
+ Votre navigateur ne supporte pas les iframes pour PDF. Vous pouvez le <a @click.prevent="downloadPDF()" href="#" class="text-indigo-600 hover:underline">télécharger</a>.
286
+ </iframe>
287
+ <div x-show="!pdfData" class="text-center text-gray-500 p-8">
288
+ <i class="fas fa-file-excel text-6xl mb-4 text-red-400"></i>
289
+ <h3 class="text-xl font-semibold mb-2 text-red-600">Échec de la génération du PDF</h3>
290
+ <p class="mb-4">Impossible d'afficher l'aperçu car le fichier PDF n'a pas pu être créé.</p>
291
+ <p>Veuillez consulter l'onglet <strong class="text-gray-700">"Log Compilation"</strong> pour identifier la cause de l'erreur.</p>
292
+ <button @click="activeTab = 'log'" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition duration-300 text-sm">
293
+ Voir le Log de Compilation
294
+ </button>
295
  </div>
296
  </div>
297
  </div>
298
+
299
  <!-- LaTeX Code -->
300
+ <div x-show="activeTab === 'latex'">
301
+ <div class="bg-gray-100 px-4 py-2 border-b border-gray-200 flex justify-between items-center rounded-t-md">
302
+ <span class="font-medium text-gray-700">Code LaTeX généré</span>
303
+ <button @click="copyToClipboard(latexCode)" title="Copier le code LaTeX" class="text-indigo-600 hover:text-indigo-800 transition duration-300 text-sm px-3 py-1 bg-white rounded border border-indigo-200 hover:bg-indigo-50">
304
  <i class="fas fa-copy mr-1"></i>Copier
305
  </button>
306
  </div>
307
+ <pre class="p-4 text-sm border-t-0 rounded-b-md rounded-t-none"><code x-text="latexCode || 'Aucun code LaTeX disponible'"></code></pre>
308
+ </div>
309
+
310
+ <!-- NOUVEAU PANNEAU : Log de Compilation -->
311
+ <div x-show="activeTab === 'log'">
312
+ <div :class="!pdfData ? 'compilation-log-error' : 'bg-gray-50 rounded-md p-4 border border-gray-200'">
313
+ <div class="flex justify-between items-center mb-3">
314
+ <h3 class="font-medium" :class="!pdfData ? 'text-red-800' : 'text-gray-700'">
315
+ <i class="fas fa-terminal mr-2"></i>Log de Compilation pdflatex
316
+ </h3>
317
+ <button @click="copyToClipboard(compilationLog)" title="Copier le log" class="text-indigo-600 hover:text-indigo-800 transition duration-300 text-sm px-3 py-1 bg-white rounded border border-indigo-200 hover:bg-indigo-50">
318
+ <i class="fas fa-copy mr-1"></i>Copier le Log
319
+ </button>
320
+ </div>
321
+ <pre class="p-3 text-xs"><code x-text="compilationLog || 'Aucun log de compilation disponible'"></code></pre>
322
+ <p x-show="!pdfData" class="mt-3 text-sm text-red-700">
323
+ <i class="fas fa-exclamation-circle mr-1"></i> Le PDF n'a pas pu être généré. Recherchez les lignes commençant par `!` (erreur) ou `Warning:` dans le log ci-dessus pour comprendre le problème. Les erreurs courantes incluent des packages manquants, des commandes inconnues ou des erreurs de syntaxe LaTeX.
324
+ </p>
325
+ <p x-show="pdfData" class="mt-3 text-sm text-green-700">
326
+ <i class="fas fa-check-circle mr-1"></i> Le PDF a été généré. Le log peut contenir des avertissements (`Warning:`) qui n'ont pas empêché la compilation mais pourraient indiquer des problèmes mineurs.
327
+ </p>
328
+ </div>
329
  </div>
330
+
331
  <!-- Thinking Process -->
332
+ <div x-show="activeTab === 'thinking'">
333
+ <div class="bg-gray-100 px-4 py-2 border-b border-gray-200 rounded-t-md">
334
+ <span class="font-medium text-gray-700">Processus de réflexion de l'IA</span>
335
  </div>
336
+ <pre class="p-4 text-sm border-t-0 rounded-b-md rounded-t-none"><code x-text="thinkingProcess || 'Aucune information sur le processus de réflexion disponible'"></code></pre>
337
  </div>
338
  </div>
339
  </div>
340
  </div>
341
  </div>
342
+
343
  <!-- Footer -->
344
  <footer class="mt-12 text-center text-gray-600 text-sm">
345
  <p>Mariam AI © 2025 - Solution d'automatisation pour les sciences</p>
346
+ <p class="mt-1">Version Alpha - Nécessite une installation LaTeX (pdflatex) fonctionnelle sur le serveur.</p>
347
+ <p class="mt-1" x-show="latexCheckMessage" x-text="latexCheckMessage"></p>
348
  </footer>
349
  </main>
350
+
351
+ <!-- Feedback Flottant -->
352
+ <div x-show="feedback.show"
353
+ class="fixed bottom-4 right-4 z-50 max-w-sm p-4 rounded-md shadow-lg text-white"
354
+ :class="{ 'bg-green-600': feedback.type === 'success', 'bg-red-600': feedback.type === 'error' }"
355
+ x-transition:enter="transition ease-out duration-300"
356
+ x-transition:enter-start="opacity-0 transform translate-y-2"
357
+ x-transition:enter-end="opacity-100 transform translate-y-0"
358
+ x-transition:leave="transition ease-in duration-200"
359
+ x-transition:leave-start="opacity-100 transform translate-y-0"
360
+ x-transition:leave-end="opacity-0 transform translate-y-2">
361
+ <div class="flex items-center">
362
+ <i :class="{ 'fas fa-check-circle': feedback.type === 'success', 'fas fa-exclamation-circle': feedback.type === 'error' }" class="mr-2 text-xl"></i>
363
+ <span x-text="feedback.message"></span>
364
+ <button @click="feedback.show = false" class="ml-auto -mr-1 -mt-1 p-1">
365
+ <i class="fas fa-times text-white opacity-70 hover:opacity-100"></i>
366
+ </button>
367
+ </div>
368
+ </div>
369
+
370
+
371
  <script>
372
  function app() {
373
  return {
374
+ // État système
375
+ systemStatus: { show: false, type: 'info', title: '', message: '' },
376
+ latexCheckFailed: false, // Indicateur spécifique pour l'échec de la vérif LaTeX
377
+ latexCheckMessage: '', // Message de la vérif LaTeX pour le footer
378
+
379
+ // Upload
 
380
  dragOver: false,
381
  imageFile: null,
382
  imagePreview: null,
383
+
384
+ // Traitement
385
  processing: false,
386
  processingStage: null,
387
  processingProgress: 0,
388
+
389
+ // Résultats
390
  showResults: false,
391
+ activeTab: 'pdf', // Onglet actif par défaut
 
 
392
  latexCode: null,
393
  thinkingProcess: null,
394
+ pdfData: null, // Contient le base64 du PDF si succès
395
+ compilationLog: null, // Contient TOUJOURS le log (succès ou échec)
396
+
397
+ // Feedback utilisateur
398
+ feedback: { show: false, type: 'success', message: '' },
399
+
400
  // Méthodes
401
  checkLatexInstallation() {
402
  fetch('/check-latex')
403
  .then(response => response.json())
404
  .then(data => {
405
+ this.latexCheckMessage = data.message; // Stocker le message pour le footer
406
  if (!data.success) {
407
+ this.latexCheckFailed = true;
408
+ // Afficher une alerte système persistante pour ce problème critique
409
+ this.systemStatus = {
410
+ show: true,
411
+ type: 'error',
412
+ title: 'Erreur de Configuration Serveur',
413
+ message: data.message + "\nL'application ne pourra pas compiler les PDF tant que ce problème n'est pas résolu."
414
+ };
415
+ // Ne pas cacher automatiquement cette erreur critique
416
+ } else {
417
+ this.latexCheckFailed = false;
418
+ this.showStatus('success', 'Configuration Vérifiée', data.message, 5000); // Message de succès court
419
  }
420
  })
421
  .catch(error => {
422
  console.error('Erreur lors de la vérification LaTeX:', error);
423
+ this.latexCheckFailed = true; // Supposer échec en cas d'erreur réseau/serveur
424
+ this.latexCheckMessage = 'Impossible de vérifier l\'installation LaTeX.';
425
+ this.showStatus('error', 'Erreur Système', 'Impossible de vérifier l\'installation LaTeX. Le serveur backend est peut-être indisponible ou une erreur s\'est produite.');
426
  });
427
  },
428
+
429
+ showStatus(type, title, message, duration = null) {
430
+ this.systemStatus = { show: true, type, title, message };
431
+ if (duration) {
 
 
 
 
 
 
 
432
  setTimeout(() => {
433
+ // Ne masquer que si le message actuel est toujours celui qu'on voulait masquer
434
+ if (this.systemStatus.message === message) {
435
+ this.systemStatus.show = false;
436
+ }
437
+ }, duration);
438
  }
439
  },
440
+
441
+ showFeedback(type, message, duration = 4000) {
442
+ this.feedback = { show: true, type, message };
443
+ setTimeout(() => { this.feedback.show = false; }, duration);
444
+ },
445
+
446
  handleDrop(event) {
447
  this.dragOver = false;
448
  const files = event.dataTransfer.files;
 
450
  this.processFile(files[0]);
451
  }
452
  },
453
+
454
  handleFileSelect(event) {
455
  const file = event.target.files[0];
456
  if (file) {
457
  this.processFile(file);
458
  }
459
+ // Réinitialiser pour permettre de sélectionner le même fichier à nouveau
460
+ event.target.value = null;
461
  },
462
+
463
  processFile(file) {
464
+ const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg'];
465
+ if (!allowedTypes.includes(file.type)) {
466
  this.showStatus('error', 'Format incorrect', 'Veuillez sélectionner une image (PNG, JPG, JPEG)');
467
  return;
468
  }
469
+ const maxSize = 10 * 1024 * 1024; // 10 MB
470
+ if (file.size > maxSize) {
471
+ this.showStatus('error', 'Fichier trop volumineux', `L'image ne doit pas dépasser ${maxSize / 1024 / 1024} Mo.`);
472
+ return;
473
+ }
474
+
475
+
476
  this.imageFile = file;
477
  const reader = new FileReader();
478
  reader.onload = (e) => {
479
  this.imagePreview = e.target.result;
480
+ // Pas de message de succès juste pour l'upload, c'est implicite
481
+ };
482
+ reader.onerror = () => {
483
+ this.showStatus('error', 'Erreur Lecture Fichier', 'Impossible de lire le fichier image sélectionné.');
484
+ this.clearUpload();
485
  };
486
  reader.readAsDataURL(file);
487
+
488
+ // Reset previous results when a new file is loaded
489
  this.resetResults();
490
+ this.systemStatus.show = false; // Cacher les anciennes alertes
491
  },
492
+
493
+ clearUpload() {
494
+ this.imageFile = null;
495
+ this.imagePreview = null;
496
+ // Ne pas réinitialiser les résultats ici, l'utilisateur veut peut-être juste changer l'image
497
+ // this.resetResults();
498
+ document.getElementById('fileInput').value = null; // Important pour re-sélectionner le même fichier
499
+ },
500
+
501
  resetResults() {
502
  this.showResults = false;
503
  this.latexCode = null;
504
  this.thinkingProcess = null;
505
  this.pdfData = null;
506
  this.compilationLog = null;
507
+ this.activeTab = 'pdf'; // Toujours revenir à l'onglet PDF par défaut
508
  },
509
+
510
  processImage() {
511
+ if (!this.imageFile || this.processing || this.latexCheckFailed) return;
512
+
513
  this.processing = true;
514
+ this.resetResults(); // Effacer les anciens résultats avant de commencer
515
+ this.systemStatus.show = false; // Cacher les alertes
516
+ this.updateProcessingStatus("Téléversement de l'image...", 5);
517
+
518
  const formData = new FormData();
519
  formData.append('image', this.imageFile);
520
+
521
+ setTimeout(() => this.updateProcessingStatus("Analyse par l'IA et génération LaTeX...", 20), 500);
522
+
 
523
  fetch('/process-image', {
524
  method: 'POST',
525
  body: formData
526
  })
527
+ .then(response => {
528
+ // Gérer les erreurs HTTP (e.g., 500 Internal Server Error)
529
+ if (!response.ok) {
530
+ // Essayer de lire le message d'erreur JSON s'il y en a un
531
+ return response.json().then(errData => {
532
+ throw new Error(errData.message || `Erreur serveur: ${response.status} ${response.statusText}`);
533
+ }).catch(() => {
534
+ // Si pas de JSON, lancer une erreur générique
535
+ throw new Error(`Erreur serveur: ${response.status} ${response.statusText}`);
536
+ });
537
+ }
538
+ return response.json(); // Traiter la réponse JSON si statut OK
539
+ })
540
  .then(data => {
541
+ // Simulation de la compilation
542
+ let progress = 60;
543
+ this.updateProcessingStatus("Compilation LaTeX vers PDF...", progress);
544
+ const interval = setInterval(() => {
545
+ progress += 5;
546
+ if (progress <= 95) {
547
+ this.updateProcessingStatus("Compilation LaTeX vers PDF...", progress);
548
+ } else {
549
+ clearInterval(interval);
550
+ }
551
+ }, 300); // Augmenter toutes les 300ms
552
+
553
+ // Traitement de la réponse (après la simulation)
554
+ setTimeout(() => {
555
+ clearInterval(interval); // S'assurer que l'intervalle est arrêté
556
+ this.processing = false;
557
+ this.processingStage = null;
558
+ this.processingProgress = 0;
559
+ this.showResults = true; // Afficher la section des résultats
560
+
561
+ // Mettre à jour les données même en cas d'échec partiel
562
+ this.latexCode = data.latex || null;
563
+ this.thinkingProcess = data.thinking || null;
564
+ this.compilationLog = data.compilation_log || null; // Très important
565
+ this.pdfData = data.pdf_base64 || null; // Sera null si échec
566
+
567
+ if (data.success) {
568
+ this.showStatus('success', 'Analyse Réussie', data.message);
569
+ this.activeTab = 'pdf'; // Afficher le PDF si succès
570
+ } else {
571
+ // Message d'erreur principal
572
+ this.showStatus('error', 'Échec du Traitement', data.message);
573
+
574
+ // Si échec de compilation, aller directement au log
575
+ if (this.compilationLog && !this.pdfData) {
576
+ this.activeTab = 'log';
577
+ } else if (this.latexCode) {
578
+ this.activeTab = 'latex'; // Sinon, au LaTeX si disponible
579
+ } else {
580
+ this.activeTab = 'pdf'; // Fallback
581
+ }
582
+ }
583
+ }, 2000); // Laisser le temps à la "compilation" de s'afficher
584
+
585
  })
586
  .catch(error => {
587
  console.error('Erreur lors du traitement de l\'image:', error);
588
  this.processing = false;
589
  this.processingStage = null;
590
+ this.processingProgress = 0;
591
+ // Afficher l'erreur venant du serveur ou une erreur générique
592
+ this.showStatus('error', 'Erreur Critique', error.message || 'Une erreur inattendue est survenue lors de la communication avec le serveur.');
593
+ this.resetResults(); // Pas de résultats partiels en cas d'erreur critique
594
  });
595
  },
596
+
597
  updateProcessingStatus(message, progress) {
598
  this.processingStage = message;
599
  this.processingProgress = progress;
600
  },
601
+
602
  downloadPDF() {
603
+ if (!this.pdfData) {
604
+ this.showFeedback('error', 'Aucun PDF à télécharger.');
605
+ return;
606
+ };
607
+
608
+ try {
609
+ const byteCharacters = atob(this.pdfData);
610
+ const byteNumbers = new Array(byteCharacters.length);
611
+ for (let i = 0; i < byteCharacters.length; i++) {
612
+ byteNumbers[i] = byteCharacters.charCodeAt(i);
613
+ }
614
+ const byteArray = new Uint8Array(byteNumbers);
615
+ const blob = new Blob([byteArray], {type: 'application/pdf'});
616
+
617
+ const link = document.createElement('a');
618
+ link.href = URL.createObjectURL(blob);
619
+ link.download = 'solution_mariam_ai.pdf';
620
+ document.body.appendChild(link);
621
+ link.click();
622
+ document.body.removeChild(link);
623
+ URL.revokeObjectURL(link.href); // Libérer la mémoire
624
+ this.showFeedback('success', 'Téléchargement lancé.');
625
+ } catch (e) {
626
+ console.error("Erreur lors de la création du lien de téléchargement PDF:", e);
627
+ this.showFeedback('error', 'Erreur lors de la préparation du téléchargement.');
628
  }
 
 
 
 
 
 
 
 
 
 
629
  },
630
+
631
  copyToClipboard(text) {
632
  if (!text) return;
633
+
634
  navigator.clipboard.writeText(text)
635
  .then(() => {
636
+ this.showFeedback('success', 'Copié dans le presse-papiers !');
637
  })
638
  .catch(err => {
639
  console.error('Erreur lors de la copie:', err);
640
+ this.showFeedback('error', 'Impossible de copier automatiquement.');
641
  });
642
  }
643
  };