Docfile commited on
Commit
5374d31
·
verified ·
1 Parent(s): ca8893f

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +594 -179
templates/index.html CHANGED
@@ -4,188 +4,498 @@
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>
@@ -193,22 +503,109 @@
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() {
@@ -216,73 +613,97 @@
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
 
@@ -309,13 +730,13 @@
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
 
@@ -327,7 +748,7 @@
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;
@@ -343,23 +764,18 @@
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();
@@ -368,12 +784,11 @@
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
  });
 
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
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
8
  <style>
9
+ :root {
10
+ --primary: #4f46e5;
11
+ --primary-hover: #4338ca;
12
+ --primary-light: #eef2ff;
13
+ --success: #10b981;
14
+ --success-light: #ecfdf5;
15
+ --error: #ef4444;
16
+ --error-light: #fef2f2;
17
+ --text: #1f2937;
18
+ --text-light: #6b7280;
19
+ --bg-light: #f9fafb;
20
+ --card-bg: #ffffff;
21
+ --border: #e5e7eb;
22
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
23
+ --shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
24
+ --radius: 0.5rem;
25
+ }
26
+
27
+ * {
28
+ box-sizing: border-box;
29
+ margin: 0;
30
+ padding: 0;
31
+ }
32
+
33
  body {
34
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
35
  line-height: 1.6;
36
+ color: var(--text);
37
+ background-color: var(--bg-light);
38
+ padding: 1.5rem;
39
  }
40
+
41
  .container {
42
+ max-width: 1000px;
43
+ margin: 0 auto;
 
 
 
 
44
  }
45
+
46
+ .header {
47
+ text-align: center;
48
+ margin-bottom: 2rem;
49
  }
50
+
51
  h1 {
52
+ font-size: 2rem;
53
+ font-weight: 700;
54
+ margin-bottom: 0.5rem;
55
+ color: var(--primary);
56
+ }
57
+
58
+ h2 {
59
+ font-size: 1.25rem;
60
+ font-weight: 600;
61
+ margin-bottom: 1rem;
62
+ color: var(--text);
63
+ }
64
+
65
+ h3 {
66
+ font-size: 1rem;
67
+ font-weight: 600;
68
+ margin-bottom: 0.75rem;
69
+ color: var(--text);
70
+ }
71
+
72
+ .subheader {
73
+ color: var(--text-light);
74
+ font-size: 1rem;
75
  }
76
+
77
+ .card {
78
+ background: var(--card-bg);
79
+ border-radius: var(--radius);
80
+ box-shadow: var(--shadow);
81
+ padding: 1.5rem;
82
+ margin-bottom: 1.5rem;
83
+ }
84
+
85
+ .status-checks {
86
+ display: grid;
87
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
88
+ gap: 1rem;
89
+ }
90
+
91
+ .status-item {
92
+ padding: 1rem;
93
+ border-radius: var(--radius);
94
+ display: flex;
95
+ align-items: center;
96
+ gap: 0.75rem;
97
  }
98
+
99
  .status-success {
100
+ background-color: var(--success-light);
101
+ border: 1px solid var(--success);
 
102
  }
103
+
104
  .status-error {
105
+ background-color: var(--error-light);
106
+ border: 1px solid var(--error);
 
107
  }
108
+
109
+ .status-icon {
110
+ font-size: 1.25rem;
111
+ }
112
+
113
+ .status-success .status-icon {
114
+ color: var(--success);
115
+ }
116
+
117
+ .status-error .status-icon {
118
+ color: var(--error);
119
+ }
120
+
121
+ .status-text {
122
+ flex: 1;
123
+ }
124
+
125
+ .file-upload {
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: 1rem;
129
+ }
130
+
131
+ .file-input-container {
132
+ position: relative;
133
+ width: 100%;
134
+ height: 150px;
135
+ border: 2px dashed var(--border);
136
+ border-radius: var(--radius);
137
+ display: flex;
138
+ flex-direction: column;
139
+ justify-content: center;
140
+ align-items: center;
141
+ gap: 0.75rem;
142
+ padding: 1.5rem;
143
+ cursor: pointer;
144
+ overflow: hidden;
145
+ transition: border-color 0.3s ease, background 0.3s ease;
146
+ }
147
+
148
+ .file-input-container:hover, .file-input-container.dragover {
149
+ border-color: var(--primary);
150
+ background: var(--primary-light);
151
+ }
152
+
153
+ .file-input {
154
+ position: absolute;
155
+ top: 0;
156
+ left: 0;
157
+ width: 100%;
158
+ height: 100%;
159
+ opacity: 0;
160
+ cursor: pointer;
161
+ }
162
+
163
+ .upload-icon {
164
+ font-size: 2rem;
165
+ color: var(--primary);
166
+ }
167
+
168
+ .upload-label {
169
+ font-weight: 500;
170
+ }
171
+
172
+ .upload-hint {
173
+ font-size: 0.875rem;
174
+ color: var(--text-light);
175
+ }
176
+
177
+ .file-preview {
178
+ display: none;
179
+ position: absolute;
180
+ top: 0;
181
+ left: 0;
182
+ width: 100%;
183
+ height: 100%;
184
+ z-index: 1;
185
+ background: rgba(255, 255, 255, 0.9);
186
+ align-items: center;
187
+ justify-content: center;
188
+ }
189
+
190
+ .file-preview img {
191
+ max-width: 90%;
192
+ max-height: 90%;
193
+ object-fit: contain;
194
+ }
195
+
196
+ .preview-active .file-preview {
197
+ display: flex;
198
+ }
199
+
200
+ .file-name {
201
+ display: none;
202
+ position: absolute;
203
+ bottom: 0;
204
+ left: 0;
205
+ right: 0;
206
+ background: rgba(255, 255, 255, 0.8);
207
+ padding: 0.5rem;
208
+ font-size: 0.875rem;
209
+ text-align: center;
210
+ word-break: break-all;
211
  }
212
+
213
+ .preview-active .file-name {
214
  display: block;
215
+ }
216
+
217
+ .clear-file {
218
+ display: none;
219
+ position: absolute;
220
+ top: 0.5rem;
221
+ right: 0.5rem;
222
+ background: white;
223
+ border-radius: 50%;
224
+ width: 24px;
225
+ height: 24px;
226
+ align-items: center;
227
+ justify-content: center;
228
+ cursor: pointer;
229
+ box-shadow: var(--shadow);
230
+ z-index: 2;
231
+ }
232
+
233
+ .preview-active .clear-file {
234
+ display: flex;
235
+ }
236
+
237
+ .button {
238
+ display: inline-flex;
239
+ align-items: center;
240
+ justify-content: center;
241
+ gap: 0.5rem;
242
+ background-color: var(--primary);
243
  color: white;
244
+ padding: 0.75rem 1.5rem;
245
  border: none;
246
+ border-radius: var(--radius);
247
+ font-weight: 500;
248
+ font-size: 1rem;
249
  cursor: pointer;
250
+ transition: background-color 0.3s, transform 0.2s;
251
+ width: 100%;
252
  }
253
+
254
+ .button:hover {
255
+ background-color: var(--primary-hover);
256
+ }
257
+
258
+ .button:active {
259
+ transform: translateY(1px);
260
  }
261
+
262
+ .button:disabled {
263
+ background-color: var(--text-light);
264
  cursor: not-allowed;
265
+ opacity: 0.7;
266
  }
267
+
268
+ .button-icon {
269
+ font-size: 1rem;
270
+ }
271
+
272
+ .loading {
273
+ display: flex;
274
+ flex-direction: column;
275
+ align-items: center;
276
+ gap: 1rem;
277
+ padding: 2rem;
278
+ }
279
+
280
+ .spinner {
281
+ width: 40px;
282
+ height: 40px;
283
+ border: 4px solid rgba(79, 70, 229, 0.2);
284
+ border-radius: 50%;
285
+ border-top-color: var(--primary);
286
+ animation: spin 1s linear infinite;
287
+ }
288
+
289
+ @keyframes spin {
290
+ 0% { transform: rotate(0deg); }
291
+ 100% { transform: rotate(360deg); }
292
+ }
293
+
294
+ .message {
295
+ padding: 1rem;
296
+ border-radius: var(--radius);
297
+ margin-bottom: 1.5rem;
298
+ display: flex;
299
+ align-items: center;
300
+ gap: 0.75rem;
301
  }
302
+
303
  .message-success {
304
+ background-color: var(--success-light);
305
+ border: 1px solid var(--success);
306
+ color: var(--success);
307
  }
308
+
309
  .message-error {
310
+ background-color: var(--error-light);
311
+ border: 1px solid var(--error);
312
+ color: var(--error);
313
+ white-space: pre-wrap;
314
+ }
315
+
316
+ .tab-container {
317
+ border: 1px solid var(--border);
318
+ border-radius: var(--radius);
319
+ overflow: hidden;
320
+ }
321
+
322
+ .tabs {
323
+ display: flex;
324
+ background: var(--bg-light);
325
+ }
326
+
327
+ .tab {
328
+ padding: 0.75rem 1.25rem;
329
+ cursor: pointer;
330
+ border-bottom: 2px solid transparent;
331
+ font-weight: 500;
332
+ color: var(--text-light);
333
+ transition: all 0.3s ease;
334
+ }
335
+
336
+ .tab.active {
337
+ color: var(--primary);
338
+ border-bottom-color: var(--primary);
339
+ }
340
+
341
+ .tab-content {
342
+ display: none;
343
+ padding: 1.5rem;
344
+ }
345
+
346
+ .tab-content.active {
347
+ display: block;
348
+ }
349
+
350
+ #pdf-viewer {
351
  width: 100%;
352
  height: 600px;
353
+ border: 1px solid var(--border);
354
+ border-radius: var(--radius);
355
+ }
356
+
357
+ .code-area {
358
+ background-color: #f8fafc;
359
+ border: 1px solid var(--border);
360
+ border-radius: 0.25rem;
361
+ padding: 1rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  max-height: 400px;
363
+ overflow-y: auto;
364
+ }
365
+
366
+ .code-area pre {
367
+ margin: 0;
368
+ white-space: pre-wrap;
369
+ word-wrap: break-word;
370
+ font-family: Consolas, Monaco, 'Andale Mono', monospace;
371
+ font-size: 0.875rem;
372
+ line-height: 1.5;
373
  }
374
+
375
+ .download-button {
376
+ display: inline-flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ gap: 0.5rem;
380
+ background-color: var(--primary);
381
+ color: white;
382
+ padding: 0.75rem 1.5rem;
383
+ border: none;
384
+ border-radius: var(--radius);
385
+ font-weight: 500;
386
+ font-size: 1rem;
387
+ cursor: pointer;
388
+ transition: background-color 0.3s, transform 0.2s;
389
+ margin: 1.5rem auto;
390
+ width: auto;
391
  }
392
+
393
  .hidden {
394
+ display: none !important;
395
+ }
396
+
397
+ /* Responsive design */
398
+ @media (max-width: 768px) {
399
+ .status-checks {
400
+ grid-template-columns: 1fr;
401
+ }
402
+
403
+ .file-upload-container {
404
+ height: 120px;
405
+ }
406
+
407
+ .tab {
408
+ padding: 0.5rem 1rem;
409
+ font-size: 0.875rem;
410
+ }
411
+
412
+ #pdf-viewer {
413
+ height: 400px;
414
+ }
415
  }
416
  </style>
417
  </head>
418
  <body>
419
  <div class="container">
420
+ <div class="header">
421
+ <h1>Mariam AI</h1>
422
+ <p class="subheader">Correcteur Intelligent d'Exercices Mathématiques</p>
 
 
 
423
  </div>
424
 
425
+ <div class="card">
426
+ <h2>État du système</h2>
427
+ <div class="status-checks">
428
+ <div id="latex-status" class="status-item">
429
+ <span class="status-icon"><i class="fas fa-spinner fa-spin"></i></span>
430
+ <span class="status-text">Vérification de LaTeX...</span>
431
+ </div>
432
+ <div id="api-status" class="status-item">
433
+ <span class="status-icon"><i class="fas fa-spinner fa-spin"></i></span>
434
+ <span class="status-text">Vérification de l'API Mariam AI...</span>
435
+ </div>
436
+ </div>
437
  </div>
438
 
439
+ <div class="card">
440
+ <h2>Soumettre un exercice</h2>
441
+ <div class="file-upload">
442
+ <div id="file-input-container" class="file-input-container">
443
+ <span class="upload-icon"><i class="fas fa-cloud-upload-alt"></i></span>
444
+ <span class="upload-label">Déposez votre image ou cliquez pour sélectionner</span>
445
+ <span class="upload-hint">Formats acceptés: JPG, PNG, GIF</span>
446
+ <input type="file" id="image-input" class="file-input" accept="image/*">
447
+
448
+ <div class="file-preview">
449
+ <img id="image-preview" src="" alt="Aperçu">
450
+ </div>
451
+ <div class="file-name" id="file-name"></div>
452
+ <div class="clear-file" id="clear-file"><i class="fas fa-times"></i></div>
453
+ </div>
454
+
455
+ <button id="process-button" class="button" disabled>
456
+ <span class="button-icon"><i class="fas fa-magic"></i></span>
457
+ <span>Générer la solution</span>
458
+ </button>
459
+ </div>
460
  </div>
461
 
462
+ <div id="loading" class="card loading hidden">
463
+ <div class="spinner"></div>
464
+ <p>Mariam AI analyse l'exercice et génère la solution...</p>
465
+ <p class="upload-hint">Cette opération peut prendre jusqu'à une minute.</p>
466
+ </div>
 
 
 
 
467
 
468
+ <div id="messages" class="message hidden"></div>
469
 
470
+ <div id="results" class="card hidden">
471
+ <h2>Résultat de l'analyse</h2>
472
+
473
+ <div class="tab-container">
474
+ <div class="tabs">
475
+ <div class="tab active" data-tab="pdf">Aperçu PDF</div>
476
+ <div class="tab" data-tab="latex">Code LaTeX</div>
477
+ <div class="tab" data-tab="thinking">Processus de réflexion</div>
478
  </div>
479
+
480
+ <div id="pdf-tab" class="tab-content active">
481
+ <iframe id="pdf-viewer" title="Aperçu du PDF de la solution"></iframe>
482
+ <div class="download-container">
483
+ <button id="download-button" class="download-button">
484
+ <i class="fas fa-download"></i> Télécharger le PDF
485
+ </button>
486
+ </div>
487
+ </div>
488
+
489
+ <div id="latex-tab" class="tab-content">
490
+ <div class="code-area">
491
+ <pre id="latex-output"></pre>
492
+ </div>
493
+ </div>
494
+
495
+ <div id="thinking-tab" class="tab-content">
496
+ <div class="code-area">
497
+ <pre id="thinking-output"></pre>
498
+ </div>
499
  </div>
500
  </div>
501
  </div>
 
503
 
504
  <script>
505
  document.addEventListener('DOMContentLoaded', () => {
506
+ // Éléments DOM
507
  const latexStatusEl = document.getElementById('latex-status');
508
  const apiStatusEl = document.getElementById('api-status');
509
+ const fileInputContainer = document.getElementById('file-input-container');
510
  const imageInput = document.getElementById('image-input');
511
+ const imagePreview = document.getElementById('image-preview');
512
+ const fileName = document.getElementById('file-name');
513
+ const clearFile = document.getElementById('clear-file');
514
  const processButton = document.getElementById('process-button');
515
  const loadingEl = document.getElementById('loading');
516
  const messagesEl = document.getElementById('messages');
517
  const resultsEl = document.getElementById('results');
518
  const pdfViewer = document.getElementById('pdf-viewer');
519
  const downloadButton = document.getElementById('download-button');
 
 
520
  const latexOutputEl = document.getElementById('latex-output');
 
521
  const thinkingOutputEl = document.getElementById('thinking-output');
522
+ const tabs = document.querySelectorAll('.tab');
523
+ const tabContents = document.querySelectorAll('.tab-content');
524
+
525
+ let currentPdfBase64 = null; // Stockage des données PDF
526
+
527
+ // --- Gestion des onglets ---
528
+ tabs.forEach(tab => {
529
+ tab.addEventListener('click', () => {
530
+ // Désactiver tous les onglets
531
+ tabs.forEach(t => t.classList.remove('active'));
532
+ tabContents.forEach(c => c.classList.remove('active'));
533
+
534
+ // Activer l'onglet sélectionné
535
+ tab.classList.add('active');
536
+ document.getElementById(`${tab.dataset.tab}-tab`).classList.add('active');
537
+ });
538
+ });
539
+
540
+ // --- Gestion du chargement des images ---
541
+ imageInput.addEventListener('change', handleFileSelect);
542
+
543
+ // Gestion du drag and drop
544
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
545
+ fileInputContainer.addEventListener(eventName, preventDefaults, false);
546
+ });
547
+
548
+ ['dragenter', 'dragover'].forEach(eventName => {
549
+ fileInputContainer.addEventListener(eventName, () => {
550
+ fileInputContainer.classList.add('dragover');
551
+ }, false);
552
+ });
553
+
554
+ ['dragleave', 'drop'].forEach(eventName => {
555
+ fileInputContainer.addEventListener(eventName, () => {
556
+ fileInputContainer.classList.remove('dragover');
557
+ }, false);
558
+ });
559
+
560
+ fileInputContainer.addEventListener('drop', handleDrop, false);
561
+
562
+ // Gestion du bouton de suppression de fichier
563
+ clearFile.addEventListener('click', (e) => {
564
+ e.stopPropagation();
565
+ clearFileInput();
566
+ });
567
+
568
+ function preventDefaults(e) {
569
+ e.preventDefault();
570
+ e.stopPropagation();
571
+ }
572
+
573
+ function handleDrop(e) {
574
+ const dt = e.dataTransfer;
575
+ const files = dt.files;
576
+
577
+ if (files && files[0]) {
578
+ imageInput.files = files;
579
+ handleFileSelect();
580
+ }
581
+ }
582
+
583
+ function handleFileSelect() {
584
+ const file = imageInput.files[0];
585
+
586
+ if (file) {
587
+ const reader = new FileReader();
588
+
589
+ reader.onload = function(e) {
590
+ imagePreview.src = e.target.result;
591
+ fileName.textContent = file.name;
592
+ fileInputContainer.classList.add('preview-active');
593
+ processButton.disabled = false;
594
+ };
595
+
596
+ reader.readAsDataURL(file);
597
+ } else {
598
+ clearFileInput();
599
+ }
600
+ }
601
 
602
+ function clearFileInput() {
603
+ imageInput.value = '';
604
+ imagePreview.src = '';
605
+ fileName.textContent = '';
606
+ fileInputContainer.classList.remove('preview-active');
607
+ processButton.disabled = true;
608
+ }
609
 
610
  // --- Fonctions Utilitaires ---
611
  function showLoading() {
 
613
  processButton.disabled = true;
614
  messagesEl.classList.add('hidden');
615
  resultsEl.classList.add('hidden');
616
+ pdfViewer.src = 'about:blank'; // Vider l'aperçu PDF
617
  latexOutputEl.textContent = '';
618
  thinkingOutputEl.textContent = '';
 
 
 
619
  currentPdfBase64 = null;
620
  }
621
 
622
  function hideLoading() {
623
  loadingEl.classList.add('hidden');
624
+ processButton.disabled = !imageInput.files[0];
625
  }
626
 
627
  function showMessage(message, isError = false) {
628
+ messagesEl.innerHTML = `
629
+ <i class="fas ${isError ? 'fa-exclamation-circle' : 'fa-check-circle'}"></i>
630
+ <div>${message}</div>
631
+ `;
632
+ messagesEl.className = `message ${isError ? 'message-error' : 'message-success'}`;
633
  messagesEl.classList.remove('hidden');
634
  }
635
 
636
  function displayResults(data) {
637
  resultsEl.classList.remove('hidden');
638
 
639
+ // Gestion du PDF
640
  if (data.pdf_base64) {
641
  pdfViewer.src = `data:application/pdf;base64,${data.pdf_base64}`;
642
  currentPdfBase64 = data.pdf_base64;
643
  downloadButton.classList.remove('hidden');
644
  } else {
645
+ pdfViewer.src = 'about:blank';
646
  downloadButton.classList.add('hidden');
647
  }
648
 
649
+ // Gestion du LaTeX
650
+ latexOutputEl.textContent = data.latex || "Aucun code LaTeX disponible.";
651
+
652
+ // Gestion du processus de réflexion
653
+ thinkingOutputEl.textContent = data.thinking || "Aucun processus de réflexion disponible.";
654
+
655
+ // Activer l'onglet approprié par défaut
656
+ if (data.pdf_base64) {
657
+ activateTab('pdf');
658
+ } else if (data.latex) {
659
+ activateTab('latex');
660
+ } else if (data.thinking) {
661
+ activateTab('thinking');
662
  }
663
  }
664
+
665
+ function activateTab(tabName) {
666
+ tabs.forEach(t => t.classList.remove('active'));
667
+ tabContents.forEach(c => c.classList.remove('active'));
668
+
669
+ document.querySelector(`.tab[data-tab="${tabName}"]`).classList.add('active');
670
+ document.getElementById(`${tabName}-tab`).classList.add('active');
671
+ }
672
 
673
  // --- Vérifications Initiales ---
674
  async function checkStatus() {
675
  try {
676
  const latexRes = await fetch('/check-latex');
677
  const latexData = await latexRes.json();
678
+
679
+ latexStatusEl.innerHTML = `
680
+ <span class="status-icon"><i class="fas ${latexData.success ? 'fa-check-circle' : 'fa-times-circle'}"></i></span>
681
+ <span class="status-text">${latexData.message}</span>
682
+ `;
683
+ latexStatusEl.className = `status-item ${latexData.success ? 'status-success' : 'status-error'}`;
684
  } catch (error) {
685
+ latexStatusEl.innerHTML = `
686
+ <span class="status-icon"><i class="fas fa-times-circle"></i></span>
687
+ <span class="status-text">Erreur lors de la vérification de LaTeX: ${error}</span>
688
+ `;
689
+ latexStatusEl.className = 'status-item status-error';
690
  }
691
 
692
  try {
693
  const apiRes = await fetch('/check-api');
694
  const apiData = await apiRes.json();
695
+
696
+ apiStatusEl.innerHTML = `
697
+ <span class="status-icon"><i class="fas ${apiData.success ? 'fa-check-circle' : 'fa-times-circle'}"></i></span>
698
+ <span class="status-text">${apiData.message}</span>
699
+ `;
700
+ apiStatusEl.className = `status-item ${apiData.success ? 'status-success' : 'status-error'}`;
701
  } catch (error) {
702
+ apiStatusEl.innerHTML = `
703
+ <span class="status-icon"><i class="fas fa-times-circle"></i></span>
704
+ <span class="status-text">Erreur lors de la vérification de l'API: ${error}</span>
705
+ `;
706
+ apiStatusEl.className = 'status-item status-error';
707
  }
708
  }
709
 
 
730
  hideLoading();
731
 
732
  if (data.success) {
733
+ showMessage('Solution générée avec succès !');
734
  displayResults(data);
735
  } else {
736
  showMessage(`Erreur : ${data.message}`, true);
737
  // Afficher le LaTeX/Thinking même en cas d'erreur de compilation
738
  if(data.latex || data.thinking) {
739
+ displayResults(data);
740
  }
741
  }
742
 
 
748
  });
749
 
750
  // --- Téléchargement du PDF ---
751
+ downloadButton.addEventListener('click', async () => {
752
  if (!currentPdfBase64) {
753
  showMessage('Aucune donnée PDF à télécharger.', true);
754
  return;
 
764
  });
765
 
766
  if (response.ok) {
 
 
 
767
  const blob = await response.blob();
768
  const url = window.URL.createObjectURL(blob);
769
  const a = document.createElement('a');
770
  a.style.display = 'none';
771
  a.href = url;
 
772
  a.download = response.headers.get('Content-Disposition')?.split('filename=')[1]?.replaceAll('"', '') || 'solution_mariam_ai.pdf';
773
  document.body.appendChild(a);
774
  a.click();
775
  window.URL.revokeObjectURL(url);
776
  a.remove();
777
+ showMessage('Téléchargement démarré.');
778
  } else {
 
779
  let errorMsg = `Échec du téléchargement (code ${response.status}).`;
780
  try {
781
  const errorData = await response.json();
 
784
  showMessage(errorMsg, true);
785
  }
786
  } catch (error) {
787
+ showMessage(`Erreur lors de la tentative de téléchargement : ${error}`, true);
788
+ console.error("Download Error:", error);
789
  }
790
  });
791
 
 
792
  // Exécuter les vérifications au chargement
793
  checkStatus();
794
  });