Docfile commited on
Commit
523118f
·
verified ·
1 Parent(s): f0c99e3

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +341 -606
templates/index.html CHANGED
@@ -3,645 +3,380 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Mariam Science PDF</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/alpinejs/3.12.0/cdn.min.js" defer></script>
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>
60
- <body x-data="app()" x-init="checkLatexInstallation()" class="min-h-screen">
61
- <!-- Header -->
62
- <header class="bg-indigo-700 text-white shadow-md">
63
- <div class="container mx-auto px-4 py-6">
64
- <div class="flex items-center justify-between">
65
- <div>
66
- <h1 class="text-3xl font-bold flex items-center">
67
- <i class="fas fa-square-root-alt mr-3"></i>
68
- Mariam Science PDF
69
- </h1>
70
- <p class="mt-2 text-indigo-200">Générateur de solutions mathématiques avec export PDF</p>
71
- </div>
72
- <div class="hidden md:block">
73
- <a href="https://mariam-241.vercel.app" class="bg-white text-indigo-700 px-4 py-2 rounded-md font-medium hover:bg-indigo-50 transition duration-300">
74
- Site Principal
75
- </a>
76
- </div>
77
- </div>
78
  </div>
79
- </header>
80
-
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>
136
- <p class="text-gray-700">Glissez votre image ici ou</p>
137
- <p class="text-indigo-600 font-medium">Cliquez pour parcourir</p>
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>
229
  </div>
230
- </div>
231
 
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;
449
- if (files.length > 0) {
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
- };
644
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  </script>
646
  </body>
647
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mariam AI - Correcteur d'Exercices</title>
 
 
 
7
  <style>
 
 
8
  body {
9
+ font-family: sans-serif;
10
+ line-height: 1.6;
11
+ margin: 20px;
12
+ background-color: #f4f4f4;
13
+ color: #333;
14
  }
15
+ .container {
16
+ max-width: 900px;
17
+ margin: auto;
18
+ background: #fff;
19
+ padding: 25px;
20
+ border-radius: 8px;
21
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
22
  }
23
+ h1, h2, h3 {
24
+ color: #444;
 
 
25
  }
26
+ h1 {
27
+ text-align: center;
28
+ margin-bottom: 30px;
 
 
 
 
 
 
29
  }
30
+ .status-checks p {
31
+ padding: 8px;
32
+ border-radius: 4px;
33
+ margin: 5px 0;
34
+ }
35
+ .status-success {
36
+ background-color: #e7f4e7;
37
+ color: #0d6a0d;
38
+ border: 1px solid #b8d9b8;
39
+ }
40
+ .status-error {
41
+ background-color: #fdecea;
42
+ color: #a61a1a;
43
+ border: 1px solid #f8c8c8;
44
+ }
45
+ #upload-form label {
46
+ display: block;
47
+ margin-bottom: 8px;
48
+ font-weight: bold;
49
+ }
50
+ #image-input {
51
+ display: block;
52
+ margin-bottom: 15px;
53
+ padding: 10px;
54
+ border: 1px solid #ccc;
55
+ border-radius: 4px;
56
+ width: calc(100% - 22px); /* Adjust for padding and border */
57
+ }
58
+ #process-button, #download-button {
59
+ background-color: #5c67f2;
60
+ color: white;
61
+ padding: 12px 20px;
62
+ border: none;
63
+ border-radius: 4px;
64
+ cursor: pointer;
65
+ font-size: 16px;
66
+ transition: background-color 0.3s ease;
67
+ }
68
+ #process-button:hover, #download-button:hover {
69
+ background-color: #4a54c4;
70
+ }
71
+ #process-button:disabled {
72
+ background-color: #ccc;
73
+ cursor: not-allowed;
74
+ }
75
+ #loading {
76
+ text-align: center;
77
+ margin: 20px 0;
78
+ font-style: italic;
79
+ color: #666;
80
+ }
81
+ #messages {
82
+ margin-top: 20px;
83
+ padding: 15px;
84
+ border-radius: 4px;
85
+ text-align: center;
86
+ }
87
+ .message-success {
88
+ background-color: #dff0d8;
89
+ color: #3c763d;
90
+ border: 1px solid #d6e9c6;
91
+ }
92
+ .message-error {
93
+ background-color: #f2dede;
94
+ color: #a94442;
95
+ border: 1px solid #ebccd1;
96
+ white-space: pre-wrap; /* Pour afficher les retours à la ligne des erreurs */
97
+ text-align: left;
98
+ }
99
+ #results {
100
+ margin-top: 30px;
101
+ border-top: 1px solid #eee;
102
+ padding-top: 20px;
103
+ }
104
+ #pdf-preview iframe {
105
+ width: 100%;
106
+ height: 600px;
107
+ border: 1px solid #ccc;
108
+ border-radius: 4px;
109
+ }
110
+ #download-button {
111
+ display: block; /* Make it a block element */
112
+ margin: 15px auto 25px auto; /* Center the button */
113
+ width: fit-content; /* Adjust width to content */
114
  }
115
 
116
+ #raw-output {
117
+ margin-top: 20px;
118
+ background-color: #f9f9f9;
119
+ border: 1px solid #ddd;
120
+ padding: 15px;
121
+ border-radius: 4px;
122
+ }
123
+ #raw-output h3 {
124
+ margin-top: 0;
125
+ margin-bottom: 10px;
126
+ border-bottom: 1px solid #eee;
127
+ padding-bottom: 5px;
128
+ }
129
+ #raw-output pre {
130
+ background-color: #efefef;
131
+ padding: 10px;
132
+ border-radius: 4px;
133
+ white-space: pre-wrap; /* Wrap long lines */
134
+ word-wrap: break-word; /* Break words if necessary */
135
+ max-height: 400px;
136
+ overflow-y: auto; /* Add scroll if content is too long */
137
  }
138
+ #raw-output code {
139
+ font-family: monospace;
140
+ font-size: 0.9em;
141
+ }
142
+ .hidden {
143
+ display: none;
144
  }
145
  </style>
146
  </head>
147
+ <body>
148
+ <div class="container">
149
+ <h1>Mariam AI - Correcteur d'Exercices Mathématiques</h1>
150
+
151
+ <div class="status-checks">
152
+ <h2>Vérifications Initiales</h2>
153
+ <p id="latex-status">Vérification de LaTeX...</p>
154
+ <p id="api-status">Vérification de la clé API Mariam AI...</p>
 
 
 
 
 
 
 
 
 
 
155
  </div>
156
+
157
+ <div id="upload-form">
158
+ <h2>Soumettre un Exercice</h2>
159
+ <label for="image-input">Choisissez une image de l'exercice :</label>
160
+ <input type="file" id="image-input" accept="image/*">
161
+ <button id="process-button">Générer la Solution</button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  </div>
163
 
164
+ <div id="loading" class="hidden">
165
+ <p>Traitement en cours... Mariam AI réfléchit et compile le PDF...</p>
166
+ <p>Cela peut prendre jusqu'à 1 minute.</p>
167
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ <div id="messages" class="hidden"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
+ <div id="results" class="hidden">
172
+ <h2>Résultats</h2>
173
+
174
+ <div id="pdf-preview">
175
+ <h3>Aperçu du PDF</h3>
176
+ <iframe id="pdf-viewer" title="Aperçu du PDF de la solution"></iframe>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </div>
 
178
 
179
+ <button id="download-button" class="hidden">Télécharger le PDF</button>
 
 
 
 
 
180
 
181
+ <div id="raw-output">
182
+ <div id="latex-section" class="hidden">
183
+ <h3>Code LaTeX Généré</h3>
184
+ <pre><code id="latex-output"></code></pre>
185
+ </div>
186
+ <div id="thinking-section" class="hidden">
187
+ <h3>Processus de Réflexion (IA)</h3>
188
+ <pre><code id="thinking-output"></code></pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  </div>
190
  </div>
191
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  </div>
193
 
 
194
  <script>
195
+ document.addEventListener('DOMContentLoaded', () => {
196
+ const latexStatusEl = document.getElementById('latex-status');
197
+ const apiStatusEl = document.getElementById('api-status');
198
+ const imageInput = document.getElementById('image-input');
199
+ const processButton = document.getElementById('process-button');
200
+ const loadingEl = document.getElementById('loading');
201
+ const messagesEl = document.getElementById('messages');
202
+ const resultsEl = document.getElementById('results');
203
+ const pdfViewer = document.getElementById('pdf-viewer');
204
+ const downloadButton = document.getElementById('download-button');
205
+ const rawOutputEl = document.getElementById('raw-output');
206
+ const latexSection = document.getElementById('latex-section');
207
+ const latexOutputEl = document.getElementById('latex-output');
208
+ const thinkingSection = document.getElementById('thinking-section');
209
+ const thinkingOutputEl = document.getElementById('thinking-output');
210
+
211
+ let currentPdfBase64 = null; // Stocker les données PDF pour le téléchargement
212
+
213
+ // --- Fonctions Utilitaires ---
214
+ function showLoading() {
215
+ loadingEl.classList.remove('hidden');
216
+ processButton.disabled = true;
217
+ messagesEl.classList.add('hidden');
218
+ resultsEl.classList.add('hidden');
219
+ pdfViewer.src = 'about:blank'; // Clear previous PDF
220
+ latexOutputEl.textContent = '';
221
+ thinkingOutputEl.textContent = '';
222
+ latexSection.classList.add('hidden');
223
+ thinkingSection.classList.add('hidden');
224
+ downloadButton.classList.add('hidden');
225
+ currentPdfBase64 = null;
226
+ }
227
+
228
+ function hideLoading() {
229
+ loadingEl.classList.add('hidden');
230
+ processButton.disabled = false;
231
+ }
232
+
233
+ function showMessage(message, isError = false) {
234
+ messagesEl.textContent = message;
235
+ messagesEl.className = isError ? 'message-error' : 'message-success';
236
+ messagesEl.classList.remove('hidden');
237
+ }
238
+
239
+ function displayResults(data) {
240
+ resultsEl.classList.remove('hidden');
241
+
242
+ if (data.pdf_base64) {
243
+ pdfViewer.src = `data:application/pdf;base64,${data.pdf_base64}`;
244
+ currentPdfBase64 = data.pdf_base64;
245
+ downloadButton.classList.remove('hidden');
246
+ } else {
247
+ pdfViewer.src = 'about:blank'; // Ensure it's blank if no PDF
248
+ downloadButton.classList.add('hidden');
249
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
+ if (data.latex) {
252
+ latexOutputEl.textContent = data.latex;
253
+ latexSection.classList.remove('hidden');
254
+ } else {
255
+ latexSection.classList.add('hidden');
256
+ }
257
+
258
+ if (data.thinking) {
259
+ thinkingOutputEl.textContent = data.thinking;
260
+ thinkingSection.classList.remove('hidden');
261
+ } else {
262
+ thinkingSection.classList.add('hidden');
263
+ }
264
+ }
265
+
266
+ // --- Vérifications Initiales ---
267
+ async function checkStatus() {
268
+ try {
269
+ const latexRes = await fetch('/check-latex');
270
+ const latexData = await latexRes.json();
271
+ latexStatusEl.textContent = latexData.message;
272
+ latexStatusEl.className = latexData.success ? 'status-success' : 'status-error';
273
+ } catch (error) {
274
+ latexStatusEl.textContent = '❌ Erreur lors de la vérification de LaTeX: ' + error;
275
+ latexStatusEl.className = 'status-error';
276
+ }
277
+
278
+ try {
279
+ const apiRes = await fetch('/check-api');
280
+ const apiData = await apiRes.json();
281
+ apiStatusEl.textContent = apiData.message;
282
+ apiStatusEl.className = apiData.success ? 'status-success' : 'status-error';
283
+ } catch (error) {
284
+ apiStatusEl.textContent = '❌ Erreur lors de la vérification de l\'API: ' + error;
285
+ apiStatusEl.className = 'status-error';
286
+ }
287
+ }
288
+
289
+ // --- Traitement de l'Image ---
290
+ processButton.addEventListener('click', async () => {
291
+ const file = imageInput.files[0];
292
+ if (!file) {
293
+ showMessage('Veuillez sélectionner un fichier image.', true);
294
+ return;
295
+ }
296
+
297
+ showLoading();
298
+
299
+ const formData = new FormData();
300
+ formData.append('image', file);
301
+
302
+ try {
303
+ const response = await fetch('/process', {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
  method: 'POST',
305
  body: formData
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  });
307
+
308
+ const data = await response.json();
309
+ hideLoading();
310
+
311
+ if (data.success) {
312
+ showMessage('Solution générée et compilée avec succès !');
313
+ displayResults(data);
314
+ } else {
315
+ showMessage(`Erreur : ${data.message}`, true);
316
+ // Afficher le LaTeX/Thinking même en cas d'erreur de compilation
317
+ if(data.latex || data.thinking) {
318
+ displayResults(data); // Affiche LaTeX/Thinking sans PDF
 
 
 
 
 
 
319
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  }
321
+
322
+ } catch (error) {
323
+ hideLoading();
324
+ showMessage(`Erreur de communication avec le serveur : ${error}`, true);
325
+ console.error("Fetch Error:", error);
 
 
 
 
 
 
 
 
326
  }
327
+ });
328
+
329
+ // --- Téléchargement du PDF ---
330
+ downloadButton.addEventListener('click', async () => {
331
+ if (!currentPdfBase64) {
332
+ showMessage('Aucune donnée PDF à télécharger.', true);
333
+ return;
334
+ }
335
+
336
+ try {
337
+ const response = await fetch('/download-pdf', {
338
+ method: 'POST',
339
+ headers: {
340
+ 'Content-Type': 'application/json',
341
+ },
342
+ body: JSON.stringify({ pdf_data: currentPdfBase64 }),
343
+ });
344
+
345
+ if (response.ok) {
346
+ // Le backend utilise send_file avec as_attachment=True,
347
+ // le navigateur devrait déclencher le téléchargement automatiquement.
348
+ // On récupère le blob pour créer un lien de secours au cas où.
349
+ const blob = await response.blob();
350
+ const url = window.URL.createObjectURL(blob);
351
+ const a = document.createElement('a');
352
+ a.style.display = 'none';
353
+ a.href = url;
354
+ // Le nom de fichier est défini côté serveur, mais on le remet ici
355
+ a.download = response.headers.get('Content-Disposition')?.split('filename=')[1]?.replaceAll('"', '') || 'solution_mariam_ai.pdf';
356
+ document.body.appendChild(a);
357
+ a.click();
358
+ window.URL.revokeObjectURL(url);
359
+ a.remove();
360
+ showMessage('Téléchargement démarré.');
361
+ } else {
362
+ // Essayer de lire le message d'erreur JSON du serveur
363
+ let errorMsg = `Échec du téléchargement (code ${response.status}).`;
364
+ try {
365
+ const errorData = await response.json();
366
+ if(errorData.message) errorMsg += ` Raison: ${errorData.message}`;
367
+ } catch(e) { /* Ignorer l'erreur si la réponse n'est pas JSON */ }
368
+ showMessage(errorMsg, true);
369
+ }
370
+ } catch (error) {
371
+ showMessage(`Erreur lors de la tentative de téléchargement : ${error}`, true);
372
+ console.error("Download Error:", error);
373
+ }
374
+ });
375
+
376
+
377
+ // Exécuter les vérifications au chargement
378
+ checkStatus();
379
+ });
380
  </script>
381
  </body>
382
  </html>