Spaces:
Running
Running
// 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 = '<i class="fas fa-times mr-1"></i> Exit AR'; | |
if (poisData.length === 0) fetchPoisAndCreateAREntities(); | |
} else { | |
arViewport.classList.add('hidden'); | |
normalView.classList.remove('hidden'); | |
arToggle.innerHTML = '<i class="fas fa-vr-cardboard mr-1"></i> 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 = `<div><div class="font-medium">${deck.deck_name}</div><div class="text-sm text-gray-600">Size: ${deck.size_meters}m | Charging: ${deck.is_charging_available ? 'Yes' : 'No'}</div></div><div class="font-bold text-sm ${statusColor}">${deck.status}</div>`; | |
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 = `<div class="ai-message user-message"><p>${message}</p></div>`; | |
chatContainer.insertAdjacentHTML('beforeend', msg); | |
aiAssistant.scrollTop = aiAssistant.scrollHeight; | |
} | |
function addAIMessage(message) { | |
const chatContainer = aiAssistant.querySelector('.flex-col'); | |
const msg = `<div class="ai-message assistant-message"><div class="font-bold text-indigo-800 mb-1">AR Guide</div><p>${message}</p></div>`; | |
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):<ul>`; | |
results.forEach(poi => { responseMessage += `<li class="mt-2 list-disc list-inside">${poi.name}</li>`; }); | |
responseMessage += "</ul>"; | |
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, '<br>'); | |
} else { | |
addAIMessage(data.response.replace(/\n/g, '<br>')); | |
} | |
}) | |
.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(); | |
}); |