| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Iris Flower Classifier</title> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| min-height: 100vh; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| padding: 20px; |
| } |
| .container { |
| background: white; |
| border-radius: 20px; |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); |
| padding: 40px; |
| max-width: 500px; |
| width: 100%; |
| } |
| h1 { |
| text-align: center; |
| color: #333; |
| margin-bottom: 5px; |
| font-size: 28px; |
| } |
| .subtitle { |
| text-align: center; |
| color: #888; |
| font-size: 14px; |
| margin-bottom: 30px; |
| } |
| .form-group { |
| margin-bottom: 20px; |
| } |
| label { |
| display: block; |
| color: #555; |
| font-weight: 600; |
| margin-bottom: 6px; |
| font-size: 14px; |
| } |
| .input-row { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| input[type="range"] { |
| flex: 1; |
| -webkit-appearance: none; |
| height: 6px; |
| border-radius: 3px; |
| background: #ddd; |
| outline: none; |
| } |
| input[type="range"]::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| background: #764ba2; |
| cursor: pointer; |
| box-shadow: 0 2px 6px rgba(118, 75, 162, 0.4); |
| } |
| .value-display { |
| min-width: 50px; |
| text-align: center; |
| font-weight: 700; |
| color: #764ba2; |
| font-size: 16px; |
| } |
| .unit { |
| color: #aaa; |
| font-size: 12px; |
| min-width: 20px; |
| } |
| button { |
| width: 100%; |
| padding: 14px; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| border: none; |
| border-radius: 12px; |
| font-size: 16px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: transform 0.2s, box-shadow 0.2s; |
| margin-top: 10px; |
| } |
| button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 25px rgba(118, 75, 162, 0.4); |
| } |
| button:active { transform: translateY(0); } |
| .result { |
| margin-top: 25px; |
| padding: 25px; |
| border-radius: 16px; |
| text-align: center; |
| display: none; |
| animation: fadeIn 0.4s ease; |
| } |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| .result .emoji { font-size: 48px; margin-bottom: 10px; } |
| .result .species-name { |
| font-size: 24px; |
| font-weight: 700; |
| margin-bottom: 5px; |
| } |
| .result .confidence { |
| font-size: 14px; |
| opacity: 0.8; |
| margin-bottom: 15px; |
| } |
| .result .description { |
| font-size: 13px; |
| opacity: 0.7; |
| line-height: 1.5; |
| } |
| .prob-bars { |
| margin-top: 15px; |
| text-align: left; |
| } |
| .prob-bar { |
| margin-bottom: 8px; |
| } |
| .prob-bar .bar-label { |
| font-size: 12px; |
| color: #555; |
| margin-bottom: 3px; |
| display: flex; |
| justify-content: space-between; |
| } |
| .prob-bar .bar-track { |
| height: 8px; |
| background: #eee; |
| border-radius: 4px; |
| overflow: hidden; |
| } |
| .prob-bar .bar-fill { |
| height: 100%; |
| border-radius: 4px; |
| transition: width 0.6s ease; |
| } |
| .model-info { |
| text-align: center; |
| margin-top: 20px; |
| font-size: 11px; |
| color: #bbb; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>Iris Flower Classifier</h1> |
| <p class="subtitle">Enter flower measurements to predict the species</p> |
|
|
| <div class="form-group"> |
| <label>Sepal Length</label> |
| <div class="input-row"> |
| <input type="range" id="sepal_length" min="4.0" max="8.0" step="0.1" value="5.8"> |
| <span class="value-display" id="sepal_length_val">5.8</span> |
| <span class="unit">cm</span> |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label>Sepal Width</label> |
| <div class="input-row"> |
| <input type="range" id="sepal_width" min="2.0" max="4.5" step="0.1" value="3.0"> |
| <span class="value-display" id="sepal_width_val">3.0</span> |
| <span class="unit">cm</span> |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label>Petal Length</label> |
| <div class="input-row"> |
| <input type="range" id="petal_length" min="1.0" max="7.0" step="0.1" value="4.0"> |
| <span class="value-display" id="petal_length_val">4.0</span> |
| <span class="unit">cm</span> |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label>Petal Width</label> |
| <div class="input-row"> |
| <input type="range" id="petal_width" min="0.1" max="2.5" step="0.1" value="1.2"> |
| <span class="value-display" id="petal_width_val">1.2</span> |
| <span class="unit">cm</span> |
| </div> |
| </div> |
|
|
| <button onclick="predict()">Classify Flower</button> |
|
|
| <div class="result" id="result"></div> |
|
|
| <div class="model-info"> |
| Model: {{ model_name }} | Accuracy: {{ "%.1f"|format(accuracy * 100) }}% |
| </div> |
| </div> |
|
|
| <script> |
| |
| document.querySelectorAll('input[type="range"]').forEach(slider => { |
| const display = document.getElementById(slider.id + '_val'); |
| slider.addEventListener('input', () => { |
| display.textContent = parseFloat(slider.value).toFixed(1); |
| }); |
| }); |
| |
| async function predict() { |
| const data = { |
| sepal_length: document.getElementById('sepal_length').value, |
| sepal_width: document.getElementById('sepal_width').value, |
| petal_length: document.getElementById('petal_length').value, |
| petal_width: document.getElementById('petal_width').value, |
| }; |
| |
| try { |
| const response = await fetch('/predict', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify(data), |
| }); |
| const result = await response.json(); |
| |
| if (result.error) { |
| alert('Error: ' + result.error); |
| return; |
| } |
| |
| const resultDiv = document.getElementById('result'); |
| resultDiv.style.display = 'block'; |
| resultDiv.style.background = result.color + '15'; |
| resultDiv.style.border = '2px solid ' + result.color + '40'; |
| |
| let probBars = ''; |
| if (result.probabilities) { |
| probBars = '<div class="prob-bars">'; |
| for (const [species, prob] of Object.entries(result.probabilities)) { |
| probBars += ` |
| <div class="prob-bar"> |
| <div class="bar-label"> |
| <span>${species}</span> |
| <span>${prob}%</span> |
| </div> |
| <div class="bar-track"> |
| <div class="bar-fill" style="width: ${prob}%; background: ${result.color};"></div> |
| </div> |
| </div>`; |
| } |
| probBars += '</div>'; |
| } |
| |
| resultDiv.innerHTML = ` |
| <div class="emoji">${result.emoji}</div> |
| <div class="species-name" style="color: ${result.color}">${result.species}</div> |
| <div class="confidence">Confidence: ${result.confidence}%</div> |
| <div class="description">${result.description}</div> |
| ${probBars} |
| `; |
| } catch (err) { |
| alert('Error connecting to server'); |
| } |
| } |
| </script> |
| </body> |
| </html> |
|
|