| <!DOCTYPE html>
|
| <html lang="fr">
|
| <head>
|
| <meta charset="UTF-8" />
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| <title>Mariam Anglais</title>
|
| <script src="https://cdn.tailwindcss.com"></script>
|
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
| <script>
|
| tailwind.config = {
|
| theme: {
|
| extend: {
|
| colors: {
|
| primary: '#4CAF50',
|
| 'primary-dark': '#3e8e41',
|
| secondary: '#2D3748',
|
| accent: '#F6AD55'
|
| },
|
| fontFamily: {
|
| sans: ['Inter', 'sans-serif']
|
| },
|
| animation: {
|
| 'gradient': 'gradient 8s ease infinite',
|
| 'float': 'float 3s ease-in-out infinite'
|
| },
|
| keyframes: {
|
| gradient: {
|
| '0%, 100%': {
|
| 'background-position': '0% 50%'
|
| },
|
| '50%': {
|
| 'background-position': '100% 50%'
|
| }
|
| },
|
| float: {
|
| '0%, 100%': {
|
| transform: 'translateY(0)'
|
| },
|
| '50%': {
|
| transform: 'translateY(-10px)'
|
| }
|
| }
|
| }
|
| }
|
| }
|
| }
|
| </script>
|
| <style>
|
| |
| .markdown-content {
|
| width: 100%;
|
| overflow-wrap: break-word;
|
| word-break: break-word;
|
| hyphens: auto;
|
| }
|
| .markdown-content pre {
|
| white-space: pre-wrap;
|
| padding: 1rem;
|
| background-color: #edf2f7;
|
| border-radius: 0.5rem;
|
| margin: 1rem 0;
|
| overflow-x: auto;
|
| position: relative;
|
| }
|
| .markdown-content code {
|
| background-color: #edf2f7;
|
| padding: 0.2rem 0.4rem;
|
| border-radius: 0.25rem;
|
| font-family: ui-monospace, monospace;
|
| }
|
| .markdown-content p {
|
| margin-bottom: 1rem;
|
| line-height: 1.6;
|
| white-space: pre-line;
|
| }
|
| .markdown-content h1 {
|
| font-size: 1.8rem;
|
| font-weight: bold;
|
| margin-top: 1.5rem;
|
| margin-bottom: 1rem;
|
| padding-bottom: 0.5rem;
|
| border-bottom: 1px solid #e2e8f0;
|
| }
|
| .markdown-content h2 {
|
| font-size: 1.5rem;
|
| font-weight: bold;
|
| margin-top: 1.25rem;
|
| margin-bottom: 0.75rem;
|
| }
|
| .markdown-content h3 {
|
| font-size: 1.25rem;
|
| font-weight: bold;
|
| margin-top: 1rem;
|
| margin-bottom: 0.5rem;
|
| }
|
| .markdown-content ul, .markdown-content ol {
|
| margin-left: 1.5rem;
|
| margin-bottom: 1rem;
|
| }
|
| .markdown-content li {
|
| margin-bottom: 0.5rem;
|
| }
|
| .markdown-content blockquote {
|
| border-left: 4px solid #e2e8f0;
|
| padding-left: 1rem;
|
| margin-left: 0;
|
| margin-right: 0;
|
| margin-bottom: 1rem;
|
| font-style: italic;
|
| color: #4a5568;
|
| }
|
| .markdown-content hr {
|
| margin: 1.5rem 0;
|
| border: 0;
|
| border-top: 1px solid #e2e8f0;
|
| }
|
| @keyframes fadeIn {
|
| from { opacity: 0; }
|
| to { opacity: 1; }
|
| }
|
| .animate-fade-in {
|
| animation: fadeIn 1s ease-out;
|
| }
|
| |
| </style>
|
| </head>
|
| <body class="bg-white min-h-screen font-sans"> |
| <div class="container mx-auto px-4 py-12 max-w-6xl">
|
| |
| <header class="text-center mb-16 animate-fade-in"> |
| <h1 class="text-6xl font-bold text-gray-900 mb-4"> |
| ✨ Mariam Anglais ✨ |
| </h1> |
| <p class="text-gray-600 text-xl font-light tracking-wide"> |
| Téléchargez vos images, choisissez votre type d'analyse et laissez la magie opérer. |
| </p> |
| </header> |
|
|
|
|
| <div class="grid md:grid-cols-2 gap-12">
|
| |
| <div class="bg-gray-50 p-8"> |
| <h2 class="text-2xl font-semibold text-primary mb-6 flex items-center gap-2">
|
| <svg class="h-8 w-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
| </svg>
|
| Téléchargement d'images
|
| </h2>
|
| <div |
| class="upload-zone bg-gray-100 p-10 text-center cursor-pointer hover:bg-gray-200 transition-colors duration-300" |
| onclick="document.getElementById('image-upload').click()"> |
| <input type="file" id="image-upload" multiple accept="image/*" class="hidden" />
|
| <p class="text-gray-600 font-medium">Cliquez ou glissez vos images ici</p>
|
| </div>
|
| <div id="preview-container" class="mt-6 grid grid-cols-2 gap-4"></div>
|
| </div>
|
|
|
| |
| <div class="bg-gray-50 p-8"> |
| <h2 class="text-2xl font-semibold text-primary mb-6 flex items-center gap-2">
|
| <svg class="h-8 w-8 text-primary" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2a4 4 0 00-4-4H3"></path>
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a4 4 0 014 4v2"></path>
|
| </svg>
|
| Choix du type d'analyse
|
| </h2>
|
| <div class="space-y-6">
|
| <label class="flex flex-col p-5 bg-white cursor-pointer hover:bg-gray-100 transition-colors duration-300">
|
| <div class="flex items-center">
|
| <input type="radio" name="analysis_type" value="text" class="h-5 w-5 text-primary" />
|
| <span class="ml-3 text-lg font-medium text-secondary">🔍 Analyse de Texte</span>
|
| </div>
|
| <p class="text-gray-500 text-sm ml-8 mt-1">
|
| Analyse de texte détaillée comprenant INTRODUCTION, SUMMARY, COMMENTARY et EVALUATION.
|
| </p>
|
| </label>
|
| <label class="flex flex-col p-5 bg-white cursor-pointer hover:bg-gray-100 transition-colors duration-300">
|
| <div class="flex items-center">
|
| <input type="radio" name="analysis_type" value="icon" class="h-5 w-5 text-primary" />
|
| <span class="ml-3 text-lg font-medium text-secondary">🧠 Document iconographique</span>
|
| </div>
|
| <p class="text-gray-500 text-sm ml-8 mt-1">
|
| Analyse et interprétation du document iconographique fourni.
|
| </p>
|
| </label>
|
| </div>
|
| <button id="submit-btn" class="w-full mt-8 bg-primary hover:bg-primary-dark text-white font-bold py-4 px-6 transition-colors duration-300 disabled:opacity-50 disabled:cursor-not-allowed">
|
| 🚀 Soumettre
|
| </button>
|
| </div>
|
| </div>
|
|
|
| |
| <div id="results" class="mt-16 bg-gray-50 p-8 hidden animate-fade-in"> |
| <div class="flex justify-between items-center mb-6">
|
| <h2 class="text-2xl font-semibold text-primary">📝 Résultat de l'analyse</h2>
|
| <button onclick="copyResults()" class="flex items-center gap-2 bg-primary/10 hover:bg-primary/20 text-primary px-4 py-2 rounded-lg transition-all duration-300">
|
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path>
|
| </svg>
|
| Copier
|
| </button>
|
| </div>
|
| <div id="analysis-result" class="markdown-content prose max-w-none"></div>
|
| </div>
|
|
|
| |
| <footer class="mt-16 text-center text-gray-600"> |
| <hr class="my-6 border-gray-300" /> |
| <p>© 2025 Mariam AI - Tous droits réservés.</p> |
| </footer> |
| </div>
|
|
|
| <script>
|
| document.addEventListener('DOMContentLoaded', () => {
|
| const imageUpload = document.getElementById('image-upload');
|
| const previewContainer = document.getElementById('preview-container');
|
| const submitBtn = document.getElementById('submit-btn');
|
| const resultsSection = document.getElementById('results');
|
| const analysisResult = document.getElementById('analysis-result');
|
|
|
| let uploadedFiles = [];
|
|
|
| marked.setOptions({
|
| breaks: true,
|
| gfm: true,
|
| headerIds: true,
|
| mangle: false,
|
| smartLists: true,
|
| smartypants: true,
|
| xhtml: true
|
| });
|
|
|
| imageUpload.addEventListener('change', handleFileSelect);
|
| submitBtn.addEventListener('click', handleSubmit);
|
|
|
| const uploadZone = document.querySelector('.upload-zone');
|
|
|
| uploadZone.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| uploadZone.classList.add('bg-gray-200'); |
| }); |
| |
| uploadZone.addEventListener('dragleave', () => { |
| uploadZone.classList.remove('bg-gray-200'); |
| }); |
|
|
| uploadZone.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| uploadZone.classList.remove('bg-gray-200'); |
| uploadedFiles = Array.from(e.dataTransfer.files).filter(file => file.type.startsWith('image/')); |
| updatePreview(); |
| }); |
|
|
| function handleFileSelect(event) {
|
| uploadedFiles = Array.from(event.target.files);
|
| updatePreview();
|
| }
|
|
|
| function updatePreview() {
|
| previewContainer.innerHTML = '';
|
| uploadedFiles.forEach((file, index) => {
|
| const preview = document.createElement('div');
|
| preview.className = 'relative group';
|
| preview.innerHTML = `
|
| <img src="${URL.createObjectURL(file)}" alt="Preview" class="w-full h-32 object-cover rounded-lg shadow group-hover:opacity-75 transition-all duration-300">
|
| <button onclick="removeImage(${index})" class="absolute top-2 right-2 bg-red-500 text-white rounded-full p-2 opacity-0 group-hover:opacity-100 hover:bg-red-600 transition-all duration-300 shadow">
|
| <svg class="w-4 h-4" 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"></path>
|
| </svg>
|
| </button>
|
| <div class="mt-2 text-sm text-gray-600 truncate">${file.name}</div>
|
| `;
|
| previewContainer.appendChild(preview);
|
| });
|
| submitBtn.disabled = uploadedFiles.length === 0;
|
| }
|
|
|
| function removeImage(index) {
|
| uploadedFiles.splice(index, 1);
|
| updatePreview();
|
| }
|
|
|
| window.copyResults = function() {
|
| const textToCopy = analysisResult.textContent;
|
| navigator.clipboard.writeText(textToCopy).then(() => {
|
| const copyButton = document.querySelector('button[onclick="copyResults()"]');
|
| const originalContent = copyButton.innerHTML;
|
| copyButton.innerHTML = `
|
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
| </svg>
|
| Copié!
|
| `;
|
| setTimeout(() => {
|
| copyButton.innerHTML = originalContent;
|
| }, 2000);
|
| });
|
| };
|
|
|
| async function handleSubmit() { |
| if (uploadedFiles.length === 0) { |
| alert('Veuillez sélectionner au moins une image.'); |
| return; |
| } |
| |
| const analysisType = document.querySelector('input[name="analysis_type"]:checked')?.value; |
| if (!analysisType) { |
| alert('Veuillez sélectionner un type d\'analyse.'); |
| return; |
| } |
| |
| const formData = new FormData(); |
| formData.append('analysis_type', analysisType); |
| uploadedFiles.forEach(file => formData.append('images', file)); |
| |
| submitBtn.disabled = true; |
| submitBtn.innerHTML = '<span class="animate-pulse">Analyse en cours...</span>'; |
| |
| |
| analysisResult.innerHTML = '<p class="text-gray-500 italic">Génération de l\'analyse en cours...</p>'; |
| resultsSection.classList.remove('hidden'); |
| resultsSection.scrollIntoView({ behavior: 'smooth' }); |
| |
| try { |
| const response = await fetch('/analyze', { |
| method: 'POST', |
| body: formData |
| }); |
| |
| if (!response.ok) { |
| const errorData = await response.json(); |
| throw new Error(errorData.error || 'Erreur inconnue'); |
| } |
| |
| const reader = response.body.getReader(); |
| const decoder = new TextDecoder(); |
| let result = ''; |
| |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) break; |
| const chunk = decoder.decode(value, { stream: true }); |
| result += chunk; |
| let markdownContent = result.replace(/\n\n/g, '\n \n'); |
| analysisResult.innerHTML = marked.parse(markdownContent); |
| } |
| } catch (error) { |
| analysisResult.innerHTML = '<p class="text-red-500">Erreur lors de l\'analyse: ' + error.message + '</p>'; |
| } finally { |
| submitBtn.disabled = false; |
| submitBtn.innerHTML = '🚀 Soumettre'; |
| } |
| } |
|
|
| window.removeImage = removeImage;
|
| });
|
| </script>
|
| </body>
|
| </html> |