// Dashboard functionality for Marine Species API class MarineDashboard { constructor() { this.currentImage = null; this.currentImageFile = null; this.isProcessing = false; this.initializeElements(); this.bindEvents(); this.checkAPIStatus(); this.loadSampleImages(); } initializeElements() { // Upload elements this.uploadArea = document.getElementById('uploadArea'); this.fileInput = document.getElementById('fileInput'); this.identifyBtn = document.getElementById('identifyBtn'); // Settings elements this.confidenceSlider = document.getElementById('confidenceSlider'); this.confidenceValue = document.getElementById('confidenceValue'); this.iouSlider = document.getElementById('iouSlider'); this.iouValue = document.getElementById('iouValue'); // Display elements this.annotatedImageContainer = document.getElementById('annotatedImageContainer'); // Results elements this.metadataSection = document.getElementById('metadataSection'); this.speciesSection = document.getElementById('speciesSection'); this.processingTime = document.getElementById('processingTime'); this.speciesCount = document.getElementById('speciesCount'); this.imageSize = document.getElementById('imageSize'); this.speciesList = document.getElementById('speciesList'); // Status elements this.statusDot = document.getElementById('statusDot'); this.statusText = document.getElementById('statusText'); this.modelInfo = document.getElementById('modelInfo'); // Model details elements this.totalSpecies = document.getElementById('totalSpecies'); this.deviceInfo = document.getElementById('deviceInfo'); // Sample images this.sampleImagesSlider = document.getElementById('sampleImagesSlider'); this.sliderPrev = document.getElementById('sliderPrev'); this.sliderNext = document.getElementById('sliderNext'); } bindEvents() { // Upload area events this.uploadArea.addEventListener('click', () => this.fileInput.click()); this.uploadArea.addEventListener('dragover', this.handleDragOver.bind(this)); this.uploadArea.addEventListener('dragleave', this.handleDragLeave.bind(this)); this.uploadArea.addEventListener('drop', this.handleDrop.bind(this)); // File input change this.fileInput.addEventListener('change', this.handleFileSelect.bind(this)); // Settings sliders this.confidenceSlider.addEventListener('input', this.updateConfidenceValue.bind(this)); this.iouSlider.addEventListener('input', this.updateIouValue.bind(this)); // Identify button this.identifyBtn.addEventListener('click', this.identifySpecies.bind(this)); // Slider controls this.sliderPrev.addEventListener('click', this.scrollSliderLeft.bind(this)); this.sliderNext.addEventListener('click', this.scrollSliderRight.bind(this)); } // API Status Check async checkAPIStatus() { try { const response = await fetch('/api/v1/health'); const data = await response.json(); if (data.model_loaded) { this.statusDot.className = 'status-dot healthy'; this.statusText.textContent = 'API Ready'; if (data.model_info) { this.modelInfo.textContent = `Model: ${data.model_info.model_name} (${data.model_info.total_classes} species)`; this.totalSpecies.textContent = data.model_info.total_classes; this.deviceInfo.textContent = data.model_info.device; } } else { this.statusDot.className = 'status-dot error'; this.statusText.textContent = 'Model Loading...'; this.modelInfo.textContent = 'Please wait while the model loads'; } } catch (error) { this.statusDot.className = 'status-dot error'; this.statusText.textContent = 'API Unavailable'; this.modelInfo.textContent = 'Unable to connect to API'; console.error('API status check failed:', error); } } // Drag and Drop Handlers handleDragOver(e) { e.preventDefault(); this.uploadArea.classList.add('dragover'); } handleDragLeave(e) { e.preventDefault(); this.uploadArea.classList.remove('dragover'); } handleDrop(e) { e.preventDefault(); this.uploadArea.classList.remove('dragover'); const files = e.dataTransfer.files; if (files.length > 0) { this.processFile(files[0]); } } // File Selection Handler handleFileSelect(e) { const file = e.target.files[0]; if (file) { this.processFile(file); } } // Process Selected File processFile(file) { // Validate file type if (!file.type.startsWith('image/')) { window.MarineAPI.utils.showNotification('Please select an image file', 'error'); return; } // Validate file size (10MB limit) if (file.size > 10 * 1024 * 1024) { window.MarineAPI.utils.showNotification('File size must be less than 10MB', 'error'); return; } this.currentImageFile = file; this.displayUploadedImage(file); this.identifyBtn.disabled = false; // Update upload area this.uploadArea.classList.add('has-image'); } // Display Uploaded Image in Upload Area displayUploadedImage(file) { const reader = new FileReader(); reader.onload = (e) => { this.currentImage = e.target.result; this.uploadArea.innerHTML = ` Uploaded image `; }; reader.readAsDataURL(file); } // Settings Handlers updateConfidenceValue() { this.confidenceValue.textContent = `${this.confidenceSlider.value}%`; } updateIouValue() { this.iouValue.textContent = `${this.iouSlider.value}%`; } // Main Identification Function async identifySpecies() { if (!this.currentImage || this.isProcessing) return; this.isProcessing = true; window.MarineAPI.utils.setLoading(this.identifyBtn, true); try { // Prepare request data const requestData = { image: this.currentImage.split(',')[1], // Remove data:image/jpeg;base64, prefix confidence_threshold: this.confidenceSlider.value / 100, iou_threshold: this.iouSlider.value / 100, image_size: 640, return_annotated_image: true }; // Make API request const response = await fetch('/api/v1/detect', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestData) }); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } const result = await response.json(); this.displayResults(result); window.MarineAPI.utils.showNotification('Species identification completed!', 'success'); } catch (error) { console.error('Identification failed:', error); window.MarineAPI.utils.showNotification('Identification failed. Please try again.', 'error'); } finally { this.isProcessing = false; window.MarineAPI.utils.setLoading(this.identifyBtn, false); } } // Display Results displayResults(result) { const { detections, annotated_image, processing_time, image_dimensions } = result; // Display annotated image if (annotated_image) { this.annotatedImageContainer.innerHTML = ` Annotated results `; } // Update metadata this.processingTime.textContent = `${processing_time.toFixed(3)}s`; this.speciesCount.textContent = detections.length; this.imageSize.textContent = `${image_dimensions.width}×${image_dimensions.height}`; // Show metadata section this.metadataSection.style.display = 'block'; // Display species list if (detections.length > 0) { this.speciesList.innerHTML = detections.map(detection => `
${detection.class_name} ${(detection.confidence * 100).toFixed(1)}%
`).join(''); this.speciesSection.style.display = 'block'; } else { this.speciesList.innerHTML = '

No marine species detected. Try adjusting the confidence threshold.

'; this.speciesSection.style.display = 'block'; } } // Load Sample Images loadSampleImages() { const sampleImages = [ { name: 'crab.png', description: 'Crab Species' }, { name: 'fish.png', description: 'Fish Species' }, { name: 'fish_2.png', description: 'Fish Variety' }, { name: 'fish_3.png', description: 'Marine Fish' }, { name: 'fish_4.png', description: 'Ocean Fish' }, { name: 'fish_5.png', description: 'Deep Sea Fish' }, { name: 'flat_fish.png', description: 'Flatfish' }, { name: 'flat_red_fish.png', description: 'Red Flatfish' }, { name: 'jelly.png', description: 'Jellyfish' }, { name: 'jelly_2.png', description: 'Jellyfish Species' }, { name: 'jelly_3.png', description: 'Marine Jelly' }, { name: 'puff.png', description: 'Pufferfish' }, { name: 'red_fish.png', description: 'Red Fish' }, { name: 'red_fish_2.png', description: 'Red Fish Species' }, { name: 'scene.png', description: 'Marine Scene' }, { name: 'scene_2.png', description: 'Ocean Scene' }, { name: 'scene_3.png', description: 'Underwater Scene' }, { name: 'scene_4.png', description: 'Deep Sea Scene' }, { name: 'scene_5.png', description: 'Marine Habitat' }, { name: 'scene_6.png', description: 'Ocean Floor' }, { name: 'soft_coral.png', description: 'Soft Coral' }, { name: 'starfish.png', description: 'Starfish' }, { name: 'starfish_2.png', description: 'Sea Star' } ].map(img => ({ ...img, url: `https://huggingface.co/seamo-ai/marina-species-v1/resolve/main/images/${img.name}` })); this.sampleImagesSlider.innerHTML = sampleImages.map(image => `
${image.description}
${image.description}
`).join(''); } // Load Sample Image async loadSampleImage(imageUrl, imageName) { try { // Show loading state window.MarineAPI.utils.showNotification('Loading sample image...', 'info'); let finalUrl = imageUrl; // Try local URL first, fallback to HuggingFace if needed try { const testResponse = await fetch(imageUrl, { method: 'HEAD' }); if (!testResponse.ok) { finalUrl = `https://huggingface.co/seamo-ai/marina-species-v1/resolve/main/images/${imageName}`; } } catch (e) { finalUrl = `https://huggingface.co/seamo-ai/marina-species-v1/resolve/main/images/${imageName}`; } // Fetch the image const response = await fetch(finalUrl); if (!response.ok) { throw new Error(`Failed to fetch image: ${response.status}`); } const blob = await response.blob(); // Convert to File object const file = new File([blob], imageName, { type: blob.type }); // Process the file this.processFile(file); window.MarineAPI.utils.showNotification('Sample image loaded successfully!', 'success'); } catch (error) { console.error('Failed to load sample image:', error); window.MarineAPI.utils.showNotification('Failed to load sample image. Please try uploading your own image.', 'error'); } } // Slider Control Functions scrollSliderLeft() { const containerWidth = this.sampleImagesSlider.clientWidth; this.sampleImagesSlider.scrollBy({ left: -containerWidth, // Scroll by full container width (2 images) behavior: 'smooth' }); } scrollSliderRight() { const containerWidth = this.sampleImagesSlider.clientWidth; this.sampleImagesSlider.scrollBy({ left: containerWidth, // Scroll by full container width (2 images) behavior: 'smooth' }); } } // Global function for copy to clipboard (used by API docs) window.copyToClipboard = function(elementId) { const element = document.getElementById(elementId); if (!element) { console.error('Element not found:', elementId); return; } const text = element.textContent; if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(() => { window.MarineAPI.utils.showNotification('Code copied to clipboard!', 'success'); }).catch(err => { console.error('Failed to copy text: ', err); fallbackCopyTextToClipboard(text); }); } else { fallbackCopyTextToClipboard(text); } }; // Fallback copy function for older browsers function fallbackCopyTextToClipboard(text) { const textArea = document.createElement("textarea"); textArea.value = text; // Avoid scrolling to bottom textArea.style.top = "0"; textArea.style.left = "0"; textArea.style.position = "fixed"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const successful = document.execCommand('copy'); if (successful) { window.MarineAPI.utils.showNotification('Code copied to clipboard!', 'success'); } else { window.MarineAPI.utils.showNotification('Failed to copy code', 'error'); } } catch (err) { console.error('Fallback: Oops, unable to copy', err); window.MarineAPI.utils.showNotification('Failed to copy code', 'error'); } document.body.removeChild(textArea); } // API Documentation Tabs document.addEventListener('DOMContentLoaded', function() { const tabButtons = document.querySelectorAll('.tab-button'); const tabContents = document.querySelectorAll('.tab-content'); tabButtons.forEach(button => { button.addEventListener('click', function() { const targetTab = this.getAttribute('data-tab'); // Remove active class from all buttons and contents tabButtons.forEach(b => b.classList.remove('active')); tabContents.forEach(c => c.classList.remove('active')); // Add active class to clicked button this.classList.add('active'); // Show corresponding tab content const targetContent = document.getElementById(targetTab + '-tab'); if (targetContent) { targetContent.classList.add('active'); } }); }); }); // Copy code functionality function copyCode(elementId) { const codeElement = document.getElementById(elementId); const text = codeElement.textContent; navigator.clipboard.writeText(text).then(function() { // Find the copy button and show feedback const button = codeElement.parentElement.querySelector('.copy-button'); const originalText = button.textContent; button.textContent = 'Copied!'; button.classList.add('copied'); setTimeout(() => { button.textContent = originalText; button.classList.remove('copied'); }, 2000); }).catch(function(err) { console.error('Failed to copy text: ', err); }); } // Make dashboard instance globally available for sample image clicks let dashboard; // Initialize dashboard when DOM is loaded document.addEventListener('DOMContentLoaded', () => { dashboard = new MarineDashboard(); });