Spaces:
Sleeping
Sleeping
| | |
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PlantAI - Detección Multimodelo</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { font-family: 'Plus Jakarta Sans', sans-serif; } | |
| .glass-effect { background: rgba(255, 255, 255, 0.8); backdrop-filter: blur(12px); } | |
| .gradient-bg { background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%); } | |
| </style> | |
| </head> | |
| <body class="gradient-bg min-h-screen text-gray-800"> | |
| <nav class="sticky top-0 z-40 w-full border-b border-green-100 bg-white/70 backdrop-blur-md"> | |
| <div class="max-w-7xl mx-auto px-4 h-16 flex items-center justify-between"> | |
| <div class="flex items-center gap-2"> | |
| <div class="bg-green-600 p-1.5 rounded-lg"> | |
| <i data-lucide="leaf" class="text-white w-5 h-5"></i> | |
| </div> | |
| <span class="text-xl font-bold tracking-tight text-gray-900">Plant<span class="text-green-600">AI</span></span> | |
| </div> | |
| </div> | |
| </nav> | |
| <main class="max-w-7xl mx-auto px-4 py-8"> | |
| <div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> | |
| <div class="lg:col-span-4 space-y-6"> | |
| <div class="bg-white rounded-3xl shadow-sm border border-green-100 p-6"> | |
| <h3 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2"> | |
| <i data-lucide="activity" class="text-green-600 w-5 h-5"></i> | |
| Estado del Sistema | |
| </h3> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div class="p-4 bg-green-50 rounded-2xl border border-green-100 text-center"> | |
| <div class="text-2xl font-bold text-green-700">5</div> | |
| <div class="text-xs font-medium text-green-600 uppercase">Modelos</div> | |
| </div> | |
| <div class="p-4 bg-blue-50 rounded-2xl border border-blue-100 text-center"> | |
| <div class="text-2xl font-bold text-blue-700">38</div> | |
| <div class="text-xs font-medium text-blue-600 uppercase">Clases</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-green-600 rounded-3xl p-6 text-white shadow-lg"> | |
| <h4 class="font-bold text-lg mb-2">Análisis Multimodal</h4> | |
| <p class="text-green-50 text-sm leading-relaxed"> | |
| Tu imagen será procesada por 5 arquitecturas diferentes (Básico, CNN, Dropout, Data Augmentation y Escala de Grises) para garantizar la máxima precisión. | |
| </p> | |
| </div> | |
| </div> | |
| <div class="lg:col-span-8 space-y-6"> | |
| <div class="bg-white rounded-3xl shadow-sm border border-gray-100 p-8"> | |
| <div id="drop-zone" class="relative group border-2 border-dashed border-gray-200 rounded-2xl p-12 text-center transition-all hover:border-green-400 hover:bg-green-50/50 cursor-pointer"> | |
| <input type="file" id="file-input" class="hidden" accept="image/*"> | |
| <div id="upload-placeholder"> | |
| <div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4"> | |
| <i data-lucide="camera" class="text-green-600 w-10 h-10"></i> | |
| </div> | |
| <h3 class="text-xl font-bold text-gray-900 mb-2">Subir imagen de la hoja</h3> | |
| <p class="text-gray-500 text-sm">Haz clic o arrastra para analizar</p> | |
| </div> | |
| <img id="image-preview" class="hidden rounded-2xl max-h-80 mx-auto shadow-xl ring-4 ring-white"> | |
| </div> | |
| <button id="predict-btn" disabled class="w-full mt-6 bg-gray-900 text-white py-4 rounded-2xl font-bold hover:bg-green-600 disabled:bg-gray-200 disabled:text-gray-400 transition-all flex items-center justify-center gap-3"> | |
| <i data-lucide="zap" class="w-5 h-5"></i> | |
| ANALIZAR EN TODOS LOS MODELOS | |
| </button> | |
| </div> | |
| <div id="results-section" class="hidden space-y-4"> | |
| <h3 class="text-xl font-bold text-gray-900 mb-4">Resultados Comparativos</h3> | |
| <div id="comparison-grid" class="grid gap-4"> | |
| </div> | |
| </div> | |
| <div id="empty-state" class="bg-gray-50 border border-dashed border-gray-200 rounded-3xl py-20 text-center"> | |
| <i data-lucide="layout-grid" class="w-12 h-12 text-gray-300 mx-auto mb-4"></i> | |
| <p class="text-gray-500 font-medium">Sube una imagen para ver el diagnóstico</p> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <div id="loader" class="hidden fixed inset-0 z-50 bg-white/90 backdrop-blur-md flex flex-col items-center justify-center"> | |
| <div class="relative w-24 h-24 mb-6"> | |
| <div class="absolute inset-0 border-4 border-green-600 rounded-full border-t-transparent animate-spin"></div> | |
| </div> | |
| <h2 class="text-2xl font-bold text-gray-900">Procesando Modelos...</h2> | |
| </div> | |
| <script> | |
| lucide.createIcons(); | |
| const dropZone = document.getElementById('drop-zone'); | |
| const fileInput = document.getElementById('file-input'); | |
| const preview = document.getElementById('image-preview'); | |
| const placeholder = document.getElementById('upload-placeholder'); | |
| const predictBtn = document.getElementById('predict-btn'); | |
| const grid = document.getElementById('comparison-grid'); | |
| const emptyState = document.getElementById('empty-state'); | |
| const resultsSection = document.getElementById('results-section'); | |
| dropZone.onclick = () => fileInput.click(); | |
| fileInput.onchange = (e) => { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = (ev) => { | |
| preview.src = ev.target.result; | |
| preview.classList.remove('hidden'); | |
| placeholder.classList.add('hidden'); | |
| predictBtn.disabled = false; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| }; | |
| predictBtn.onclick = async () => { | |
| const formData = new FormData(); | |
| formData.append('file', fileInput.files[0]); | |
| const loader = document.getElementById('loader'); | |
| loader.classList.remove('hidden'); | |
| try { | |
| const response = await fetch('/predict_all', { method: 'POST', body: formData }); | |
| const results = await response.json(); | |
| emptyState.classList.add('hidden'); | |
| resultsSection.classList.remove('hidden'); | |
| grid.innerHTML = ''; | |
| results.forEach(res => { | |
| // Definimos el estado general basado en tu lógica de Python | |
| const isHealthy = res.status === "Sana"; | |
| // Usamos colores para la etiqueta de estado, pero azul para la interfaz técnica | |
| const badgeColor = isHealthy ? "green" : "orange"; | |
| const labelText = isHealthy ? "SANA / SALUDABLE" : "PROBLEMA DETECTADO"; | |
| grid.innerHTML += ` | |
| <div class="bg-white border border-gray-200 rounded-2xl p-5 shadow-sm"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <div> | |
| <h4 class="font-bold text-gray-900">${res.name}</h4> | |
| <span class="text-[10px] bg-gray-100 text-gray-500 px-2 py-0.5 rounded-full uppercase font-bold tracking-tighter">Análisis de Red</span> | |
| </div> | |
| <div class="px-3 py-1 rounded-lg bg-${badgeColor}-50 border border-${badgeColor}-100"> | |
| <span class="text-[10px] font-extrabold text-${badgeColor}-700 tracking-tight">${labelText}</span> | |
| </div> | |
| </div> | |
| <div class="space-y-3"> | |
| <div> | |
| <div class="text-[10px] text-gray-400 uppercase font-bold mb-1 tracking-widest">Especie y Patología detectada</div> | |
| <div class="text-sm font-semibold text-gray-700 bg-gray-50 p-2.5 rounded-xl border border-gray-100"> | |
| ${res.prediction} | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between text-[10px] mb-1"> | |
| <span class="font-bold text-gray-400 uppercase">Nivel de Seguridad</span> | |
| <span class="font-bold text-gray-600">${res.confidence}%</span> | |
| </div> | |
| <div class="w-full bg-gray-100 h-1.5 rounded-full overflow-hidden"> | |
| <div class="bg-blue-600 h-full rounded-full transition-all duration-1000 ease-out" | |
| style="width: ${res.confidence}%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| // Re-inicializar iconos de Lucide | |
| lucide.createIcons(); | |
| // Scroll suave a resultados | |
| resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } catch (e) { | |
| alert("Error de conexión con el servidor de IA"); | |
| console.error(e); | |
| } finally { | |
| loader.classList.add('hidden'); | |
| } | |
| }; | |
| </script> | |
| </body> | |
| </html> |