Mariam-cc / templates /index.html
Docfile's picture
Update templates/index.html
fa6e48b verified
raw
history blame
19.7 kB
<!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>
<!-- CDN pour Tailwind CSS -->
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
<!-- CDN pour Marked -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- CDN pour SweetAlert2 -->
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<!-- CDN pour Moment.js -->
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.1/moment.min.js"></script>
<style>
/* Styles pour le loader */
.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);
}
}
/* Styles pour l'animation d'apparition en fondu */
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* Styles pour la zone de drop d'image */
.upload-zone {
border: 2px dashed #6366F1;
transition: all 0.3s ease;
}
.upload-zone:hover {
border-color: #4F46E5;
background-color: #EEF2FF;
}
/* Styles pour le contenu Markdown */
.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;
}
/* Ajustement de la taille des popups SweetAlert2 */
.swal2-popup {
width: 90% !important;
max-width: 1200px !important;
}
.swal2-html-container {
max-height: 80vh !important;
overflow-y: auto !important;
}
/* Styles pour l'indicateur de scroll horizontal */
.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;
}
/* Styles pour la prévisualisation de l'image */
.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;
}
/* Styles pour les boutons d'onglets */
.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;
}
/* Styles pour le contenu des onglets */
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Styles pour la liste des sauvegardes */
#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>
// ...(Le JavaScript de la réponse précédente va ici)
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>