document.addEventListener('DOMContentLoaded', () => { const input = document.getElementById('question'); const sendBtn = document.getElementById('sendBtn'); const chatContainer = document.getElementById('chat'); const globalIntent = document.getElementById('global-intent'); const globalConfidence = document.getElementById('global-confidence'); const inputContainer = document.getElementById('input-container'); let isWaiting = false; let messageCounter = 0; // To generate unique IDs for maps // Auto-resize textarea input.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px'; if (parseFloat(this.style.height) > 200) { this.style.overflowY = 'auto'; } else { this.style.overflowY = 'hidden'; } // Enable/disable send button if(this.value.trim().length > 0 && !isWaiting) { sendBtn.removeAttribute('disabled'); } else { sendBtn.setAttribute('disabled', 'true'); } }); input.addEventListener('focus', () => inputContainer.classList.add('focused')); input.addEventListener('blur', () => inputContainer.classList.remove('focused')); input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); sendBtn.addEventListener('click', () => { sendMessage(); }); // Handle clicks on recommendation chips chatContainer.addEventListener('click', (e) => { if (e.target.classList.contains('rec-chip')) { const question = e.target.textContent; input.value = question; // Hack to trigger auto-resize logic before sending input.dispatchEvent(new Event('input')); sendMessage(); } }); async function sendMessage() { const text = input.value.trim(); if (!text || isWaiting) return; // Reset input input.value = ''; input.style.height = 'auto'; sendBtn.setAttribute('disabled', 'true'); // Add User Message appendMessage('user', text); // Scroll to bottom scrollToBottom(); // Show loading indicator isWaiting = true; const loadingId = appendLoading(); scrollToBottom(); try { const response = await fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ question: text }) }); if (!response.ok) { throw new Error('Erreur de communication avec le serveur'); } const data = await response.json(); // Remove loading document.getElementById(loadingId).remove(); // Handle Bot Response handleBotResponse(data); } catch (error) { console.error('Error:', error); document.getElementById(loadingId).remove(); appendMessage('bot', 'Désolé, une erreur est survenue lors de la communication avec le serveur.'); } finally { isWaiting = false; if(input.value.trim().length > 0) { sendBtn.removeAttribute('disabled'); } scrollToBottom(); } } function appendMessage(role, text) { messageCounter++; const row = document.createElement('div'); row.className = `message-row ${role}`; const avatarSvg = role === 'user' ? '' : ''; // Format basic markdown-like text elements (newlines) const formattedText = text.replace(/\n/g, '
'); row.innerHTML = `
${avatarSvg}

${formattedText}

`; chatContainer.appendChild(row); return row; } function appendLoading() { const id = 'loading-' + Date.now(); const row = document.createElement('div'); row.className = 'message-row bot'; row.id = id; row.innerHTML = `
`; chatContainer.appendChild(row); return id; } function handleBotResponse(data) { messageCounter++; const row = document.createElement('div'); row.className = 'message-row bot'; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; const avatar = document.createElement('div'); avatar.className = 'avatar bot'; avatar.innerHTML = ''; const bodyDiv = document.createElement('div'); bodyDiv.className = 'message-body'; // 1. Add Answer Text const textP = document.createElement('p'); textP.innerHTML = (data.response || '').replace(/\n/g, '
'); bodyDiv.appendChild(textP); // Update header tracking if (data.intent) globalIntent.textContent = `Intent: ${data.intent}`; if (data.confidence !== undefined) { const confVal = typeof data.confidence === 'number' ? data.confidence.toFixed(1) : parseFloat(data.confidence).toFixed(1); globalConfidence.textContent = `Confiance: ${confVal}%`; } // Rich Content Container const richContainer = document.createElement('div'); richContainer.className = 'rich-content'; // 2. Service Card const hasService = data.service && data.service !== ''; const hasLocation = data.lat !== null && data.lon !== null && data.lat !== undefined; if (hasService || hasLocation || (data.link && data.link !== '')) { const serviceCard = document.createElement('div'); serviceCard.className = 'service-card'; let btnHtml = ''; if (data.link && data.link !== '') { // Ensure proper link parsing if needed btnHtml = ` Ouvrir le lien `; } let navBtnHtml = ''; if (data.lat && data.lon) { const mapUrl = `https://www.google.com/maps?q=${data.lat},${data.lon}`; navBtnHtml = ` Y aller `; } const serviceName = hasService ? data.service : (hasLocation ? "Emplacement trouvé" : "Lien identifié"); serviceCard.innerHTML = `

${serviceName}

Information identifiée pour cette requête

${btnHtml} ${navBtnHtml}
`; richContainer.appendChild(serviceCard); } // 3. Map Container let mapId = null; if (data.lat && data.lon) { mapId = `map-${messageCounter}`; const mapWrapper = document.createElement('div'); mapWrapper.className = 'map-container'; mapWrapper.id = mapId; richContainer.appendChild(mapWrapper); } // 4. Recommendations if (data.recs && Array.isArray(data.recs) && data.recs.length > 0) { const recsWrapper = document.createElement('div'); recsWrapper.className = 'recs-container'; data.recs.forEach(rec => { if (rec.trim() !== '') { const chip = document.createElement('button'); chip.className = 'rec-chip'; chip.textContent = rec; recsWrapper.appendChild(chip); } }); richContainer.appendChild(recsWrapper); } if (richContainer.children.length > 0) { bodyDiv.appendChild(richContainer); } // Add analytics quietly at bottom of message const analyticsP = document.createElement('div'); analyticsP.className = 'analytics-data'; analyticsP.innerHTML = ` Intent: ${data.intent || 'N/A'} Confiance: ${data.confidence !== undefined ? (typeof data.confidence === 'number' ? data.confidence.toFixed(1) : parseFloat(data.confidence).toFixed(1)) : '--'}% `; bodyDiv.appendChild(analyticsP); contentDiv.appendChild(avatar); contentDiv.appendChild(bodyDiv); row.appendChild(contentDiv); chatContainer.appendChild(row); // Initialize Map after DOM insertion if (mapId) { setTimeout(() => { initMap(mapId, parseFloat(data.lat), parseFloat(data.lon), data.service || 'Emplacement'); }, 100); } scrollToBottom(); } function initMap(elementId, lat, lon, title) { const map = L.map(elementId).setView([lat, lon], 15); // Use CartoDB Dark Matter tiles to match the ChatGPT dark theme natively L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap © CARTO', subdomains: 'abcd', maxZoom: 20 }).addTo(map); const marker = L.marker([lat, lon]).addTo(map); marker.bindPopup(`${title}`).openPopup(); // Invalidate size to ensure it renders correctly in a dynamic container setTimeout(() => map.invalidateSize(), 300); } function scrollToBottom() { chatContainer.scrollTop = chatContainer.scrollHeight; } // ===================================== // GLOBAL MAP MODAL LOGIC // ===================================== const globalMapBtn = document.getElementById('global-map-btn'); const globalModal = document.getElementById('global-map-modal'); const closeModal = document.getElementById('close-modal-span'); let globalLeafletMap = null; if (globalMapBtn) { globalMapBtn.addEventListener('click', async () => { globalModal.style.display = 'block'; // Initialize map only once if (!globalLeafletMap) { globalLeafletMap = L.map('global-map').setView([34.88, -1.30], 13); // Default view L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap', subdomains: 'abcd', maxZoom: 20 }).addTo(globalLeafletMap); } // Must invalidate size since it was display:none setTimeout(() => globalLeafletMap.invalidateSize(), 300); // Fetch all services try { const resp = await fetch('/api/services'); const data = await resp.json(); if (data.status === 'success' && data.services && data.services.length > 0) { const bounds = []; data.services.forEach(srv => { const marker = L.marker([srv.lat, srv.lon]).addTo(globalLeafletMap); // Action button inside popup to search the service directly const popupContent = `
${srv.service}
`; marker.bindPopup(popupContent); bounds.push([srv.lat, srv.lon]); }); // Center map on all pins if (bounds.length > 0) { globalLeafletMap.fitBounds(bounds, {padding: [50, 50]}); } } } catch(e) { console.error("Error loading global map services", e); } }); } if (closeModal) { closeModal.addEventListener('click', () => { globalModal.style.display = 'none'; }); } window.addEventListener('click', (e) => { if (e.target == globalModal) { globalModal.style.display = 'none'; } }); });