Spaces:
Running
Running
<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"> | |
<!-- Use a CDN that hosts the specific atom-one-dark theme --> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css"> | |
<style> | |
: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: #282c34; /* atom-one-dark background */ | |
--code-text: #abb2bf; /* atom-one-dark default text */ | |
--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; | |
/* Removed text-align: center; allowing content to align left */ | |
} | |
h1, h2, h3 { | |
color: var(--primary-color); | |
margin-top: 0; | |
text-align: center; /* Center headings specifically */ | |
} | |
h2 { | |
margin-top: 1.5em; /* Add space above h2 */ | |
} | |
.feature-list { | |
list-style-type: none; | |
padding: 0; | |
margin: 30px auto; /* Center list container */ | |
text-align: left; | |
max-width: 500px; /* Constrain width for better readability */ | |
} | |
.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; /* Ensure consistent icon alignment */ | |
text-align: center; | |
} | |
.feature-list i.fa-times-circle { | |
color: #aaa; /* Muted color for disabled features */ | |
} | |
.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: 20px 10px; | |
border: none; | |
cursor: pointer; | |
font-size: 1rem; | |
} | |
.cta-button:hover { | |
background-color: var(--secondary-color); | |
transform: translateY(-2px); | |
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); | |
} | |
.cta-button:disabled { | |
background-color: #ccc; | |
cursor: not-allowed; | |
transform: none; | |
box-shadow: none; | |
} | |
.upgrade-section { | |
margin-top: 40px; | |
padding-top: 20px; | |
border-top: 1px solid #ddd; | |
text-align: center; | |
} | |
footer { | |
text-align: center; | |
padding: 20px 0; | |
color: #666; | |
font-size: 0.9rem; | |
} | |
#solutionOutput { | |
margin-top: 30px; | |
text-align: left; /* Ensure solution text aligns left */ | |
} | |
#solution { | |
background: #fff; | |
padding: 20px; | |
border-radius: 8px; | |
border: 1px solid #eee; /* Add a subtle border */ | |
margin-top: 15px; | |
line-height: 1.8; | |
font-size: 16px; | |
overflow-wrap: break-word; /* Wrap long lines */ | |
white-space: pre-wrap; /* Preserve whitespace and newlines from response */ | |
} | |
/* Styling for Markdown code blocks generated by Python */ | |
#solution pre { | |
background-color: var(--code-bg); | |
color: var(--code-text); | |
padding: 15px; | |
border-radius: 5px; | |
overflow-x: auto; | |
margin: 15px 0; /* Add spacing around code blocks */ | |
box-shadow: inset 0 1px 3px rgba(0,0,0,0.2); | |
} | |
#solution pre code { | |
font-family: 'Courier New', Courier, monospace; | |
font-size: 14px; | |
line-height: 1.5; | |
white-space: pre; /* Ensure pre formatting is respected within code tag */ | |
background: none; /* Remove double background */ | |
padding: 0; /* Remove double padding */ | |
} | |
/* Styling for the execution result block (if Python wraps it) */ | |
#solution .output-section { /* Use if Python wraps result in <div class="output-section"> */ | |
background-color: var(--output-bg); | |
padding: 15px; | |
border-radius: 5px; | |
border: 1px solid #ddd; | |
color: #333; | |
font-family: 'Courier New', monospace; | |
font-size: 14px; | |
white-space: pre-wrap; | |
overflow-x: auto; | |
margin: 15px 0; | |
} | |
/* Styling for result block if using Markdown ``` only */ | |
#solution pre code:not(.language-python) { | |
/* Style non-Python code blocks (likely execution results) differently */ | |
background-color: var(--output-bg); | |
color: #333; | |
display: block; /* Make it block level for padding */ | |
padding: 10px; | |
border-radius: 4px; | |
border: 1px solid #eee; | |
} | |
/* Identify result block by preceding text */ | |
#solution strong:contains("Résultat d'exécution:") + pre { | |
/* Style the <pre> immediately following the specific strong tag */ | |
border: 1px dashed #ccc; | |
background-color: #fdfdfd; | |
} | |
#solution strong:contains("Résultat d'exécution:") + pre code { | |
/* Style the <code> inside that specific <pre> */ | |
background-color: transparent; /* No extra background */ | |
color: #222; | |
} | |
/* Removed step-section styling as structure is now simpler */ | |
/* Removed indicator styles (thinking, executing etc.) */ | |
.loading-indicator { | |
display: flex; | |
align-items: center; | |
justify-content: center; /* Center content */ | |
padding: 15px; | |
margin: 20px 0; | |
border-radius: 8px; | |
font-size: 1rem; | |
background-color: #e3f2fd; | |
color: #1565c0; | |
border: 1px solid #bbdefb; | |
} | |
.loading-indicator i { | |
margin-right: 10px; | |
animation: spin 1.5s linear infinite; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
/* MathJax specific styles for better overflow handling */ | |
.MathJax, mjx-container { | |
overflow-x: auto ; | |
overflow-y: hidden ; | |
max-width: 100% ; | |
min-width: 0 ; /* Prevent minimum width issues */ | |
padding: 5px 0; /* Add a bit of vertical padding */ | |
display: block ; /* Ensure it behaves like a block */ | |
text-align: left ; /* Align LaTeX left by default */ | |
} | |
mjx-container[display="true"] { | |
display: block ; | |
margin: 1em 0 ; /* Spacing for display math */ | |
text-align: center ; /* Center display math */ | |
} | |
@media (max-width: 768px) { | |
.container { | |
padding: 15px; | |
} | |
.content-box { | |
padding: 20px; | |
} | |
#solution pre { | |
padding: 10px; | |
} | |
#solution pre code { | |
font-size: 13px; | |
} | |
} | |
.upload-section { | |
text-align: center; /* Center upload elements */ | |
} | |
#imagePreview { | |
display: none; | |
margin: 20px auto; | |
max-width: 90%; /* Responsive max width */ | |
max-height: 400px; /* Limit preview height */ | |
} | |
#preview { | |
max-width: 100%; | |
max-height: 400px; /* Match container height */ | |
height: auto; /* Maintain aspect ratio */ | |
width: auto; /* Maintain aspect ratio */ | |
border-radius: 8px; | |
box-shadow: var(--box-shadow); | |
border: 1px solid #ddd; | |
} | |
#uploadStatus { | |
margin-top: 15px; | |
font-size: 0.9em; | |
color: #555; | |
} | |
.error-message { | |
color: red; | |
background-color: #ffeeee; | |
border: 1px solid red; | |
padding: 10px; | |
margin: 15px 0; | |
border-radius: 5px; | |
text-align: left; | |
} | |
</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 style="text-align: center;">Vous utilisez actuellement la version gratuite de Math Solver.</p> <!-- Centered paragraph --> | |
<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"> | |
<i class="fas fa-upload"></i> Télécharger une image | |
</button> | |
</form> | |
<p id="uploadStatus"></p> | |
<div id="imagePreview"> | |
<img id="preview" alt="Aperçu de l'image"> | |
</div> | |
<button id="solveButton" class="cta-button" style="display: none; background-color: var(--secondary-color);" disabled> | |
<i class="fas fa-calculator"></i> Résoudre ce problème | |
</button> | |
</div> | |
<!-- Solution Output Area --> | |
<div id="solutionOutput" style="display: none;"> <!-- Keep hidden initially --> | |
<hr style="margin: 30px 0;"> <!-- Separator --> | |
<h3>Solution :</h3> | |
<div id="loadingIndicator" class="loading-indicator" style="display: none;"> | |
<i class="fas fa-spinner"></i> <!-- Changed to spinner --> | |
<span>Résolution en cours...</span> | |
</div> | |
<div id="solution"></div> <!-- Solution content will appear here --> | |
<div id="errorContainer" class="error-message" style="display: none;"></div> <!-- Error messages --> | |
</div> | |
<div class="feature-list"> | |
<h2>Fonctionnalités :</h2> | |
<ul> <!-- Removed ul class --> | |
<li><i class="fas fa-check-circle"></i> Résolution de problèmes mathématiques</li> | |
<li><i class="fas fa-check-circle"></i> Explication des étapes (si fournies par l'IA)</li> | |
<li><i class="fas fa-check-circle"></i> Utilisation de LaTeX pour les formules</li> | |
<li><i class="fas fa-check-circle"></i> Exécution de code Python pour calculs</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="upgrade-section"> | |
<h2>Besoin de plus ?</h2> | |
<p>Passez à la version Pro pour des fonctionnalités avancées et des résolutions illimitées.</p> | |
<a href="/" class="cta-button">Découvrir la version Pro</a> <!-- Link to Pro version page --> | |
</div> | |
</div> | |
<footer> | |
<p>© 2025 Math Solver. Tous droits réservés.</p> | |
</footer> | |
</div> | |
<!-- Use latest highlight.js --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> | |
<!-- Add languages you expect, python is essential --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/python.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script> <!-- For shell output maybe --> | |
<!-- Use Marked.js for Markdown rendering --> | |
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> | |
<!-- MathJax Configuration --> | |
<script> | |
window.MathJax = { | |
tex: { | |
inlineMath: [['$', '$'], ['\\(', '\\)']], | |
displayMath: [['$$', '$$'], ['\\[', '\\]']], | |
processEscapes: true, | |
processEnvironments: true, | |
packages: {'[+]': ['ams', 'noerrors', 'physics', 'cancel', 'color', 'mhchem', 'mathtools']} // Load common packages | |
}, | |
options: { | |
enableMenu: false, // Disable right-click menu | |
ignoreHtmlClass: 'tex2jax_ignore', // Class to ignore | |
processHtmlClass: 'tex2jax_process', // Class to process (can add to #solution if needed) | |
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code'] // Skip these tags | |
}, | |
startup: { | |
// Function to run once MathJax is ready | |
ready: () => { | |
console.log('MathJax is ready'); | |
MathJax.startup.defaultReady(); | |
// You could potentially trigger an initial typeset if needed | |
// MathJax.startup.promise.then(() => { MathJax.typesetPromise(); }); | |
} | |
}, | |
loader: { | |
load: ['[tex]/ams', '[tex]/noerrors', '[tex]/physics', '[tex]/cancel', '[tex]/color', '[tex]/mhchem', '[tex]/mathtools'] // Ensure packages are loaded | |
}, | |
svg: { | |
fontCache: 'global' // Cache fonts for better performance | |
} | |
}; | |
</script> | |
<!-- Load MathJax AFTER configuration --> | |
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script> | |
<script> | |
// DOM Elements | |
const imageInput = document.getElementById('imageInput'); | |
const uploadButton = document.getElementById('uploadButton'); | |
const imageForm = document.getElementById('imageForm'); | |
const uploadStatus = document.getElementById('uploadStatus'); | |
const imagePreview = document.getElementById('imagePreview'); | |
const preview = document.getElementById('preview'); | |
const solveButton = document.getElementById('solveButton'); | |
const solutionOutput = document.getElementById('solutionOutput'); | |
const loadingIndicator = document.getElementById('loadingIndicator'); | |
const solutionDiv = document.getElementById('solution'); | |
const errorContainer = document.getElementById('errorContainer'); | |
// --- Event Listeners --- | |
// Trigger file input when upload button is clicked | |
uploadButton.addEventListener('click', () => imageInput.click()); | |
// Handle image selection | |
imageInput.addEventListener('change', function(event) { | |
const file = event.target.files[0]; | |
if (file) { | |
// Basic client-side validation (type and size) | |
if (!file.type.startsWith('image/')) { | |
uploadStatus.textContent = 'Erreur : Veuillez sélectionner un fichier image.'; | |
uploadStatus.style.color = 'red'; | |
imagePreview.style.display = 'none'; | |
solveButton.style.display = 'none'; | |
solveButton.disabled = true; | |
return; | |
} | |
// Optional: Size limit (e.g., 10MB) | |
const maxSize = 10 * 1024 * 1024; | |
if (file.size > maxSize) { | |
uploadStatus.textContent = 'Erreur : L\'image est trop grande (max 10MB).'; | |
uploadStatus.style.color = 'red'; | |
imagePreview.style.display = 'none'; | |
solveButton.style.display = 'none'; | |
solveButton.disabled = true; | |
return; | |
} | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
preview.src = e.target.result; | |
imagePreview.style.display = 'block'; | |
solveButton.style.display = 'inline-block'; | |
solveButton.disabled = false; // Enable solve button | |
uploadStatus.textContent = `Image sélectionnée : ${file.name}`; | |
uploadStatus.style.color = '#555'; // Reset color | |
// Clear previous solution/error | |
solutionOutput.style.display = 'none'; | |
solutionDiv.innerHTML = ''; | |
errorContainer.style.display = 'none'; | |
errorContainer.textContent = ''; | |
} | |
reader.onerror = function() { | |
uploadStatus.textContent = 'Erreur : Impossible de lire le fichier image.'; | |
uploadStatus.style.color = 'red'; | |
imagePreview.style.display = 'none'; | |
solveButton.style.display = 'none'; | |
solveButton.disabled = true; | |
} | |
reader.readAsDataURL(file); | |
} else { | |
// No file selected or selection cancelled | |
uploadStatus.textContent = ''; | |
imagePreview.style.display = 'none'; | |
solveButton.style.display = 'none'; | |
solveButton.disabled = true; | |
} | |
}); | |
// Handle form submission (clicking the solve button) | |
solveButton.addEventListener('click', function() { | |
const file = imageInput.files[0]; | |
if (!file) { | |
showError('Veuillez d\'abord sélectionner une image.'); | |
return; | |
} | |
const formData = new FormData(imageForm); | |
// Reset UI for new request | |
solutionOutput.style.display = 'block'; // Show the output area | |
loadingIndicator.style.display = 'flex'; // Show loading spinner | |
solutionDiv.innerHTML = ''; // Clear previous solution | |
errorContainer.style.display = 'none'; // Hide previous error | |
errorContainer.textContent = ''; | |
solveButton.disabled = true; // Disable button during processing | |
uploadButton.disabled = true; // Disable upload during processing | |
fetch('/solved', { // Ensure this matches your Flask route | |
method: 'POST', | |
body: formData | |
}) | |
.then(response => { | |
if (!response.ok) { | |
// Try to parse JSON error body from server | |
return response.json().then(errData => { | |
// Throw an error with the message from server JSON | |
throw new Error(errData.error || `Erreur HTTP ${response.status}`); | |
}).catch(() => { | |
// If JSON parsing fails, throw generic HTTP error | |
throw new Error(`Erreur HTTP ${response.status}: ${response.statusText}`); | |
}); | |
} | |
return response.json(); // Parse successful response as JSON | |
}) | |
.then(data => { | |
loadingIndicator.style.display = 'none'; // Hide loading indicator | |
if (data.error) { | |
// Handle errors returned in the JSON payload | |
showError(data.error); | |
} else if (data.solution) { | |
// Use Marked.js to render the Markdown solution from the server | |
// This handles ```python blocks, standard markdown, etc. | |
// Ensure Marked converts ```python to <pre><code class="language-python"> | |
marked.setOptions({ | |
highlight: function(code, lang) { | |
const language = hljs.getLanguage(lang) ? lang : 'plaintext'; | |
return hljs.highlight(code, { language, ignoreIllegals: true }).value; | |
}, | |
langPrefix: 'language-', // standard prefix for hljs | |
pedantic: false, | |
gfm: true, // Enable GitHub Flavored Markdown | |
breaks: true, // Convert single newlines in paragraphs into <br> | |
smartLists: true, | |
smartypants: false, // Avoid changing quotes/dashes | |
xhtml: false | |
}); | |
solutionDiv.innerHTML = marked.parse(data.solution); | |
// --- MathJax Typesetting --- | |
// Check if MathJax is loaded and ready before typesetting | |
if (window.MathJax && MathJax.startup) { | |
MathJax.startup.promise = MathJax.startup.promise.then(() => { | |
console.log("Typesetting MathJax content..."); | |
// Typeset the entire solutionDiv AFTER rendering Markdown | |
return MathJax.typesetPromise([solutionDiv]); | |
}).catch((err) => { | |
console.error('MathJax typesetting failed:', err); | |
showError('Erreur lors du rendu des formules mathématiques.'); | |
}); | |
} else { | |
console.warn('MathJax not fully loaded or ready yet.'); | |
// Optionally, try typesetting later or inform user | |
showError('MathJax n\'est pas chargé, le rendu LaTeX peut échouer.'); | |
} | |
// --- Optional: Post-processing (e.g., styling result blocks) --- | |
// Example: Add specific style to result blocks identified by text | |
const resultHeaders = solutionDiv.querySelectorAll('strong'); | |
resultHeaders.forEach(header => { | |
if (header.textContent.includes("Résultat d'exécution:")) { | |
const nextElement = header.nextElementSibling; | |
if (nextElement && nextElement.tagName === 'PRE') { | |
nextElement.classList.add('execution-result-block'); // Add a class for styling | |
} | |
} | |
}); | |
} else { | |
showError('La réponse reçue est vide ou invalide.'); | |
} | |
}) | |
.catch(error => { | |
console.error('Fetch Error:', error); | |
loadingIndicator.style.display = 'none'; | |
// Display the caught error message | |
showError(`Erreur de communication : ${error.message}`); | |
}) | |
.finally(() => { | |
// Re-enable buttons regardless of success or failure | |
solveButton.disabled = false; | |
uploadButton.disabled = false; | |
// Scroll to the solution area | |
// Use smooth scrolling if supported | |
solutionOutput.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
}); | |
}); | |
// Helper function to display errors | |
function showError(message) { | |
errorContainer.textContent = message; | |
errorContainer.style.display = 'block'; | |
solutionDiv.innerHTML = ''; // Clear any partial solution | |
loadingIndicator.style.display = 'none'; // Ensure loading is hidden | |
} | |
// Initialize Highlight.js (optional, if not done by Marked config) | |
// hljs.highlightAll(); // Usually called after content is added | |
</script> | |
</body> | |
</html> |