Spaces:
Running
Running
Update templates/index.html
Browse files- templates/index.html +242 -543
templates/index.html
CHANGED
@@ -6,11 +6,12 @@
|
|
6 |
<title>Math Solver - Version Gratuite</title>
|
7 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
|
8 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css" rel="stylesheet">
|
9 |
-
<!--
|
10 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script>
|
11 |
<!-- CSS pour KaTeX -->
|
12 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.css">
|
13 |
<style>
|
|
|
14 |
:root {
|
15 |
--primary-color: #4a6fa5;
|
16 |
--secondary-color: #166088;
|
@@ -62,20 +63,24 @@
|
|
62 |
box-shadow: var(--box-shadow);
|
63 |
padding: 30px;
|
64 |
margin-bottom: 30px;
|
|
|
65 |
}
|
66 |
|
67 |
-
|
|
|
68 |
text-align: center;
|
69 |
}
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
74 |
text-align: center;
|
75 |
}
|
76 |
-
.content-box .upload-section
|
77 |
-
|
78 |
-
margin-
|
79 |
}
|
80 |
.content-box .upgrade-section {
|
81 |
text-align: center;
|
@@ -93,9 +98,6 @@
|
|
93 |
.feature-list {
|
94 |
list-style-type: none;
|
95 |
padding: 0;
|
96 |
-
margin: 30px auto; /* Centrer la liste elle-même */
|
97 |
-
max-width: 600px; /* Limiter la largeur pour meilleure lisibilité */
|
98 |
-
text-align: left;
|
99 |
}
|
100 |
|
101 |
.feature-list li {
|
@@ -128,7 +130,6 @@
|
|
128 |
margin: 10px; /* Ajustement marge */
|
129 |
border: none;
|
130 |
cursor: pointer;
|
131 |
-
font-size: 1rem; /* Taille de police cohérente */
|
132 |
}
|
133 |
|
134 |
.cta-button:hover {
|
@@ -169,11 +170,12 @@
|
|
169 |
}
|
170 |
|
171 |
/* Style de base pour tous les blocs dans #solution */
|
172 |
-
|
|
|
173 |
padding: 20px;
|
174 |
margin: 0;
|
175 |
border-radius: 0; /* Sera ajusté pour first/last child */
|
176 |
-
overflow-x: auto;
|
177 |
background-color: #f9f9f9; /* Fond par défaut */
|
178 |
border-bottom: 1px solid #eee; /* Séparateur par défaut */
|
179 |
/* Ajout pour débordement horizontal */
|
@@ -181,7 +183,6 @@
|
|
181 |
overflow-wrap: break-word; /* Standard CSS */
|
182 |
}
|
183 |
|
184 |
-
/* --- Sections spécifiques --- */
|
185 |
.code-section {
|
186 |
background-color: transparent !important; /* Le fond est géré par header/content */
|
187 |
padding: 0 !important;
|
@@ -192,71 +193,51 @@
|
|
192 |
background-color: var(--output-bg) !important;
|
193 |
border-left: 4px solid var(--secondary-color); /* Distinction visuelle */
|
194 |
padding-left: calc(20px - 4px);
|
195 |
-
font-family: 'Courier New', Courier, monospace;
|
196 |
-
font-size: 0.95em;
|
197 |
-
white-space: pre-wrap; /* Respecter les retours à la ligne et espaces de la sortie */
|
198 |
-
word-break: break-all; /* Casser les longues lignes sans espaces */
|
199 |
}
|
200 |
|
201 |
.step-section {
|
202 |
background-color: #ffffff; /* Fond blanc pour les étapes normales */
|
203 |
border-left: 4px solid var(--primary-color);
|
204 |
padding-left: calc(20px - 4px); /* Compenser la bordure */
|
205 |
-
font-size:
|
206 |
line-height: 1.8;
|
207 |
}
|
208 |
-
.error-message { /* Style pour les messages d'erreur injectés */
|
209 |
-
color: #a94442; /* Rouge foncé */
|
210 |
-
background-color: #f2dede; /* Rose pâle */
|
211 |
-
border-color: #ebccd1 !important; /* Bordure rouge plus pâle */
|
212 |
-
border-left-width: 4px !important;
|
213 |
-
border-style: solid;
|
214 |
-
}
|
215 |
-
.error-message strong {
|
216 |
-
color: #a94442;
|
217 |
-
}
|
218 |
-
.error-message pre { /* Si l'erreur contient du code brut */
|
219 |
-
background-color: #eacccc;
|
220 |
-
color: #843534;
|
221 |
-
border-color: #d9a9a9;
|
222 |
-
}
|
223 |
-
|
224 |
|
225 |
/* --- Amélioration du rendu KaTeX --- */
|
226 |
.step-section .katex {
|
227 |
font-size: 1.1em; /* Légèrement plus grand que le texte normal */
|
228 |
line-height: normal; /* Laisser KaTeX gérer */
|
229 |
text-indent: 0;
|
230 |
-
text-rendering:
|
231 |
-
|
232 |
-
|
|
|
233 |
}
|
234 |
|
235 |
.step-section .katex-display {
|
236 |
display: block; /* Comportement block pour $$...$$ */
|
237 |
text-align: center; /* Centrer les équations display */
|
238 |
-
margin:
|
239 |
overflow-x: auto; /* Permet le scroll horizontal si équation trop large */
|
240 |
overflow-y: hidden;
|
241 |
-
padding: 0.5em 0.2em; /* Un peu d'espace interne,
|
242 |
-
|
243 |
-
|
244 |
-
/* border-radius: 4px; */ /* Optionnel : coins arrondis */
|
245 |
}
|
246 |
/* Conteneur interne créé par KaTeX */
|
247 |
.step-section .katex-display > .katex {
|
248 |
display: inline-block; /* Permet le centrage et évite la pleine largeur */
|
249 |
text-align: initial; /* Texte de l'équation aligné à gauche par défaut */
|
250 |
max-width: 100%;
|
|
|
251 |
}
|
252 |
/* --- Fin Amélioration KaTeX --- */
|
253 |
|
254 |
-
/* --- Sections de Code Dédiées (Python) --- */
|
255 |
.code-header {
|
256 |
background-color: #343a40;
|
257 |
color: white;
|
258 |
padding: 10px 15px;
|
259 |
-
font-size:
|
260 |
font-family: 'Courier New', monospace;
|
261 |
display: flex;
|
262 |
justify-content: space-between;
|
@@ -270,132 +251,30 @@
|
|
270 |
color: #e6e6e6;
|
271 |
overflow-x: auto;
|
272 |
font-family: 'Courier New', monospace;
|
273 |
-
font-size:
|
274 |
line-height: 1.5;
|
275 |
border-bottom: 1px solid #444; /* Bordure sous le code */
|
276 |
}
|
277 |
-
.code-content pre { /* S'assurer que pre dans le bloc code est stylé correctement */
|
278 |
-
margin: 0;
|
279 |
-
padding: 0;
|
280 |
-
background-color: transparent;
|
281 |
-
border: none;
|
282 |
-
border-radius: 0;
|
283 |
-
}
|
284 |
-
.code-content pre code {
|
285 |
-
font-size: 1em; /* Hérite de .code-content */
|
286 |
-
line-height: inherit;
|
287 |
-
background-color: transparent;
|
288 |
-
color: inherit;
|
289 |
-
padding: 0;
|
290 |
-
}
|
291 |
-
/* --- Fin Sections de Code Dédiées --- */
|
292 |
|
293 |
-
|
294 |
-
#solution >
|
295 |
-
#solution > .code-section:first-child .code-header {
|
296 |
border-top-left-radius: 8px;
|
297 |
border-top-right-radius: 8px;
|
298 |
}
|
299 |
|
300 |
-
#solution >
|
301 |
-
#solution > .code-section:last-child .code-content {
|
302 |
border-bottom-left-radius: 8px;
|
303 |
border-bottom-right-radius: 8px;
|
304 |
border-bottom: none; /* Pas de bordure en bas du dernier élément */
|
305 |
}
|
306 |
|
307 |
-
#solution >
|
308 |
border-radius: 8px;
|
309 |
}
|
310 |
-
/* Supprimer la bordure du bas si le suivant est une section de code */
|
311 |
-
#solution > *:not(:last-child):not(.code-section) {
|
312 |
-
border-bottom: 1px solid #eee;
|
313 |
-
}
|
314 |
-
#solution > .code-section + * { /* Si un élément suit une section code */
|
315 |
-
border-top: 1px solid #eee; /* Ajouter une ligne de séparation au dessus */
|
316 |
-
}
|
317 |
-
#solution > .code-section .code-content {
|
318 |
-
border-bottom: none; /* Pas de double bordure */
|
319 |
-
}
|
320 |
-
|
321 |
-
|
322 |
-
/* --- Amélioration du rendu Markdown --- */
|
323 |
-
.step-section *:first-child { margin-top: 0; } /* Éviter marge sup en début de bloc */
|
324 |
-
.step-section *:last-child { margin-bottom: 0; } /* Éviter marge inf en fin de bloc */
|
325 |
-
|
326 |
-
.step-section p { margin-bottom: 1em; } /* Espacement standard paragraphe */
|
327 |
-
|
328 |
-
.step-section ul, .step-section ol {
|
329 |
-
margin-block-start: 1em;
|
330 |
-
margin-block-end: 1em;
|
331 |
-
padding-inline-start: 30px; /* Indentation standard */
|
332 |
-
}
|
333 |
-
.step-section ul li, .step-section ol li {
|
334 |
-
margin-bottom: 0.5em;
|
335 |
-
}
|
336 |
-
.step-section ul ul,
|
337 |
-
.step-section ol ol,
|
338 |
-
.step-section ul ol,
|
339 |
-
.step-section ol ul {
|
340 |
-
margin-block-start: 0.5em;
|
341 |
-
margin-block-end: 0.5em;
|
342 |
-
}
|
343 |
|
344 |
-
/*
|
345 |
-
.step-section .table-wrapper { /* Conteneur pour le défilement horizontal */
|
346 |
-
overflow-x: auto;
|
347 |
-
margin: 1em 0;
|
348 |
-
}
|
349 |
-
.step-section table {
|
350 |
-
border-collapse: collapse;
|
351 |
-
width: 100%; /* Prend la largeur du wrapper */
|
352 |
-
min-width: 400px; /* Largeur minimale pour éviter écrasement */
|
353 |
-
border: 1px solid #ccc; /* Bordure extérieure */
|
354 |
-
}
|
355 |
-
.step-section th,
|
356 |
-
.step-section td {
|
357 |
-
border: 1px solid #ddd;
|
358 |
-
padding: 8px 12px;
|
359 |
-
text-align: left;
|
360 |
-
}
|
361 |
-
.step-section th {
|
362 |
-
background-color: #f2f2f2;
|
363 |
-
font-weight: bold;
|
364 |
-
}
|
365 |
-
.step-section tr:nth-child(even) {
|
366 |
-
background-color: #f9f9f9;
|
367 |
-
}
|
368 |
-
|
369 |
-
/* Rendu des blocs de code en Markdown (pas Python dédié) */
|
370 |
-
.step-section pre {
|
371 |
-
background-color: #f5f5f5;
|
372 |
-
border-radius: 4px;
|
373 |
-
padding: 12px;
|
374 |
-
overflow-x: auto;
|
375 |
-
margin: 1em 0;
|
376 |
-
border: 1px solid #ddd;
|
377 |
-
line-height: 1.45; /* Consistant avec hljs */
|
378 |
-
}
|
379 |
-
.step-section pre code { /* Style pour le code dans pre */
|
380 |
-
background-color: transparent;
|
381 |
-
padding: 0;
|
382 |
-
border-radius: 0;
|
383 |
-
font-size: 0.95em; /* Un peu plus petit */
|
384 |
-
color: #333; /* Couleur de base du code */
|
385 |
-
}
|
386 |
-
/* Style pour `code` inline */
|
387 |
-
.step-section code:not(pre *) {
|
388 |
-
font-family: Consolas, 'Courier New', Courier, monospace; /* Police plus adaptée */
|
389 |
-
background-color: #f0f0f0; /* Gris clair */
|
390 |
-
padding: 3px 6px; /* Un peu plus d'espace */
|
391 |
-
border-radius: 3px;
|
392 |
-
font-size: 0.9em;
|
393 |
-
color: #c7254e; /* Couleur style "rouge" pour code inline */
|
394 |
-
border: 1px solid #e8e8e8;
|
395 |
-
word-break: break-word; /* Casser si trop long */
|
396 |
-
}
|
397 |
-
|
398 |
-
/* --- Indicateurs de statut --- */
|
399 |
.thinking-indicator, .executing-indicator, .answering-indicator {
|
400 |
display: flex;
|
401 |
align-items: center;
|
@@ -407,7 +286,6 @@
|
|
407 |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
408 |
max-width: 400px; /* Limiter la largeur */
|
409 |
}
|
410 |
-
|
411 |
.thinking-indicator { background-color: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb; }
|
412 |
.executing-indicator { background-color: #ede7f6; color: #5e35b1; border: 1px solid #d1c4e9; }
|
413 |
.answering-indicator { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; }
|
@@ -417,165 +295,114 @@
|
|
417 |
animation: pulse 1.5s infinite ease-in-out;
|
418 |
font-size: 1.1rem;
|
419 |
}
|
420 |
-
/* --- Fin Indicateurs --- */
|
421 |
-
|
422 |
@keyframes pulse {
|
423 |
0% { opacity: 0.6; transform: scale(1); }
|
424 |
50% { opacity: 1; transform: scale(1.05); }
|
425 |
100% { opacity: 0.6; transform: scale(1); }
|
426 |
}
|
427 |
|
428 |
-
/* ---
|
429 |
-
.
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
background-color: #f9f9f9;
|
435 |
}
|
436 |
-
.
|
437 |
-
|
|
|
|
|
|
|
|
|
438 |
}
|
439 |
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
.step-section h2,
|
448 |
-
.step-section h3,
|
449 |
-
.step-section h4,
|
450 |
-
.step-section h5,
|
451 |
-
.step-section h6 {
|
452 |
-
margin-top: 1.8em; /* Plus d'espace avant les titres */
|
453 |
-
margin-bottom: 0.8em;
|
454 |
-
color: var(--primary-color);
|
455 |
-
font-weight: 600; /* Un peu plus gras */
|
456 |
-
padding-bottom: 0.3em;
|
457 |
-
border-bottom: 1px solid #eee; /* Souligner légèrement les titres */
|
458 |
-
line-height: 1.3; /* Ajuster interligne titre */
|
459 |
-
}
|
460 |
-
.step-section h1:first-child, /* Pas de marge sup pour le tout premier titre */
|
461 |
-
.step-section h2:first-child,
|
462 |
-
.step-section h3:first-child,
|
463 |
-
.step-section h4:first-child,
|
464 |
-
.step-section h5:first-child,
|
465 |
-
.step-section h6:first-child { margin-top: 0; }
|
466 |
-
|
467 |
-
.step-section h1 { font-size: 1.8em; border-bottom-width: 2px; }
|
468 |
-
.step-section h2 { font-size: 1.6em; }
|
469 |
-
.step-section h3 { font-size: 1.4em; }
|
470 |
-
.step-section h4 { font-size: 1.2em; }
|
471 |
-
.step-section h5 { font-size: 1.1em; color: var(--secondary-color); } /* Couleur différente pour H5/H6 */
|
472 |
-
.step-section h6 { font-size: 1.0em; color: #666; font-style: italic;}
|
473 |
|
474 |
</style>
|
475 |
</head>
|
476 |
<body>
|
477 |
<div class="container">
|
478 |
<header>
|
479 |
-
|
480 |
-
|
481 |
</header>
|
482 |
|
483 |
<div class="content-box">
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
|
|
500 |
<h2>Soumettez votre problème</h2>
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
<p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555; min-height: 1.2em;"></p>
|
507 |
-
|
508 |
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
|
518 |
<!-- Section pour afficher la solution -->
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
544 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
|
|
|
|
|
545 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js"></script>
|
546 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min.js"></script>
|
547 |
-
<!-- Marked.js est déjà chargé dans le <head> -->
|
548 |
|
549 |
<script>
|
550 |
-
//
|
551 |
-
marked.setOptions({
|
552 |
-
gfm: true,
|
553 |
-
breaks: false, // Standard GFM: ne pas convertir les simples retours à la ligne en <br>
|
554 |
-
pedantic: false,
|
555 |
-
smartLists: true,
|
556 |
-
smartypants: false, // Désactiver pour éviter interférence avec $
|
557 |
-
xhtml: true,
|
558 |
-
highlight: function(code, lang) {
|
559 |
-
// Utilise highlight.js pour la coloration dans les blocs ```lang ... ```
|
560 |
-
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
561 |
-
return hljs.highlight(code, { language, ignoreIllegals: true }).value;
|
562 |
-
},
|
563 |
-
// Ajouter un wrapper pour les tables pour permettre le scroll horizontal
|
564 |
-
renderer: (function() {
|
565 |
-
const renderer = new marked.Renderer();
|
566 |
-
// Conserver le rendu par défaut pour la plupart des éléments
|
567 |
-
const defaultTableRenderer = renderer.table;
|
568 |
-
renderer.table = function(header, body) {
|
569 |
-
// Appeler le rendu par défaut pour obtenir le HTML de la table
|
570 |
-
const tableHtml = defaultTableRenderer.call(this, header, body);
|
571 |
-
// Envelopper la table dans un div pour le style et le défilement
|
572 |
-
return `<div class="table-wrapper">${tableHtml}</div>`;
|
573 |
-
};
|
574 |
-
return renderer;
|
575 |
-
})()
|
576 |
-
});
|
577 |
-
|
578 |
-
// --- Configuration KaTeX ---
|
579 |
const katexOptions = {
|
580 |
delimiters: [
|
581 |
{left: '$$', right: '$$', display: true},
|
@@ -583,341 +410,213 @@
|
|
583 |
{left: '\\(', right: '\\)', display: false},
|
584 |
{left: '\\[', right: '\\]', display: true}
|
585 |
],
|
586 |
-
throwOnError: false, // Ne pas bloquer en cas d'erreur LaTeX
|
587 |
-
strict: (errorCode) => {
|
588 |
-
//
|
|
|
589 |
if (errorCode === 'unicodeTextInMathMode') {
|
590 |
-
return 'ignore'; //
|
591 |
}
|
592 |
-
|
593 |
-
// 'error' arrêterait le rendu de cette formule
|
594 |
-
return 'warn';
|
595 |
-
},
|
596 |
-
macros: { // Optionnel: Définir des macros LaTeX personnalisées
|
597 |
-
// "\\RR": "\\mathbb{R}"
|
598 |
},
|
599 |
-
//
|
|
|
|
|
|
|
|
|
600 |
};
|
601 |
|
602 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
document.getElementById('imageInput').addEventListener('change', function(event) {
|
604 |
const file = event.target.files[0];
|
605 |
-
const uploadStatus = document.getElementById('uploadStatus');
|
606 |
-
const imagePreviewDiv = document.getElementById('imagePreview');
|
607 |
-
const previewImg = document.getElementById('preview');
|
608 |
-
const solveButton = document.getElementById('solveButton');
|
609 |
-
const solutionOutputDiv = document.getElementById('solutionOutput');
|
610 |
-
const solutionContainer = document.getElementById('solution');
|
611 |
-
|
612 |
if (file) {
|
613 |
const reader = new FileReader();
|
614 |
reader.onload = function(e) {
|
615 |
-
|
616 |
-
|
617 |
-
solveButton.style.display = 'inline-block';
|
618 |
-
uploadStatus.textContent = `Image sélectionnée : ${file.name}`;
|
619 |
-
|
620 |
-
|
621 |
-
solutionContainer.innerHTML = '';
|
622 |
-
document.getElementById('loadingIndicator').style.display = 'none';
|
623 |
}
|
624 |
reader.readAsDataURL(file);
|
625 |
-
uploadStatus.textContent = 'Chargement de l\'aperçu...';
|
626 |
-
} else {
|
627 |
-
imagePreviewDiv.style.display = 'none';
|
628 |
-
solveButton.style.display = 'none';
|
629 |
-
uploadStatus.textContent = '';
|
630 |
}
|
631 |
});
|
632 |
|
633 |
-
// --- Gestion Clic Bouton Résoudre ---
|
634 |
document.getElementById('solveButton').addEventListener('click', function() {
|
635 |
const formData = new FormData(document.getElementById('imageForm'));
|
636 |
const solutionOutputDiv = document.getElementById('solutionOutput');
|
637 |
const loadingIndicator = document.getElementById('loadingIndicator');
|
638 |
const solutionContainer = document.getElementById('solution');
|
639 |
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
return;
|
644 |
-
}
|
645 |
-
|
646 |
-
solutionOutputDiv.style.display = 'block'; // Afficher la section solution
|
647 |
-
loadingIndicator.innerHTML = `<i class="fas fa-spinner fa-spin indicator-icon"></i><span>Envoi de l'image...</span>`;
|
648 |
-
loadingIndicator.className = 'thinking-indicator'; // Classe par défaut
|
649 |
-
loadingIndicator.style.display = 'flex'; // Afficher l'indicateur de chargement
|
650 |
-
solutionContainer.innerHTML = ''; // Effacer la solution précédente
|
651 |
-
|
652 |
-
// Scroller pour rendre l'indicateur visible
|
653 |
loadingIndicator.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
654 |
|
655 |
-
fetch('/solved', { // Endpoint Flask
|
656 |
method: 'POST',
|
657 |
body: formData
|
658 |
})
|
659 |
.then(response => {
|
660 |
-
if (!response.ok
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
// Essayer d'extraire un message d'erreur plus précis si c'est du JSON
|
666 |
-
try {
|
667 |
-
const errorJson = JSON.parse(text);
|
668 |
-
errorMsg += `\n${errorJson.error || errorJson.message || text}`;
|
669 |
-
} catch (e) {
|
670 |
-
errorMsg += `\n${text}`; // Afficher le texte brut si ce n'est pas du JSON
|
671 |
-
}
|
672 |
-
} else {
|
673 |
-
errorMsg += "\nAucun détail supplémentaire fourni par le serveur.";
|
674 |
-
}
|
675 |
-
throw new Error(errorMsg);
|
676 |
-
});
|
677 |
-
}
|
678 |
-
// Préparation à la lecture du flux SSE
|
679 |
const reader = response.body.getReader();
|
680 |
const decoder = new TextDecoder();
|
681 |
let buffer = '';
|
682 |
-
let currentMode = 'thinking'; // Suivre l'état actuel
|
683 |
-
|
684 |
-
// Mettre à jour l'indicateur pour le premier état
|
685 |
-
updateLoadingIndicator('thinking');
|
686 |
|
687 |
function processStream({ done, value }) {
|
688 |
if (done) {
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
displayError("Aucune réponse reçue du serveur après la fin du flux.", solutionContainer);
|
693 |
-
}
|
694 |
-
loadingIndicator.style.display = 'none'; // Cacher l'indicateur final
|
695 |
-
// Optionnel : Passe finale de rendu KaTeX pour toute la solution
|
696 |
-
try {
|
697 |
-
if (window.renderMathInElement) { // Vérifier si la fonction existe
|
698 |
-
renderMathInElement(solutionContainer, katexOptions);
|
699 |
-
}
|
700 |
-
} catch (e) {
|
701 |
-
console.error('Erreur de rendu KaTeX final:', e);
|
702 |
-
}
|
703 |
return;
|
704 |
}
|
705 |
|
706 |
-
// Ajouter les nouvelles données au buffer
|
707 |
buffer += decoder.decode(value, { stream: true });
|
|
|
|
|
708 |
|
709 |
-
|
710 |
-
let boundary = buffer.indexOf('\n\n');
|
711 |
-
while (boundary >= 0) {
|
712 |
-
const message = buffer.substring(0, boundary).trim(); // Prendre un message complet
|
713 |
-
buffer = buffer.substring(boundary + 2); // Enlever le message traité du buffer
|
714 |
-
|
715 |
if (message.startsWith('data: ')) {
|
716 |
try {
|
717 |
-
const data = JSON.parse(message.substring(6));
|
718 |
|
719 |
// --- Gérer les mises à jour de mode ---
|
720 |
-
if (data.mode
|
721 |
-
|
722 |
-
|
723 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
724 |
}
|
725 |
|
726 |
// --- Gérer les blocs de contenu ---
|
727 |
-
if (data.content) {
|
728 |
-
loadingIndicator.style.display = 'none';
|
729 |
-
const
|
|
|
|
|
|
|
730 |
|
731 |
-
//
|
732 |
-
// On se base sur la présence de classes spécifiques envoyées par le serveur
|
733 |
const tempDiv = document.createElement('div');
|
734 |
-
tempDiv.innerHTML =
|
|
|
735 |
const codeSection = tempDiv.querySelector('.code-section');
|
736 |
const outputSection = tempDiv.querySelector('.output-section');
|
737 |
|
738 |
-
let elementToAppend; // L'élément final à ajouter au DOM
|
739 |
-
|
740 |
if (codeSection) {
|
741 |
-
//
|
742 |
-
|
743 |
-
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
} else {
|
749 |
-
console.warn("highlight.js (hljs) non trouvé.");
|
750 |
-
}
|
751 |
});
|
|
|
|
|
752 |
|
753 |
} else if (outputSection) {
|
754 |
-
//
|
755 |
-
|
756 |
-
|
|
|
|
|
757 |
|
758 |
} else {
|
759 |
-
//
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
let placeholderIndex = 0;
|
768 |
-
const placeholderPrefix = "___KXTEMP_"; // Préfixe court et unique
|
769 |
-
|
770 |
-
// 1. Protéger $$...$$ (Display Math)
|
771 |
-
// Regex: trouve $$ suivi de n'importe quoi (non-gourmand) jusqu'à $$
|
772 |
-
processedMdContent = processedMdContent.replace(/(\$\$[\s\S]*?\$\$)/g, (match) => {
|
773 |
-
const placeholder = `${placeholderPrefix}D${placeholderIndex++}___`;
|
774 |
-
latexPlaceholders[placeholder] = match;
|
775 |
-
return placeholder;
|
776 |
-
});
|
777 |
-
|
778 |
-
// 2. Protéger $...$ (Inline Math)
|
779 |
-
// Regex: trouve un $ non précédé par \ ou un autre $, suivi de caractères (non-$), jusqu'au prochain $ non précédé par \.
|
780 |
-
// Attention: cette regex est simplifiée et peut avoir des edge cases.
|
781 |
-
processedMdContent = processedMdContent.replace(/(?<![\$\\])\$([^$]+?)\$(?![\$])/g, (match) => {
|
782 |
-
// (?<![\$\\]) : Ne pas être précédé par $ ou \
|
783 |
-
// \$ : Le dollar de début
|
784 |
-
// ([^$]+?) : Contenu (au moins un caractère, non gourmand)
|
785 |
-
// \$ : Le dollar de fin
|
786 |
-
// (?![\$]) : Ne pas être suivi par un $
|
787 |
-
const placeholder = `${placeholderPrefix}I${placeholderIndex++}___`;
|
788 |
-
latexPlaceholders[placeholder] = match;
|
789 |
-
return placeholder;
|
790 |
-
});
|
791 |
-
|
792 |
-
|
793 |
-
// 3. Parser le Markdown *avec* les placeholders
|
794 |
-
let htmlContent = marked.parse(processedMdContent);
|
795 |
-
|
796 |
-
// 4. Restaurer le LaTeX original à partir des placeholders
|
797 |
-
// Utiliser une fonction pour la robustesse
|
798 |
-
htmlContent = htmlContent.replace(new RegExp(placeholderPrefix + "(D|I)(\\d+)___", "g"), (match) => {
|
799 |
-
return latexPlaceholders[match] || match; // Remplace ou laisse le placeholder si non trouvé
|
800 |
-
});
|
801 |
-
|
802 |
-
// 5. Injecter le HTML final dans le div
|
803 |
-
stepDiv.innerHTML = htmlContent;
|
804 |
-
|
805 |
-
// 6. Rendre le LaTeX DANS ce div spécifique avec KaTeX (après ajout au DOM)
|
806 |
-
// Note: KaTeX sera appelé après l'ajout au DOM ci-dessous
|
807 |
-
|
808 |
-
} catch (e) {
|
809 |
-
console.error('Erreur pendant le traitement Markdown/Placeholder:', e);
|
810 |
-
// En cas d'erreur majeure, afficher le contenu brut
|
811 |
-
stepDiv.innerHTML = `<p class="error-message"><strong>Erreur de rendu Markdown :</strong> ${e.message}</p><pre>${rawContent.replace(/</g, "<")}</pre>`;
|
812 |
-
}
|
813 |
}
|
|
|
814 |
|
815 |
-
// --- Ajouter l'élément préparé au conteneur ---
|
816 |
-
if (elementToAppend) {
|
817 |
-
solutionContainer.appendChild(elementToAppend);
|
818 |
-
|
819 |
-
// --- Appeler KaTeX si c'était une step-section ---
|
820 |
-
if (elementToAppend.classList.contains('step-section')) {
|
821 |
-
try {
|
822 |
-
if (window.renderMathInElement) {
|
823 |
-
renderMathInElement(elementToAppend, katexOptions);
|
824 |
-
} else {
|
825 |
-
console.warn("KaTeX auto-render (renderMathInElement) non trouvé.");
|
826 |
-
}
|
827 |
-
} catch (renderError) {
|
828 |
-
console.error('Erreur rendu KaTeX sur le bloc:', renderError, elementToAppend);
|
829 |
-
// Afficher un message d'erreur dans le bloc lui-même
|
830 |
-
const errorDiv = document.createElement('div');
|
831 |
-
errorDiv.className = 'error-message';
|
832 |
-
errorDiv.innerHTML = `<strong>Erreur Rendu LaTeX:</strong> ${renderError.message || renderError}`;
|
833 |
-
elementToAppend.appendChild(errorDiv);
|
834 |
-
}
|
835 |
-
}
|
836 |
-
|
837 |
-
// Faire défiler pour voir le nouveau contenu
|
838 |
-
elementToAppend.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
839 |
-
}
|
840 |
-
} // Fin if(data.content)
|
841 |
|
842 |
-
// --- Gérer les erreurs spécifiques
|
843 |
if (data.error) {
|
844 |
-
|
845 |
-
|
|
|
|
|
|
|
|
|
|
|
846 |
}
|
847 |
|
848 |
} catch (e) {
|
849 |
console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
|
850 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
851 |
loadingIndicator.style.display = 'none';
|
852 |
}
|
853 |
-
} else if (message.trim() !== '') {
|
854 |
-
// Ignorer les lignes vides mais logguer les autres lignes non-SSE
|
855 |
-
console.warn("Message SSE ignoré (ne commence pas par 'data:'):", message);
|
856 |
}
|
|
|
|
|
|
|
|
|
857 |
|
858 |
-
// Chercher la prochaine limite de message
|
859 |
-
boundary = buffer.indexOf('\n\n');
|
860 |
-
} // Fin while boundary
|
861 |
|
862 |
// Continuer à lire le flux
|
863 |
-
reader.read().then(processStream);
|
864 |
-
}
|
865 |
|
866 |
// Démarrer le traitement du flux
|
867 |
reader.read().then(processStream);
|
868 |
-
|
869 |
})
|
870 |
.catch(error => {
|
871 |
-
// Gérer les erreurs de fetch initial (connexion, DNS, etc.) ou les erreurs levées plus tôt
|
872 |
console.error('Erreur Fetch ou connexion:', error);
|
873 |
-
|
874 |
-
|
|
|
|
|
|
|
|
|
875 |
});
|
876 |
});
|
877 |
|
878 |
-
//
|
879 |
-
function updateLoadingIndicator(mode) {
|
880 |
-
const loadingIndicator = document.getElementById('loadingIndicator');
|
881 |
-
const modes = {
|
882 |
-
thinking: { icon: 'fa-brain', text: 'Je réfléchis au problème...', class: 'thinking-indicator' },
|
883 |
-
answering: { icon: 'fa-pencil-alt', text: 'Je rédige la réponse...', class: 'answering-indicator' },
|
884 |
-
executing_code: { icon: 'fa-play', text: 'Exécution du code Python...', class: 'executing-indicator' },
|
885 |
-
code_result: { icon: 'fa-terminal', text: 'Analyse des résultats du code...', class: 'executing-indicator' },
|
886 |
-
// Ajouter d'autres modes si nécessaire
|
887 |
-
default: { icon: 'fa-sync-alt fa-spin', text: 'Traitement en cours...', class: 'thinking-indicator' }
|
888 |
-
};
|
889 |
-
const modeInfo = modes[mode] || modes.default;
|
890 |
-
|
891 |
-
loadingIndicator.className = modeInfo.class; // Appliquer la classe CSS pour le style
|
892 |
-
loadingIndicator.innerHTML = `<i class="fas ${modeInfo.icon} indicator-icon"></i><span>${modeInfo.text}</span>`;
|
893 |
-
}
|
894 |
-
|
895 |
-
// --- Fonction pour afficher les erreurs de manière cohérente ---
|
896 |
-
function displayError(errorMessage, container) {
|
897 |
-
const errorDiv = document.createElement('div');
|
898 |
-
// Utiliser la classe CSS définie pour les erreurs
|
899 |
-
errorDiv.className = 'step-section error-message';
|
900 |
-
// Utiliser marked pour permettre un formatage simple dans les erreurs si besoin
|
901 |
-
try {
|
902 |
-
errorDiv.innerHTML = `<strong>Erreur :</strong> ${marked.parseInline(errorMessage || 'Une erreur inconnue est survenue.')}`;
|
903 |
-
} catch (e) { // Fallback si marked échoue
|
904 |
-
errorDiv.innerHTML = `<strong>Erreur :</strong> ${errorMessage || 'Une erreur inconnue est survenue.'}`;
|
905 |
-
}
|
906 |
-
container.appendChild(errorDiv);
|
907 |
-
errorDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
908 |
-
}
|
909 |
-
|
910 |
-
|
911 |
-
// --- Initialisation au chargement de la page ---
|
912 |
document.addEventListener('DOMContentLoaded', function() {
|
913 |
-
|
914 |
-
|
915 |
-
|
916 |
-
|
917 |
-
}
|
918 |
-
} catch (e) {
|
919 |
-
console.error('Erreur KaTeX initiale sur le corps de la page:', e)
|
920 |
-
}
|
921 |
});
|
922 |
</script>
|
923 |
</body>
|
|
|
6 |
<title>Math Solver - Version Gratuite</title>
|
7 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
|
8 |
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css" rel="stylesheet">
|
9 |
+
<!-- PAS BESOIN de marked.js pour cette approche -->
|
10 |
+
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script> -->
|
11 |
<!-- CSS pour KaTeX -->
|
12 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.css">
|
13 |
<style>
|
14 |
+
/* --- Styles CSS (inchangés par rapport à la version précédente) --- */
|
15 |
:root {
|
16 |
--primary-color: #4a6fa5;
|
17 |
--secondary-color: #166088;
|
|
|
63 |
box-shadow: var(--box-shadow);
|
64 |
padding: 30px;
|
65 |
margin-bottom: 30px;
|
66 |
+
/* text-align: center; enlevé pour que le contenu dynamique ne soit pas centré par défaut */
|
67 |
}
|
68 |
|
69 |
+
/* Ajustement pour le contenu généré */
|
70 |
+
.content-box > h1, .content-box > p:not(#uploadStatus) {
|
71 |
text-align: center;
|
72 |
}
|
73 |
+
.content-box .feature-list {
|
74 |
+
text-align: left; /* Garder le texte des features aligné à gauche */
|
75 |
+
max-width: 600px;
|
76 |
+
margin: 30px auto;
|
77 |
+
}
|
78 |
+
.content-box .feature-list h2 {
|
79 |
text-align: center;
|
80 |
}
|
81 |
+
.content-box .upload-section {
|
82 |
+
text-align: center;
|
83 |
+
margin-top: 30px; /* Ajouter de l'espace avant */
|
84 |
}
|
85 |
.content-box .upgrade-section {
|
86 |
text-align: center;
|
|
|
98 |
.feature-list {
|
99 |
list-style-type: none;
|
100 |
padding: 0;
|
|
|
|
|
|
|
101 |
}
|
102 |
|
103 |
.feature-list li {
|
|
|
130 |
margin: 10px; /* Ajustement marge */
|
131 |
border: none;
|
132 |
cursor: pointer;
|
|
|
133 |
}
|
134 |
|
135 |
.cta-button:hover {
|
|
|
170 |
}
|
171 |
|
172 |
/* Style de base pour tous les blocs dans #solution */
|
173 |
+
/* Utiliser 'section' pour les blocs pour plus de sémantique */
|
174 |
+
#solution > section {
|
175 |
padding: 20px;
|
176 |
margin: 0;
|
177 |
border-radius: 0; /* Sera ajusté pour first/last child */
|
178 |
+
overflow-x: auto; /* Permet le scroll horizontal si contenu trop large */
|
179 |
background-color: #f9f9f9; /* Fond par défaut */
|
180 |
border-bottom: 1px solid #eee; /* Séparateur par défaut */
|
181 |
/* Ajout pour débordement horizontal */
|
|
|
183 |
overflow-wrap: break-word; /* Standard CSS */
|
184 |
}
|
185 |
|
|
|
186 |
.code-section {
|
187 |
background-color: transparent !important; /* Le fond est géré par header/content */
|
188 |
padding: 0 !important;
|
|
|
193 |
background-color: var(--output-bg) !important;
|
194 |
border-left: 4px solid var(--secondary-color); /* Distinction visuelle */
|
195 |
padding-left: calc(20px - 4px);
|
|
|
|
|
|
|
|
|
196 |
}
|
197 |
|
198 |
.step-section {
|
199 |
background-color: #ffffff; /* Fond blanc pour les étapes normales */
|
200 |
border-left: 4px solid var(--primary-color);
|
201 |
padding-left: calc(20px - 4px); /* Compenser la bordure */
|
202 |
+
font-size: 16px;
|
203 |
line-height: 1.8;
|
204 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
205 |
|
206 |
/* --- Amélioration du rendu KaTeX --- */
|
207 |
.step-section .katex {
|
208 |
font-size: 1.1em; /* Légèrement plus grand que le texte normal */
|
209 |
line-height: normal; /* Laisser KaTeX gérer */
|
210 |
text-indent: 0;
|
211 |
+
/* text-rendering: optimizeLegibility; */ /* Peut améliorer mais coûteux */
|
212 |
+
color: #333; /* Assurer la couleur du texte */
|
213 |
+
/* display: inline-block; */ /* Peut causer des problèmes de wrapping */
|
214 |
+
/* white-space: normal; */ /* Assurer le retour à la ligne si nécessaire */
|
215 |
}
|
216 |
|
217 |
.step-section .katex-display {
|
218 |
display: block; /* Comportement block pour $$...$$ */
|
219 |
text-align: center; /* Centrer les équations display */
|
220 |
+
margin: 1em 0; /* Espacement vertical */
|
221 |
overflow-x: auto; /* Permet le scroll horizontal si équation trop large */
|
222 |
overflow-y: hidden;
|
223 |
+
padding: 0.5em 0.2em; /* Un peu d'espace interne, même horizontal */
|
224 |
+
background-color: #fdfdfd; /* Fond très légèrement différent pour les blocs */
|
225 |
+
border-radius: 4px;
|
|
|
226 |
}
|
227 |
/* Conteneur interne créé par KaTeX */
|
228 |
.step-section .katex-display > .katex {
|
229 |
display: inline-block; /* Permet le centrage et évite la pleine largeur */
|
230 |
text-align: initial; /* Texte de l'équation aligné à gauche par défaut */
|
231 |
max-width: 100%;
|
232 |
+
/* white-space: nowrap; */ /* Empêche le retour à la ligne DANS l'équation, utiliser scroll */
|
233 |
}
|
234 |
/* --- Fin Amélioration KaTeX --- */
|
235 |
|
|
|
236 |
.code-header {
|
237 |
background-color: #343a40;
|
238 |
color: white;
|
239 |
padding: 10px 15px;
|
240 |
+
font-size: 14px;
|
241 |
font-family: 'Courier New', monospace;
|
242 |
display: flex;
|
243 |
justify-content: space-between;
|
|
|
251 |
color: #e6e6e6;
|
252 |
overflow-x: auto;
|
253 |
font-family: 'Courier New', monospace;
|
254 |
+
font-size: 14px;
|
255 |
line-height: 1.5;
|
256 |
border-bottom: 1px solid #444; /* Bordure sous le code */
|
257 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
|
259 |
+
/* --- Règles pour les coins arrondis (simplifié) --- */
|
260 |
+
#solution > section:first-child,
|
261 |
+
#solution > section.code-section:first-child .code-header {
|
262 |
border-top-left-radius: 8px;
|
263 |
border-top-right-radius: 8px;
|
264 |
}
|
265 |
|
266 |
+
#solution > section:last-child,
|
267 |
+
#solution > section.code-section:last-child .code-content {
|
268 |
border-bottom-left-radius: 8px;
|
269 |
border-bottom-right-radius: 8px;
|
270 |
border-bottom: none; /* Pas de bordure en bas du dernier élément */
|
271 |
}
|
272 |
|
273 |
+
#solution > section:only-child {
|
274 |
border-radius: 8px;
|
275 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
276 |
|
277 |
+
/* --- Indicateurs --- */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
278 |
.thinking-indicator, .executing-indicator, .answering-indicator {
|
279 |
display: flex;
|
280 |
align-items: center;
|
|
|
286 |
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
287 |
max-width: 400px; /* Limiter la largeur */
|
288 |
}
|
|
|
289 |
.thinking-indicator { background-color: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb; }
|
290 |
.executing-indicator { background-color: #ede7f6; color: #5e35b1; border: 1px solid #d1c4e9; }
|
291 |
.answering-indicator { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; }
|
|
|
295 |
animation: pulse 1.5s infinite ease-in-out;
|
296 |
font-size: 1.1rem;
|
297 |
}
|
|
|
|
|
298 |
@keyframes pulse {
|
299 |
0% { opacity: 0.6; transform: scale(1); }
|
300 |
50% { opacity: 1; transform: scale(1.05); }
|
301 |
100% { opacity: 0.6; transform: scale(1); }
|
302 |
}
|
303 |
|
304 |
+
/* --- Classe pour les erreurs --- */
|
305 |
+
.error-message {
|
306 |
+
background-color: #fff0f0 !important;
|
307 |
+
color: red !important;
|
308 |
+
border-color: red !important;
|
309 |
+
font-weight: bold;
|
|
|
310 |
}
|
311 |
+
.error-message code { /* Code dans les messages d'erreur */
|
312 |
+
background-color: #fdd;
|
313 |
+
color: #c00;
|
314 |
+
padding: 2px 4px;
|
315 |
+
border-radius: 3px;
|
316 |
+
font-family: monospace;
|
317 |
}
|
318 |
|
319 |
+
/* Styles spécifiques pour le contenu sans Markdown (peut nécessiter ajustement) */
|
320 |
+
.step-section p { /* Ajouter marge basique pour les paragraphes si pas de Markdown */
|
321 |
+
margin-bottom: 1em;
|
322 |
+
}
|
323 |
+
.step-section p:last-child {
|
324 |
+
margin-bottom: 0;
|
325 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
326 |
|
327 |
</style>
|
328 |
</head>
|
329 |
<body>
|
330 |
<div class="container">
|
331 |
<header>
|
332 |
+
<div class="logo">Math Solver</div>
|
333 |
+
<div class="subtitle">La solution intelligente pour vos problèmes mathématiques</div>
|
334 |
</header>
|
335 |
|
336 |
<div class="content-box">
|
337 |
+
<h1>Version Gratuite</h1>
|
338 |
+
<p>Vous utilisez actuellement la version gratuite de Math Solver qui vous permet de résoudre 3 problèmes par jour.</p>
|
339 |
+
|
340 |
+
<div class="feature-list">
|
341 |
+
<h2>Fonctionnalités disponibles :</h2>
|
342 |
+
<!-- Liste des fonctionnalités -->
|
343 |
+
<ul>
|
344 |
+
<li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques basiques</li>
|
345 |
+
<li><i class="fas fa-check-circle"></i> 3 résolutions gratuites par jour</li>
|
346 |
+
<li><i class="fas fa-check-circle"></i> Explication des étapes de résolution</li>
|
347 |
+
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Mode d'exécution de code avancé (version Pro)</span></li>
|
348 |
+
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Résolutions illimitées (version Pro)</span></li>
|
349 |
+
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Support prioritaire (version Pro)</span></li>
|
350 |
+
</ul>
|
351 |
+
</div>
|
352 |
+
|
353 |
+
<div class="upload-section">
|
354 |
<h2>Soumettez votre problème</h2>
|
355 |
+
<form id="imageForm" enctype="multipart/form-data">
|
356 |
+
<input type="file" id="imageInput" name="image" accept="image/*" style="display: none;">
|
357 |
+
<button type="button" id="uploadButton" class="cta-button" onclick="document.getElementById('imageInput').click()">
|
358 |
+
<i class="fas fa-upload"></i> Télécharger une image
|
359 |
+
</button>
|
360 |
<p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555; min-height: 1.2em;"></p>
|
361 |
+
</form>
|
362 |
|
363 |
+
<div id="imagePreview" style="display: none; margin: 20px auto; max-width: 400px; max-height: 300px; overflow: hidden; border: 1px solid #ddd; border-radius: 8px;">
|
364 |
+
<img id="preview" style="display: block; width: 100%; height: auto; border-radius: 8px;">
|
365 |
+
</div>
|
366 |
|
367 |
+
<button id="solveButton" class="cta-button" style="display: none; background-color: var(--secondary-color);">
|
368 |
+
<i class="fas fa-calculator"></i> Résoudre ce problème
|
369 |
+
</button>
|
370 |
+
</div>
|
371 |
|
372 |
<!-- Section pour afficher la solution -->
|
373 |
+
<div id="solutionOutput"> <!-- Initialement caché via CSS -->
|
374 |
+
<h3>Solution :</h3>
|
375 |
+
<div id="loadingIndicator" class="thinking-indicator" style="display: none;">
|
376 |
+
<i class="fas fa-brain indicator-icon"></i>
|
377 |
+
<span>Je réfléchis au problème...</span>
|
378 |
+
</div>
|
379 |
+
<!-- Container pour les blocs de contenu dynamiques -->
|
380 |
+
<div id="solution">
|
381 |
+
<!-- Le contenu (steps, code, output) sera ajouté ici par JS -->
|
382 |
+
</div>
|
383 |
+
</div>
|
384 |
+
|
385 |
+
<div class="upgrade-section">
|
386 |
+
<h2>Besoin de plus de puissance ?</h2>
|
387 |
+
<p>Passez à la version Pro pour des fonctionnalités avancées et des résolutions illimitées.</p>
|
388 |
+
<a href="#" class="cta-button">Passer à la version Pro</a>
|
389 |
+
</div>
|
390 |
+
</div>
|
391 |
+
|
392 |
+
<footer>
|
393 |
+
<p>© 2025 Math Solver. Tous droits réservés.</p>
|
394 |
+
</footer>
|
395 |
+
</div>
|
396 |
+
|
397 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
398 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
|
399 |
+
|
400 |
+
<!-- KaTeX Scripts -->
|
401 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js"></script>
|
402 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min.js"></script>
|
|
|
403 |
|
404 |
<script>
|
405 |
+
// Configuration de KaTeX (options globales)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
406 |
const katexOptions = {
|
407 |
delimiters: [
|
408 |
{left: '$$', right: '$$', display: true},
|
|
|
410 |
{left: '\\(', right: '\\)', display: false},
|
411 |
{left: '\\[', right: '\\]', display: true}
|
412 |
],
|
413 |
+
throwOnError: false, // Ne pas bloquer toute la page en cas d'erreur LaTeX
|
414 |
+
strict: (errorCode, errorMsg, token) => {
|
415 |
+
// Log plus détaillé des erreurs KaTeX pour le débogage
|
416 |
+
// console.warn(`KaTeX ${errorCode}: ${errorMsg}`, token);
|
417 |
if (errorCode === 'unicodeTextInMathMode') {
|
418 |
+
return 'ignore'; // Permet certains caractères unicode si besoin
|
419 |
}
|
420 |
+
return 'warn'; // Affiche les autres erreurs dans la console sans planter
|
|
|
|
|
|
|
|
|
|
|
421 |
},
|
422 |
+
// IMPORTANT: Pour autoriser le rendu dans différentes situations
|
423 |
+
// trust: true peut être nécessaire si le contenu vient d'une source
|
424 |
+
// non sûre et utilise des commandes spécifiques, mais essayons sans d'abord.
|
425 |
+
// trust: true,
|
426 |
+
output: 'htmlAndMathml' // Améliore l'accessibilité
|
427 |
};
|
428 |
|
429 |
+
// Fonction pour déclencher le rendu KaTeX sur un élément spécifique
|
430 |
+
// En utilisant setTimeout pour s'assurer que le DOM est mis à jour
|
431 |
+
function triggerKatexRender(element) {
|
432 |
+
// Utiliser setTimeout pour décaler légèrement l'exécution
|
433 |
+
setTimeout(() => {
|
434 |
+
try {
|
435 |
+
if (window.renderMathInElement) {
|
436 |
+
window.renderMathInElement(element, katexOptions);
|
437 |
+
} else {
|
438 |
+
console.error("renderMathInElement n'est pas disponible. Vérifiez le chargement de KaTeX auto-render.");
|
439 |
+
}
|
440 |
+
} catch (e) {
|
441 |
+
console.error('Erreur lors du rendu KaTeX sur élément:', e, element);
|
442 |
+
}
|
443 |
+
}, 0); // 0 ms de délai suffit généralement
|
444 |
+
}
|
445 |
+
|
446 |
+
|
447 |
document.getElementById('imageInput').addEventListener('change', function(event) {
|
448 |
const file = event.target.files[0];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
449 |
if (file) {
|
450 |
const reader = new FileReader();
|
451 |
reader.onload = function(e) {
|
452 |
+
document.getElementById('preview').src = e.target.result;
|
453 |
+
document.getElementById('imagePreview').style.display = 'block';
|
454 |
+
document.getElementById('solveButton').style.display = 'inline-block';
|
455 |
+
document.getElementById('uploadStatus').textContent = `Image sélectionnée : ${file.name}`;
|
456 |
+
document.getElementById('solutionOutput').style.display = 'none';
|
457 |
+
document.getElementById('solution').innerHTML = '';
|
|
|
|
|
458 |
}
|
459 |
reader.readAsDataURL(file);
|
|
|
|
|
|
|
|
|
|
|
460 |
}
|
461 |
});
|
462 |
|
|
|
463 |
document.getElementById('solveButton').addEventListener('click', function() {
|
464 |
const formData = new FormData(document.getElementById('imageForm'));
|
465 |
const solutionOutputDiv = document.getElementById('solutionOutput');
|
466 |
const loadingIndicator = document.getElementById('loadingIndicator');
|
467 |
const solutionContainer = document.getElementById('solution');
|
468 |
|
469 |
+
solutionOutputDiv.style.display = 'block';
|
470 |
+
loadingIndicator.style.display = 'flex';
|
471 |
+
solutionContainer.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
472 |
loadingIndicator.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
473 |
|
474 |
+
fetch('/solved', { // Endpoint Flask
|
475 |
method: 'POST',
|
476 |
body: formData
|
477 |
})
|
478 |
.then(response => {
|
479 |
+
if (!response.ok) {
|
480 |
+
return response.text().then(text => {
|
481 |
+
throw new Error(`Erreur serveur: ${response.status} ${response.statusText}\n${text || '(Aucun détail)'}`);
|
482 |
+
});
|
483 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
484 |
const reader = response.body.getReader();
|
485 |
const decoder = new TextDecoder();
|
486 |
let buffer = '';
|
|
|
|
|
|
|
|
|
487 |
|
488 |
function processStream({ done, value }) {
|
489 |
if (done) {
|
490 |
+
loadingIndicator.style.display = 'none';
|
491 |
+
// Passe finale de rendu KaTeX pour être sûr (peut-être redondant mais sûr)
|
492 |
+
triggerKatexRender(solutionContainer);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
493 |
return;
|
494 |
}
|
495 |
|
|
|
496 |
buffer += decoder.decode(value, { stream: true });
|
497 |
+
const messages = buffer.split(/\r?\n\r?\n/);
|
498 |
+
buffer = messages.pop();
|
499 |
|
500 |
+
messages.forEach(message => {
|
|
|
|
|
|
|
|
|
|
|
501 |
if (message.startsWith('data: ')) {
|
502 |
try {
|
503 |
+
const data = JSON.parse(message.substring(6));
|
504 |
|
505 |
// --- Gérer les mises à jour de mode ---
|
506 |
+
if (data.mode) {
|
507 |
+
const modes = {
|
508 |
+
thinking: { icon: 'fa-brain', text: 'Je réfléchis...', class: 'thinking-indicator' },
|
509 |
+
answering: { icon: 'fa-pencil-alt', text: 'Je rédige la réponse...', class: 'answering-indicator' },
|
510 |
+
executing_code: { icon: 'fa-play', text: 'Exécution du code...', class: 'executing-indicator' },
|
511 |
+
code_result: { icon: 'fa-terminal', text: 'Traitement des résultats...', class: 'executing-indicator' }
|
512 |
+
};
|
513 |
+
const modeInfo = modes[data.mode] || { icon: 'fa-sync-alt fa-spin', text: 'Traitement...', class: 'thinking-indicator' };
|
514 |
+
|
515 |
+
loadingIndicator.className = `${modeInfo.class}`;
|
516 |
+
loadingIndicator.innerHTML = `<i class="fas ${modeInfo.icon} indicator-icon"></i><span>${modeInfo.text}</span>`;
|
517 |
+
loadingIndicator.style.display = 'flex';
|
518 |
}
|
519 |
|
520 |
// --- Gérer les blocs de contenu ---
|
521 |
+
if (data.content !== undefined && data.content !== null) { // Vérifier existence et non-nullité
|
522 |
+
loadingIndicator.style.display = 'none';
|
523 |
+
const content = data.content;
|
524 |
+
|
525 |
+
// Créer un élément conteneur générique (section)
|
526 |
+
const blockElement = document.createElement('section');
|
527 |
|
528 |
+
// Analyser le fragment HTML reçu pour détecter le type de bloc
|
|
|
529 |
const tempDiv = document.createElement('div');
|
530 |
+
tempDiv.innerHTML = content;
|
531 |
+
|
532 |
const codeSection = tempDiv.querySelector('.code-section');
|
533 |
const outputSection = tempDiv.querySelector('.output-section');
|
534 |
|
|
|
|
|
535 |
if (codeSection) {
|
536 |
+
// C'est une section de code Python dédiée
|
537 |
+
// Remplacer le contenu de blockElement par codeSection
|
538 |
+
blockElement.outerHTML = codeSection.outerHTML;
|
539 |
+
// Il faut récupérer la référence au nouvel élément ajouté
|
540 |
+
const addedCodeSection = solutionContainer.appendChild(blockElement.cloneNode(true));
|
541 |
+
addedCodeSection.querySelectorAll('pre code').forEach((block) => {
|
542 |
+
hljs.highlightElement(block);
|
|
|
|
|
|
|
543 |
});
|
544 |
+
// Déclencher KaTeX au cas où il y aurait du LaTeX dans les commentaires du code (?)
|
545 |
+
// triggerKatexRender(addedCodeSection); // Probablement pas nécessaire pour le code pur
|
546 |
|
547 |
} else if (outputSection) {
|
548 |
+
// C'est une section de sortie de code
|
549 |
+
blockElement.outerHTML = outputSection.outerHTML;
|
550 |
+
const addedOutputSection = solutionContainer.appendChild(blockElement.cloneNode(true));
|
551 |
+
// Déclencher KaTeX si la sortie peut contenir du LaTeX
|
552 |
+
triggerKatexRender(addedOutputSection);
|
553 |
|
554 |
} else {
|
555 |
+
// C'est une section d'étape (texte + LaTeX)
|
556 |
+
// **Approche simplifiée : injecter le HTML brut**
|
557 |
+
blockElement.className = 'step-section'; // Appliquer la classe pour le style
|
558 |
+
blockElement.innerHTML = content; // Injecter directement
|
559 |
+
solutionContainer.appendChild(blockElement);
|
560 |
+
|
561 |
+
// **Déclencher le rendu KaTeX sur ce bloc spécifique**
|
562 |
+
triggerKatexRender(blockElement);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
563 |
}
|
564 |
+
}
|
565 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
566 |
|
567 |
+
// --- Gérer les erreurs spécifiques ---
|
568 |
if (data.error) {
|
569 |
+
const errorElement = document.createElement('section');
|
570 |
+
errorElement.className = 'step-section error-message'; // Utiliser classe d'erreur
|
571 |
+
// Utiliser innerHTML pour interpréter d'éventuelles balises simples (gras, etc.)
|
572 |
+
// ou juste textContent si l'erreur est toujours du texte brut.
|
573 |
+
errorElement.innerHTML = `<strong>Erreur :</strong> ${data.error || 'Une erreur inconnue est survenue.'}`;
|
574 |
+
solutionContainer.appendChild(errorElement);
|
575 |
+
loadingIndicator.style.display = 'none';
|
576 |
}
|
577 |
|
578 |
} catch (e) {
|
579 |
console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
|
580 |
+
const errorElement = document.createElement('section');
|
581 |
+
errorElement.className = 'step-section error-message';
|
582 |
+
errorElement.style.color = 'orange'; // Erreur de parsing client
|
583 |
+
errorElement.style.backgroundColor = '#fff8e1';
|
584 |
+
errorElement.style.borderColor = 'orange';
|
585 |
+
errorElement.textContent = `Erreur de traitement du message reçu: ${message.substring(0, 150)}... (Voir console pour détails)`;
|
586 |
+
solutionContainer.appendChild(errorElement);
|
587 |
loadingIndicator.style.display = 'none';
|
588 |
}
|
|
|
|
|
|
|
589 |
}
|
590 |
+
});
|
591 |
+
|
592 |
+
// Défiler vers le bas pour voir le nouveau contenu au fur et à mesure
|
593 |
+
solutionContainer.lastChild?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
594 |
|
|
|
|
|
|
|
595 |
|
596 |
// Continuer à lire le flux
|
597 |
+
return reader.read().then(processStream);
|
598 |
+
}
|
599 |
|
600 |
// Démarrer le traitement du flux
|
601 |
reader.read().then(processStream);
|
|
|
602 |
})
|
603 |
.catch(error => {
|
|
|
604 |
console.error('Erreur Fetch ou connexion:', error);
|
605 |
+
const errorElement = document.createElement('section');
|
606 |
+
errorElement.className = 'step-section error-message';
|
607 |
+
errorElement.innerHTML = `<strong>Erreur de connexion ou serveur :</strong> ${error.message || error}`;
|
608 |
+
solutionContainer.appendChild(errorElement);
|
609 |
+
loadingIndicator.style.display = 'none';
|
610 |
+
errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
611 |
});
|
612 |
});
|
613 |
|
614 |
+
// Premier rendu KaTeX au chargement (pour contenu statique éventuel)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
615 |
document.addEventListener('DOMContentLoaded', function() {
|
616 |
+
// Inutile de le faire sur tout le body si le contenu est dynamique
|
617 |
+
// Mais on peut le laisser au cas où il y aurait du LaTeX statique
|
618 |
+
// triggerKatexRender(document.body);
|
619 |
+
console.log("DOM chargé, KaTeX auto-render prêt.");
|
|
|
|
|
|
|
|
|
620 |
});
|
621 |
</script>
|
622 |
</body>
|