Docfile's picture
Update templates/maj.html
1bfe49c verified
raw
history blame
25.4 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">
<!-- 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 !important;
overflow-y: hidden !important;
max-width: 100% !important;
min-width: 0 !important; /* Prevent minimum width issues */
padding: 5px 0; /* Add a bit of vertical padding */
display: block !important; /* Ensure it behaves like a block */
text-align: left !important; /* Align LaTeX left by default */
}
mjx-container[display="true"] {
display: block !important;
margin: 1em 0 !important; /* Spacing for display math */
text-align: center !important; /* 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>