File size: 13,102 Bytes
79914b8 1f4ef47 ad494e0 79914b8 1f4ef47 1c9f000 1f4ef47 45da2f6 1f4ef47 45da2f6 1f4ef47 79914b8 1f4ef47 79914b8 45da2f6 1f4ef47 45da2f6 79914b8 1f4ef47 1c9f000 45da2f6 1f4ef47 10970c2 1c9f000 ae43fd8 1f4ef47 1c9f000 1f4ef47 45da2f6 1f4ef47 1c9f000 1f4ef47 79914b8 1f4ef47 79914b8 1f4ef47 43f84cb 1f4ef47 45da2f6 79914b8 1f4ef47 45da2f6 1f4ef47 79914b8 1f4ef47 79914b8 1f4ef47 43f84cb 1f4ef47 45da2f6 1f4ef47 45da2f6 1f4ef47 79914b8 1f4ef47 45da2f6 1f4ef47 45da2f6 1f4ef47 45da2f6 1f4ef47 45da2f6 1f4ef47 45da2f6 1f4ef47 45da2f6 79914b8 1f4ef47 79914b8 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 |
<!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>
/* Custom scrollbar for pre block (optional) */
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; /* sky-500 */
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(); // Hide general error if a specific one is not present in this message
}
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) { // If not a critical error, keep status positive
// Status already set by switch
} 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'); // Ensure status area is visible to show error
}
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> |