Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Insect Classifier</title> | |
| <style> | |
| /* Hapus kata 'padding' yang salah posisi */ | |
| body { | |
| font-family: Arial, sans-serif; | |
| background: #f0f8ff; | |
| text-align: center; | |
| margin: 0; | |
| padding: 20px; | |
| } | |
| h1 { | |
| color: #333; | |
| } | |
| h4 { | |
| color: #666; | |
| margin-top: 0; | |
| } | |
| .container { | |
| display: flex; | |
| justify-content: center; | |
| gap: 30px; | |
| margin-top: 30px; | |
| } | |
| .card { | |
| background: white; | |
| padding: 20px; | |
| border-radius: 12px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); | |
| width: 300px; | |
| height: 450px; /* Diperhatikan apakah cukup tinggi jika deskripsi panjang */ | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| input[type="file"] { | |
| margin: 10px 0; | |
| } | |
| /* #modelSelect tidak digunakan di HTML ini, bisa dihapus jika tidak ada elemennya */ | |
| /* | |
| #modelSelect { | |
| margin: 10px 0; | |
| padding: 5px; | |
| font-size: 14px; | |
| } | |
| */ | |
| #preview { | |
| max-width: 90%; | |
| max-height: 150px; | |
| border-radius: 10px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.2); | |
| margin-bottom: 10px; | |
| } | |
| button { | |
| background-color: #4CAF50; | |
| color: white; | |
| padding: 10px 20px; | |
| border: none; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| } | |
| button:hover { | |
| background-color: #45a049; | |
| } | |
| .loading { | |
| display: none; | |
| margin-top: 10px; | |
| font-style: italic; | |
| color: #555; | |
| } | |
| #result { | |
| font-size: 18px; | |
| font-weight: bold; | |
| color: #333; | |
| margin-top: 20px; | |
| white-space: pre-line; | |
| } | |
| /* Menggabungkan style description dan pseudo-elementnya */ | |
| #description { | |
| margin-top: 20px; | |
| text-align: justify; | |
| font-size: 14px; | |
| color: #555; | |
| border-top: 1px solid #ccc; /* Garis pemisah */ | |
| padding-top: 15px; /* Spasi setelah garis */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>Insect Classification</h1> | |
| <h4>Diah Retno Utami - 4TIB</h4> | |
| <div class="container"> | |
| <div class="card"> | |
| <h2>Upload Image</h2> | |
| <input type="file" id="upload" accept="image/*" onchange="previewImage()"/> | |
| <img id="preview" src="" alt="Image Preview"/> | |
| <button onclick="uploadImage()">Predict</button> | |
| </div> | |
| <div class="card"> | |
| <h2>Prediction Result</h2> | |
| <div id="result">No prediction yet.</div> | |
| <p class="loading" id="loading">Predicting...</p> | |
| <div id="description"></div> | |
| </div> | |
| </div> | |
| <script> | |
| function previewImage() { | |
| const file = document.getElementById('upload').files[0]; | |
| if (file) { // Tambahkan pengecekan file ada atau tidak | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| document.getElementById('preview').src = e.target.result; | |
| } | |
| reader.readAsDataURL(file); | |
| } else { | |
| document.getElementById('preview').src = ""; // Reset gambar jika tidak ada file | |
| } | |
| } | |
| async function uploadImage() { | |
| const fileInput = document.getElementById('upload'); | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| alert('Please select an image.'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append("file", file); | |
| document.getElementById('loading').style.display = 'block'; | |
| document.getElementById('result').innerText = 'No prediction yet.'; | |
| document.getElementById('description').innerText = ''; // Reset deskripsi | |
| try { | |
| // !!! KRITIKAL: UBAH URL API INI UNTUK KOMPATIBILITAS DEPLOYMENT DAN LOKAL !!! | |
| const response = await fetch('/predict/', { // <-- PERUBAHAN DI SINI | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { // Tambahkan pengecekan jika response tidak OK (misal 404, 500) | |
| const errorText = await response.text(); | |
| throw new Error(`HTTP error! status: ${response.status}, message: ${errorText}`); | |
| } | |
| const result = await response.json(); | |
| document.getElementById('result').innerText = | |
| `CNN Model:\n Predicted Class: ${result.cnn.predicted_class}\n Accuracy: ${(result.cnn.confidence * 100).toFixed(2)}%\n\n` + | |
| `MobileNetV2 Model:\n Predicted Class: ${result.mobilenet.predicted_class}\n Accuracy: ${(result.mobilenet.confidence * 100).toFixed(2)}%`; | |
| // !!! KRITIKAL: GUNAKAN overall_description DARI BACKEND !!! | |
| document.getElementById('description').innerText = result.overall_description; // <-- PERUBAHAN DI SINI | |
| } catch (error) { | |
| console.error("Error during prediction:", error); // Log error lebih detail untuk debugging | |
| document.getElementById('result').innerText = `Error during prediction: ${error.message || error}`; | |
| document.getElementById('description').innerText = 'Failed to get description.'; | |
| } finally { | |
| document.getElementById('loading').style.display = 'none'; | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |