Spaces:
Running
Running
Update templates/index.html
Browse files- 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"
|
68 |
-
class="
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
71 |
</div>
|
72 |
<div>
|
73 |
<p class="font-medium" x-text="systemStatus.title"></p>
|
74 |
-
|
|
|
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
|
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">
|
128 |
-
|
129 |
-
<div class="mt-4">
|
130 |
-
<button @click="processImage()"
|
131 |
-
:disabled="!imageFile || processing"
|
132 |
-
:class="{
|
|
|
|
|
|
|
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-
|
147 |
-
<div class="bg-indigo-600 h-
|
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 |
-
|
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-
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
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-
|
186 |
-
|
187 |
</div>
|
188 |
-
|
189 |
<div class="flex flex-wrap">
|
190 |
<!-- Tabs -->
|
191 |
-
<div class="w-full px-4 py-
|
192 |
-
<div class="flex flex-wrap -mx-
|
193 |
-
<div class="px-
|
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-
|
197 |
-
<i class="fas fa-file-pdf mr-2"></i>PDF
|
|
|
|
|
198 |
</button>
|
199 |
</div>
|
200 |
-
<div class="px-
|
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-
|
204 |
<i class="fas fa-code mr-2"></i>Code LaTeX
|
205 |
</button>
|
206 |
</div>
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
class="
|
211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'"
|
221 |
-
<
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
226 |
</div>
|
227 |
</div>
|
228 |
</div>
|
229 |
-
|
230 |
<!-- LaTeX Code -->
|
231 |
-
<div x-show="activeTab === 'latex'"
|
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'"
|
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
|
|
|
257 |
</footer>
|
258 |
</main>
|
259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
260 |
<script>
|
261 |
function app() {
|
262 |
return {
|
263 |
-
// État
|
264 |
-
systemStatus: {
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
}
|
293 |
})
|
294 |
.catch(error => {
|
295 |
console.error('Erreur lors de la vérification LaTeX:', error);
|
296 |
-
|
|
|
|
|
297 |
});
|
298 |
},
|
299 |
-
|
300 |
-
showStatus(type, title, message) {
|
301 |
-
this.systemStatus = {
|
302 |
-
|
303 |
-
type,
|
304 |
-
title,
|
305 |
-
message
|
306 |
-
};
|
307 |
-
|
308 |
-
// Auto-hide after 10 seconds for success messages
|
309 |
-
if (type === 'success') {
|
310 |
setTimeout(() => {
|
311 |
-
|
312 |
-
|
|
|
|
|
|
|
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 |
-
|
333 |
-
if (!file.type
|
334 |
this.showStatus('error', 'Format incorrect', 'Veuillez sélectionner une image (PNG, JPG, JPEG)');
|
335 |
return;
|
336 |
}
|
337 |
-
|
338 |
-
|
|
|
|
|
|
|
|
|
|
|
339 |
this.imageFile = file;
|
340 |
const reader = new FileReader();
|
341 |
reader.onload = (e) => {
|
342 |
this.imagePreview = e.target.result;
|
343 |
-
|
|
|
|
|
|
|
|
|
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.
|
365 |
-
this.
|
366 |
-
|
367 |
-
|
368 |
const formData = new FormData();
|
369 |
formData.append('image', this.imageFile);
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
fetch('/process-image', {
|
375 |
method: 'POST',
|
376 |
body: formData
|
377 |
})
|
378 |
-
.then(response =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
379 |
.then(data => {
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
409 |
});
|
410 |
},
|
411 |
-
|
412 |
updateProcessingStatus(message, progress) {
|
413 |
this.processingStage = message;
|
414 |
this.processingProgress = progress;
|
415 |
},
|
416 |
-
|
417 |
downloadPDF() {
|
418 |
-
if (!this.pdfData)
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
444 |
})
|
445 |
.catch(err => {
|
446 |
console.error('Erreur lors de la copie:', err);
|
447 |
-
|
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 |
};
|