Spaces:
Running
Running
Update templates/index.html
Browse files- templates/index.html +373 -147
templates/index.html
CHANGED
@@ -62,11 +62,9 @@
|
|
62 |
box-shadow: var(--box-shadow);
|
63 |
padding: 30px;
|
64 |
margin-bottom: 30px;
|
65 |
-
/* text-align: center; enlevé pour que le contenu dynamique ne soit pas centré par défaut */
|
66 |
}
|
67 |
|
68 |
-
/*
|
69 |
-
.content-box > h1, .content-box > p {
|
70 |
text-align: center;
|
71 |
}
|
72 |
.content-box .feature-list h2 {
|
@@ -75,6 +73,10 @@
|
|
75 |
.content-box .upload-section {
|
76 |
text-align: center;
|
77 |
}
|
|
|
|
|
|
|
|
|
78 |
.content-box .upgrade-section {
|
79 |
text-align: center;
|
80 |
margin-top: 30px;
|
@@ -126,6 +128,7 @@
|
|
126 |
margin: 10px; /* Ajustement marge */
|
127 |
border: none;
|
128 |
cursor: pointer;
|
|
|
129 |
}
|
130 |
|
131 |
.cta-button:hover {
|
@@ -178,6 +181,7 @@
|
|
178 |
overflow-wrap: break-word; /* Standard CSS */
|
179 |
}
|
180 |
|
|
|
181 |
.code-section {
|
182 |
background-color: transparent !important; /* Le fond est géré par header/content */
|
183 |
padding: 0 !important;
|
@@ -188,15 +192,35 @@
|
|
188 |
background-color: var(--output-bg) !important;
|
189 |
border-left: 4px solid var(--secondary-color); /* Distinction visuelle */
|
190 |
padding-left: calc(20px - 4px);
|
|
|
|
|
|
|
|
|
191 |
}
|
192 |
|
193 |
.step-section {
|
194 |
background-color: #ffffff; /* Fond blanc pour les étapes normales */
|
195 |
border-left: 4px solid var(--primary-color);
|
196 |
padding-left: calc(20px - 4px); /* Compenser la bordure */
|
197 |
-
font-size:
|
198 |
line-height: 1.8;
|
199 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
|
201 |
/* --- Amélioration du rendu KaTeX --- */
|
202 |
.step-section .katex {
|
@@ -204,18 +228,22 @@
|
|
204 |
line-height: normal; /* Laisser KaTeX gérer */
|
205 |
text-indent: 0;
|
206 |
text-rendering: auto;
|
207 |
-
/*
|
|
|
208 |
}
|
209 |
|
210 |
.step-section .katex-display {
|
211 |
display: block; /* Comportement block pour $$...$$ */
|
212 |
text-align: center; /* Centrer les équations display */
|
213 |
-
margin:
|
214 |
overflow-x: auto; /* Permet le scroll horizontal si équation trop large */
|
215 |
overflow-y: hidden;
|
216 |
-
padding: 0.5em 0; /* Un peu d'espace interne */
|
|
|
|
|
|
|
217 |
}
|
218 |
-
/*
|
219 |
.step-section .katex-display > .katex {
|
220 |
display: inline-block; /* Permet le centrage et évite la pleine largeur */
|
221 |
text-align: initial; /* Texte de l'équation aligné à gauche par défaut */
|
@@ -223,11 +251,12 @@
|
|
223 |
}
|
224 |
/* --- Fin Amélioration KaTeX --- */
|
225 |
|
|
|
226 |
.code-header {
|
227 |
background-color: #343a40;
|
228 |
color: white;
|
229 |
padding: 10px 15px;
|
230 |
-
font-size:
|
231 |
font-family: 'Courier New', monospace;
|
232 |
display: flex;
|
233 |
justify-content: space-between;
|
@@ -241,12 +270,27 @@
|
|
241 |
color: #e6e6e6;
|
242 |
overflow-x: auto;
|
243 |
font-family: 'Courier New', monospace;
|
244 |
-
font-size:
|
245 |
line-height: 1.5;
|
246 |
border-bottom: 1px solid #444; /* Bordure sous le code */
|
247 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
248 |
|
249 |
-
/* --- Règles pour les coins arrondis --- */
|
250 |
#solution > *:first-child,
|
251 |
#solution > .code-section:first-child .code-header {
|
252 |
border-top-left-radius: 8px;
|
@@ -263,18 +307,32 @@
|
|
263 |
#solution > *:only-child {
|
264 |
border-radius: 8px;
|
265 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
|
267 |
/* --- Amélioration du rendu Markdown --- */
|
|
|
|
|
|
|
|
|
|
|
268 |
.step-section ul, .step-section ol {
|
269 |
margin-block-start: 1em;
|
270 |
margin-block-end: 1em;
|
271 |
padding-inline-start: 30px; /* Indentation standard */
|
272 |
}
|
273 |
-
|
274 |
.step-section ul li, .step-section ol li {
|
275 |
margin-bottom: 0.5em;
|
276 |
}
|
277 |
-
/* Meilleur espacement pour les listes imbriquées */
|
278 |
.step-section ul ul,
|
279 |
.step-section ol ol,
|
280 |
.step-section ul ol,
|
@@ -284,11 +342,14 @@
|
|
284 |
}
|
285 |
|
286 |
/* Amélioration des tables en Markdown */
|
|
|
|
|
|
|
|
|
287 |
.step-section table {
|
288 |
border-collapse: collapse;
|
289 |
-
width:
|
290 |
-
|
291 |
-
margin: 1em auto; /* Centrer les tableaux */
|
292 |
border: 1px solid #ccc; /* Bordure extérieure */
|
293 |
}
|
294 |
.step-section th,
|
@@ -305,31 +366,36 @@
|
|
305 |
background-color: #f9f9f9;
|
306 |
}
|
307 |
|
308 |
-
/*
|
309 |
.step-section pre {
|
310 |
-
background-color: #
|
311 |
border-radius: 4px;
|
312 |
padding: 12px;
|
313 |
overflow-x: auto;
|
314 |
margin: 1em 0;
|
315 |
border: 1px solid #ddd;
|
|
|
316 |
}
|
317 |
.step-section pre code { /* Style pour le code dans pre */
|
318 |
-
background-color: transparent;
|
319 |
padding: 0;
|
320 |
border-radius: 0;
|
321 |
font-size: 0.95em; /* Un peu plus petit */
|
|
|
322 |
}
|
323 |
/* Style pour `code` inline */
|
324 |
.step-section code:not(pre *) {
|
325 |
-
font-family: 'Courier New', Courier, monospace;
|
326 |
-
background-color: #
|
327 |
-
padding:
|
328 |
border-radius: 3px;
|
329 |
font-size: 0.9em;
|
330 |
-
color: #c7254e; /* Couleur pour code inline */
|
|
|
|
|
331 |
}
|
332 |
|
|
|
333 |
.thinking-indicator, .executing-indicator, .answering-indicator {
|
334 |
display: flex;
|
335 |
align-items: center;
|
@@ -351,6 +417,7 @@
|
|
351 |
animation: pulse 1.5s infinite ease-in-out;
|
352 |
font-size: 1.1rem;
|
353 |
}
|
|
|
354 |
|
355 |
@keyframes pulse {
|
356 |
0% { opacity: 0.6; transform: scale(1); }
|
@@ -358,11 +425,11 @@
|
|
358 |
100% { opacity: 0.6; transform: scale(1); }
|
359 |
}
|
360 |
|
361 |
-
/* Améliorations supplémentaires pour Markdown */
|
362 |
.step-section blockquote {
|
363 |
border-left: 4px solid #ccc; /* Gris standard */
|
364 |
-
margin:
|
365 |
-
padding: 0.
|
366 |
color: #555;
|
367 |
background-color: #f9f9f9;
|
368 |
}
|
@@ -372,7 +439,7 @@
|
|
372 |
|
373 |
.step-section hr {
|
374 |
border: none;
|
375 |
-
border-top:
|
376 |
margin: 2em 0;
|
377 |
}
|
378 |
|
@@ -382,19 +449,27 @@
|
|
382 |
.step-section h4,
|
383 |
.step-section h5,
|
384 |
.step-section h6 {
|
385 |
-
margin-top: 1.
|
386 |
margin-bottom: 0.8em;
|
387 |
color: var(--primary-color);
|
388 |
font-weight: 600; /* Un peu plus gras */
|
389 |
-
padding-bottom: 0.
|
390 |
border-bottom: 1px solid #eee; /* Souligner légèrement les titres */
|
|
|
391 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
392 |
.step-section h1 { font-size: 1.8em; border-bottom-width: 2px; }
|
393 |
.step-section h2 { font-size: 1.6em; }
|
394 |
.step-section h3 { font-size: 1.4em; }
|
395 |
.step-section h4 { font-size: 1.2em; }
|
396 |
-
.step-section h5 { font-size: 1.1em; }
|
397 |
-
.step-section h6 { font-size: 1.0em; color: #666; }
|
398 |
|
399 |
</style>
|
400 |
</head>
|
@@ -412,9 +487,9 @@
|
|
412 |
<div class="feature-list">
|
413 |
<h2>Fonctionnalités disponibles :</h2>
|
414 |
<ul class="feature-list">
|
415 |
-
<li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques
|
416 |
<li><i class="fas fa-check-circle"></i> 3 résolutions gratuites par jour</li>
|
417 |
-
<li><i class="fas fa-check-circle"></i> Explication des étapes
|
418 |
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Mode d'exécution de code avancé (version Pro)</span></li>
|
419 |
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Résolutions illimitées (version Pro)</span></li>
|
420 |
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Support prioritaire (version Pro)</span></li>
|
@@ -428,7 +503,7 @@
|
|
428 |
<button type="button" id="uploadButton" class="cta-button" onclick="document.getElementById('imageInput').click()">
|
429 |
<i class="fas fa-upload"></i> Télécharger une image
|
430 |
</button>
|
431 |
-
<p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555;"></p>
|
432 |
</form>
|
433 |
|
434 |
<div id="imagePreview" style="display: none; margin: 20px auto; max-width: 400px; max-height: 300px; overflow: hidden; border: 1px solid #ddd; border-radius: 8px;">
|
@@ -444,8 +519,7 @@
|
|
444 |
<div id="solutionOutput"> <!-- Initialement caché via CSS -->
|
445 |
<h3>Solution :</h3>
|
446 |
<div id="loadingIndicator" class="thinking-indicator" style="display: none;">
|
447 |
-
|
448 |
-
<span>Je réfléchis au problème...</span>
|
449 |
</div>
|
450 |
<!-- Container pour les blocs de contenu dynamiques -->
|
451 |
<div id="solution">
|
@@ -465,30 +539,43 @@
|
|
465 |
</footer>
|
466 |
</div>
|
467 |
|
|
|
468 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
|
469 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
|
470 |
-
|
471 |
-
<!-- KaTeX Scripts -->
|
472 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js"></script>
|
473 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min.js"></script>
|
|
|
474 |
|
475 |
<script>
|
476 |
-
// Configuration
|
477 |
marked.setOptions({
|
478 |
gfm: true,
|
479 |
-
breaks:
|
|
|
480 |
smartLists: true,
|
481 |
-
smartypants:
|
482 |
xhtml: true,
|
483 |
-
|
484 |
-
// Utilise highlight.js pour la coloration
|
485 |
-
// (différent des blocs de code Python dédiés)
|
486 |
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
487 |
-
return hljs.highlight(code, { language }).value;
|
488 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
489 |
});
|
490 |
|
491 |
-
// Configuration
|
492 |
const katexOptions = {
|
493 |
delimiters: [
|
494 |
{left: '$$', right: '$$', display: true},
|
@@ -496,201 +583,340 @@
|
|
496 |
{left: '\\(', right: '\\)', display: false},
|
497 |
{left: '\\[', right: '\\]', display: true}
|
498 |
],
|
499 |
-
throwOnError: false, // Ne pas bloquer
|
500 |
-
// trust: true, // Activer seulement si besoin de commandes comme \htmlClass
|
501 |
strict: (errorCode) => {
|
502 |
-
//
|
503 |
if (errorCode === 'unicodeTextInMathMode') {
|
504 |
return 'ignore'; // Ou 'warn'
|
505 |
}
|
506 |
-
|
507 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
508 |
};
|
509 |
|
|
|
510 |
document.getElementById('imageInput').addEventListener('change', function(event) {
|
511 |
const file = event.target.files[0];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
512 |
if (file) {
|
513 |
const reader = new FileReader();
|
514 |
reader.onload = function(e) {
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
// Cacher l'ancienne solution
|
520 |
-
|
521 |
-
|
|
|
522 |
}
|
523 |
reader.readAsDataURL(file);
|
|
|
|
|
|
|
|
|
|
|
524 |
}
|
525 |
});
|
526 |
|
|
|
527 |
document.getElementById('solveButton').addEventListener('click', function() {
|
528 |
const formData = new FormData(document.getElementById('imageForm'));
|
529 |
const solutionOutputDiv = document.getElementById('solutionOutput');
|
530 |
const loadingIndicator = document.getElementById('loadingIndicator');
|
531 |
const solutionContainer = document.getElementById('solution');
|
532 |
|
|
|
|
|
|
|
|
|
|
|
|
|
533 |
solutionOutputDiv.style.display = 'block'; // Afficher la section solution
|
|
|
|
|
534 |
loadingIndicator.style.display = 'flex'; // Afficher l'indicateur de chargement
|
535 |
solutionContainer.innerHTML = ''; // Effacer la solution précédente
|
536 |
-
|
|
|
537 |
loadingIndicator.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
538 |
|
539 |
-
fetch('/solved', { // Endpoint Flask
|
540 |
method: 'POST',
|
541 |
body: formData
|
542 |
})
|
543 |
.then(response => {
|
544 |
-
if (!response.ok) {
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
549 |
const reader = response.body.getReader();
|
550 |
const decoder = new TextDecoder();
|
551 |
let buffer = '';
|
|
|
|
|
|
|
|
|
552 |
|
553 |
function processStream({ done, value }) {
|
554 |
if (done) {
|
555 |
-
|
556 |
-
|
|
|
|
|
|
|
|
|
|
|
557 |
try {
|
558 |
-
|
|
|
|
|
559 |
} catch (e) {
|
560 |
console.error('Erreur de rendu KaTeX final:', e);
|
561 |
}
|
562 |
return;
|
563 |
}
|
564 |
|
|
|
565 |
buffer += decoder.decode(value, { stream: true });
|
566 |
-
const messages = buffer.split(/\r?\n\r?\n/); // Sépare les messages SSE
|
567 |
-
buffer = messages.pop(); // Garde le dernier morceau potentiellement incomplet
|
568 |
|
569 |
-
messages
|
|
|
|
|
|
|
|
|
|
|
570 |
if (message.startsWith('data: ')) {
|
571 |
try {
|
572 |
-
const data = JSON.parse(message.substring(6)); //
|
573 |
|
574 |
// --- Gérer les mises à jour de mode ---
|
575 |
-
if (data.mode) {
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
executing_code: { icon: 'fa-play', text: 'Exécution du code...', class: 'executing-indicator' }, // Icône différente
|
580 |
-
code_result: { icon: 'fa-terminal', text: 'Traitement des résultats...', class: 'executing-indicator' }
|
581 |
-
};
|
582 |
-
const modeInfo = modes[data.mode] || { icon: 'fa-sync-alt fa-spin', text: 'Traitement...', class: 'thinking-indicator' }; // Fallback
|
583 |
-
|
584 |
-
loadingIndicator.className = `${modeInfo.class}`; // Assigner la classe CSS
|
585 |
-
loadingIndicator.innerHTML = `<i class="fas ${modeInfo.icon} indicator-icon"></i><span>${modeInfo.text}</span>`;
|
586 |
-
loadingIndicator.style.display = 'flex';
|
587 |
}
|
588 |
|
589 |
// --- Gérer les blocs de contenu ---
|
590 |
if (data.content) {
|
591 |
-
loadingIndicator.style.display = 'none'; // Cacher l'indicateur
|
592 |
-
const
|
593 |
-
const tempDiv = document.createElement('div');
|
594 |
-
tempDiv.innerHTML = content; // Analyser le fragment HTML reçu
|
595 |
|
|
|
|
|
|
|
|
|
596 |
const codeSection = tempDiv.querySelector('.code-section');
|
597 |
const outputSection = tempDiv.querySelector('.output-section');
|
598 |
-
|
|
|
599 |
|
600 |
if (codeSection) {
|
601 |
-
//
|
602 |
-
|
603 |
-
|
604 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
605 |
});
|
|
|
606 |
} else if (outputSection) {
|
607 |
-
//
|
608 |
-
|
|
|
|
|
609 |
} else {
|
610 |
-
//
|
611 |
-
// Créer un conteneur propre pour cette étape
|
612 |
const stepDiv = document.createElement('div');
|
613 |
-
stepDiv.className = 'step-section';
|
|
|
614 |
|
615 |
try {
|
616 |
-
|
617 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
618 |
stepDiv.innerHTML = htmlContent;
|
619 |
|
620 |
-
//
|
621 |
-
|
622 |
-
|
623 |
-
// 3. Rendre le LaTeX DANS ce div spécifique avec KaTeX
|
624 |
-
renderMathInElement(stepDiv, katexOptions);
|
625 |
|
626 |
} catch (e) {
|
627 |
-
console.error('Erreur pendant le traitement Markdown
|
628 |
-
// En cas d'erreur, afficher le contenu brut
|
629 |
-
stepDiv.innerHTML = `<p>Erreur de rendu :</p><pre>${
|
630 |
-
solutionContainer.appendChild(stepDiv);
|
631 |
}
|
632 |
}
|
633 |
-
}
|
634 |
|
635 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
636 |
if (data.error) {
|
637 |
-
|
638 |
-
|
639 |
-
errorDiv.className = 'step-section error-message'; // Réutiliser step-section avec une classe d'erreur
|
640 |
-
errorDiv.style.color = 'red';
|
641 |
-
errorDiv.style.backgroundColor = '#fff0f0';
|
642 |
-
errorDiv.style.borderColor = 'red';
|
643 |
-
errorDiv.innerHTML = `<strong>Erreur :</strong> ${marked.parse(data.error || 'Une erreur inconnue est survenue.')}`; // Permet le markdown dans l'erreur
|
644 |
-
solutionContainer.appendChild(errorDiv);
|
645 |
-
loadingIndicator.style.display = 'none';
|
646 |
}
|
647 |
|
648 |
} catch (e) {
|
649 |
console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
|
650 |
-
|
651 |
-
errorDiv.className = 'step-section error-message';
|
652 |
-
errorDiv.style.color = 'orange';
|
653 |
-
errorDiv.style.backgroundColor = '#fff8e1';
|
654 |
-
errorDiv.style.borderColor = 'orange';
|
655 |
-
errorDiv.textContent = `Erreur de traitement du message reçu: ${message.substring(0, 150)}...`;
|
656 |
-
solutionContainer.appendChild(errorDiv);
|
657 |
loadingIndicator.style.display = 'none';
|
658 |
}
|
|
|
|
|
|
|
659 |
}
|
660 |
-
});
|
661 |
-
|
662 |
-
// Défiler vers le bas pour voir le nouveau contenu au fur et à mesure
|
663 |
-
solutionContainer.lastChild?.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
664 |
|
|
|
|
|
|
|
665 |
|
666 |
// Continuer à lire le flux
|
667 |
-
|
668 |
-
}
|
669 |
|
670 |
// Démarrer le traitement du flux
|
671 |
reader.read().then(processStream);
|
|
|
672 |
})
|
673 |
.catch(error => {
|
|
|
674 |
console.error('Erreur Fetch ou connexion:', error);
|
675 |
-
|
676 |
-
|
677 |
-
errorDiv.style.color = 'red';
|
678 |
-
errorDiv.style.backgroundColor = '#fff0f0';
|
679 |
-
errorDiv.style.borderColor = 'red';
|
680 |
-
errorDiv.innerHTML = `<strong>Erreur de connexion ou serveur :</strong> ${error.message || error}`;
|
681 |
-
solutionContainer.appendChild(errorDiv);
|
682 |
-
loadingIndicator.style.display = 'none';
|
683 |
-
errorDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
684 |
});
|
685 |
});
|
686 |
|
687 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
688 |
document.addEventListener('DOMContentLoaded', function() {
|
|
|
689 |
try {
|
690 |
-
|
691 |
-
|
|
|
692 |
} catch (e) {
|
693 |
-
console.error('Erreur KaTeX initiale:', e)
|
694 |
}
|
695 |
});
|
696 |
</script>
|
|
|
62 |
box-shadow: var(--box-shadow);
|
63 |
padding: 30px;
|
64 |
margin-bottom: 30px;
|
|
|
65 |
}
|
66 |
|
67 |
+
.content-box > h1, .content-box > p:first-of-type { /* Cibler le premier paragraphe après h1 */
|
|
|
68 |
text-align: center;
|
69 |
}
|
70 |
.content-box .feature-list h2 {
|
|
|
73 |
.content-box .upload-section {
|
74 |
text-align: center;
|
75 |
}
|
76 |
+
.content-box .upload-section h2 {
|
77 |
+
color: var(--primary-color);
|
78 |
+
margin-bottom: 20px;
|
79 |
+
}
|
80 |
.content-box .upgrade-section {
|
81 |
text-align: center;
|
82 |
margin-top: 30px;
|
|
|
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 {
|
|
|
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 |
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: 1rem; /* Taille de base */
|
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 {
|
|
|
228 |
line-height: normal; /* Laisser KaTeX gérer */
|
229 |
text-indent: 0;
|
230 |
text-rendering: auto;
|
231 |
+
/* Eviter le saut de ligne au milieu d'une formule inline si possible */
|
232 |
+
white-space: nowrap;
|
233 |
}
|
234 |
|
235 |
.step-section .katex-display {
|
236 |
display: block; /* Comportement block pour $$...$$ */
|
237 |
text-align: center; /* Centrer les équations display */
|
238 |
+
margin: 1.2em 0; /* Espacement vertical un peu plus grand */
|
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, moins sur les côtés */
|
242 |
+
/* background-color: #fdfdfd; */ /* Optionnel : léger fond différent */
|
243 |
+
/* border: 1px solid #eee; */ /* Optionnel : bordure légère */
|
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 */
|
|
|
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: 0.9em; /* Plus petit */
|
260 |
font-family: 'Courier New', monospace;
|
261 |
display: flex;
|
262 |
justify-content: space-between;
|
|
|
270 |
color: #e6e6e6;
|
271 |
overflow-x: auto;
|
272 |
font-family: 'Courier New', monospace;
|
273 |
+
font-size: 0.9em; /* Plus petit */
|
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 |
+
/* --- Règles pour les coins arrondis et bordures --- */
|
294 |
#solution > *:first-child,
|
295 |
#solution > .code-section:first-child .code-header {
|
296 |
border-top-left-radius: 8px;
|
|
|
307 |
#solution > *:only-child {
|
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,
|
|
|
342 |
}
|
343 |
|
344 |
/* Amélioration des tables en Markdown */
|
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,
|
|
|
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;
|
|
|
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); }
|
|
|
425 |
100% { opacity: 0.6; transform: scale(1); }
|
426 |
}
|
427 |
|
428 |
+
/* --- Améliorations supplémentaires pour Markdown --- */
|
429 |
.step-section blockquote {
|
430 |
border-left: 4px solid #ccc; /* Gris standard */
|
431 |
+
margin: 1.5em 0; /* Plus de marge verticale */
|
432 |
+
padding: 0.8em 15px; /* Plus de padding */
|
433 |
color: #555;
|
434 |
background-color: #f9f9f9;
|
435 |
}
|
|
|
439 |
|
440 |
.step-section hr {
|
441 |
border: none;
|
442 |
+
border-top: 2px solid #eee; /* Ligne plus visible */
|
443 |
margin: 2em 0;
|
444 |
}
|
445 |
|
|
|
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>
|
|
|
487 |
<div class="feature-list">
|
488 |
<h2>Fonctionnalités disponibles :</h2>
|
489 |
<ul class="feature-list">
|
490 |
+
<li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques</li>
|
491 |
<li><i class="fas fa-check-circle"></i> 3 résolutions gratuites par jour</li>
|
492 |
+
<li><i class="fas fa-check-circle"></i> Explication détaillée des étapes</li>
|
493 |
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Mode d'exécution de code avancé (version Pro)</span></li>
|
494 |
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Résolutions illimitées (version Pro)</span></li>
|
495 |
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Support prioritaire (version Pro)</span></li>
|
|
|
503 |
<button type="button" id="uploadButton" class="cta-button" onclick="document.getElementById('imageInput').click()">
|
504 |
<i class="fas fa-upload"></i> Télécharger une image
|
505 |
</button>
|
506 |
+
<p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555; min-height: 1.2em;"></p>
|
507 |
</form>
|
508 |
|
509 |
<div id="imagePreview" style="display: none; margin: 20px auto; max-width: 400px; max-height: 300px; overflow: hidden; border: 1px solid #ddd; border-radius: 8px;">
|
|
|
519 |
<div id="solutionOutput"> <!-- Initialement caché via CSS -->
|
520 |
<h3>Solution :</h3>
|
521 |
<div id="loadingIndicator" class="thinking-indicator" style="display: none;">
|
522 |
+
<!-- Contenu mis à jour par JS -->
|
|
|
523 |
</div>
|
524 |
<!-- Container pour les blocs de contenu dynamiques -->
|
525 |
<div id="solution">
|
|
|
539 |
</footer>
|
540 |
</div>
|
541 |
|
542 |
+
<!-- JS Libraries -->
|
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 |
+
// --- Configuration Marked.js ---
|
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 |
{left: '\\(', right: '\\)', display: false},
|
584 |
{left: '\\[', right: '\\]', display: true}
|
585 |
],
|
586 |
+
throwOnError: false, // Ne pas bloquer en cas d'erreur LaTeX mineure
|
|
|
587 |
strict: (errorCode) => {
|
588 |
+
// Gérer ou ignorer certains avertissements KaTeX si nécessaire
|
589 |
if (errorCode === 'unicodeTextInMathMode') {
|
590 |
return 'ignore'; // Ou 'warn'
|
591 |
}
|
592 |
+
// 'warn' affiche l'erreur dans la console sans arrêter le rendu
|
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 |
+
// trust: (context) => context.command === '\\htmlClass' // Activer si besoin de commandes spécifiques
|
600 |
};
|
601 |
|
602 |
+
// --- Gestion Upload Image ---
|
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 |
+
previewImg.src = e.target.result;
|
616 |
+
imagePreviewDiv.style.display = 'block';
|
617 |
+
solveButton.style.display = 'inline-block';
|
618 |
+
uploadStatus.textContent = `Image sélectionnée : ${file.name}`;
|
619 |
+
// Cacher l'ancienne solution et l'indicateur
|
620 |
+
solutionOutputDiv.style.display = 'none';
|
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 |
+
// Vérifier s'il y a une image sélectionnée
|
641 |
+
if (!document.getElementById('imageInput').files[0]) {
|
642 |
+
alert("Veuillez d'abord sélectionner une image.");
|
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 (adapter si nécessaire)
|
656 |
method: 'POST',
|
657 |
body: formData
|
658 |
})
|
659 |
.then(response => {
|
660 |
+
if (!response.ok || !response.body) {
|
661 |
+
// Tenter de lire le message d'erreur du corps si disponible
|
662 |
+
return response.text().then(text => {
|
663 |
+
let errorMsg = `Erreur serveur: ${response.status} ${response.statusText}`;
|
664 |
+
if (text) {
|
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 |
+
// Le flux est terminé
|
690 |
+
if (solutionContainer.innerHTML === '') {
|
691 |
+
// Si rien n'a été affiché, montrer un message
|
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 |
+
// Traiter les messages complets dans le buffer (séparés par \n\n)
|
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)); // Extraire le JSON
|
718 |
|
719 |
// --- Gérer les mises à jour de mode ---
|
720 |
+
if (data.mode && data.mode !== currentMode) {
|
721 |
+
currentMode = data.mode;
|
722 |
+
updateLoadingIndicator(currentMode);
|
723 |
+
loadingIndicator.style.display = 'flex'; // Assurer qu'il est visible
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
724 |
}
|
725 |
|
726 |
// --- Gérer les blocs de contenu ---
|
727 |
if (data.content) {
|
728 |
+
loadingIndicator.style.display = 'none'; // Cacher l'indicateur dès qu'on reçoit du contenu
|
729 |
+
const rawContent = data.content;
|
|
|
|
|
730 |
|
731 |
+
// Détecter si c'est du code/output dédié ou un step normal
|
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 = rawContent; // Parser temporairement pour inspection
|
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 |
+
// --- Section Code Python Dédiée ---
|
742 |
+
elementToAppend = codeSection;
|
743 |
+
// Appliquer highlight.js sur les blocs de code DANS cette section
|
744 |
+
elementToAppend.querySelectorAll('pre code').forEach((block) => {
|
745 |
+
// Vérifier si hljs est chargé
|
746 |
+
if (window.hljs) {
|
747 |
+
hljs.highlightElement(block);
|
748 |
+
} else {
|
749 |
+
console.warn("highlight.js (hljs) non trouvé.");
|
750 |
+
}
|
751 |
});
|
752 |
+
|
753 |
} else if (outputSection) {
|
754 |
+
// --- Section Output de Code ---
|
755 |
+
elementToAppend = outputSection;
|
756 |
+
// Pas de traitement spécial nécessaire ici (styles CSS gèrent l'affichage)
|
757 |
+
|
758 |
} else {
|
759 |
+
// --- Section d'Étape (Markdown + LaTeX) ---
|
|
|
760 |
const stepDiv = document.createElement('div');
|
761 |
+
stepDiv.className = 'step-section';
|
762 |
+
elementToAppend = stepDiv;
|
763 |
|
764 |
try {
|
765 |
+
let processedMdContent = rawContent;
|
766 |
+
const latexPlaceholders = {};
|
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 envoyées par le serveur ---
|
843 |
if (data.error) {
|
844 |
+
displayError(`Erreur reçue du serveur: ${data.error}`, solutionContainer);
|
845 |
+
loadingIndicator.style.display = 'none'; // Cacher l'indicateur en cas d'erreur fatale
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
846 |
}
|
847 |
|
848 |
} catch (e) {
|
849 |
console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
|
850 |
+
displayError(`Erreur interne lors du traitement de la réponse: ${message.substring(0, 150)}...`, solutionContainer);
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
} // Fin processStream
|
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 |
+
displayError(`Erreur de communication avec le serveur: ${error.message || error}`, solutionContainer);
|
874 |
+
loadingIndicator.style.display = 'none'; // Cacher l'indicateur
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
875 |
});
|
876 |
});
|
877 |
|
878 |
+
// --- Fonction pour mettre à jour l'indicateur de chargement ---
|
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 |
+
// Optionnel : Rendre le LaTeX statique si présent dans la page initiale
|
914 |
try {
|
915 |
+
if (window.renderMathInElement) {
|
916 |
+
renderMathInElement(document.body, katexOptions);
|
917 |
+
}
|
918 |
} catch (e) {
|
919 |
+
console.error('Erreur KaTeX initiale sur le corps de la page:', e)
|
920 |
}
|
921 |
});
|
922 |
</script>
|