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, "&lt;").replace(/>/g, "&gt;");
        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
    }
});