File size: 13,196 Bytes
74e7a9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
047cc76
74e7a9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AR City Explorer | Smart Glasses Companion</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/aframe.min.js"></script>
    <script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar-nft.js"></script>

    <style>
        /* Your original CSS styles are fine, no changes needed here. */
        @keyframes pulse { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } }
        .pulse { animation: pulse 2s infinite; }
        .ar-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.8); z-index: 9999; display: flex; flex-direction: column; justify-content: center; align-items: center; color: white; }
        .ai-message { max-width: 80%; margin-bottom: 10px; padding: 12px; border-radius: 18px; position: relative; }
        .user-message { background-color: #3b82f6; color: white; align-self: flex-end; border-bottom-right-radius: 4px; }
        .assistant-message { background-color: #f3f4f6; color: #111827; align-self: flex-start; border-bottom-left-radius: 4px; }
        .glasses-pairing { background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%); }
        .loading-spinner { width: 50px; height: 50px; border: 5px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: #fff; animation: spin 1s ease-in-out infinite; }
        @keyframes spin { to { transform: rotate(360deg); } }
    </style>
</head>

<body class="bg-gray-100 font-sans">
    <div class="max-w-md mx-auto bg-white shadow-lg rounded-lg overflow-hidden min-h-screen flex flex-col">
        <header class="bg-indigo-900 text-white p-4 flex justify-between items-center">
            <div class="flex items-center space-x-2">
                <i class="fas fa-glasses text-2xl"></i>
                <h1 class="text-xl font-bold">AR City Explorer</h1>
            </div>
            <div class="flex space-x-3">
                <button id="arToggle" class="bg-indigo-700 hover:bg-indigo-600 px-3 py-1 rounded-full text-sm flex items-center">
                    <i class="fas fa-vr-cardboard mr-1"></i> AR Mode
                </button>
                <button id="settingsBtn" class="bg-indigo-700 hover:bg-indigo-600 px-3 py-1 rounded-full text-sm">
                    <i class="fas fa-cog"></i>
                </button>
            </div>
        </header>

        <main class="flex-1 overflow-hidden">
            <div id="arViewport" class="hidden relative">
                 <a-scene id="arScene" vr-mode-ui="enabled: false" arjs='sourceType: webcam; videoTexture: true; debugUIEnabled: false;'>
                    <a-camera gps-new-camera='gpsMinDistance: 5'></a-camera>
                </a-scene>
            </div>

            <div id="normalView" class="h-full flex flex-col">
                <div id="aiAssistant" class="flex-1 overflow-y-auto p-4 bg-gray-50">
                    <div class="flex flex-col space-y-2">
                        <div class="ai-message assistant-message">
                            <div class="font-bold text-indigo-800 mb-1">AR Guide</div>
                            <p>Hello! Ask me about nearby landmarks, or switch to AR mode to explore.</p>
                        </div>
                    </div>
                </div>
                <div class="p-4 border-t border-gray-200 bg-white">
                    <div class="flex space-x-2">
                        <input type="text" id="userInput" placeholder="Ask about places..." class="flex-1 border border-gray-300 rounded-full px-4 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-500">
                        <button id="sendBtn" class="bg-indigo-600 text-white rounded-full w-10 h-10 flex items-center justify-center">
                            <i class="fas fa-paper-plane"></i>
                        </button>
                        <button id="voiceBtn" class="bg-gray-200 text-gray-700 rounded-full w-10 h-10 flex items-center justify-center">
                            <i class="fas fa-microphone"></i>
                        </button>
                    </div>
                </div>
            </div>
        </main>

        <nav class="bg-white border-t border-gray-200 p-2 flex justify-around">
            </nav>
    </div>

    <div id="objectModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
        <div class="bg-white rounded-xl p-6 max-w-sm w-full">
            <div class="flex justify-between items-center mb-4">
                <h3 id="objectTitle" class="text-xl font-bold text-gray-800"></h3>
                <button id="closeObjectModal" class="text-gray-500"><i class="fas fa-times"></i></button>
            </div>
            <img id="objectImage" src="" alt="Landmark" class="w-full h-48 object-cover rounded-lg mb-4">
            <div id="objectDescription" class="text-gray-700 mb-4"></div>
        </div>
    </div>
    <script>
        document.addEventListener('DOMContentLoaded', () => {
            // --- CONFIGURATION ---
            const config = {
                // IMPORTANT: Paste your full and correct Cloudflare Worker URL here
                backendUrl: 'https://ar-city-explorer-backend.aiagents.workers.dev/'
            };

            // --- GLOBAL STATE ---
            let poisData = []; // This will hold all our landmark data from the backend
            let arActive = false;
            let glassesPaired = false; // You can manage this state as needed

            // --- 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');

            // --- CORE 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';
                    // Fetch data only if it hasn't been fetched yet
                    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() {
                console.log('Fetching POIs from backend...');
                fetch(`${config.backendUrl}/api/pois`)
                    .then(response => {
                        if (!response.ok) throw new Error(`Network error: ${response.statusText}`);
                        return response.json();
                    })
                    .then(pois => {
                        console.log('POIs received:', pois);
                        poisData = pois; // Store the data globally
                        const scene = document.querySelector('a-scene');
                        if (!scene) return console.error('A-Frame scene not found!');

                        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); // Set ID for click detection
                            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 your connection and the backend URL.');
                        toggleARView(false);
                    });
            }

            // --- MODAL AND UI FUNCTIONS ---
            
            function showObjectInfo(poiId) {
                const poi = poisData.find(p => p.id === poiId);
                if (!poi) return;
                objectTitle.textContent = poi.name;
                objectDescription.textContent = poi.description || "No description available.";
                objectImage.src = `https://via.placeholder.com/300x200?text=${encodeURIComponent(poi.name)}`;
                objectModal.classList.remove('hidden');
            }

            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 = '';

                if (poisData.length === 0) {
                    addAIMessage("I'm still loading location data. Please try again in a moment.");
                    return;
                }

                const results = poisData.filter(poi =>
                    poi.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
                    (poi.description && poi.description.toLowerCase().includes(searchTerm.toLowerCase()))
                );

                if (results.length > 0) {
                    let response = `I found ${results.length} result(s):<ul>`;
                    results.forEach(poi => { response += `<li class="mt-2 list-disc list-inside">${poi.name}</li>`; });
                    response += "</ul>";
                    addAIMessage(response);
                } else {
                    addAIMessage(`Sorry, I couldn't find anything matching "${searchTerm}".`);
                }
            }
            
            // --- EVENT LISTENERS ---
            
            arToggle.addEventListener('click', () => toggleARView(!arActive));

            document.querySelector('a-scene').addEventListener('click', (event) => {
                if (event.target.hasAttribute('data-poi-id')) {
                    const poiId = parseInt(event.target.getAttribute('data-poi-id'), 10);
                    showObjectInfo(poiId);
                }
            });

            closeObjectModal.addEventListener('click', () => objectModal.classList.add('hidden'));
            
            sendBtn.addEventListener('click', handleSearch);
            userInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') handleSearch();
            });

            // Initial fetch of data for the search bar to work immediately
            fetchPoisAndCreateAREntities();
        });
    </script>
</body>
</html>