Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Prediction Results</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <style> | |
| body { | |
| background-color: #f4f8f4; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| .header { | |
| background-color: #28a745; | |
| color: white; | |
| padding: 20px; | |
| text-align: center; | |
| font-weight: bold; | |
| } | |
| .container { | |
| margin-top: 30px; | |
| padding: 20px; | |
| background-color: white; | |
| border-radius: 15px; | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.1); | |
| opacity: 0; | |
| transition: opacity 0.5s ease-in-out; | |
| } | |
| .result-label { | |
| font-weight: bold; | |
| color: #555; | |
| } | |
| .form-control[readonly] { | |
| font-size: 1.2rem; | |
| text-align: center; | |
| font-weight: bold; | |
| color: #28a745; | |
| background-color: #e9f5e9; | |
| border: 2px solid #28a745; | |
| } | |
| .gauge-container { | |
| margin-top: 20px; | |
| text-align: center; | |
| } | |
| .info-card { | |
| background-color: #f8f9fa; | |
| border: 1px solid #dee2e6; | |
| border-radius: 10px; | |
| padding: 25px; | |
| margin-top: 40px; | |
| text-align: center; | |
| } | |
| .btn-start-motor { | |
| background-color: #28a745; | |
| color: white; | |
| font-size: 1.2rem; | |
| padding: 12px 25px; | |
| font-weight: bold; | |
| border-radius: 8px; | |
| transition: background-color 0.3s; | |
| } | |
| .btn-start-motor:hover { | |
| background-color: #218838; | |
| } | |
| .camera-container { | |
| margin-top: 20px; | |
| display: none; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 20px; | |
| } | |
| video { | |
| width: 100%; | |
| max-width: 720px; | |
| height: auto; | |
| border-radius: 10px; | |
| border: 2px solid #ddd; | |
| background-color: #000; | |
| } | |
| .timer { | |
| font-size: 2rem; | |
| color: white; | |
| background-color: #28a745; | |
| font-weight: bold; | |
| padding: 10px 25px; | |
| border-radius: 10px; | |
| display: inline-block; | |
| min-width: 120px; | |
| text-align: center; | |
| } | |
| .timer-finished { | |
| font-size: 1.5rem; | |
| padding: 15px 40px; | |
| background-color: #28a745; | |
| } | |
| .alert-message { | |
| font-size: 1.5rem; | |
| color: #dc3545; | |
| font-weight: bold; | |
| margin-top: 20px; | |
| text-align: center; | |
| } | |
| /* Modal small style tweak */ | |
| .modal-header { | |
| background-color: #28a745; | |
| color: white; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>Water Requirement Prediction Results</h1> | |
| </div> | |
| <div class="container"> | |
| <div class="row"> | |
| <!-- Water Requirement --> | |
| <div class="col-md-6 mb-4"> | |
| <label for="water_requirement" class="result-label">Predicted Water Requirement (mΒ³/sq.m):</label> | |
| <input type="text" class="form-control" id="water_requirement" value="{{ water_requirement }}" readonly> | |
| <div class="gauge-container"> | |
| {{ water_gauge|safe }} | |
| </div> | |
| </div> | |
| <!-- Motor On Time --> | |
| <div class="col-md-6 mb-4"> | |
| <label for="estimated_time" class="result-label">Estimated Motor On-Time:</label> | |
| <input type="text" class="form-control" id="estimated_time" value="{{ estimated_time_duration }} {{ time_unit }}" readonly> | |
| <div class="gauge-container"> | |
| {{ time_gauge|safe }} | |
| </div> | |
| </div> | |
| </div> | |
| <!-- AI Monitoring Section --> | |
| <div class="info-card"> | |
| <h4>Want to automatically inspect your irrigation process?</h4> | |
| <p class="mt-3">Use our AI-driven Irrigation Monitoring to watch the process from your phone. You will receive a WhatsApp message to start the motor. Once started, you can monitor it live here.</p> | |
| <button id="startMonitoring" class="btn btn-start-motor">Start AI Monitoring</button> | |
| </div> | |
| <!-- The camera and timer --> | |
| <div class="camera-container" id="cameraContainer"> | |
| <h4 class="text-center mb-0">Live Irrigation Feed</h4> | |
| <video id="videoElement" autoplay playsinline></video> | |
| <div class="timer" id="timer"></div> | |
| </div> | |
| <div class="alert-message" id="alertMessage"></div> | |
| </div> | |
| <!-- --- START: Modal asking 1 or 0 --- --> | |
| <div class="modal fade" id="startModal" tabindex="-1" aria-labelledby="startModalLabel" aria-hidden="true"> | |
| <div class="modal-dialog modal-dialog-centered"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title" id="startModalLabel">Start Irrigation</h5> | |
| <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> | |
| </div> | |
| <div class="modal-body"> | |
| <p>Would you like to start the motor now?</p> | |
| <p class="mb-0"><strong>Reply 1</strong> = Start <strong>Reply 0</strong> = Cancel</p> | |
| </div> | |
| <div class="modal-footer"> | |
| <button id="modalCancelBtn" type="button" class="btn btn-secondary" data-bs-dismiss="modal">0 β Don't Start</button> | |
| <button id="modalStartBtn" type="button" class="btn btn-success">1 β Start</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- --- END: Modal --- --> | |
| <!-- Bootstrap bundle (includes Popper) --> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script> | |
| <script> | |
| // Fade in container | |
| window.onload = function() { | |
| document.querySelector('.container').style.opacity = 1; | |
| // Show the modal automatically on load | |
| var startModalEl = document.getElementById('startModal'); | |
| var bsModal = new bootstrap.Modal(startModalEl, { backdrop: 'static', keyboard: false }); | |
| bsModal.show(); | |
| // Button handlers for modal | |
| document.getElementById('modalStartBtn').addEventListener('click', function() { | |
| bsModal.hide(); | |
| // Programmatically click the Start Monitoring button so existing logic executes (camera + client timer + server POST) | |
| document.getElementById('startMonitoring').click(); | |
| }); | |
| document.getElementById('modalCancelBtn').addEventListener('click', function() { | |
| // User chose "0" - do not start monitoring | |
| bsModal.hide(); | |
| document.getElementById('alertMessage').textContent = "Motor was not started."; | |
| }); | |
| }; | |
| document.getElementById('startMonitoring').addEventListener('click', function() { | |
| document.getElementById('cameraContainer').style.display = 'flex'; | |
| if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | |
| navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } }) | |
| .then(function(stream) { | |
| document.getElementById('videoElement').srcObject = stream; | |
| }) | |
| .catch(function(err) { | |
| console.log("Camera Error: " + err); | |
| alert("Could not access the camera. Please ensure you have given permission."); | |
| }); | |
| } | |
| let estimatedTime = parseFloat("{{ estimated_time_duration }}"); | |
| let timeUnit = "{{ time_unit }}"; | |
| let estimatedTimeSeconds = timeUnit === "minutes" ? estimatedTime * 60 : estimatedTime; | |
| let timerElem = document.getElementById('timer'); | |
| let timeRemaining = Math.floor(estimatedTimeSeconds); | |
| // Note: start_motor fetch is already below. We keep it so server-side timer + completion messages work. | |
| let countdown = setInterval(function() { | |
| if (timeRemaining <= 0) { | |
| clearInterval(countdown); | |
| timerElem.textContent = "Finished"; | |
| timerElem.classList.add('timer-finished'); | |
| let beep = new Audio('/static/alarn_tune.mp3'); | |
| beep.play(); | |
| document.getElementById('alertMessage').textContent = "Irrigation time is over. Motor is off automatically."; | |
| } else { | |
| timerElem.textContent = timeRemaining + "s"; | |
| } | |
| timeRemaining--; | |
| }, 1000); | |
| // Call the server endpoint which now starts the server-side timer (so finalization/WhatsApp behavior remains consistent) | |
| fetch('/start_motor', { method: 'POST' }) | |
| .then(response => response.json()) | |
| .then(data => { | |
| console.log("Monitoring request sent:", data); | |
| if (data && data.status === "motor_started") { | |
| // optionally show a confirmation to user | |
| document.getElementById('alertMessage').textContent = `Motor started for ${data.estimated_time_duration} ${data.time_unit}.`; | |
| } else { | |
| if (data && data.status === "no_pending_task") { | |
| document.getElementById('alertMessage').textContent = "No pending irrigation task found. Please submit a new prediction."; | |
| } | |
| } | |
| }) | |
| .catch(error => { | |
| console.error("Error sending monitoring request:", error); | |
| document.getElementById('alertMessage').textContent = "Error starting motor on server. Check logs."; | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |