|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Résolveur Mathématique IA</title> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js"></script> |
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> |
|
<style> |
|
|
|
:root { |
|
--bg-color: #f4f7fa; |
|
--main-color: #ffffff; |
|
--primary-color: #3b82f6; |
|
--primary-hover: #2563eb; |
|
--text-color: #1f2937; |
|
--text-light: #6b7280; |
|
--border-color: #e5e7eb; |
|
--dark-bg: #111827; |
|
--success-color: #10b981; |
|
--warning-color: #f59e0b; |
|
--error-color: #ef4444; |
|
--shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); |
|
--border-radius: 0.75rem; |
|
} |
|
|
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: 'Inter', sans-serif; |
|
background-color: var(--bg-color); |
|
color: var(--text-color); |
|
line-height: 1.6; |
|
padding: 2rem; |
|
} |
|
|
|
|
|
.container { |
|
max-width: 1200px; |
|
margin: 0 auto; |
|
background-color: var(--main-color); |
|
border-radius: var(--border-radius); |
|
box-shadow: var(--shadow); |
|
overflow: hidden; |
|
} |
|
|
|
|
|
.header { |
|
padding: 2.5rem; |
|
text-align: center; |
|
border-bottom: 1px solid var(--border-color); |
|
background: linear-gradient(to top, #ffffff, #f9fafb); |
|
} |
|
.header h1 { |
|
font-size: 2.25rem; |
|
font-weight: 700; |
|
letter-spacing: -0.025em; |
|
color: var(--text-color); |
|
} |
|
.header h1 .icon { |
|
color: var(--primary-color); |
|
} |
|
.header p { |
|
margin-top: 0.5rem; |
|
color: var(--text-light); |
|
font-size: 1.1rem; |
|
max-width: 600px; |
|
margin-left: auto; |
|
margin-right: auto; |
|
} |
|
|
|
|
|
.main-content { |
|
padding: 2.5rem; |
|
} |
|
|
|
.upload-area { |
|
border: 2px dashed var(--border-color); |
|
border-radius: var(--border-radius); |
|
padding: 3rem; |
|
text-align: center; |
|
background-color: #fcfdff; |
|
transition: all 0.3s ease; |
|
} |
|
.upload-area.dragover { |
|
border-color: var(--primary-color); |
|
background-color: #eff6ff; |
|
} |
|
.upload-icon { |
|
font-size: 3rem; |
|
color: var(--primary-color); |
|
margin-bottom: 1rem; |
|
} |
|
.upload-area p { |
|
color: var(--text-light); |
|
margin-bottom: 1.5rem; |
|
} |
|
|
|
|
|
.btn { |
|
display: inline-block; |
|
background-color: var(--primary-color); |
|
color: white; |
|
padding: 0.8rem 2rem; |
|
border: none; |
|
border-radius: 9999px; |
|
font-size: 1rem; |
|
font-weight: 500; |
|
cursor: pointer; |
|
text-decoration: none; |
|
transition: background-color 0.2s ease, transform 0.2s ease; |
|
} |
|
.btn:hover { |
|
background-color: var(--primary-hover); |
|
transform: translateY(-2px); |
|
} |
|
|
|
|
|
.results-grid { |
|
margin-top: 2.5rem; |
|
display: grid; |
|
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); |
|
gap: 2rem; |
|
} |
|
|
|
.panel { |
|
background-color: #f9fafb; |
|
border: 1px solid var(--border-color); |
|
border-radius: var(--border-radius); |
|
padding: 1.5rem; |
|
} |
|
.panel h3 { |
|
font-size: 1.25rem; |
|
font-weight: 600; |
|
margin-bottom: 1rem; |
|
border-bottom: 1px solid var(--border-color); |
|
padding-bottom: 0.75rem; |
|
} |
|
|
|
#extractedText { |
|
white-space: pre-wrap; |
|
word-wrap: break-word; |
|
font-family: 'Courier New', monospace; |
|
background-color: var(--main-color); |
|
padding: 1rem; |
|
border-radius: 0.5rem; |
|
max-height: 450px; |
|
overflow-y: auto; |
|
color: var(--text-light); |
|
} |
|
|
|
|
|
#logContainer { |
|
height: 450px; |
|
overflow-y: auto; |
|
background: var(--dark-bg); |
|
color: #d1d5db; |
|
padding: 1rem; |
|
border-radius: 0.5rem; |
|
font-family: 'Courier New', monospace; |
|
font-size: 0.875rem; |
|
} |
|
.log-entry { |
|
margin-bottom: 0.25rem; |
|
padding: 0.2rem 0.5rem; |
|
border-radius: 3px; |
|
} |
|
.log-timestamp { color: #9ca3af; margin-right: 0.5rem; } |
|
.log-info { color: #3b82f6; } |
|
.log-success { color: #10b981; } |
|
.log-warning { color: #f59e0b; } |
|
.log-error { color: #ef4444; font-weight: bold; } |
|
|
|
|
|
#status-section { |
|
padding: 1.5rem 2.5rem; |
|
background-color: #f9fafb; |
|
border-top: 1px solid var(--border-color); |
|
text-align: center; |
|
} |
|
.status-badge { |
|
display: inline-flex; |
|
align-items: center; |
|
gap: 0.5rem; |
|
padding: 0.5rem 1.25rem; |
|
border-radius: 9999px; |
|
font-weight: 500; |
|
} |
|
.status-processing { background-color: #fef3c7; color: #92400e; } |
|
.status-completed { background-color: #d1fae5; color: #065f46; } |
|
.status-failed { background-color: #fee2e2; color: #991b1b; } |
|
|
|
#downloadSection { |
|
margin-top: 1rem; |
|
} |
|
|
|
.hidden { |
|
display: none; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="container"> |
|
<header class="header"> |
|
<h1><span class="icon">🧮</span> Résolveur Mathématique IA</h1> |
|
<p>Soumettez une image d'un problème mathématique et laissez nos agents IA générer une solution rigoureuse, étape par étape.</p> |
|
</header> |
|
|
|
<main class="main-content"> |
|
|
|
<section id="upload-section"> |
|
<div class="upload-area" id="uploadArea"> |
|
<div class="upload-icon">📄</div> |
|
<p>Glissez-déposez votre image ici, ou cliquez sur le bouton pour la sélectionner.</p> |
|
|
|
<button type="button" class="btn" id="selectFileBtn">Choisir un fichier</button> |
|
|
|
<input type="file" id="fileInput" accept="image/*" class="hidden"> |
|
</div> |
|
</section> |
|
|
|
|
|
<section id="status-section" class="hidden"> |
|
<div id="statusBadge" class="status-badge"></div> |
|
<div id="downloadSection" class="hidden"> |
|
<a href="#" id="downloadBtn" class="btn">📥 Télécharger la solution</a> |
|
</div> |
|
</section> |
|
|
|
|
|
<section id="results-grid" class="results-grid hidden"> |
|
<div class="panel"> |
|
<h3>📝 Texte extrait de l'image</h3> |
|
<div id="extractedText">Le texte de votre image apparaîtra ici...</div> |
|
</div> |
|
<div class="panel"> |
|
<h3>📊 Journal de traitement</h3> |
|
<div id="logContainer"></div> |
|
</div> |
|
</section> |
|
|
|
</main> |
|
</div> |
|
|
|
<script> |
|
const socket = io(); |
|
let currentTaskId = null; |
|
|
|
|
|
const uploadArea = document.getElementById('uploadArea'); |
|
const fileInput = document.getElementById('fileInput'); |
|
const selectFileBtn = document.getElementById('selectFileBtn'); |
|
const statusSection = document.getElementById('status-section'); |
|
const statusBadge = document.getElementById('statusBadge'); |
|
const resultsGrid = document.getElementById('results-grid'); |
|
const extractedTextEl = document.getElementById('extractedText'); |
|
const logContainer = document.getElementById('logContainer'); |
|
const downloadSection = document.getElementById('downloadSection'); |
|
const downloadBtn = document.getElementById('downloadBtn'); |
|
|
|
|
|
|
|
|
|
selectFileBtn.addEventListener('click', () => { |
|
fileInput.click(); |
|
}); |
|
|
|
|
|
fileInput.addEventListener('change', (e) => { |
|
if (e.target.files.length > 0) { |
|
handleFileUpload(e.target.files[0]); |
|
} |
|
}); |
|
|
|
|
|
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { |
|
uploadArea.addEventListener(eventName, preventDefaults, false); |
|
}); |
|
function preventDefaults(e) { |
|
e.preventDefault(); |
|
e.stopPropagation(); |
|
} |
|
|
|
['dragenter', 'dragover'].forEach(eventName => { |
|
uploadArea.addEventListener(eventName, () => uploadArea.classList.add('dragover'), false); |
|
}); |
|
['dragleave', 'drop'].forEach(eventName => { |
|
uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('dragover'), false); |
|
}); |
|
|
|
uploadArea.addEventListener('drop', (e) => { |
|
const files = e.dataTransfer.files; |
|
if (files.length > 0) { |
|
handleFileUpload(files[0]); |
|
} |
|
}); |
|
|
|
|
|
|
|
function resetUI() { |
|
statusSection.classList.add('hidden'); |
|
resultsGrid.classList.add('hidden'); |
|
downloadSection.classList.add('hidden'); |
|
logContainer.innerHTML = ''; |
|
extractedTextEl.textContent = 'Le texte de votre image apparaîtra ici...'; |
|
currentTaskId = null; |
|
} |
|
|
|
function handleFileUpload(file) { |
|
resetUI(); |
|
const formData = new FormData(); |
|
formData.append('file', file); |
|
|
|
updateStatus('processing', '🚀 Envoi et analyse de l\'image...'); |
|
statusSection.classList.remove('hidden'); |
|
|
|
fetch('/upload', { |
|
method: 'POST', |
|
body: formData |
|
}) |
|
.then(response => { |
|
if (!response.ok) { |
|
throw new Error(`Erreur serveur: ${response.statusText}`); |
|
} |
|
return response.json(); |
|
}) |
|
.then(data => { |
|
if (data.error) { |
|
throw new Error(data.error); |
|
} |
|
currentTaskId = data.task_id; |
|
extractedTextEl.textContent = data.extracted_text; |
|
resultsGrid.classList.remove('hidden'); |
|
updateStatus('processing', '🧠 Résolution en cours...'); |
|
}) |
|
.catch(error => { |
|
updateStatus('failed', `❌ Erreur critique : ${error.message}`); |
|
console.error('Upload Error:', error); |
|
}); |
|
} |
|
|
|
function updateStatus(status, message) { |
|
statusBadge.className = `status-badge status-${status}`; |
|
statusBadge.textContent = message; |
|
} |
|
|
|
function addLog(timestamp, level, message) { |
|
const logEntry = document.createElement('div'); |
|
logEntry.className = `log-entry log-${level}`; |
|
logEntry.innerHTML = `<span class="log-timestamp">[${timestamp}]</span> ${message}`; |
|
logContainer.appendChild(logEntry); |
|
logContainer.scrollTop = logContainer.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 !'); |
|
downloadSection.classList.remove('hidden'); |
|
downloadBtn.href = `/download/${currentTaskId}`; |
|
} |
|
}); |
|
|
|
socket.on('task_failed', (data) => { |
|
if (data.task_id === currentTaskId) { |
|
updateStatus('failed', '⚠️ Échec de la résolution (solution partielle disponible)'); |
|
downloadSection.classList.remove('hidden'); |
|
downloadBtn.href = `/download/${currentTaskId}`; |
|
} |
|
}); |
|
|
|
socket.on('task_error', (data) => { |
|
if (data.task_id === currentTaskId) { |
|
updateStatus('failed', `❌ Erreur système: ${data.error}`); |
|
} |
|
}); |
|
</script> |
|
</body> |
|
</html> |