Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>ChiliGuard • Smart Leaf Disease Detector</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" /> | |
| <style> | |
| :root { | |
| --bg: #0f172a; | |
| --main-card-bg: #1e293b; | |
| --header-green: #065f46; | |
| --accent-green: #10b981; | |
| --text-white: #ffffff; | |
| --text-dim: #94a3b8; | |
| --danger-red: #ef4444; | |
| --border: rgba(255, 255, 255, 0.1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | |
| background-color: var(--bg); | |
| color: var(--text-white); | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 20px; | |
| } | |
| .main-container { | |
| width: 100%; | |
| max-width: 1100px; | |
| background-color: var(--main-card-bg); | |
| border-radius: 24px; | |
| overflow: hidden; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| border: 1px solid var(--border); | |
| } | |
| /* Header Styles */ | |
| .header { | |
| background-color: var(--header-green); | |
| padding: 30px 20px; | |
| text-align: center; | |
| } | |
| .header h1 { | |
| font-size: 3rem; | |
| font-weight: 800; | |
| letter-spacing: -1px; | |
| margin-bottom: 4px; | |
| } | |
| .header p { | |
| font-size: 1rem; | |
| opacity: 0.9; | |
| font-weight: 500; | |
| } | |
| /* Upload Area Styles */ | |
| .upload-section { | |
| padding: 25px; | |
| display: flex; | |
| align-items: center; | |
| gap: 20px; | |
| background-color: rgba(0, 0, 0, 0.15); | |
| } | |
| .drop-zone { | |
| flex: 1; | |
| border: 2px dashed var(--accent-green); | |
| border-radius: 16px; | |
| padding: 20px; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| background-color: rgba(16, 185, 129, 0.03); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .drop-zone:hover { | |
| background-color: rgba(16, 185, 129, 0.08); | |
| transform: translateY(-2px); | |
| } | |
| .drop-zone i { | |
| font-size: 2.5rem; | |
| color: var(--accent-green); | |
| margin-bottom: 8px; | |
| } | |
| .drop-zone p { | |
| font-size: 0.95rem; | |
| color: var(--text-white); | |
| margin-bottom: 4px; | |
| } | |
| #fileName { | |
| font-size: 0.85rem; | |
| color: var(--accent-green); | |
| font-weight: 600; | |
| } | |
| .analyze-btn { | |
| background-color: #475569; | |
| color: white; | |
| border: none; | |
| padding: 14px 28px; | |
| border-radius: 12px; | |
| font-size: 1rem; | |
| font-weight: 700; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| transition: all 0.2s; | |
| height: fit-content; | |
| } | |
| .analyze-btn:hover:not(:disabled) { | |
| background-color: #64748b; | |
| transform: scale(1.02); | |
| } | |
| .analyze-btn:disabled { | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| /* Result Section Styles */ | |
| .result-container { | |
| display: none; | |
| padding: 30px; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 30px; | |
| align-items: center; | |
| } | |
| .image-side { | |
| position: relative; | |
| background-color: #000; | |
| border-radius: 16px; | |
| overflow: hidden; | |
| box-shadow: 0 15px 30px rgba(0, 0, 0, 0.4); | |
| } | |
| .image-side img { | |
| width: 100%; | |
| height: auto; | |
| display: block; | |
| } | |
| #detectionCanvas { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| pointer-events: none; | |
| } | |
| .text-side { | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| text-align: center; | |
| padding: 10px; | |
| } | |
| .disease-result { | |
| font-size: 4.5rem; | |
| font-weight: 900; | |
| color: var(--danger-red); | |
| line-height: 1.1; | |
| margin-bottom: 20px; | |
| text-transform: capitalize; | |
| text-shadow: 0 0 35px rgba(239, 68, 68, 0.4); | |
| } | |
| .confidence-result { | |
| font-size: 2rem; | |
| font-weight: 800; | |
| color: var(--accent-green); | |
| } | |
| .confidence-result span { | |
| color: var(--text-white); | |
| } | |
| /* Utils */ | |
| #loadingOverlay { | |
| position: fixed; | |
| inset: 0; | |
| background-color: rgba(15, 23, 42, 0.9); | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| flex-direction: column; | |
| z-index: 1000; | |
| gap: 20px; | |
| font-size: 1.3rem; | |
| font-weight: 600; | |
| } | |
| .spinner { | |
| width: 50px; | |
| height: 50px; | |
| border: 5px solid rgba(255, 255, 255, 0.1); | |
| border-top-color: var(--accent-green); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { | |
| transform: rotate(360deg); | |
| } | |
| } | |
| .error-msg { | |
| background-color: rgba(239, 68, 68, 0.1); | |
| color: var(--danger-red); | |
| padding: 15px; | |
| margin: 20px; | |
| border-radius: 10px; | |
| text-align: center; | |
| display: none; | |
| border: 1px solid var(--danger-red); | |
| } | |
| @media (max-width: 800px) { | |
| .result-container { | |
| grid-template-columns: 1fr; | |
| padding: 20px; | |
| } | |
| .disease-result { | |
| font-size: 3rem; | |
| } | |
| .header h1 { | |
| font-size: 2.2rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="main-container"> | |
| <!-- Brand Header --> | |
| <header class="header"> | |
| <h1>ChiliGuard</h1> | |
| <p>AI-Powered Chili Leaf Disease Detection</p> | |
| </header> | |
| <!-- Upload Interface --> | |
| <section class="upload-section"> | |
| <label for="fileInput" class="drop-zone" id="dropZone"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <p>Drop or click to upload chili leaf image</p> | |
| <div id="fileName">No file selected</div> | |
| <input type="file" id="fileInput" accept="image/*" hidden> | |
| </label> | |
| <button id="analyzeBtn" class="analyze-btn" disabled> | |
| <i class="fas fa-search"></i> Analyze Leaf | |
| </button> | |
| </section> | |
| <!-- Error Notification --> | |
| <div id="errorBox" class="error-msg"></div> | |
| <!-- Main Display for Results --> | |
| <main class="result-container" id="resultArea"> | |
| <!-- Left: Visual Analysis (YOLOv9c) --> | |
| <div class="image-side"> | |
| <img id="leafPreview" src="" alt="Analyzed Crop"> | |
| <canvas id="detectionCanvas"></canvas> | |
| </div> | |
| <!-- Right: Prediction Data (MobileNetV3) --> | |
| <div class="text-side"> | |
| <div id="diseaseName" class="disease-result"></div> | |
| <div class="confidence-result"> | |
| Confidence: <span id="confidenceValue"></span> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Global Loading Overlay --> | |
| <div id="loadingOverlay"> | |
| <div class="spinner"></div> | |
| <p>Analyzing Health Status...</p> | |
| </div> | |
| <script> | |
| const API_URL = "https://nomandiu9-chiliprediction.hf.space/predict"; | |
| const fileInput = document.getElementById('fileInput'); | |
| const analyzeBtn = document.getElementById('analyzeBtn'); | |
| const fileName = document.getElementById('fileName'); | |
| const errorBox = document.getElementById('errorBox'); | |
| const loadingOverlay = document.getElementById('loadingOverlay'); | |
| const resultArea = document.getElementById('resultArea'); | |
| const leafPreview = document.getElementById('leafPreview'); | |
| const diseaseNameDisplay = document.getElementById('diseaseName'); | |
| const confidenceDisplay = document.getElementById('confidenceValue'); | |
| const canvas = document.getElementById('detectionCanvas'); | |
| // File Selection Feedback | |
| fileInput.addEventListener('change', () => { | |
| if (fileInput.files.length > 0) { | |
| fileName.textContent = fileInput.files[0].name; | |
| analyzeBtn.disabled = false; | |
| errorBox.style.display = 'none'; | |
| } else { | |
| fileName.textContent = 'No file selected'; | |
| analyzeBtn.disabled = true; | |
| } | |
| }); | |
| // Form Submission | |
| analyzeBtn.addEventListener('click', async () => { | |
| const file = fileInput.files[0]; | |
| if (!file) return; | |
| loadingOverlay.style.display = 'flex'; | |
| analyzeBtn.disabled = true; | |
| errorBox.style.display = 'none'; | |
| resultArea.style.display = 'none'; | |
| const formData = new FormData(); | |
| formData.append('image', file); | |
| try { | |
| const response = await fetch(API_URL, { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) throw new Error(`Server status: ${response.status}`); | |
| const data = await response.json(); | |
| // Set Preview and Handle Canvas | |
| const url = URL.createObjectURL(file); | |
| leafPreview.src = url; | |
| // Update Text purely from MobileNetV3 | |
| diseaseNameDisplay.textContent = data.mobilenet.disease; | |
| confidenceDisplay.textContent = `${data.mobilenet.confidence.toFixed(2)}%`; | |
| leafPreview.onload = () => { | |
| // Draw boxes purely from YOLOv9c (No labels on boxes) | |
| drawDetections(data.yolo.boxes); | |
| resultArea.style.display = 'grid'; | |
| loadingOverlay.style.display = 'none'; | |
| analyzeBtn.disabled = false; | |
| }; | |
| } catch (err) { | |
| console.error(err); | |
| errorBox.textContent = `Error: ${err.message}`; | |
| errorBox.style.display = 'block'; | |
| loadingOverlay.style.display = 'none'; | |
| analyzeBtn.disabled = false; | |
| } | |
| }); | |
| function drawDetections(boxes) { | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = leafPreview.naturalWidth; | |
| canvas.height = leafPreview.naturalHeight; | |
| ctx.clearRect(0, 0, canvas.width, canvas.height); | |
| if (!boxes || boxes.length === 0) return; | |
| ctx.lineWidth = Math.max(4, Math.floor(canvas.width / 110)); | |
| ctx.strokeStyle = '#ef4444'; // Red rectangle per mockup | |
| boxes.forEach(box => { | |
| const [x1, y1, x2, y2] = box.bbox; | |
| ctx.strokeRect(x1, y1, x2 - x1, y2 - y1); | |
| }); | |
| } | |
| // Drag and Drop support | |
| const dropZone = document.getElementById('dropZone'); | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropZone.addEventListener(eventName, e => { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| }, false); | |
| }); | |
| dropZone.addEventListener('drop', e => { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| fileInput.files = files; | |
| const event = new Event('change'); | |
| fileInput.dispatchEvent(event); | |
| }, false); | |
| </script> | |
| </body> | |
| </html> |