|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Résolveur Mathématique IMO</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.4/socket.io.js"></script> |
|
<style> |
|
* { margin: 0; padding: 0; box-sizing: border-box; } |
|
body { |
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
min-height: 100vh; |
|
padding: 20px; |
|
} |
|
.container { |
|
max-width: 1200px; |
|
margin: 0 auto; |
|
background: white; |
|
border-radius: 15px; |
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1); |
|
overflow: hidden; |
|
} |
|
.header { |
|
background: linear-gradient(135deg, #2196F3, #21CBF3); |
|
padding: 30px; |
|
text-align: center; |
|
color: white; |
|
} |
|
.header h1 { font-size: 2.5em; margin-bottom: 10px; } |
|
.header p { font-size: 1.1em; opacity: 0.9; } |
|
|
|
.upload-section { |
|
padding: 40px; |
|
text-align: center; |
|
border-bottom: 1px solid #eee; |
|
} |
|
.upload-box { |
|
border: 3px dashed #ddd; |
|
border-radius: 10px; |
|
padding: 40px; |
|
transition: all 0.3s ease; |
|
cursor: pointer; |
|
} |
|
.upload-box:hover { border-color: #2196F3; background: #f8f9ff; } |
|
.upload-box.dragover { border-color: #2196F3; background: #e3f2fd; } |
|
|
|
.btn { |
|
background: linear-gradient(135deg, #2196F3, #21CBF3); |
|
color: white; |
|
padding: 12px 30px; |
|
border: none; |
|
border-radius: 25px; |
|
font-size: 16px; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
} |
|
.btn:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(33,150,243,0.3); } |
|
|
|
.logs-section { |
|
display: flex; |
|
height: 600px; |
|
} |
|
.extracted-text { |
|
flex: 1; |
|
padding: 20px; |
|
border-right: 1px solid #eee; |
|
} |
|
.logs-panel { |
|
flex: 1; |
|
padding: 20px; |
|
} |
|
.log-container { |
|
height: 500px; |
|
overflow-y: auto; |
|
background: #1e1e1e; |
|
color: #fff; |
|
padding: 15px; |
|
border-radius: 8px; |
|
font-family: 'Courier New', monospace; |
|
font-size: 13px; |
|
} |
|
.log-entry { |
|
margin-bottom: 5px; |
|
padding: 5px; |
|
border-radius: 3px; |
|
} |
|
.log-info { color: #4CAF50; } |
|
.log-success { color: #8BC34A; background: rgba(139,195,74,0.1); } |
|
.log-warning { color: #FF9800; } |
|
.log-error { color: #F44336; background: rgba(244,67,54,0.1); } |
|
|
|
.status-bar { |
|
padding: 20px; |
|
background: #f5f5f5; |
|
text-align: center; |
|
} |
|
.status-badge { |
|
display: inline-block; |
|
padding: 8px 16px; |
|
border-radius: 20px; |
|
font-weight: bold; |
|
text-transform: uppercase; |
|
} |
|
.status-processing { background: #FFC107; color: #333; } |
|
.status-completed { background: #4CAF50; color: white; } |
|
.status-failed { background: #F44336; color: white; } |
|
|
|
.download-section { |
|
padding: 20px; |
|
text-align: center; |
|
display: none; |
|
} |
|
.hidden { display: none; } |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<div class="header"> |
|
<h1>🧮 Résolveur Mathématique IMO</h1> |
|
<p>Uploadez une image de votre problème mathématique et obtenez une solution rigoureuse</p> |
|
</div> |
|
|
|
<div class="upload-section"> |
|
<div class="upload-box" id="uploadBox"> |
|
<h3>📁 Glisser-déposer votre image ici</h3> |
|
<p>ou cliquez pour sélectionner un fichier</p> |
|
<input type="file" id="fileInput" accept="image/*" style="display: none;"> |
|
|
|
<button class="btn" id="uploadButton"> |
|
Choisir un fichier |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div class="status-bar"> |
|
<div id="statusBadge" class="status-badge" style="display: none;">En attente</div> |
|
<div id="taskInfo" style="margin-top: 10px; display: none;"></div> |
|
</div> |
|
|
|
<div class="logs-section hidden" id="logsSection"> |
|
<div class="extracted-text"> |
|
<h3>📝 Texte extrait de l'image</h3> |
|
<div id="extractedText" style="background: #f9f9f9; padding: 15px; border-radius: 8px; margin-top: 10px; white-space: pre-wrap; max-height: 450px; overflow-y: auto;"></div> |
|
</div> |
|
<div class="logs-panel"> |
|
<h3>📊 Logs de traitement en temps réel</h3> |
|
<div id="logContainer" class="log-container"></div> |
|
</div> |
|
</div> |
|
|
|
<div class="download-section" id="downloadSection"> |
|
<h3>✅ Solution prête !</h3> |
|
<button class="btn" id="downloadBtn">📥 Télécharger la solution</button> |
|
</div> |
|
</div> |
|
<script> |
|
const socket = io(); |
|
let currentTaskId = null; |
|
|
|
|
|
const uploadBox = document.getElementById('uploadBox'); |
|
const fileInput = document.getElementById('fileInput'); |
|
|
|
const uploadButton = document.getElementById('uploadButton'); |
|
|
|
|
|
uploadBox.addEventListener('click', () => fileInput.click()); |
|
|
|
|
|
uploadButton.addEventListener('click', (e) => { |
|
|
|
|
|
e.stopPropagation(); |
|
fileInput.click(); |
|
}); |
|
|
|
|
|
uploadBox.addEventListener('dragover', (e) => { |
|
e.preventDefault(); |
|
uploadBox.classList.add('dragover'); |
|
}); |
|
uploadBox.addEventListener('dragleave', () => { |
|
uploadBox.classList.remove('dragover'); |
|
}); |
|
uploadBox.addEventListener('drop', (e) => { |
|
e.preventDefault(); |
|
uploadBox.classList.remove('dragover'); |
|
const files = e.dataTransfer.files; |
|
if (files.length > 0) { |
|
uploadFile(files[0]); |
|
} |
|
}); |
|
|
|
|
|
fileInput.addEventListener('change', (e) => { |
|
if (e.target.files.length > 0) { |
|
uploadFile(e.target.files[0]); |
|
} |
|
}); |
|
|
|
function uploadFile(file) { |
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
updateStatus('processing', 'Upload en cours...'); |
|
|
|
fetch('/upload', { |
|
method: 'POST', |
|
body: formData |
|
}) |
|
.then(response => response.json()) |
|
.then(data => { |
|
if (data.error) { |
|
updateStatus('failed', data.error); |
|
} else { |
|
currentTaskId = data.task_id; |
|
document.getElementById('extractedText').textContent = data.extracted_text; |
|
document.getElementById('logsSection').classList.remove('hidden'); |
|
updateStatus('processing', 'Résolution en cours...'); |
|
} |
|
}) |
|
.catch(error => { |
|
updateStatus('failed', 'Erreur lors de l\'upload'); |
|
console.error('Error:', error); |
|
}); |
|
} |
|
|
|
function updateStatus(status, message) { |
|
const badge = document.getElementById('statusBadge'); |
|
const info = document.getElementById('taskInfo'); |
|
|
|
badge.style.display = 'inline-block'; |
|
badge.className = `status-badge status-${status}`; |
|
badge.textContent = status === 'processing' ? 'En cours' : |
|
status === 'completed' ? 'Terminé' : 'Échec'; |
|
|
|
info.style.display = 'block'; |
|
info.textContent = message; |
|
} |
|
|
|
function addLog(timestamp, level, message) { |
|
const container = document.getElementById('logContainer'); |
|
const logEntry = document.createElement('div'); |
|
logEntry.className = `log-entry log-${level}`; |
|
logEntry.innerHTML = `<span style="color: #666;">[${timestamp}]</span> ${message}`; |
|
container.appendChild(logEntry); |
|
container.scrollTop = container.scrollHeight; |
|
} |
|
|
|
|
|
socket.on('log_update', (data) => { |
|
if (data.task_id === currentTaskId) { |
|
addLog(data.log.timestamp, data.log.level, data.log.message); |
|
} |
|
}); |
|
|
|
socket.on('task_completed', (data) => { |
|
if (data.task_id === currentTaskId) { |
|
updateStatus('completed', 'Solution générée avec succès !'); |
|
const downloadSection = document.getElementById('downloadSection'); |
|
downloadSection.style.display = 'block'; |
|
|
|
document.getElementById('downloadBtn').onclick = () => { |
|
window.location.href = `/download/${currentTaskId}`; |
|
}; |
|
} |
|
}); |
|
|
|
socket.on('task_failed', (data) => { |
|
if (data.task_id === currentTaskId) { |
|
updateStatus('failed', 'Échec de la résolution (solution partielle disponible)'); |
|
const downloadSection = document.getElementById('downloadSection'); |
|
downloadSection.style.display = 'block'; |
|
|
|
document.getElementById('downloadBtn').onclick = () => { |
|
window.location.href = `/download/${currentTaskId}`; |
|
}; |
|
} |
|
}); |
|
|
|
socket.on('task_error', (data) => { |
|
if (data.task_id === currentTaskId) { |
|
updateStatus('failed', `Erreur: ${data.error}`); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |