Spaces:
Build error
Build error
File size: 14,597 Bytes
cfb75f4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | // 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 = '<p>No birds detected (or confidence too low).</p>';
}
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, "<").replace(/>/g, ">");
messageElement.innerHTML = `<strong>${role === 'assistant' ? 'AI' : 'You'}:</strong> ${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
}
}); |