|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Résolution d'exercices (Maths, Physique et Chimie)</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
|
|
pre::-webkit-scrollbar { |
|
width: 8px; |
|
height: 8px; |
|
} |
|
pre::-webkit-scrollbar-track { |
|
background: #f1f1f1; |
|
border-radius: 10px; |
|
} |
|
pre::-webkit-scrollbar-thumb { |
|
background: #888; |
|
border-radius: 10px; |
|
} |
|
pre::-webkit-scrollbar-thumb:hover { |
|
background: #555; |
|
} |
|
.spinner { |
|
border: 4px solid rgba(0, 0, 0, 0.1); |
|
width: 36px; |
|
height: 36px; |
|
border-radius: 50%; |
|
border-left-color: #0ea5e9; |
|
animation: spin 1s ease infinite; |
|
} |
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-slate-100 text-slate-800 min-h-screen flex flex-col items-center justify-center p-4 font-sans"> |
|
|
|
<div class="bg-white p-6 sm:p-8 rounded-xl shadow-2xl w-full max-w-2xl"> |
|
<header class="mb-6 sm:mb-8 text-center"> |
|
<h1 class="text-2xl sm:text-3xl font-bold text-sky-700"> |
|
Résolution d'Exercices |
|
</h1> |
|
<p class="text-sm text-slate-500">Mathématiques, Physique et Chimie</p> |
|
</header> |
|
|
|
<form id="solveForm" class="space-y-6"> |
|
<div> |
|
<label for="imageUpload" class="block text-sm font-medium text-gray-700 mb-1"> |
|
Télécharger une image de l'exercice : |
|
</label> |
|
<input type="file" id="imageUpload" name="image" accept="image/*" required |
|
class="block w-full text-sm text-slate-500 |
|
file:mr-4 file:py-2 file:px-4 |
|
file:rounded-full file:border-0 |
|
file:text-sm file:font-semibold |
|
file:bg-sky-50 file:text-sky-700 |
|
hover:file:bg-sky-100 |
|
border border-gray-300 rounded-md p-2 focus:outline-none focus:ring-2 focus:ring-sky-500"> |
|
</div> |
|
|
|
<div> |
|
<span class="block text-sm font-medium text-gray-700 mb-2">Choisir le format de la solution :</span> |
|
<div class="space-y-2"> |
|
<div class="flex items-center"> |
|
<input id="promptRefined" name="prompt_type" type="radio" value="refined" checked |
|
class="h-4 w-4 text-sky-600 border-gray-300 focus:ring-sky-500"> |
|
<label for="promptRefined" class="ml-2 block text-sm text-gray-900"> |
|
Format Raffiné & Complet <span class="text-xs text-gray-500">(mise en page avancée)</span> |
|
</label> |
|
</div> |
|
<div class="flex items-center"> |
|
<input id="promptLight" name="prompt_type" type="radio" value="light" |
|
class="h-4 w-4 text-sky-600 border-gray-300 focus:ring-sky-500"> |
|
<label for="promptLight" class="ml-2 block text-sm text-gray-900"> |
|
Format simple |
|
</label> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<button type="submit" id="submitButton" |
|
class="w-full bg-sky-600 hover:bg-sky-700 text-white font-bold py-2.5 px-4 rounded-md |
|
focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 |
|
transition duration-150 ease-in-out |
|
disabled:bg-slate-400 disabled:cursor-not-allowed"> |
|
Résoudre l'exercice |
|
</button> |
|
</form> |
|
|
|
<div id="statusArea" class="mt-6 space-y-3 hidden"> |
|
<div class="flex items-center space-x-3 p-3 rounded-md bg-sky-50 border border-sky-200"> |
|
<div id="spinner" class="spinner hidden"></div> |
|
<p id="statusMessage" class="text-sm font-medium text-sky-700"></p> |
|
</div> |
|
<div id="errorMessage" class="p-3 rounded-md bg-red-50 border border-red-200 text-red-700 text-sm hidden"></div> |
|
<div id="errorDetailMessage" class="p-3 rounded-md bg-amber-50 border border-amber-200 text-amber-700 text-sm hidden"></div> |
|
</div> |
|
|
|
<div id="resultArea" class="mt-6 hidden"> |
|
<h3 class="text-lg font-semibold text-slate-700 mb-2">Code LaTeX Généré :</h3> |
|
<div class="bg-gray-900 text-gray-100 p-4 rounded-md shadow"> |
|
<pre><code id="latexOutput" class="text-sm whitespace-pre-wrap break-all block max-h-96 overflow-auto"></code></pre> |
|
</div> |
|
<button id="copyLatexButton" class="mt-3 bg-slate-200 hover:bg-slate-300 text-slate-700 text-sm font-medium py-1.5 px-3 rounded-md transition duration-150 ease-in-out"> |
|
Copier le LaTeX |
|
</button> |
|
</div> |
|
|
|
</div> |
|
|
|
<footer class="mt-8 text-center text-xs text-slate-500"> |
|
<p>© 2025 Mariam AI - Solution Propulsée par Mariam</p> |
|
</footer> |
|
|
|
<script> |
|
const solveForm = document.getElementById('solveForm'); |
|
const submitButton = document.getElementById('submitButton'); |
|
const imageUpload = document.getElementById('imageUpload'); |
|
|
|
const statusArea = document.getElementById('statusArea'); |
|
const spinner = document.getElementById('spinner'); |
|
const statusMessage = document.getElementById('statusMessage'); |
|
const errorMessage = document.getElementById('errorMessage'); |
|
const errorDetailMessage = document.getElementById('errorDetailMessage'); |
|
|
|
const resultArea = document.getElementById('resultArea'); |
|
const latexOutput = document.getElementById('latexOutput'); |
|
const copyLatexButton = document.getElementById('copyLatexButton'); |
|
|
|
let eventSource = null; |
|
|
|
solveForm.addEventListener('submit', async function(event) { |
|
event.preventDefault(); |
|
|
|
if (!imageUpload.files || imageUpload.files.length === 0) { |
|
showError("Veuillez sélectionner un fichier image."); |
|
return; |
|
} |
|
|
|
submitButton.disabled = true; |
|
showStatus("Initialisation...", true); |
|
hideError(); |
|
hideResult(); |
|
|
|
const formData = new FormData(solveForm); |
|
|
|
try { |
|
const response = await fetch('/solve', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
const errorData = await response.json().catch(() => ({ error: 'Erreur serveur inconnue' })); |
|
throw new Error(errorData.error || `Erreur HTTP: ${response.status}`); |
|
} |
|
|
|
const data = await response.json(); |
|
if (data.task_id) { |
|
showStatus("Tâche créée. En attente de la réponse...", true); |
|
startStreaming(data.task_id); |
|
} else { |
|
throw new Error("ID de tâche non reçu."); |
|
} |
|
|
|
} catch (error) { |
|
showError(`Erreur lors de la soumission : ${error.message}`); |
|
submitButton.disabled = false; |
|
hideStatus(); |
|
} |
|
}); |
|
|
|
function startStreaming(taskId) { |
|
if (eventSource) { |
|
eventSource.close(); |
|
} |
|
eventSource = new EventSource(`/stream/${taskId}`); |
|
|
|
showStatus("Connexion au flux de progression...", true); |
|
|
|
eventSource.onmessage = function(event) { |
|
const data = JSON.parse(event.data); |
|
|
|
let userFriendlyStatus = data.status; |
|
switch(data.status) { |
|
case 'pending': userFriendlyStatus = "En attente de traitement..."; break; |
|
case 'processing': userFriendlyStatus = "Traitement de l'image..."; break; |
|
case 'generating_latex': userFriendlyStatus = "Génération du code LaTeX en cours..."; break; |
|
case 'cleaning_latex': userFriendlyStatus = "Nettoyage du code LaTeX..."; break; |
|
case 'generating_pdf': userFriendlyStatus = "Génération du PDF en cours..."; break; |
|
case 'completed': userFriendlyStatus = "Terminé ! Solution générée (LaTeX et PDF)."; break; |
|
case 'completed_tex_only': userFriendlyStatus = "Terminé ! Solution LaTeX générée (PDF non disponible)."; break; |
|
case 'pdf_error': userFriendlyStatus = "Erreur lors de la génération du PDF. Le code LaTeX est disponible."; break; |
|
case 'error': userFriendlyStatus = "Une erreur est survenue."; break; |
|
} |
|
showStatus(userFriendlyStatus, !['completed', 'error', 'pdf_error', 'completed_tex_only'].includes(data.status)); |
|
|
|
|
|
if (data.error) { |
|
showError(data.error); |
|
} else { |
|
hideError(); |
|
} |
|
if (data.error_detail) { |
|
showErrorDetail(data.error_detail); |
|
} else { |
|
hideErrorDetail(); |
|
} |
|
|
|
|
|
if (data.response) { |
|
latexOutput.textContent = data.response; |
|
showResult(); |
|
} |
|
|
|
if (['completed', 'error', 'pdf_error', 'completed_tex_only'].includes(data.status)) { |
|
eventSource.close(); |
|
submitButton.disabled = false; |
|
if (data.status !== 'error' && !data.error) { |
|
|
|
} else if (data.error) { |
|
showStatus("Traitement terminé avec des erreurs.", false); |
|
} |
|
} |
|
}; |
|
|
|
eventSource.onerror = function(err) { |
|
console.error("Erreur EventSource:", err); |
|
showError("Erreur de connexion au serveur pour les mises à jour. Veuillez réessayer."); |
|
showStatus("Déconnecté.", false); |
|
eventSource.close(); |
|
submitButton.disabled = false; |
|
}; |
|
} |
|
|
|
function showStatus(message, showSpinner = false) { |
|
statusArea.classList.remove('hidden'); |
|
statusMessage.textContent = message; |
|
if (showSpinner) { |
|
spinner.classList.remove('hidden'); |
|
} else { |
|
spinner.classList.add('hidden'); |
|
} |
|
} |
|
|
|
function hideStatus() { |
|
statusArea.classList.add('hidden'); |
|
spinner.classList.add('hidden'); |
|
} |
|
|
|
function showError(message) { |
|
errorMessage.textContent = message; |
|
errorMessage.classList.remove('hidden'); |
|
statusArea.classList.remove('hidden'); |
|
} |
|
function hideError() { |
|
errorMessage.classList.add('hidden'); |
|
errorMessage.textContent = ''; |
|
} |
|
|
|
function showErrorDetail(message) { |
|
errorDetailMessage.textContent = message; |
|
errorDetailMessage.classList.remove('hidden'); |
|
statusArea.classList.remove('hidden'); |
|
} |
|
function hideErrorDetail() { |
|
errorDetailMessage.classList.add('hidden'); |
|
errorDetailMessage.textContent = ''; |
|
} |
|
|
|
|
|
function showResult() { |
|
resultArea.classList.remove('hidden'); |
|
} |
|
function hideResult() { |
|
resultArea.classList.add('hidden'); |
|
latexOutput.textContent = ''; |
|
} |
|
|
|
copyLatexButton.addEventListener('click', () => { |
|
if (latexOutput.textContent) { |
|
navigator.clipboard.writeText(latexOutput.textContent) |
|
.then(() => { |
|
const originalText = copyLatexButton.textContent; |
|
copyLatexButton.textContent = 'Copié !'; |
|
setTimeout(() => { |
|
copyLatexButton.textContent = originalText; |
|
}, 2000); |
|
}) |
|
.catch(err => { |
|
console.error('Erreur de copie: ', err); |
|
alert('Erreur lors de la copie du texte.'); |
|
}); |
|
} |
|
}); |
|
|
|
</script> |
|
</body> |
|
</html> |