Mariam-cards / templates /index.html
Docfile's picture
Update templates/index.html
4c53cd5 verified
raw
history blame
22.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">
<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 !important; /* 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 !important; /* Adjust padding */
min-width: 0 !important; /* Prevent excessive width */
max-width: 100% !important; /* Ensure it doesn't overflow container */
}
/* Inline math container */
mjx-container[display="inline"] {
display: inline-block !important; /* Inline math behaves like text */
margin: 0 !important; /* No vertical margin for inline math */
overflow-x: visible !important; /* Allow inline math to flow naturally */
}
mjx-assistive-mml { display: none !important; }
.MathJax nobr,.MathJax .mjx-chtml{ display: inline-block !important; white-space: normal !important; }
span.MathJax_Preview { display: none !important; }
</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>