Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Iris Flower Prediction - Hugging Face Spaces</title> | |
| <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E🌸%3C/text%3E%3C/svg%3E"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary-color: #7c3aed; | |
| --primary-hover: #6d28d9; | |
| --secondary-color: #f59e0b; | |
| --success-color: #10b981; | |
| --error-color: #ef4444; | |
| --warning-color: #f59e0b; | |
| --text-primary: #1f2937; | |
| --text-secondary: #6b7280; | |
| --bg-primary: #ffffff; | |
| --bg-secondary: #f9fafb; | |
| --bg-tertiary: #f3f4f6; | |
| --border-color: #e5e7eb; | |
| --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); | |
| --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); | |
| --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| color: var(--text-primary); | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| color: white; | |
| } | |
| .header h1 { | |
| font-size: 3rem; | |
| font-weight: 700; | |
| margin-bottom: 1rem; | |
| text-shadow: 0 2px 4px rgba(0,0,0,0.1); | |
| } | |
| .header p { | |
| font-size: 1.2rem; | |
| opacity: 0.9; | |
| max-width: 600px; | |
| margin: 0 auto; | |
| } | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 2rem; | |
| align-items: start; | |
| } | |
| .prediction-card { | |
| background: var(--bg-primary); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| box-shadow: var(--shadow-lg); | |
| backdrop-filter: blur(10px); | |
| } | |
| .info-card { | |
| background: var(--bg-primary); | |
| border-radius: 16px; | |
| padding: 2rem; | |
| box-shadow: var(--shadow-lg); | |
| backdrop-filter: blur(10px); | |
| } | |
| .card-title { | |
| font-size: 1.5rem; | |
| font-weight: 600; | |
| margin-bottom: 1.5rem; | |
| color: var(--text-primary); | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .input-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| .input-label { | |
| display: block; | |
| font-weight: 500; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-primary); | |
| } | |
| .input-field { | |
| width: 100%; | |
| padding: 0.75rem 1rem; | |
| border: 2px solid var(--border-color); | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| transition: all 0.3s ease; | |
| background: var(--bg-secondary); | |
| } | |
| .input-field:focus { | |
| outline: none; | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); | |
| } | |
| .input-field.error { | |
| border-color: var(--error-color); | |
| } | |
| .button-group { | |
| display: flex; | |
| gap: 1rem; | |
| margin-bottom: 1.5rem; | |
| } | |
| .btn { | |
| padding: 0.75rem 1.5rem; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| flex: 1; | |
| } | |
| .btn-primary { | |
| background: var(--primary-color); | |
| color: white; | |
| } | |
| .btn-primary:hover { | |
| background: var(--primary-hover); | |
| transform: translateY(-1px); | |
| } | |
| .btn-secondary { | |
| background: var(--bg-tertiary); | |
| color: var(--text-primary); | |
| } | |
| .btn-secondary:hover { | |
| background: var(--border-color); | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .result-container { | |
| min-height: 120px; | |
| border-radius: 8px; | |
| padding: 1rem; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| text-align: center; | |
| transition: all 0.3s ease; | |
| } | |
| .result-container.success { | |
| background: rgba(16, 185, 129, 0.1); | |
| border: 1px solid rgba(16, 185, 129, 0.2); | |
| color: var(--success-color); | |
| } | |
| .result-container.error { | |
| background: rgba(239, 68, 68, 0.1); | |
| border: 1px solid rgba(239, 68, 68, 0.2); | |
| color: var(--error-color); | |
| } | |
| .loading { | |
| display: inline-block; | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid rgba(124, 58, 237, 0.3); | |
| border-radius: 50%; | |
| border-top: 3px solid var(--primary-color); | |
| animation: spin 1s linear infinite; | |
| margin-right: 0.5rem; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .species-info { | |
| margin-top: 1rem; | |
| padding: 1rem; | |
| background: var(--bg-secondary); | |
| border-radius: 8px; | |
| border-left: 4px solid var(--primary-color); | |
| } | |
| .species-name { | |
| font-weight: 600; | |
| font-size: 1.1rem; | |
| margin-bottom: 0.5rem; | |
| text-transform: capitalize; | |
| } | |
| .confidence-bar { | |
| width: 100%; | |
| height: 8px; | |
| background: var(--bg-tertiary); | |
| border-radius: 4px; | |
| overflow: hidden; | |
| margin: 0.5rem 0; | |
| } | |
| .confidence-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--success-color), var(--primary-color)); | |
| transition: width 0.3s ease; | |
| } | |
| .info-grid { | |
| display: grid; | |
| gap: 1rem; | |
| } | |
| .info-item { | |
| padding: 1rem; | |
| background: var(--bg-secondary); | |
| border-radius: 8px; | |
| border-left: 4px solid var(--secondary-color); | |
| } | |
| .info-item h4 { | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-primary); | |
| } | |
| .info-item p { | |
| color: var(--text-secondary); | |
| font-size: 0.9rem; | |
| } | |
| .validation-message { | |
| color: var(--error-color); | |
| font-size: 0.875rem; | |
| margin-top: 0.5rem; | |
| display: none; | |
| } | |
| .validation-message.show { | |
| display: block; | |
| } | |
| .footer { | |
| text-align: center; | |
| margin-top: 3rem; | |
| color: rgba(255, 255, 255, 0.8); | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| .header h1 { | |
| font-size: 2rem; | |
| } | |
| .container { | |
| padding: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🌸 Iris Flower Prediction</h1> | |
| <p>Predict iris flower species using machine learning based on sepal length measurements</p> | |
| </div> | |
| <div class="main-content"> | |
| <div class="prediction-card"> | |
| <div class="card-title"> | |
| <span>🔮</span> | |
| Make Prediction | |
| </div> | |
| <div class="input-group"> | |
| <label class="input-label" for="sepalLength">Sepal Length (cm)</label> | |
| <input | |
| type="number" | |
| id="sepalLength" | |
| class="input-field" | |
| step="0.1" | |
| placeholder="Enter sepal length (e.g., 5.1)" | |
| min="0.1" | |
| max="10.0" | |
| > | |
| <div class="validation-message" id="validationMessage"></div> | |
| </div> | |
| <div class="button-group"> | |
| <button onclick="predictIris()" id="predictButton" class="btn btn-primary"> | |
| Predict Species | |
| </button> | |
| <button onclick="resetForm()" class="btn btn-secondary"> | |
| Reset | |
| </button> | |
| </div> | |
| <div id="predictionResult" class="result-container"> | |
| Enter a sepal length to get started | |
| </div> | |
| </div> | |
| <div class="info-card"> | |
| <div class="card-title"> | |
| <span>ℹ️</span> | |
| About Iris Flowers | |
| </div> | |
| <div class="info-grid"> | |
| <div class="info-item"> | |
| <h4>Iris Setosa</h4> | |
| <p>Small flowers with narrow petals, typically blue to purple. Grows in wet areas.</p> | |
| </div> | |
| <div class="info-item"> | |
| <h4>Iris Versicolor</h4> | |
| <p>Medium-sized flowers with wider petals, blue to purple with yellow markings.</p> | |
| </div> | |
| <div class="info-item"> | |
| <h4>Iris Virginica</h4> | |
| <p>Large flowers with broad petals, blue to purple (sometimes white).</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="footer"> | |
| <p>Built with ❤️ for Hugging Face Spaces</p> | |
| </div> | |
| </div> | |
| <script> | |
| const sepalLengthInput = document.getElementById('sepalLength'); | |
| const predictionResultDiv = document.getElementById('predictionResult'); | |
| const predictButton = document.getElementById('predictButton'); | |
| const validationMessage = document.getElementById('validationMessage'); | |
| function validateInput(value) { | |
| if (isNaN(value) || value <= 0) { | |
| validationMessage.textContent = 'Please enter a valid positive number'; | |
| validationMessage.classList.add('show'); | |
| sepalLengthInput.classList.add('error'); | |
| return false; | |
| } | |
| if (value > 10) { | |
| validationMessage.textContent = 'Sepal length should be less than 10 cm'; | |
| validationMessage.classList.add('show'); | |
| sepalLengthInput.classList.add('error'); | |
| return false; | |
| } | |
| validationMessage.classList.remove('show'); | |
| sepalLengthInput.classList.remove('error'); | |
| return true; | |
| } | |
| function resetForm() { | |
| sepalLengthInput.value = ''; | |
| predictionResultDiv.textContent = 'Enter a sepal length to get started'; | |
| predictionResultDiv.className = 'result-container'; | |
| validationMessage.classList.remove('show'); | |
| sepalLengthInput.classList.remove('error'); | |
| } | |
| async function predictIris() { | |
| const sepalLength = parseFloat(sepalLengthInput.value); | |
| if (!validateInput(sepalLength)) { | |
| return; | |
| } | |
| // Show loading state | |
| predictButton.disabled = true; | |
| predictionResultDiv.innerHTML = '<div class="loading"></div> Analyzing iris flower...'; | |
| predictionResultDiv.className = 'result-container'; | |
| const data = { | |
| sepal_length: sepalLength | |
| }; | |
| try { | |
| const response = await fetch('/api/predict', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(data) | |
| }); | |
| if (!response.ok) { | |
| const errorData = await response.json().catch(() => ({ detail: 'Unknown server error' })); | |
| throw new Error(errorData.detail || `HTTP error! status: ${response.status}`); | |
| } | |
| const result = await response.json(); | |
| // Create result HTML | |
| const confidencePercent = Math.round(result.confidence * 100); | |
| const resultHTML = ` | |
| <div> | |
| <div style="font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem;"> | |
| ${result.prediction.charAt(0).toUpperCase() + result.prediction.slice(1)} | |
| </div> | |
| <div style="margin-bottom: 1rem;"> | |
| Confidence: ${confidencePercent}% | |
| </div> | |
| <div class="confidence-bar"> | |
| <div class="confidence-fill" style="width: ${confidencePercent}%"></div> | |
| </div> | |
| <div class="species-info"> | |
| <div class="species-name">${result.prediction.charAt(0).toUpperCase() + result.prediction.slice(1)}</div> | |
| <div style="font-size: 0.9rem; color: var(--text-secondary);"> | |
| ${result.species_info.description || ''} | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| predictionResultDiv.innerHTML = resultHTML; | |
| predictionResultDiv.className = 'result-container success'; | |
| } catch (error) { | |
| console.error('Error:', error); | |
| predictionResultDiv.textContent = `Error: ${error.message}`; | |
| predictionResultDiv.className = 'result-container error'; | |
| } finally { | |
| predictButton.disabled = false; | |
| } | |
| } | |
| // Add input validation on change | |
| sepalLengthInput.addEventListener('input', function() { | |
| validateInput(parseFloat(this.value)); | |
| }); | |
| // Add enter key support | |
| sepalLengthInput.addEventListener('keypress', function(e) { | |
| if (e.key === 'Enter') { | |
| predictIris(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |