// Final Corrected Version: June 13, 2025 document.addEventListener('DOMContentLoaded', () => { // --- CONFIGURATION --- const config = { backendUrl: 'https://ar-city-explorer-backend.aiagents.workers.dev' }; // --- GLOBAL STATE --- let poisData = []; let arActive = false; let userSettings = { showHistorical: true, showMenus: true, showNavigation: false, voiceResponses: true }; // --- DOM ELEMENT SELECTORS --- const arToggle = document.getElementById('arToggle'); const arViewport = document.getElementById('arViewport'); const normalView = document.getElementById('normalView'); const aiAssistant = document.getElementById('aiAssistant'); const userInput = document.getElementById('userInput'); const sendBtn = document.getElementById('sendBtn'); const objectModal = document.getElementById('objectModal'); const objectTitle = document.getElementById('objectTitle'); const objectDescription = document.getElementById('objectDescription'); const objectImage = document.getElementById('objectImage'); const closeObjectModal = document.getElementById('closeObjectModal'); const settingsBtn = document.getElementById('settingsBtn'); const settingsModal = document.getElementById('settingsModal'); const closeSettingsModal = document.getElementById('closeSettingsModal'); const aerodeckSection = document.getElementById('aerodeckSection'); const aerodeckList = document.getElementById('aerodeckList'); // Add these lines to the list of selectors const glassesBtn = document.getElementById('glassesBtn'); const glassesModal = document.getElementById('glassesModal'); const closeGlassesModal = document.getElementById('closeGlassesModal'); // --- FUNCTIONS --- function toggleARView(showAR) { arActive = showAR; if (arActive) { normalView.classList.add('hidden'); arViewport.classList.remove('hidden'); arToggle.innerHTML = ' Exit AR'; if (poisData.length === 0) fetchPoisAndCreateAREntities(); } else { arViewport.classList.add('hidden'); normalView.classList.remove('hidden'); arToggle.innerHTML = ' AR Mode'; } } function fetchPoisAndCreateAREntities() { fetch(`${config.backendUrl}/api/pois`) .then(response => { if (!response.ok) throw new Error('Network error'); return response.json(); }) .then(pois => { poisData = pois; const scene = document.querySelector('a-scene'); if (!scene) return; pois.forEach(poi => { const entity = document.createElement('a-entity'); entity.setAttribute('gps-new-entity-place', { latitude: poi.latitude, longitude: poi.longitude }); const box = document.createElement('a-box'); box.setAttribute('material', 'color: red; opacity: 0.7;'); box.setAttribute('scale', '10 10 10'); box.setAttribute('position', '0 5 0'); box.setAttribute('data-poi-id', poi.id); entity.appendChild(box); const text = document.createElement('a-text'); text.setAttribute('value', poi.name); text.setAttribute('look-at', '[gps-new-camera]'); text.setAttribute('scale', '50 50 50'); text.setAttribute('position', '0 15 0'); entity.appendChild(text); scene.appendChild(entity); }); }) .catch(error => { console.error('Failed to load POIs:', error); alert('Could not load city data. Check connection and backend URL.'); toggleARView(false); }); } function showObjectInfo(poiId) { objectTitle.textContent = 'Loading...'; objectDescription.textContent = ''; if (aerodeckSection) aerodeckSection.classList.add('hidden'); fetch(`${config.backendUrl}/api/pois/${poiId}`) .then(response => { if (!response.ok) throw new Error('POI not found'); return response.json(); }) .then(data => { objectTitle.textContent = data.name; objectDescription.textContent = data.description || "No description available."; objectImage.src = `https://via.placeholder.com/300x200?text=${encodeURIComponent(data.name)}`; if (data.aerodecks && data.aerodecks.length > 0) { aerodeckList.innerHTML = ''; data.aerodecks.forEach(deck => { const itemElement = document.createElement('div'); itemElement.className = 'flex justify-between items-center p-2 bg-gray-100 rounded'; const statusColor = deck.status === 'Operational' ? 'text-green-500' : 'text-orange-500'; itemElement.innerHTML = `
${deck.deck_name}
Size: ${deck.size_meters}m | Charging: ${deck.is_charging_available ? 'Yes' : 'No'}
${deck.status}
`; aerodeckList.appendChild(itemElement); }); if (aerodeckSection) aerodeckSection.classList.remove('hidden'); } if (objectModal) objectModal.classList.remove('hidden'); }) .catch(error => { console.error("Error fetching POI details:", error); alert("Could not load details for this location."); }); } function addUserMessage(message) { const chatContainer = aiAssistant.querySelector('.flex-col'); const msg = `

${message}

`; chatContainer.insertAdjacentHTML('beforeend', msg); aiAssistant.scrollTop = aiAssistant.scrollHeight; } function addAIMessage(message) { const chatContainer = aiAssistant.querySelector('.flex-col'); const msg = `
AR Guide

${message}

`; chatContainer.insertAdjacentHTML('beforeend', msg); aiAssistant.scrollTop = aiAssistant.scrollHeight; } function handleSearch() { const searchTerm = userInput.value.trim(); if (!searchTerm) return; addUserMessage(searchTerm); userInput.value = ''; const searchKeywords = ['search', 'find', 'show', 'where is', 'landmark', 'park', 'beach', 'hollywood', 'map', 'navigate']; const isLocalSearch = searchKeywords.some(keyword => searchTerm.toLowerCase().includes(keyword)); if (isLocalSearch) { const stopWords = ['show', 'me', 'find', 'is', 'a', 'the', 'for', 'where', 'search', 'of', 'at']; const searchTokens = searchTerm.toLowerCase().split(' ').filter(word => !stopWords.includes(word) && word.length > 2); if (searchTokens.length === 0) return addAIMessage("Please be more specific in your local search."); const results = poisData.filter(poi => { const poiText = (poi.name + ' ' + (poi.description || '')).toLowerCase(); return searchTokens.some(token => { if (poiText.includes(token)) return true; if (token.endsWith('s')) return poiText.includes(token.slice(0, -1)); return false; }); }); if (results.length > 0) { let responseMessage = `I found ${results.length} local result(s):"; addAIMessage(responseMessage); } else { addAIMessage(`Sorry, I couldn't find any local places matching "${searchTerm}".`); } } else { addAIMessage("AI is thinking..."); fetch(`${config.backendUrl}/api/ask`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: searchTerm }) }) .then(response => { if (!response.ok) throw new Error('Network error'); return response.json(); }) .then(data => { const chatContainer = aiAssistant.querySelector('.flex-col'); const thinkingMessage = Array.from(chatContainer.querySelectorAll('.assistant-message')).pop(); if (thinkingMessage && thinkingMessage.textContent.includes("AI is thinking...")) { thinkingMessage.querySelector('p').innerHTML = data.response.replace(/\n/g, '
'); } else { addAIMessage(data.response.replace(/\n/g, '
')); } }) .catch(error => { console.error('Error asking AI:', error); const chatContainer = aiAssistant.querySelector('.flex-col'); const thinkingMessage = Array.from(chatContainer.querySelectorAll('.assistant-message')).pop(); if (thinkingMessage && thinkingMessage.textContent.includes("AI is thinking...")) { thinkingMessage.querySelector('p').innerHTML = "Sorry, I had trouble connecting to the AI."; } else { addAIMessage("Sorry, I had trouble connecting to the AI."); } }); } } // --- EVENT LISTENERS (Corrected Version) --- // AR Mode Toggle if(arToggle) arToggle.addEventListener('click', () => toggleARView(!arActive)); // Clicking on AR objects in the scene const sceneEl = document.querySelector('a-scene'); if (sceneEl) { sceneEl.addEventListener('click', (event) => { if (event.target.hasAttribute('data-poi-id')) { const poiId = parseInt(event.target.getAttribute('data-poi-id'), 10); showObjectInfo(poiId); } }); } // Info Modal Close Button if(closeObjectModal) closeObjectModal.addEventListener('click', () => objectModal.classList.add('hidden')); // Search Bar Send Button & Enter Key if(sendBtn) sendBtn.addEventListener('click', handleSearch); if(userInput) userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') handleSearch(); }); // Settings Modal Open/Close Logic if(settingsBtn) { settingsBtn.addEventListener('click', () => { if(settingsModal) settingsModal.classList.remove('hidden'); }); } if(closeSettingsModal) { closeSettingsModal.addEventListener('click', () => { if(settingsModal) settingsModal.classList.add('hidden'); }); } if(settingsModal) { settingsModal.addEventListener('click', (event) => { if (event.target === settingsModal) settingsModal.classList.add('hidden'); }); } // Glasses Modal Open/Close Logic if (glassesBtn) { glassesBtn.addEventListener('click', () => { if(glassesModal) glassesModal.classList.remove('hidden'); }); } if (closeGlassesModal) { closeGlassesModal.addEventListener('click', () => { if(glassesModal) glassesModal.classList.add('hidden'); }); } if (glassesModal) { glassesModal.addEventListener('click', (event) => { if (event.target === glassesModal) glassesModal.classList.add('hidden'); }); } // Settings Toggles Functionality const settingToggles = settingsModal ? settingsModal.querySelectorAll('input[data-setting]') : []; settingToggles.forEach(toggle => { const settingName = toggle.dataset.setting; if (userSettings[settingName] !== undefined) { toggle.checked = userSettings[settingName]; } toggle.addEventListener('change', (event) => { const changedSettingName = event.target.dataset.setting; userSettings[changedSettingName] = event.target.checked; // console.log('Settings updated:', userSettings); // For testing }); }); // --- INITIALIZATION --- fetchPoisAndCreateAREntities(); });