Spaces:
Runtime error
Runtime error
<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"> | |
<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: #2c323c; /* Background for code blocks generated by marked+hljs */ | |
--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; | |
} | |
h1 { | |
color: var(--primary-color); | |
margin-top: 0; | |
} | |
.feature-list { | |
list-style-type: none; | |
padding: 0; | |
margin: 30px 0; | |
text-align: left; | |
} | |
.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; } | |
.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; } | |
.cta-button:hover { background-color: var(--secondary-color); transform: translateY(-2px); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } | |
.upgrade-section { margin-top: 30px; padding: 20px; border-top: 1px solid #ddd; } | |
footer { text-align: center; padding: 20px 0; color: #666; font-size: 0.9rem; } | |
/* --- Styles for Solution Area --- */ | |
#solutionOutput { | |
margin-top: 30px; | |
text-align: left; | |
display: none; | |
} | |
#solutionContainer { | |
background: #fff; | |
padding: 20px; | |
border-radius: 8px; | |
text-align: left; | |
line-height: 1.8; | |
font-size: 16px; | |
box-shadow: var(--box-shadow); | |
overflow-x: hidden; /* Prevent container scroll, let content scroll if needed */ | |
max-height: 70vh; /* Limit height and enable scrolling */ | |
overflow-y: auto; /* Enable vertical scroll */ | |
scroll-behavior: smooth; | |
} | |
/* Styles for elements generated by Marked.js */ | |
#solutionContainer p { margin-bottom: 1em; } | |
#solutionContainer ul, #solutionContainer ol { margin-left: 2em; margin-bottom: 1em; } | |
#solutionContainer li { margin-bottom: 0.5em; } | |
#solutionContainer h1, #solutionContainer h2, #solutionContainer h3 { margin-top: 1.5em; margin-bottom: 0.8em; font-weight: bold; color: var(--primary-color); } | |
#solutionContainer h1 { font-size: 1.8em; } | |
#solutionContainer h2 { font-size: 1.5em; } | |
#solutionContainer h3 { font-size: 1.2em; } | |
#solutionContainer strong, #solutionContainer b { font-weight: bold; } | |
#solutionContainer em, #solutionContainer i { font-style: italic; } | |
#solutionContainer table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; } | |
#solutionContainer th, #solutionContainer td { border: 1px solid #d1d5db; padding: 0.5rem; text-align: left; } | |
#solutionContainer th { background-color: #f3f4f6; font-weight: 600; } | |
#solutionContainer blockquote { border-left: 4px solid #ccc; margin-left: 0; padding-left: 1em; color: #666; } | |
/* Styles for Code Blocks (generated by Marked + Highlight.js) */ | |
#solutionContainer pre { | |
margin: 1.5em 0; | |
border-radius: 8px; | |
overflow: hidden; /* Contains the code block */ | |
box-shadow: 0 2px 5px rgba(0,0,0,0.1); | |
} | |
#solutionContainer pre code.hljs { | |
display: block; | |
padding: 15px; | |
background-color: var(--code-bg); /* Use defined code background */ | |
color: #e6e6e6; /* Default text color for atom-one-dark */ | |
overflow-x: auto; /* Enable horizontal scroll for long code lines */ | |
font-family: 'Courier New', monospace; | |
font-size: 14px; | |
line-height: 1.5; | |
border-radius: 0 0 8px 8px; /* Bottom radius */ | |
} | |
/* Optional: Add a language header like in the previous code */ | |
/* This would require modifying how marked generates the HTML or adding JS */ | |
/* Styles for Loading Indicators */ | |
.thinking-indicator, .executing-indicator, .answering-indicator { display: flex; align-items: center; padding: 10px; margin: 10px 0; border-radius: 8px; font-size: 0.9rem; } | |
.thinking-indicator { background-color: #e3f2fd; color: #1565c0; } | |
.executing-indicator { background-color: #ede7f6; color: #5e35b1; } | |
.answering-indicator { background-color: #e8f5e9; color: #2e7d32; } | |
.indicator-icon { margin-right: 10px; animation: pulse 1.5s infinite ease-in-out; } | |
@keyframes pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } } | |
/* --- MathJax Specific Styles (from previous attempts, seem okay) --- */ | |
mjx-container { | |
overflow-x: auto ; /* Ensure horizontal scroll for wide math */ | |
overflow-y: hidden; | |
display: block; /* Block math */ | |
margin: 1em 0; /* Vertical spacing for block math */ | |
text-align: initial; | |
padding: 2px 0 ; /* Adjust padding */ | |
min-width: 0 ; /* Prevent excessive width */ | |
max-width: 100% ; /* Ensure it doesn't overflow container */ | |
} | |
/* Inline math container */ | |
mjx-container[display="inline"] { | |
display: inline-block ; /* Inline math behaves like text */ | |
margin: 0 ; /* No vertical margin for inline math */ | |
overflow-x: visible ; /* Allow inline math to flow naturally */ | |
} | |
mjx-assistive-mml { display: none ; } | |
.MathJax nobr,.MathJax .mjx-chtml{ display: inline-block ; white-space: normal ; } | |
span.MathJax_Preview { display: none ; } | |
</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> | |
<!-- Feature list, Upload section remain the same --> | |
<div class="feature-list"> | |
<h2>Fonctionnalités disponibles :</h2> | |
<ul class="feature-list"> | |
<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"> | |
<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> | |
</form> | |
<p id="uploadStatus"></p> | |
<div id="imagePreview" style="display: none; margin: 20px auto; max-width: 500px;"> | |
<img id="preview" style="width: 100%; border-radius: 8px; box-shadow: var(--box-shadow);"> | |
</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> | |
<div id="solutionOutput"> | |
<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> | |
<!-- Single container for the entire solution --> | |
<div id="solutionContainer"></div> | |
</div> | |
<!-- Upgrade section remains the same --> | |
<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> | |
<!-- Dependencies --> | |
<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> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/marked.min.js"></script> <!-- Use marked 4.x --> | |
<!-- MathJax Configuration and Loading --> | |
<script> | |
window.MathJax = { | |
tex: { | |
inlineMath: [['$', '$'], ['\\(', '\\)']], | |
displayMath: [['$$', '$$'], ['\\[', '\\]']], | |
processEscapes: true, | |
processEnvironments: true, | |
packages: {'[+]': ['ams', 'noerrors', 'physics', 'cancel', 'color', 'mhchem', 'mathtools']} | |
}, | |
options: { | |
enableMenu: false, | |
ignoreHtmlClass: 'no-mathjax', // Ignore specific classes if needed | |
processHtmlClass: 'mathjax-process', // Process only specific classes if needed | |
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre'] // Skip pre for manual highlight control | |
}, | |
loader: { | |
load: ['[tex]/ams', '[tex]/noerrors', '[tex]/physics', '[tex]/cancel', '[tex]/color', '[tex]/mhchem', '[tex]/mathtools'] | |
}, | |
startup: { | |
// Function to run once MathJax is ready | |
ready: () => { | |
console.log('MathJax is ready'); | |
window.mathJaxReady = true; // Flag for our script | |
MathJax.startup.defaultReady(); // Perform default startup actions | |
} | |
}, | |
svg: { | |
fontCache: 'global' | |
} | |
}; | |
</script> | |
<script id="MathJax-script" async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.2/es5/tex-svg.js"></script> | |
<!-- Main Application Logic --> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// DOM Elements | |
const imageInput = document.getElementById('imageInput'); | |
const imagePreview = document.getElementById('imagePreview'); | |
const previewImage = document.getElementById('preview'); | |
const solveButton = document.getElementById('solveButton'); | |
const uploadStatus = document.getElementById('uploadStatus'); | |
const solutionOutput = document.getElementById('solutionOutput'); | |
const loadingIndicator = document.getElementById('loadingIndicator'); | |
const solutionContainer = document.getElementById('solutionContainer'); | |
const imageForm = document.getElementById('imageForm'); | |
// State Variables | |
let solutionBuffer = ''; | |
let currentMode = null; // To potentially handle 'thinking' vs 'answering' differently if needed | |
let updateTimeout = null; | |
// Configure Marked and Highlight.js | |
marked.setOptions({ | |
highlight: function(code, lang) { | |
const language = hljs.getLanguage(lang) ? lang : 'plaintext'; | |
return hljs.highlight(code, { language }).value; | |
}, | |
langPrefix: 'hljs language-', // CSS class prefix for hljs | |
gfm: true, // Enable GitHub Flavored Markdown | |
breaks: true // Convert single line breaks to <br> | |
}); | |
// Function to update the display (debounced) | |
const updateDisplay = async () => { | |
if (!solutionContainer) return; | |
// Parse the entire buffer with Marked | |
solutionContainer.innerHTML = marked.parse(solutionBuffer); | |
// Ensure MathJax is ready before typesetting | |
if (window.mathJaxReady) { | |
try { | |
// Tell MathJax to process the updated container | |
await MathJax.typesetPromise([solutionContainer]); | |
console.log("MathJax typesetting complete."); | |
} catch (e) { | |
console.error('MathJax typesetting failed:', e); | |
} | |
} else { | |
console.warn("MathJax not ready, typesetting skipped for this update."); | |
// Optionally, queue this update or retry later | |
} | |
// Highlight code blocks (already done by marked's highlight option) | |
// hljs.highlightAll(); // Not needed if using marked's highlight option | |
// Scroll to bottom | |
solutionContainer.scrollTop = solutionContainer.scrollHeight; | |
updateTimeout = null; // Clear the timeout flag | |
}; | |
// Schedule display update (debouncing) | |
const scheduleUpdate = () => { | |
if (!updateTimeout) { | |
updateTimeout = setTimeout(updateDisplay, 150); // Update slightly faster | |
} | |
}; | |
// Image selection handler | |
imageInput.addEventListener('change', function(event) { | |
const file = event.target.files[0]; | |
if (file) { | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
previewImage.src = e.target.result; | |
imagePreview.style.display = 'block'; | |
solveButton.style.display = 'inline-block'; | |
uploadStatus.textContent = `Image sélectionnée : ${file.name}`; | |
} | |
reader.readAsDataURL(file); | |
} | |
}); | |
// Solve button handler | |
solveButton.addEventListener('click', function() { | |
const formData = new FormData(imageForm); | |
solutionOutput.style.display = 'block'; | |
loadingIndicator.style.display = 'flex'; | |
solutionContainer.innerHTML = ''; // Clear previous solution | |
solutionBuffer = ''; // Reset buffer | |
currentMode = null; // Reset mode | |
fetch('/solved', { // Ensure this endpoint matches your backend | |
method: 'POST', | |
body: formData | |
}) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const reader = response.body.getReader(); | |
const decoder = new TextDecoder(); | |
let streamBuffer = ''; // Buffer for incoming stream chunks | |
function processStream({ done, value }) { | |
if (done) { | |
loadingIndicator.style.display = 'none'; | |
// Process any remaining data in streamBuffer (optional, depends on backend) | |
if (streamBuffer && streamBuffer.startsWith('data: ')) { | |
try { | |
const data = JSON.parse(streamBuffer.substr(6)); | |
if (data.content) { | |
solutionBuffer += data.content; // Add final piece | |
} | |
} catch (e) { | |
console.error('Error parsing final chunk:', e, streamBuffer); | |
} | |
} | |
// Final forced update | |
clearTimeout(updateTimeout); // Cancel any scheduled update | |
updateDisplay(); // Render everything received | |
return; | |
} | |
streamBuffer += decoder.decode(value, { stream: true }); | |
const lines = streamBuffer.split('\n\n'); | |
streamBuffer = lines.pop(); // Keep incomplete chunk | |
for (const line of lines) { | |
if (line.startsWith('data: ')) { | |
try { | |
const data = JSON.parse(line.substr(6)); | |
if (data.mode) { | |
currentMode = data.mode; // Store current mode if backend provides it | |
// Update loading indicator based on mode (same as before) | |
if (data.mode === 'thinking') { loadingIndicator.className = 'thinking-indicator'; loadingIndicator.innerHTML = '<i class="fas fa-brain indicator-icon"></i><span>Je réfléchis...</span>'; } | |
else if (data.mode === 'answering') { loadingIndicator.className = 'answering-indicator'; loadingIndicator.innerHTML = '<i class="fas fa-pencil-alt indicator-icon"></i><span>Rédaction...</span>'; } | |
else if (data.mode === 'executing_code') { loadingIndicator.className = 'executing-indicator'; loadingIndicator.innerHTML = '<i class="fas fa-code indicator-icon"></i><span>Exécution...</span>'; } | |
else if (data.mode === 'code_result') { loadingIndicator.className = 'executing-indicator'; loadingIndicator.innerHTML = '<i class="fas fa-terminal indicator-icon"></i><span>Résultats...</span>'; } | |
} | |
if (data.content) { | |
solutionBuffer += data.content; // Append content to buffer | |
scheduleUpdate(); // Schedule an update | |
} | |
if (data.error) { | |
// Display error prominently | |
solutionBuffer += `\n\n**Erreur:** ${data.error}\n\n`; | |
scheduleUpdate(); // Show the error | |
loadingIndicator.style.display = 'none'; | |
} | |
} catch (e) { | |
console.error('Error parsing JSON from stream:', e, line); | |
// Optionally display a parsing error message to the user | |
solutionBuffer += `\n\n**Erreur interne:** Impossible d'interpréter une partie de la réponse.\n\n`; | |
scheduleUpdate(); | |
} | |
} | |
} | |
// Continue reading the stream | |
return reader.read().then(processStream); | |
} | |
// Start processing the stream | |
return reader.read().then(processStream); | |
}) | |
.catch(error => { | |
console.error('Fetch error:', error); | |
solutionContainer.innerHTML = `<p style="color: red; font-weight: bold;">Erreur de connexion ou du serveur: ${error.message}</p>`; | |
loadingIndicator.style.display = 'none'; | |
}); | |
}); | |
}); | |
</script> | |
</body> | |
</html> |