Mariam-cards / templates /index.html
Docfile's picture
Update templates/index.html
3557cac verified
raw
history blame
28.9 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Math Solver - Version Gratuite</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css" rel="stylesheet">
<!-- PAS BESOIN de marked.js pour cette approche -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.3.0/marked.min.js"></script> -->
<!-- CSS pour KaTeX -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.css">
<style>
/* --- Styles CSS (inchangés par rapport à la version précédente) --- */
:root {
--primary-color: #4a6fa5;
--secondary-color: #166088;
--accent-color: #4fc3f7;
--background-color: #f8f9fa;
--text-color: #333;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--code-bg: #2c323c;
--output-bg: #f1f8f9;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin: 0;
padding: 0;
background-color: var(--background-color);
color: var(--text-color);
}
.container {
max-width: 1000px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
padding: 20px 0;
margin-bottom: 30px;
}
.logo {
font-size: 2.5rem;
font-weight: bold;
color: var(--primary-color);
margin-bottom: 10px;
}
.subtitle {
font-size: 1.2rem;
color: var(--secondary-color);
margin-bottom: 20px;
}
.content-box {
background-color: white;
border-radius: 10px;
box-shadow: var(--box-shadow);
padding: 30px;
margin-bottom: 30px;
/* text-align: center; enlevé pour que le contenu dynamique ne soit pas centré par défaut */
}
/* Ajustement pour le contenu généré */
.content-box > h1, .content-box > p:not(#uploadStatus) {
text-align: center;
}
.content-box .feature-list {
text-align: left; /* Garder le texte des features aligné à gauche */
max-width: 600px;
margin: 30px auto;
}
.content-box .feature-list h2 {
text-align: center;
}
.content-box .upload-section {
text-align: center;
margin-top: 30px; /* Ajouter de l'espace avant */
}
.content-box .upgrade-section {
text-align: center;
margin-top: 30px;
padding: 20px;
border-top: 1px solid #ddd;
}
h1 {
color: var(--primary-color);
margin-top: 0;
}
.feature-list {
list-style-type: none;
padding: 0;
}
.feature-list li {
padding: 10px 0;
margin-bottom: 10px;
display: flex;
align-items: center;
}
.feature-list i {
color: var(--accent-color);
margin-right: 10px;
font-size: 1.2rem;
width: 20px; /* Donner une largeur fixe pour l'alignement */
text-align: center;
}
.feature-list .fa-times-circle {
color: #aaa; /* Griser l'icône aussi */
}
.cta-button {
display: inline-block;
background-color: var(--primary-color);
color: white;
padding: 12px 25px;
border-radius: 5px;
text-decoration: none;
font-weight: bold;
transition: all 0.3s ease;
margin: 10px; /* Ajustement marge */
border: none;
cursor: pointer;
}
.cta-button:hover {
background-color: var(--secondary-color);
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
footer {
text-align: center;
padding: 20px 0;
color: #666;
font-size: 0.9rem;
}
#solutionOutput {
margin-top: 30px;
text-align: left;
display: none; /* Initialement caché */
}
#solutionOutput h3 {
text-align: center; /* Centrer le titre "Solution :" */
color: var(--primary-color);
margin-bottom: 20px;
}
#solution {
background: transparent; /* Le fond sera sur les divs enfants */
padding: 0;
border-radius: 8px;
text-align: left;
line-height: 1.8;
font-size: 16px;
box-shadow: var(--box-shadow);
overflow: hidden; /* Pour que les border-radius s'appliquent aux enfants */
margin-top: 20px; /* Espace entre indicateur et contenu */
}
/* Style de base pour tous les blocs dans #solution */
/* Utiliser 'section' pour les blocs pour plus de sémantique */
#solution > section {
padding: 20px;
margin: 0;
border-radius: 0; /* Sera ajusté pour first/last child */
overflow-x: auto; /* Permet le scroll horizontal si contenu trop large */
background-color: #f9f9f9; /* Fond par défaut */
border-bottom: 1px solid #eee; /* Séparateur par défaut */
/* Ajout pour débordement horizontal */
word-wrap: break-word; /* Pour casser les mots longs si nécessaire */
overflow-wrap: break-word; /* Standard CSS */
}
.code-section {
background-color: transparent !important; /* Le fond est géré par header/content */
padding: 0 !important;
border-bottom: none !important; /* Le content aura la bordure */
}
.output-section {
background-color: var(--output-bg) !important;
border-left: 4px solid var(--secondary-color); /* Distinction visuelle */
padding-left: calc(20px - 4px);
}
.step-section {
background-color: #ffffff; /* Fond blanc pour les étapes normales */
border-left: 4px solid var(--primary-color);
padding-left: calc(20px - 4px); /* Compenser la bordure */
font-size: 16px;
line-height: 1.8;
}
/* --- Amélioration du rendu KaTeX --- */
.step-section .katex {
font-size: 1.1em; /* Légèrement plus grand que le texte normal */
line-height: normal; /* Laisser KaTeX gérer */
text-indent: 0;
/* text-rendering: optimizeLegibility; */ /* Peut améliorer mais coûteux */
color: #333; /* Assurer la couleur du texte */
/* display: inline-block; */ /* Peut causer des problèmes de wrapping */
/* white-space: normal; */ /* Assurer le retour à la ligne si nécessaire */
}
.step-section .katex-display {
display: block; /* Comportement block pour $$...$$ */
text-align: center; /* Centrer les équations display */
margin: 1em 0; /* Espacement vertical */
overflow-x: auto; /* Permet le scroll horizontal si équation trop large */
overflow-y: hidden;
padding: 0.5em 0.2em; /* Un peu d'espace interne, même horizontal */
background-color: #fdfdfd; /* Fond très légèrement différent pour les blocs */
border-radius: 4px;
}
/* Conteneur interne créé par KaTeX */
.step-section .katex-display > .katex {
display: inline-block; /* Permet le centrage et évite la pleine largeur */
text-align: initial; /* Texte de l'équation aligné à gauche par défaut */
max-width: 100%;
/* white-space: nowrap; */ /* Empêche le retour à la ligne DANS l'équation, utiliser scroll */
}
/* --- Fin Amélioration KaTeX --- */
.code-header {
background-color: #343a40;
color: white;
padding: 10px 15px;
font-size: 14px;
font-family: 'Courier New', monospace;
display: flex;
justify-content: space-between;
align-items: center;
}
.code-content {
margin: 0;
padding: 15px;
background-color: var(--code-bg);
color: #e6e6e6;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
border-bottom: 1px solid #444; /* Bordure sous le code */
}
/* --- Règles pour les coins arrondis (simplifié) --- */
#solution > section:first-child,
#solution > section.code-section:first-child .code-header {
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
#solution > section:last-child,
#solution > section.code-section:last-child .code-content {
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
border-bottom: none; /* Pas de bordure en bas du dernier élément */
}
#solution > section:only-child {
border-radius: 8px;
}
/* --- Indicateurs --- */
.thinking-indicator, .executing-indicator, .answering-indicator {
display: flex;
align-items: center;
justify-content: center; /* Centrer le contenu */
padding: 12px 15px;
margin: 0 auto 20px auto; /* Centrer l'indicateur lui-même */
border-radius: 8px;
font-size: 0.95rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
max-width: 400px; /* Limiter la largeur */
}
.thinking-indicator { background-color: #e3f2fd; color: #1565c0; border: 1px solid #bbdefb; }
.executing-indicator { background-color: #ede7f6; color: #5e35b1; border: 1px solid #d1c4e9; }
.answering-indicator { background-color: #e8f5e9; color: #2e7d32; border: 1px solid #c8e6c9; }
.indicator-icon {
margin-right: 12px;
animation: pulse 1.5s infinite ease-in-out;
font-size: 1.1rem;
}
@keyframes pulse {
0% { opacity: 0.6; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
100% { opacity: 0.6; transform: scale(1); }
}
/* --- Classe pour les erreurs --- */
.error-message {
background-color: #fff0f0 !important;
color: red !important;
border-color: red !important;
font-weight: bold;
}
.error-message code { /* Code dans les messages d'erreur */
background-color: #fdd;
color: #c00;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
}
/* Styles spécifiques pour le contenu sans Markdown (peut nécessiter ajustement) */
.step-section p { /* Ajouter marge basique pour les paragraphes si pas de Markdown */
margin-bottom: 1em;
}
.step-section p:last-child {
margin-bottom: 0;
}
</style>
</head>
<body>
<div class="container">
<header>
<div class="logo">Math Solver</div>
<div class="subtitle">La solution intelligente pour vos problèmes mathématiques</div>
</header>
<div class="content-box">
<h1>Version Gratuite</h1>
<p>Vous utilisez actuellement la version gratuite de Math Solver qui vous permet de résoudre 3 problèmes par jour.</p>
<div class="feature-list">
<h2>Fonctionnalités disponibles :</h2>
<!-- Liste des fonctionnalités -->
<ul>
<li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques basiques</li>
<li><i class="fas fa-check-circle"></i> 3 résolutions gratuites par jour</li>
<li><i class="fas fa-check-circle"></i> Explication des étapes de résolution</li>
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Mode d'exécution de code avancé (version Pro)</span></li>
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Résolutions illimitées (version Pro)</span></li>
<li><i class="fas fa-times-circle"></i> <span style="color: #999;">Support prioritaire (version Pro)</span></li>
</ul>
</div>
<div class="upload-section">
<h2>Soumettez votre problème</h2>
<form id="imageForm" enctype="multipart/form-data">
<input type="file" id="imageInput" name="image" accept="image/*" style="display: none;">
<button type="button" id="uploadButton" class="cta-button" onclick="document.getElementById('imageInput').click()">
<i class="fas fa-upload"></i> Télécharger une image
</button>
<p id="uploadStatus" style="margin-top: 10px; font-size: 0.9em; color: #555; min-height: 1.2em;"></p>
</form>
<div id="imagePreview" style="display: none; margin: 20px auto; max-width: 400px; max-height: 300px; overflow: hidden; border: 1px solid #ddd; border-radius: 8px;">
<img id="preview" style="display: block; width: 100%; height: auto; border-radius: 8px;">
</div>
<button id="solveButton" class="cta-button" style="display: none; background-color: var(--secondary-color);">
<i class="fas fa-calculator"></i> Résoudre ce problème
</button>
</div>
<!-- Section pour afficher la solution -->
<div id="solutionOutput"> <!-- Initialement caché via CSS -->
<h3>Solution :</h3>
<div id="loadingIndicator" class="thinking-indicator" style="display: none;">
<i class="fas fa-brain indicator-icon"></i>
<span>Je réfléchis au problème...</span>
</div>
<!-- Container pour les blocs de contenu dynamiques -->
<div id="solution">
<!-- Le contenu (steps, code, output) sera ajouté ici par JS -->
</div>
</div>
<div class="upgrade-section">
<h2>Besoin de plus de puissance ?</h2>
<p>Passez à la version Pro pour des fonctionnalités avancées et des résolutions illimitées.</p>
<a href="#" class="cta-button">Passer à la version Pro</a>
</div>
</div>
<footer>
<p>© 2025 Math Solver. Tous droits réservés.</p>
</footer>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/python.min.js"></script>
<!-- KaTeX Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/katex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.8/contrib/auto-render.min.js"></script>
<script>
// Configuration de KaTeX (options globales)
const katexOptions = {
delimiters: [
{left: '$$', right: '$$', display: true},
{left: '$', right: '$', display: false},
{left: '\\(', right: '\\)', display: false},
{left: '\\[', right: '\\]', display: true}
],
throwOnError: false, // Ne pas bloquer toute la page en cas d'erreur LaTeX
strict: (errorCode, errorMsg, token) => {
// Log plus détaillé des erreurs KaTeX pour le débogage
// console.warn(`KaTeX ${errorCode}: ${errorMsg}`, token);
if (errorCode === 'unicodeTextInMathMode') {
return 'ignore'; // Permet certains caractères unicode si besoin
}
return 'warn'; // Affiche les autres erreurs dans la console sans planter
},
// IMPORTANT: Pour autoriser le rendu dans différentes situations
// trust: true peut être nécessaire si le contenu vient d'une source
// non sûre et utilise des commandes spécifiques, mais essayons sans d'abord.
// trust: true,
output: 'htmlAndMathml' // Améliore l'accessibilité
};
// Fonction pour déclencher le rendu KaTeX sur un élément spécifique
// En utilisant setTimeout pour s'assurer que le DOM est mis à jour
function triggerKatexRender(element) {
// Utiliser setTimeout pour décaler légèrement l'exécution
setTimeout(() => {
try {
if (window.renderMathInElement) {
window.renderMathInElement(element, katexOptions);
} else {
console.error("renderMathInElement n'est pas disponible. Vérifiez le chargement de KaTeX auto-render.");
}
} catch (e) {
console.error('Erreur lors du rendu KaTeX sur élément:', e, element);
}
}, 0); // 0 ms de délai suffit généralement
}
document.getElementById('imageInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = function(e) {
document.getElementById('preview').src = e.target.result;
document.getElementById('imagePreview').style.display = 'block';
document.getElementById('solveButton').style.display = 'inline-block';
document.getElementById('uploadStatus').textContent = `Image sélectionnée : ${file.name}`;
document.getElementById('solutionOutput').style.display = 'none';
document.getElementById('solution').innerHTML = '';
}
reader.readAsDataURL(file);
}
});
document.getElementById('solveButton').addEventListener('click', function() {
const formData = new FormData(document.getElementById('imageForm'));
const solutionOutputDiv = document.getElementById('solutionOutput');
const loadingIndicator = document.getElementById('loadingIndicator');
const solutionContainer = document.getElementById('solution');
solutionOutputDiv.style.display = 'block';
loadingIndicator.style.display = 'flex';
solutionContainer.innerHTML = '';
loadingIndicator.scrollIntoView({ behavior: 'smooth', block: 'center' });
fetch('/solved', { // Endpoint Flask
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
return response.text().then(text => {
throw new Error(`Erreur serveur: ${response.status} ${response.statusText}\n${text || '(Aucun détail)'}`);
});
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function processStream({ done, value }) {
if (done) {
loadingIndicator.style.display = 'none';
// Passe finale de rendu KaTeX pour être sûr (peut-être redondant mais sûr)
triggerKatexRender(solutionContainer);
return;
}
buffer += decoder.decode(value, { stream: true });
const messages = buffer.split(/\r?\n\r?\n/);
buffer = messages.pop();
messages.forEach(message => {
if (message.startsWith('data: ')) {
try {
const data = JSON.parse(message.substring(6));
// --- Gérer les mises à jour de mode ---
if (data.mode) {
const modes = {
thinking: { icon: 'fa-brain', text: 'Je réfléchis...', class: 'thinking-indicator' },
answering: { icon: 'fa-pencil-alt', text: 'Je rédige la réponse...', class: 'answering-indicator' },
executing_code: { icon: 'fa-play', text: 'Exécution du code...', class: 'executing-indicator' },
code_result: { icon: 'fa-terminal', text: 'Traitement des résultats...', class: 'executing-indicator' }
};
const modeInfo = modes[data.mode] || { icon: 'fa-sync-alt fa-spin', text: 'Traitement...', class: 'thinking-indicator' };
loadingIndicator.className = `${modeInfo.class}`;
loadingIndicator.innerHTML = `<i class="fas ${modeInfo.icon} indicator-icon"></i><span>${modeInfo.text}</span>`;
loadingIndicator.style.display = 'flex';
}
// --- Gérer les blocs de contenu ---
if (data.content !== undefined && data.content !== null) { // Vérifier existence et non-nullité
loadingIndicator.style.display = 'none';
const content = data.content;
// Créer un élément conteneur générique (section)
const blockElement = document.createElement('section');
// Analyser le fragment HTML reçu pour détecter le type de bloc
const tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
const codeSection = tempDiv.querySelector('.code-section');
const outputSection = tempDiv.querySelector('.output-section');
if (codeSection) {
// C'est une section de code Python dédiée
// Remplacer le contenu de blockElement par codeSection
blockElement.outerHTML = codeSection.outerHTML;
// Il faut récupérer la référence au nouvel élément ajouté
const addedCodeSection = solutionContainer.appendChild(blockElement.cloneNode(true));
addedCodeSection.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
// Déclencher KaTeX au cas où il y aurait du LaTeX dans les commentaires du code (?)
// triggerKatexRender(addedCodeSection); // Probablement pas nécessaire pour le code pur
} else if (outputSection) {
// C'est une section de sortie de code
blockElement.outerHTML = outputSection.outerHTML;
const addedOutputSection = solutionContainer.appendChild(blockElement.cloneNode(true));
// Déclencher KaTeX si la sortie peut contenir du LaTeX
triggerKatexRender(addedOutputSection);
} else {
// C'est une section d'étape (texte + LaTeX)
// **Approche simplifiée : injecter le HTML brut**
blockElement.className = 'step-section'; // Appliquer la classe pour le style
blockElement.innerHTML = content; // Injecter directement
solutionContainer.appendChild(blockElement);
// **Déclencher le rendu KaTeX sur ce bloc spécifique**
triggerKatexRender(blockElement);
}
}
// --- Gérer les erreurs spécifiques ---
if (data.error) {
const errorElement = document.createElement('section');
errorElement.className = 'step-section error-message'; // Utiliser classe d'erreur
// Utiliser innerHTML pour interpréter d'éventuelles balises simples (gras, etc.)
// ou juste textContent si l'erreur est toujours du texte brut.
errorElement.innerHTML = `<strong>Erreur :</strong> ${data.error || 'Une erreur inconnue est survenue.'}`;
solutionContainer.appendChild(errorElement);
loadingIndicator.style.display = 'none';
}
} catch (e) {
console.error('Erreur analyse JSON ou traitement message SSE:', e, message);
const errorElement = document.createElement('section');
errorElement.className = 'step-section error-message';
errorElement.style.color = 'orange'; // Erreur de parsing client
errorElement.style.backgroundColor = '#fff8e1';
errorElement.style.borderColor = 'orange';
errorElement.textContent = `Erreur de traitement du message reçu: ${message.substring(0, 150)}... (Voir console pour détails)`;
solutionContainer.appendChild(errorElement);
loadingIndicator.style.display = 'none';
}
}
});
// Défiler vers le bas pour voir le nouveau contenu au fur et à mesure
solutionContainer.lastChild?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
// Continuer à lire le flux
return reader.read().then(processStream);
}
// Démarrer le traitement du flux
reader.read().then(processStream);
})
.catch(error => {
console.error('Erreur Fetch ou connexion:', error);
const errorElement = document.createElement('section');
errorElement.className = 'step-section error-message';
errorElement.innerHTML = `<strong>Erreur de connexion ou serveur :</strong> ${error.message || error}`;
solutionContainer.appendChild(errorElement);
loadingIndicator.style.display = 'none';
errorElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
});
// Premier rendu KaTeX au chargement (pour contenu statique éventuel)
document.addEventListener('DOMContentLoaded', function() {
// Inutile de le faire sur tout le body si le contenu est dynamique
// Mais on peut le laisser au cas où il y aurait du LaTeX statique
// triggerKatexRender(document.body);
console.log("DOM chargé, KaTeX auto-render prêt.");
});
</script>
</body>
</html>