// static/js/script.js document.addEventListener('DOMContentLoaded', () => { // --- Get references to DOM elements --- const uploadForm = document.getElementById('upload-form'); const photoInput = document.getElementById('photo-input'); const fileNameDisplay = document.getElementById('file-name-display'); const resultsArea = document.getElementById('results-area'); const resultImage = document.getElementById('result-image'); const detectionButtons = document.getElementById('detection-buttons'); const birdInfoArea = document.getElementById('bird-info-area'); const loadingIndicator = document.getElementById('loading-indicator'); const errorMessage = document.getElementById('error-message'); // Bird Info Elements const birdNameEl = document.getElementById('bird-name'); const birdGenusEl = document.getElementById('bird-genus'); const birdLocationsEl = document.getElementById('bird-locations'); const birdMatingEl = document.getElementById('bird-mating'); const birdShortInfoEl = document.getElementById('bird-short-info'); const birdExampleImageEl = document.getElementById('bird-example-image'); const birdAudioEl = document.getElementById('bird-audio'); const chatButton = document.getElementById('chat-button'); const chatBirdNameSpan = document.getElementById('chat-bird-name'); // Chat Interface Elements const chatInterface = document.getElementById('chat-interface'); const chatHistory = document.getElementById('chat-history'); const chatInput = document.getElementById('chat-input'); const sendChatButton = document.getElementById('send-chat-button'); const chatInterfaceBirdNameSpan = document.getElementById('chat-interface-bird-name'); const chatLoadingIndicator = document.getElementById('chat-loading-indicator'); let currentBirdForChat = null; // Store the bird name for the chat context let currentChatHistory = []; // Store conversation history {role: 'user'/'assistant', content: '...'} // --- Update file name display on selection --- photoInput.addEventListener('change', () => { if (photoInput.files.length > 0) { fileNameDisplay.textContent = photoInput.files[0].name; } else { fileNameDisplay.textContent = 'No file chosen'; } }); // --- Handle Image Upload and Prediction --- uploadForm.addEventListener('submit', async (event) => { event.preventDefault(); // Prevent default form submission clearState(); // Clear previous results and errors if (!photoInput.files || photoInput.files.length === 0) { displayError('Please select an image file first.'); return; } loadingIndicator.style.display = 'flex'; // Show loading indicator const formData = new FormData(); formData.append('photo', photoInput.files[0]); try { const response = await fetch('/predict', { method: 'POST', body: formData, // Headers are not typically needed for FormData, browser sets Content-Type }); loadingIndicator.style.display = 'none'; // Hide loading if (!response.ok) { // Try to parse error message from backend JSON response let errorMsg = `HTTP error! Status: ${response.status}`; try { const errorData = await response.json(); errorMsg = errorData.error || errorMsg; } catch (e) { // If response is not JSON, use status text errorMsg = response.statusText || errorMsg; } throw new Error(errorMsg); } const data = await response.json(); // Display results if (data.result_image_url) { resultImage.src = data.result_image_url; resultImage.alt = "Processed image showing bird detections"; // Better alt text } else { resultImage.src = ""; // Clear image if no URL resultImage.alt = ""; } detectionButtons.innerHTML = ''; // Clear previous buttons if (data.detections && data.detections.length > 0) { data.detections.forEach(detection => { const button = document.createElement('button'); button.classList.add('button'); // Add button class for styling button.textContent = `${detection.name} (${(detection.confidence * 100).toFixed(0)}%)`; button.dataset.birdName = detection.name; // Store bird name in data attribute button.addEventListener('click', handleBirdButtonClick); detectionButtons.appendChild(button); }); } else { detectionButtons.innerHTML = '
No birds detected (or confidence too low).
'; } resultsArea.style.display = 'block'; // Show results section } catch (error) { console.error('Upload/Prediction Error:', error); displayError(`Prediction failed: ${error.message}`); loadingIndicator.style.display = 'none'; // Ensure loading is hidden on error } }); // --- Handle Clicking a Bird Button --- async function handleBirdButtonClick(event) { const birdName = event.target.dataset.birdName; // **** ADD LOGS HERE **** console.log(`Detection button clicked for: ${birdName}`); // Log 1: Check if handler runs and gets name // **** END LOGS **** currentBirdForChat = birdName; // This is the critical line // **** ADD LOGS HERE **** console.log(`currentBirdForChat variable has been set to: ${currentBirdForChat}`); // Log 2: Check if assignment happens // **** END LOGS **** // clearState(true); // Clear previous results but keep detections visible // Highlight the selected button (optional) document.querySelectorAll('#detection-buttons .button').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); // You'll need CSS for '.active' try { // Fetch bird info from backend const response = await fetch(`/bird_info/${encodeURIComponent(birdName)}`); if (!response.ok) { let errorMsg = `Error fetching info (${response.status})`; try { const errorData = await response.json(); errorMsg = errorData.error || errorMsg; } catch (e) { /* Ignore if error is not JSON */ } throw new Error(errorMsg); } const info = await response.json(); // Populate bird info area birdNameEl.textContent = birdName; birdGenusEl.textContent = info.genus || 'N/A'; birdLocationsEl.textContent = info.locations || 'N/A'; birdMatingEl.textContent = info.mating_patterns || 'N/A'; birdShortInfoEl.textContent = info.short_info || 'N/A'; birdExampleImageEl.src = info.image_path || ''; // Use path from backend birdExampleImageEl.alt = `Example photo of ${birdName}`; // Handle audio - ensure controls are shown only if src is valid if (info.audio_path) { birdAudioEl.src = info.audio_path; // Use path from backend birdAudioEl.style.display = 'block'; // Or 'inline-block' birdAudioEl.parentElement.style.display = 'block'; // Show the 'Bird Call:' label too } else { birdAudioEl.src = ''; birdAudioEl.style.display = 'none'; birdAudioEl.parentElement.style.display = 'none'; // Hide label if no audio } // Update chat button text and show info area chatBirdNameSpan.textContent = birdName; birdInfoArea.style.display = 'block'; // Show the info card } catch (error) { // **** ADD LOGS HERE **** console.error(`Error inside handleBirdButtonClick for ${birdName}:`, error); // Log 3: Check for errors within this function // **** END LOGS **** displayError(`Error fetching info for ${birdName}: ${error.message}`); birdInfoArea.style.display = 'none'; } } // --- Handle Clicking "Chat about [Bird]" Button --- chatButton.addEventListener('click', () => { // **** ADD LOGS HERE **** console.log("Chat button clicked!"); // Check if listener fires console.log("Value of currentBirdForChat:", currentBirdForChat); // Check the variable if (currentBirdForChat) { console.log("Condition met, attempting to show chat interface."); // Check if it enters the 'if' block chatInterfaceBirdNameSpan.textContent = currentBirdForChat; chatHistory.innerHTML = ''; // Clear previous chat history visually currentChatHistory = []; // Clear internal history chatInput.value = ''; // Clear input field chatInterface.style.display = 'block'; // Show chat card addChatMessage('assistant', `Hi! Ask me anything more about the ${currentBirdForChat}.`); chatInput.focus(); // Focus the input field } else { // **** ADD LOG HERE **** console.warn("Chat button clicked, but 'currentBirdForChat' is null or empty. Cannot open chat."); } }); // --- Handle Sending a Chat Message --- sendChatButton.addEventListener('click', sendChatMessageToServer); chatInput.addEventListener('keypress', (e) => { // Send message on Enter key press, but not Shift+Enter if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); // Prevent default Enter behavior (like adding a new line) sendChatMessageToServer(); } }); async function sendChatMessageToServer() { const userMessage = chatInput.value.trim(); if (!userMessage || !currentBirdForChat) { return; // Don't send empty messages or if no bird context } addChatMessage('user', userMessage); // Display user message immediately chatInput.value = ''; // Clear input field chatInput.disabled = true; // Disable input while waiting sendChatButton.disabled = true; chatLoadingIndicator.style.display = 'flex'; // Show chat loading try { const response = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ bird_name: currentBirdForChat, message: userMessage, history: currentChatHistory // Send history context }) }); chatLoadingIndicator.style.display = 'none'; // Hide loading if (!response.ok) { let errorMsg = `Chat API error (${response.status})`; try { const errorData = await response.json(); // Use backend's reply if it's an error message, otherwise use generic error errorMsg = errorData.reply || errorData.error || errorMsg; } catch(e) { /* Ignore if error is not JSON */ } // Display error as an AI message addChatMessage('assistant', `Sorry, I encountered an error: ${errorMsg}`); // Don't throw here, just show the error message in chat } else { const data = await response.json(); addChatMessage('assistant', data.reply); // Display AI response } } catch (error) { console.error('Chat Send/Receive Error:', error); chatLoadingIndicator.style.display = 'none'; // Hide loading on network error too addChatMessage('assistant', `Sorry, I couldn't connect to the chat service: ${error.message}`); } finally { chatInput.disabled = false; // Re-enable input sendChatButton.disabled = false; chatInput.focus(); // Focus back on input } } // --- Helper function to add messages to the chat history (visual and internal) --- function addChatMessage(role, message) { // Add to internal history (role should be 'user' or 'assistant' for OpenAI) currentChatHistory.push({ role: role === 'AI' ? 'assistant' : role, content: message }); // Add to visual chat history const messageElement = document.createElement('p'); // Sanitize message slightly before inserting - basic protection // For robust protection, use a proper sanitization library if messages can contain HTML const sanitizedMessage = message.replace(//g, ">"); messageElement.innerHTML = `${role === 'assistant' ? 'AI' : 'You'}: ${sanitizedMessage}`; chatHistory.appendChild(messageElement); chatHistory.scrollTop = chatHistory.scrollHeight; // Scroll to the bottom } // --- Helper function to display errors --- function displayError(message) { errorMessage.textContent = message; errorMessage.style.display = 'block'; // Make sure error area is visible } // --- Helper function to clear state --- function clearState(keepDetections = false) { errorMessage.textContent = ''; // Clear errors errorMessage.style.display = 'none'; if (!keepDetections) { resultsArea.style.display = 'none'; // Hide detection results resultImage.src = ''; detectionButtons.innerHTML = ''; } birdInfoArea.style.display = 'none'; // Hide bird info chatInterface.style.display = 'none'; // Hide chat currentBirdForChat = null; currentChatHistory = []; // Don't clear the file input/display unless starting fresh } });