// 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 = ``;
chatContainer.insertAdjacentHTML('beforeend', msg);
aiAssistant.scrollTop = aiAssistant.scrollHeight;
}
function addAIMessage(message) {
const chatContainer = aiAssistant.querySelector('.flex-col');
const msg = ``;
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):`;
results.forEach(poi => { responseMessage += `- ${poi.name}
`; });
responseMessage += "
";
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();
});