Ohpdf / templates /index.html
Docfile's picture
Update templates/index.html
d62ab00 verified
raw
history blame
10.8 kB
<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;">
<!-- MODIFICATION 1: Suppression du onclick et ajout d'un id -->
<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;
// Gestion de l'upload
const uploadBox = document.getElementById('uploadBox');
const fileInput = document.getElementById('fileInput');
// MODIFICATION 2: Sélection du bouton par son nouvel id
const uploadButton = document.getElementById('uploadButton');
// L'utilisateur peut cliquer sur toute la zone
uploadBox.addEventListener('click', () => fileInput.click());
// MODIFICATION 3: Ajout d'un écouteur dédié au bouton
uploadButton.addEventListener('click', (e) => {
// Empêche le clic de "remonter" à la div parente (uploadBox),
// ce qui évite de déclencher l'ouverture de la fenêtre deux fois.
e.stopPropagation();
fileInput.click();
});
// Gestion du glisser-déposer (Drag & Drop)
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]);
}
});
// Gestion de la sélection de fichier
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;
}
// WebSocket events
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>