Spaces:
Running
Running
<html lang="fr"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Mariam AI - Assistant Français Intelligent</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap'); | |
body { | |
font-family: 'Plus Jakarta Sans', sans-serif; | |
background-color: #fafafa; | |
} | |
.glassmorphism { | |
background: rgba(255, 255, 255, 0.95); | |
backdrop-filter: blur(10px); | |
border: 1px solid rgba(255, 255, 255, 0.3); | |
} | |
.card-hover { | |
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
} | |
.card-hover:hover { | |
transform: translateY(-4px); | |
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.08); | |
} | |
.gradient-text { | |
background: linear-gradient(135deg, #1a365d 0%, #3182ce 100%); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
} | |
.loading-dot { | |
animation: bounce 1.4s infinite; | |
} | |
.loading-dot:nth-child(2) { | |
animation-delay: 0.2s; | |
} | |
.loading-dot:nth-child(3) { | |
animation-delay: 0.4s; | |
} | |
@keyframes bounce { | |
0%, | |
80%, | |
100% { | |
transform: translateY(0); | |
} | |
40% { | |
transform: translateY(-6px); | |
} | |
} | |
.custom-radio:checked+span { | |
background: linear-gradient(135deg, #1a365d 0%, #3182ce 100%); | |
color: white; | |
} | |
.backup-item { | |
cursor: pointer; | |
} | |
.backup-content { | |
display: none; | |
margin-top: 10px; | |
} | |
.backup-content-expanded { | |
display: block; | |
} | |
/* Styles for multiple image upload */ | |
.image-preview { | |
display: flex; | |
flex-wrap: wrap; | |
gap: 10px; | |
margin-top: 10px; | |
} | |
.image-preview-item { | |
position: relative; | |
width: 100px; | |
height: 100px; | |
} | |
.image-preview-item img { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
border-radius: 5px; | |
} | |
.remove-image { | |
position: absolute; | |
top: 5px; | |
right: 5px; | |
background-color: rgba(0, 0, 0, 0.5); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
padding: 2px 6px; | |
cursor: pointer; | |
font-size: 12px; | |
} | |
.remove-image:hover { | |
background-color: rgba(0, 0, 0, 0.7); | |
} | |
/* Style pour préserver les espaces dans le markdown */ | |
.prose { | |
white-space: pre-wrap; /* preserve line breaks and spaces */ | |
word-wrap: break-word; /* break long words */ | |
overflow-wrap: break-word; /* allow words to break */ | |
} | |
/* Style optionnel pour améliorer la lisibilité sur mobile */ | |
@media (max-width: 640px) { | |
.prose { | |
font-size: 0.95rem; | |
line-height: 1.6; | |
} | |
} | |
</style> | |
</head> | |
<body class="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-50"> | |
<nav class="glassmorphism sticky top-0 z-50 border-b border-blue-100"> | |
<div class="container mx-auto px-6 py-4"> | |
<div class="flex items-center justify-between"> | |
<div class="flex items-center space-x-4"> | |
<div class="bg-blue-600 rounded-lg p-2 shadow-lg"> | |
<i class="fas fa-robot text-white text-xl"></i> | |
</div> | |
<h1 class="text-2xl font-bold gradient-text">Mariam AI</h1> | |
</div> | |
<div class="text-blue-900 font-medium">Assistant Français Intelligent</div> | |
</div> | |
</div> | |
</nav> | |
<main class="container mx-auto px-6 py-12"> | |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
<!-- Section Travail Argumentatif --> | |
<div class="card-hover glassmorphism rounded-2xl overflow-hidden"> | |
<div class="p-8"> | |
<div class="flex items-center space-x-4 mb-8"> | |
<div class="bg-blue-100 rounded-lg p-3"> | |
<i class="fas fa-pen-fancy text-blue-600 text-xl"></i> | |
</div> | |
<h2 class="text-2xl font-bold text-gray-800">Travail Argumentatif</h2> | |
</div> | |
<form id="francais-form" class="space-y-8"> | |
<div class="space-y-3"> | |
<label for="sujet-francais" class="block text-sm font-medium text-gray-700"> | |
<i class="fas fa-book-open mr-2 text-blue-500"></i>Sujet | |
</label> | |
<textarea id="sujet-francais" name="sujet" rows="4" | |
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-200 transition-all duration-200 resize-none" | |
placeholder="Entrez votre sujet ici..."></textarea> | |
</div> | |
<div class="space-y-4"> | |
<label class="block text-sm font-medium text-gray-700"> | |
<i class="fas fa-tasks mr-2 text-blue-500"></i>Type d'argumentation | |
</label> | |
<div class="grid grid-cols-3 gap-4"> | |
<label class="relative"> | |
<input type="radio" name="choix" value="Etaye" | |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked> | |
<span | |
class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400"> | |
Étayer | |
</span> | |
</label> | |
<label class="relative"> | |
<input type="radio" name="choix" value="refute" | |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer"> | |
<span | |
class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400"> | |
Réfuter | |
</span> | |
</label> | |
<label class="relative"> | |
<input type="radio" name="choix" value="discuter" | |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer"> | |
<span | |
class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400"> | |
Discuter | |
</span> | |
</label> | |
</div> | |
</div> | |
<div class="space-y-4"> | |
<label class="block text-sm font-medium text-gray-700"> | |
<i class="fas fa-feather-alt mr-2 text-blue-500"></i>Style d'écriture | |
</label> | |
<div class="grid grid-cols-2 gap-4"> | |
<label class="relative"> | |
<input type="radio" name="style" value="raffiné" | |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer" checked> | |
<span | |
class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400"> | |
Raffiné | |
</span> | |
</label> | |
<label class="relative"> | |
<input type="radio" name="style" value="Normal" | |
class="custom-radio absolute opacity-0 w-full h-full cursor-pointer"> | |
<span | |
class="flex items-center justify-center p-3 border-2 border-gray-200 rounded-xl text-sm font-medium transition-all duration-200 hover:border-blue-400"> | |
Normal | |
</span> | |
</label> | |
</div> | |
</div> | |
<button type="submit" | |
class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-6 py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg"> | |
<div class="flex items-center justify-center space-x-3"> | |
<i class="fas fa-magic"></i> | |
<span>Générer</span> | |
</div> | |
</button> | |
</form> | |
<div id="francais-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none"> | |
<!-- Le contenu généré sera inséré ici --> | |
</div> | |
</div> | |
</div> | |
<!-- Section Étude de texte --> | |
<div class="card-hover glassmorphism rounded-2xl overflow-hidden"> | |
<div class="p-8"> | |
<div class="flex items-center space-x-4 mb-8"> | |
<div class="bg-blue-100 rounded-lg p-3"> | |
<i class="fas fa-file-alt text-blue-600 text-xl"></i> | |
</div> | |
<h2 class="text-2xl font-bold text-gray-800">Étude de texte</h2> | |
</div> | |
<form id="etude-texte-form" class="space-y-8" enctype="multipart/form-data"> | |
<div class="space-y-4"> | |
<label class="block text-sm font-medium text-gray-700"> | |
<i class="fas fa-image mr-2 text-blue-500"></i>Image du texte | |
</label> | |
<div class="border-3 border-dashed border-gray-300 rounded-xl p-8 text-center cursor-pointer hover:border-blue-400 transition-all duration-200" | |
id="drop-zone"> | |
<input type="file" id="image-upload" name="images" accept="image/*" class="hidden" multiple> | |
<div class="space-y-3"> | |
<div class="bg-blue-50 rounded-full w-16 h-16 flex items-center justify-center mx-auto"> | |
<i class="fas fa-cloud-upload-alt text-3xl text-blue-500"></i> | |
</div> | |
<p class="text-sm text-gray-600 font-medium">Glissez vos images ici ou cliquez | |
pour sélectionner</p> | |
<p class="text-xs text-gray-400">PNG, JPG jusqu'à 10MB</p> | |
</div> | |
</div> | |
<div id="image-preview" class="image-preview"></div> | |
</div> | |
<button type="submit" | |
class="w-full bg-gradient-to-r from-blue-600 to-blue-800 text-white px-6 py-4 rounded-xl font-medium hover:from-blue-700 hover:to-blue-900 transition-all duration-300 transform hover:scale-[1.02] active:scale-[0.98] shadow-lg"> | |
<div class="flex items-center justify-center space-x-3"> | |
<i class="fas fa-search"></i> | |
<span>Analyser</span> | |
</div> | |
</button> | |
</form> | |
<div id="etude-texte-output" class="mt-8 p-6 bg-blue-50 rounded-xl prose max-w-none"> | |
<!-- Le contenu analysé sera inséré ici --> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="mt-12"> | |
<h2 class="text-2xl font-bold text-gray-800 mb-4">Sauvegardes</h2> | |
<div id="backups-list" class="space-y-4"> | |
<!-- Les sauvegardes seront listées ici --> | |
</div> | |
</div> | |
</main> | |
<script> | |
function initializeFileUpload() { | |
const dropZone = document.getElementById('drop-zone'); | |
const fileInput = document.getElementById('image-upload'); | |
const imagePreview = document.getElementById('image-preview'); | |
dropZone.addEventListener('click', () => fileInput.click()); | |
['dragenter', 'dragover'].forEach(eventName => { | |
dropZone.addEventListener(eventName, (e) => { | |
e.preventDefault(); | |
dropZone.classList.add('border-blue-500', 'bg-blue-50'); | |
}); | |
}); | |
['dragleave', 'drop'].forEach(eventName => { | |
dropZone.addEventListener(eventName, (e) => { | |
e.preventDefault(); | |
dropZone.classList.remove('border-blue-500', 'bg-blue-50'); | |
}); | |
}); | |
dropZone.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
dropZone.classList.remove('border-blue-500', 'bg-blue-50'); | |
const files = e.dataTransfer.files; | |
handleFiles(files); | |
}); | |
fileInput.addEventListener('change', () => { | |
const files = fileInput.files; | |
handleFiles(files); | |
}); | |
function handleFiles(files) { | |
for (let i = 0; i < files.length; i++) { | |
const file = files[i]; | |
if (!file.type.startsWith('image/')) continue; | |
const reader = new FileReader(); | |
reader.onload = (e) => { | |
const previewItem = document.createElement('div'); | |
previewItem.classList.add('image-preview-item'); | |
previewItem.innerHTML = ` | |
<img src="${e.target.result}" alt="${file.name}"> | |
<button class="remove-image" title="Supprimer">×</button> | |
`; | |
imagePreview.appendChild(previewItem); | |
// Remove image event | |
previewItem.querySelector('.remove-image').addEventListener('click', () => { | |
imagePreview.removeChild(previewItem); | |
}); | |
}; | |
reader.readAsDataURL(file); | |
} | |
} | |
} | |
function sauvegarderReponse(titre, contenu) { | |
const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); | |
const dateSauvegarde = new Date().toISOString(); | |
sauvegardes.push({ | |
titre, | |
contenu, | |
date: dateSauvegarde | |
}); | |
localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes)); | |
afficherSauvegardes(); | |
} | |
function supprimerSauvegarde(index) { | |
let sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); | |
sauvegardes.splice(index, 1); | |
localStorage.setItem('mariam_ai_sauvegardes', JSON.stringify(sauvegardes)); | |
afficherSauvegardes(); | |
} | |
function afficherSauvegardes() { | |
const sauvegardes = JSON.parse(localStorage.getItem('mariam_ai_sauvegardes') || '[]'); | |
const backupsList = document.getElementById('backups-list'); | |
backupsList.innerHTML = ''; | |
if (sauvegardes.length === 0) { | |
backupsList.innerHTML = '<p class="text-gray-600">Aucune sauvegarde pour le moment.</p>'; | |
return; | |
} | |
sauvegardes.forEach((sauvegarde, index) => { | |
const sauvegardeDiv = document.createElement('div'); | |
sauvegardeDiv.className = 'bg-white rounded-lg shadow-md p-4 relative backup-item'; | |
sauvegardeDiv.innerHTML = ` | |
<h3 class="text-lg font-semibold text-gray-800">${sauvegarde.titre}</h3> | |
<p class="text-sm text-gray-600 mb-2">${new Date(sauvegarde.date).toLocaleString()}</p> | |
<div class="text-sm text-gray-700 backup-content prose max-w-none">${marked.parse(sauvegarde.contenu)}</div> | |
<button class="absolute top-2 right-2 text-red-500 hover:text-red-700 focus:outline-none" data-index="${index}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
`; | |
backupsList.appendChild(sauvegardeDiv); | |
// Gestion de l'expansion/contraction du contenu | |
const backupContentDiv = sauvegardeDiv.querySelector('.backup-content'); | |
const backupItemDiv = sauvegardeDiv; // Utilisez toute la | |
backupItemDiv.addEventListener('click', (e) => { | |
if (e.target.tagName === "BUTTON") return; // Ignorer les clics sur le bouton supprimer | |
backupContentDiv.classList.toggle('backup-content-expanded'); | |
MathJax.typesetPromise([backupContentDiv]); // Re-typer le contenu MathJax si nécessaire | |
}); | |
const deleteButton = sauvegardeDiv.querySelector('button'); | |
deleteButton.addEventListener('click', () => { | |
if (confirm('Êtes-vous sûr de vouloir supprimer cette sauvegarde ?')) { | |
supprimerSauvegarde(index); | |
} | |
}); | |
}); | |
} | |
async function submitFrancaisForm() { | |
const form = document.getElementById('francais-form'); | |
const output = document.getElementById('francais-output'); | |
form.addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
output.innerHTML = ` | |
<div class="flex items-center justify-center space-x-2 text-blue-600"> | |
<div class="loading-dot">[x] </div> | |
<div class="loading-dot">[x] </div> | |
<div class="loading-dot">[x] </div> | |
<span class="ml-3 text-sm font-medium text-gray-600">Génération en cours...</span> | |
</div>`; | |
const formData = new FormData(form); | |
try { | |
const response = await fetch('/api/francais', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
const sujet = formData.get('sujet'); | |
output.innerHTML = marked.parse(result.output); | |
sauvegarderReponse(sujet, result.output); | |
MathJax.typesetPromise([output]); | |
} catch (error) { | |
output.innerHTML = ` | |
<div class="flex items-center space-x-2 text-red-500 bg-red-50 p-4 rounded-lg"> | |
<i class="fas fa-exclamation-circle"></i> | |
<span class="text-sm font-medium">Une erreur est survenue. Veuillez réessayer.</span> | |
</div>`; | |
} | |
}); | |
} | |
async function submitEtudeTexteForm() { | |
const form = document.getElementById('etude-texte-form'); | |
const output = document.getElementById('etude-texte-output'); | |
form.addEventListener('submit', async (e) => { | |
e.preventDefault(); | |
output.innerHTML = ` | |
<div class="flex items-center justify-center space-x-2 text-blue-600"> <div class="loading-dot">[x] </div> | |
<div class="loading-dot">[x] </div> | |
<div class="loading-dot">[x] </div> | |
<span class="ml-3 text-sm font-medium text-gray-600">Analyse en cours...</span> | |
</div>`; | |
const formData = new FormData(form); | |
try { | |
const response = await fetch('/api/etude-texte', { | |
method: 'POST', | |
body: formData | |
}); | |
const result = await response.json(); | |
output.innerHTML = marked.parse(result.output); | |
// Titre par défaut pour les analyses d'images | |
const titre = "Analyse d'image " + new Date().toLocaleString(); | |
sauvegarderReponse(titre, result.output) | |
MathJax.typesetPromise([output]); | |
} catch (error) { | |
output.innerHTML = ` | |
<div class="flex items-center space-x-2 text-red-500 bg-red-50 p-4 rounded-lg"> | |
<i class="fas fa-exclamation-circle"></i> | |
<span class="text-sm font-medium">Une erreur est survenue. Veuillez réessayer.</span> | |
</div>`; | |
} | |
}); | |
} | |
// Animation des cartes au défilement | |
function animateOnScroll() { | |
const cards = document.querySelectorAll('.card-hover'); | |
const observer = new IntersectionObserver((entries) => { | |
entries.forEach(entry => { | |
if (entry.isIntersecting) { | |
entry.target.style.opacity = '1'; | |
entry.target.style.transform = 'translateY(0)'; | |
} | |
}); | |
}, { | |
threshold: 0.1 | |
}); | |
cards.forEach(card => { | |
card.style.opacity = '0'; | |
card.style.transform = 'translateY(20px)'; | |
card.style.transition = 'all 0.6s cubic-bezier(0.4, 0, 0.2, 1)'; | |
observer.observe(card); | |
}); | |
} | |
// Effet de focus amélioré pour les zones de texte | |
function enhanceTextareaFocus() { | |
const textareas = document.querySelectorAll('textarea'); | |
textareas.forEach(textarea => { | |
textarea.addEventListener('focus', () => { | |
textarea.parentElement.classList.add('ring-2', 'ring-blue-200'); | |
}); | |
textarea.addEventListener('blur', () => { | |
textarea.parentElement.classList.remove('ring-2', 'ring-blue-200'); | |
}); | |
}); | |
} | |
// Effet de hover pour les boutons radio | |
function enhanceRadioButtons() { | |
const radioLabels = document.querySelectorAll('input[type="radio"] + span'); | |
radioLabels.forEach(label => { | |
label.addEventListener('mouseenter', () => { | |
if (!label.previousElementSibling.checked) { | |
label.style.borderColor = '#3182ce'; | |
} | |
}); | |
label.addEventListener('mouseleave', () => { | |
if (!label.previousElementSibling.checked) { | |
label.style.borderColor = '#e5e7eb'; // Couleur de bordure par défaut de Tailwind pour les éléments non actifs | |
} | |
}); | |
}); | |
} | |
// Initialisation avec tous les effets | |
document.addEventListener('DOMContentLoaded', () => { | |
initializeFileUpload(); | |
submitFrancaisForm(); | |
submitEtudeTexteForm(); | |
animateOnScroll(); | |
enhanceTextareaFocus(); | |
enhanceRadioButtons(); | |
// Message de bienvenue subtil (vous pouvez supprimer cette partie si vous ne la souhaitez pas) | |
const welcomeMessage = document.createElement('div'); | |
welcomeMessage.innerHTML = ` | |
<div class="fixed bottom-6 right-6 bg-white rounded-xl shadow-lg p-4 transform transition-all duration-500 opacity-0 translate-y-4"> | |
<div class="flex items-center space-x-3"> | |
<div class="bg-blue-100 rounded-lg p-2"> | |
<i class="fas fa-robot text-blue-600"></i> | |
</div> | |
<div> | |
<p class="text-sm font-medium text-gray-800">Bienvenue sur Mariam AI</p> | |
<p class="text-xs text-gray-500">Je suis là pour vous aider</p> | |
</div> | |
</div> | |
</div> | |
`; | |
document.body.appendChild(welcomeMessage); | |
setTimeout(() => { | |
welcomeMessage.firstElementChild.classList.remove('opacity-0', 'translate-y-4'); | |
}, 1000); | |
setTimeout(() => { | |
welcomeMessage.firstElementChild.classList.add('opacity-0', 'translate-y-4'); | |
setTimeout(() => welcomeMessage.remove(), 500); | |
}, 5000); | |
afficherSauvegardes(); // Appel de la fonction pour afficher les sauvegardes | |
}); | |
</script> | |
</script> | |
</body> | |
</html> |