|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
|
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Mariam AI - Analyse d'Image</title> |
|
|
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> |
|
|
|
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1/moment.min.js"></script> |
|
<style> |
|
|
|
.loader { |
|
width: 48px; |
|
height: 48px; |
|
border: 5px solid #FFF; |
|
border-bottom-color: #6366F1; |
|
border-radius: 50%; |
|
display: inline-block; |
|
box-sizing: border-box; |
|
animation: rotation 1s linear infinite; |
|
} |
|
|
|
@keyframes rotation { |
|
0% { |
|
transform: rotate(0deg); |
|
} |
|
|
|
100% { |
|
transform: rotate(360deg); |
|
} |
|
} |
|
|
|
|
|
.fade-in { |
|
animation: fadeIn 0.5s ease-in; |
|
} |
|
|
|
@keyframes fadeIn { |
|
0% { |
|
opacity: 0; |
|
} |
|
|
|
100% { |
|
opacity: 1; |
|
} |
|
} |
|
|
|
|
|
.upload-zone { |
|
border: 2px dashed #6366F1; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.upload-zone:hover { |
|
border-color: #4F46E5; |
|
background-color: #EEF2FF; |
|
} |
|
|
|
|
|
.markdown-content { |
|
overflow-x: auto; |
|
padding-bottom: 1rem; |
|
} |
|
|
|
.markdown-content table { |
|
border-collapse: collapse; |
|
min-width: 100%; |
|
width: max-content; |
|
margin: 1rem 0; |
|
} |
|
|
|
.markdown-content th, |
|
.markdown-content td { |
|
border: 1px solid #e5e7eb; |
|
padding: 0.75rem; |
|
text-align: left; |
|
white-space: nowrap; |
|
min-width: 150px; |
|
} |
|
|
|
.markdown-content th { |
|
background-color: #f9fafb; |
|
position: sticky; |
|
top: 0; |
|
z-index: 10; |
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.markdown-content::-webkit-scrollbar { |
|
height: 8px; |
|
} |
|
|
|
.markdown-content::-webkit-scrollbar-track { |
|
background: #f1f1f1; |
|
border-radius: 4px; |
|
} |
|
|
|
.markdown-content::-webkit-scrollbar-thumb { |
|
background: #6366F1; |
|
border-radius: 4px; |
|
} |
|
|
|
.markdown-content::-webkit-scrollbar-thumb:hover { |
|
background: #4F46E5; |
|
} |
|
|
|
|
|
.swal2-popup { |
|
width: 90% !important; |
|
max-width: 1200px !important; |
|
} |
|
|
|
.swal2-html-container { |
|
max-height: 80vh !important; |
|
overflow-y: auto !important; |
|
} |
|
|
|
|
|
.scroll-indicator { |
|
position: absolute; |
|
bottom: 20px; |
|
right: 20px; |
|
background: rgba(99, 102, 241, 0.9); |
|
color: white; |
|
padding: 8px 12px; |
|
border-radius: 4px; |
|
font-size: 0.875rem; |
|
opacity: 0; |
|
transition: opacity 0.3s ease; |
|
pointer-events: none; |
|
} |
|
|
|
.scroll-indicator.visible { |
|
opacity: 1; |
|
} |
|
|
|
|
|
.image-preview-container { |
|
position: relative; |
|
max-width: 100%; |
|
margin: 1rem auto; |
|
border-radius: 0.5rem; |
|
overflow: hidden; |
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), |
|
0 2px 4px -1px rgba(0, 0, 0, 0.06); |
|
} |
|
|
|
.image-preview-container img { |
|
display: block; |
|
max-width: 100%; |
|
height: auto; |
|
max-height: 400px; |
|
object-fit: contain; |
|
} |
|
|
|
.remove-image { |
|
position: absolute; |
|
top: 0.5rem; |
|
right: 0.5rem; |
|
background-color: rgba(255, 255, 255, 0.9); |
|
border-radius: 50%; |
|
padding: 0.5rem; |
|
cursor: pointer; |
|
transition: all 0.2s ease; |
|
} |
|
|
|
.remove-image:hover { |
|
background-color: #EF4444; |
|
color: white; |
|
} |
|
|
|
|
|
.tab-buttons { |
|
display: flex; |
|
justify-content: center; |
|
margin-bottom: 1rem; |
|
} |
|
|
|
.tab-buttons button { |
|
margin: 0 0.5rem; |
|
padding: 0.5rem 1rem; |
|
border: 1px solid #6366F1; |
|
background-color: #EEF2FF; |
|
color: #6366F1; |
|
border-radius: 0.25rem; |
|
cursor: pointer; |
|
transition: background-color 0.3s ease; |
|
} |
|
|
|
.tab-buttons button.active { |
|
background-color: #6366F1; |
|
color: white; |
|
} |
|
|
|
|
|
.tab-content { |
|
display: none; |
|
} |
|
|
|
.tab-content.active { |
|
display: block; |
|
} |
|
|
|
|
|
#backupList li { |
|
margin-bottom: 0.5rem; |
|
padding: 0.75rem; |
|
border: 1px solid #e5e7eb; |
|
border-radius: 0.25rem; |
|
background-color: white; |
|
cursor: pointer; |
|
display: flex; |
|
justify-content: space-between; |
|
align-items: center; |
|
} |
|
|
|
#backupList li:hover { |
|
background-color: #f9fafb; |
|
} |
|
|
|
#backupList .backup-date { |
|
font-size: 0.8rem; |
|
color: #9ca3af; |
|
} |
|
|
|
#backupList button { |
|
padding: 0.25rem 0.5rem; |
|
background-color: #EF4444; |
|
color: white; |
|
border-radius: 0.25rem; |
|
} |
|
</style> |
|
</head> |
|
|
|
<body class="bg-gray-50"> |
|
<div class="min-h-screen"> |
|
<header class="bg-white shadow-sm"> |
|
<div class="max-w-7xl mx-auto px-4 py-4 sm:px-6 lg:px-8"> |
|
<h1 class="text-3xl font-bold text-indigo-600">Mariam AI</h1> |
|
<p class="mt-1 text-sm text-gray-500">Assistant pour commentaire composé.</p> |
|
</div> |
|
</header> |
|
|
|
<main class="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8"> |
|
<div class="bg-white rounded-lg shadow p-6 mb-8"> |
|
<form id="uploadForm" class="space-y-6"> |
|
<div class="upload-zone rounded-lg p-8 text-center"> |
|
<input type="file" id="imageInput" accept="image/*" required class="hidden" |
|
onchange="handleImageSelect()"> |
|
<label for="imageInput" class="cursor-pointer"> |
|
<div class="space-y-2"> |
|
<svg class="mx-auto h-12 w-12 text-indigo-500" fill="none" viewBox="0 0 24 24" |
|
stroke="currentColor"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" /> |
|
</svg> |
|
<div id="fileName" class="text-sm text-gray-500"> |
|
Cliquez ou glissez une image ici |
|
</div> |
|
</div> |
|
</label> |
|
</div> |
|
|
|
<div id="imagePreview" class="hidden image-preview-container"> |
|
<img id="preview" src="#" alt="Prévisualisation"> |
|
<button type="button" class="remove-image" onclick="removeImage()"> |
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> |
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |
|
d="M6 18L18 6M6 6l12 12" /> |
|
</svg> |
|
</button> |
|
</div> |
|
|
|
<div class="flex justify-center"> |
|
<button type="submit" |
|
class="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200"> |
|
Analyser l'image |
|
</button> |
|
</div> |
|
</form> |
|
</div> |
|
|
|
<div id="loading" class="hidden"> |
|
<div class="flex flex-col items-center justify-center space-y-4"> |
|
<span class="loader"></span> |
|
<p class="text-gray-500">Analyse en cours, veuillez patienter...</p> |
|
</div> |
|
</div> |
|
|
|
<div id="results" class="space-y-8 hidden"> |
|
<div class="tab-buttons"> |
|
<button id="showDissertation" class="active">Dissertation</button> |
|
<button id="showTableau">Tableau d'analyse</button> |
|
<button id="showBackups">Sauvegardes</button> |
|
</div> |
|
|
|
<div id="dissertationTab" class="tab-content active bg-white rounded-lg shadow p-6"> |
|
<h2 class="text-2xl font-bold text-gray-900 mb-4">Dissertation</h2> |
|
<div id="dissertationResult" class="prose max-w-none markdown-content"> |
|
|
|
</div> |
|
<button id="saveDissertation" |
|
class="mt-4 px-6 py-3 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors duration-200"> |
|
Sauvegarder la dissertation |
|
</button> |
|
</div> |
|
|
|
<div id="tableauTab" class="tab-content bg-white rounded-lg shadow p-6 hidden"> |
|
<h2 class="text-2xl font-bold text-gray-900 mb-4">Tableau d'analyse</h2> |
|
<div id="tableauResult" class="markdown-content"> |
|
|
|
</div> |
|
<button id="saveTableau" |
|
class="mt-4 px-6 py-3 bg-green-600 text-white rounded-md hover:bg-green-700 transition-colors duration-200"> |
|
Sauvegarder le tableau |
|
</button> |
|
</div> |
|
|
|
<div id="backupsTab" class="tab-content bg-white rounded-lg shadow p-6 hidden"> |
|
<h2 class="text-2xl font-bold text-gray-900 mb-4">Sauvegardes locales</h2> |
|
<ul id="backupList" class="space-y-2"> |
|
|
|
</ul> |
|
</div> |
|
|
|
</div> |
|
</main> |
|
</div> |
|
|
|
<script> |
|
|
|
|
|
let tableauContent = ''; |
|
let dissertationContent = ''; |
|
let backups = loadBackups(); |
|
|
|
function handleImageSelect() { |
|
const input = document.getElementById('imageInput'); |
|
const preview = document.getElementById('preview'); |
|
const previewContainer = document.getElementById('imagePreview'); |
|
const fileName = document.getElementById('fileName'); |
|
|
|
if (input.files && input.files[0]) { |
|
const reader = new FileReader(); |
|
|
|
reader.onload = function (e) { |
|
preview.src = e.target.result; |
|
previewContainer.classList.remove('hidden'); |
|
previewContainer.classList.add('fade-in'); |
|
}; |
|
|
|
reader.readAsDataURL(input.files[0]); |
|
fileName.textContent = input.files[0].name; |
|
} else { |
|
removeImage(); |
|
} |
|
} |
|
|
|
function removeImage() { |
|
const input = document.getElementById('imageInput'); |
|
const preview = document.getElementById('preview'); |
|
const previewContainer = document.getElementById('imagePreview'); |
|
const fileName = document.getElementById('fileName'); |
|
|
|
input.value = ''; |
|
preview.src = '#'; |
|
previewContainer.classList.add('hidden'); |
|
fileName.textContent = 'Cliquez ou glissez une image ici'; |
|
} |
|
|
|
function showScrollIndicator(container) { |
|
if (container.scrollWidth > container.clientWidth) { |
|
const indicator = document.createElement('div'); |
|
indicator.className = 'scroll-indicator visible'; |
|
indicator.textContent = '← Faites défiler →'; |
|
container.parentElement.appendChild(indicator); |
|
|
|
setTimeout(() => { |
|
indicator.classList.remove('visible'); |
|
}, 3000); |
|
|
|
container.addEventListener('scroll', () => { |
|
if (container.scrollLeft > 0) { |
|
indicator.classList.add('visible'); |
|
} else { |
|
indicator.classList.remove('visible'); |
|
} |
|
|
|
clearTimeout(container.scrollTimeout); |
|
container.scrollTimeout = setTimeout(() => { |
|
indicator.classList.remove('visible'); |
|
}, 1000); |
|
}); |
|
} |
|
} |
|
|
|
document.getElementById('showTableau').addEventListener('click', () => { |
|
showTab('tableau'); |
|
renderTableauContent(); |
|
}); |
|
|
|
document.getElementById('showDissertation').addEventListener('click', () => { |
|
showTab('dissertation'); |
|
}); |
|
|
|
document.getElementById('showBackups').addEventListener('click', () => { |
|
showTab('backups'); |
|
updateBackupList(); |
|
}); |
|
|
|
function showTab(tabName) { |
|
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active')); |
|
document.querySelectorAll('.tab-buttons button').forEach(button => button.classList.remove('active')); |
|
|
|
document.getElementById(`${tabName}Tab`).classList.add('active'); |
|
document.getElementById(`show${tabName.charAt(0).toUpperCase() + tabName.slice(1)}`).classList.add('active'); |
|
} |
|
|
|
function loadBackups() { |
|
try { |
|
const storedBackups = localStorage.getItem('mariamAIBackups'); |
|
return storedBackups ? JSON.parse(storedBackups) : []; |
|
} catch (error) { |
|
console.error("Erreur lors du chargement des sauvegardes :", error); |
|
return []; |
|
} |
|
} |
|
|
|
function saveBackup(type, content) { |
|
const timestamp = moment().format('YYYY-MM-DD HH:mm:ss'); |
|
backups.push({ timestamp, type, content }); |
|
localStorage.setItem('mariamAIBackups', JSON.stringify(backups)); |
|
updateBackupList(); |
|
Swal.fire({ |
|
icon: 'success', |
|
title: 'Sauvegarde réussie!', |
|
}); |
|
} |
|
|
|
function updateBackupList() { |
|
const backupList = document.getElementById('backupList'); |
|
backupList.innerHTML = ''; |
|
|
|
backups.forEach((backup, index) => { |
|
const listItem = document.createElement('li'); |
|
listItem.innerHTML = ` |
|
<span>${backup.type === 'dissertation' ? 'Dissertation' : 'Tableau'} - ${backup.timestamp}</span> |
|
<div> |
|
<button data-index="${index}" class="view-backup bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded text-xs">Voir</button> |
|
<button data-index="${index}" class="delete-backup bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-2 rounded text-xs">Supprimer</button> |
|
</div> |
|
`; |
|
backupList.appendChild(listItem); |
|
|
|
listItem.querySelector('.view-backup').addEventListener('click', viewBackup); |
|
listItem.querySelector('.delete-backup').addEventListener('click', deleteBackup); |
|
}); |
|
} |
|
|
|
function viewBackup(event) { |
|
const index = event.target.dataset.index; |
|
const backup = backups[index]; |
|
|
|
Swal.fire({ |
|
title: backup.type === 'dissertation' ? 'Dissertation sauvegardée' : 'Tableau sauvegardé', |
|
html: marked.parse(backup.content), |
|
width: '90%', |
|
customClass: { |
|
htmlContainer: 'markdown-content' |
|
}, |
|
didRender: () => { |
|
const container = document.querySelector('.markdown-content'); |
|
showScrollIndicator(container); |
|
} |
|
}); |
|
} |
|
|
|
function deleteBackup(event) { |
|
const index = event.target.dataset.index; |
|
Swal.fire({ |
|
title: 'Êtes-vous sûr?', |
|
text: "Vous ne pourrez pas revenir en arrière !", |
|
icon: 'warning', |
|
showCancelButton: true, |
|
confirmButtonColor: '#3085d6', |
|
cancelButtonColor: '#d33', |
|
confirmButtonText: 'Oui, supprimer!' |
|
}).then((result) => { |
|
if (result.isConfirmed) { |
|
backups.splice(index, 1); |
|
localStorage.setItem('mariamAIBackups', JSON.stringify(backups)); |
|
updateBackupList(); |
|
Swal.fire( |
|
'Supprimé!', |
|
'Votre fichier a été supprimé.', |
|
'success' |
|
) |
|
} |
|
}); |
|
} |
|
|
|
document.getElementById('saveDissertation').addEventListener('click', () => { |
|
saveBackup('dissertation', dissertationContent); |
|
}); |
|
|
|
document.getElementById('saveTableau').addEventListener('click', () => { |
|
saveBackup('tableau', tableauContent); |
|
}); |
|
|
|
document.getElementById('uploadForm').addEventListener('submit', async (e) => { |
|
e.preventDefault(); |
|
|
|
const imageInput = document.getElementById('imageInput'); |
|
if (!imageInput.files || !imageInput.files[0]) { |
|
Swal.fire({ |
|
icon: 'error', |
|
title: 'Aucune image sélectionnée', |
|
text: 'Veuillez sélectionner une image à analyser.', |
|
}); |
|
return; |
|
} |
|
|
|
const loading = document.getElementById('loading'); |
|
const results = document.getElementById('results'); |
|
const dissertationResult = document.getElementById('dissertationResult'); |
|
|
|
const formData = new FormData(); |
|
formData.append('image', imageInput.files[0]); |
|
|
|
loading.classList.remove('hidden'); |
|
results.classList.add('hidden'); |
|
|
|
try { |
|
const response = await fetch('/analyze', { |
|
method: 'POST', |
|
body: formData |
|
}); |
|
|
|
if (!response.ok) { |
|
const errorData = await response.json(); |
|
const errorMessage = errorData.error || `Erreur serveur ${response.status}: ${response.statusText}`; |
|
throw new Error(errorMessage); |
|
} |
|
|
|
const data = await response.json(); |
|
dissertationContent = data.dissertation; |
|
tableauContent = data.tableau; |
|
|
|
dissertationResult.innerHTML = marked.parse(data.dissertation); |
|
|
|
results.classList.remove('hidden'); |
|
results.classList.add('fade-in'); |
|
showTab('dissertation'); |
|
|
|
} catch (error) { |
|
console.error("Erreur lors de l'analyse :", error); |
|
Swal.fire({ |
|
icon: 'error', |
|
title: 'Erreur', |
|
text: error.message || 'Une erreur est survenue lors de la communication avec le serveur.', |
|
}); |
|
|
|
} finally { |
|
loading.classList.add('hidden'); |
|
} |
|
}); |
|
|
|
function renderTableauContent() { |
|
const tableauResult = document.getElementById('tableauResult'); |
|
if (tableauContent) { |
|
tableauResult.innerHTML = marked.parse(tableauContent); |
|
showScrollIndicator(tableauResult); |
|
} |
|
} |
|
</script> |
|
</body> |
|
|
|
</html> |