Spaces:
Sleeping
Sleeping
| <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> | |
| </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> |