function sendMessage() { const input = document.getElementById('messageInput'); const message = input.value.trim(); if (!message) return; addUserMessage(message); input.value = ''; showTypingIndicator(); // Use non-streaming for reliability (avoids double API calls) fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: message, stream: false }) }) .then(response => response.json()) .then(data => { hideTypingIndicator(); handleBotResponse(data); }) .catch(err => { hideTypingIndicator(); addBotMessage('Sorry, something went wrong. Please try again.'); console.error('Error:', err); }); } function sendMessageWithStreaming(message) { let streamingMessageDiv = null; let accumulatedText = ''; // Create message div for streaming const chatMessages = document.getElementById('chatMessages'); streamingMessageDiv = document.createElement('div'); streamingMessageDiv.className = 'message bot-message'; streamingMessageDiv.innerHTML = `
`; fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: message, stream: true }) }) .then(async response => { hideTypingIndicator(); if (!response.ok) { throw new Error('Network response was not ok'); } const reader = response.body.getReader(); const decoder = new TextDecoder(); chatMessages.appendChild(streamingMessageDiv); const textElement = streamingMessageDiv.querySelector('.streaming-text'); while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\n'); for (const line of lines) { if (line.startsWith('data: ')) { const data = JSON.parse(line.slice(6)); if (data.chunk) { accumulatedText += data.chunk; textElement.textContent = accumulatedText; scrollToBottom(); } if (data.done && data.result) { // Remove streaming message and handle final result streamingMessageDiv.remove(); handleBotResponse(data.result); } } } } }) .catch(error => { hideTypingIndicator(); if (streamingMessageDiv && streamingMessageDiv.parentNode) { streamingMessageDiv.remove(); } // Fallback to non-streaming fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ message: message, stream: false }) }) .then(response => response.json()) .then(data => handleBotResponse(data)) .catch(err => { addBotMessage('Sorry, something went wrong. Please try again.'); console.error('Error:', err); }); }); } function handleBotResponse(data) { if (data.type === 'message') { addBotMessage(data.content); } else if (data.type === 'appointment_result') { if (data.status === 'confirmed') { addAppointmentCard(data.details); } else if (data.status === 'cancelled') { addBotMessage(data.details.message); // Refresh appointments list if modal is open const appointmentsModal = document.getElementById('appointmentsModal'); if (appointmentsModal && appointmentsModal.style.display === 'block') { showAppointments(); } } else if (data.status === 'not_found') { addBotMessage(data.details.message || 'Appointment not found.'); } else { addBotMessage(data.details.message || 'Unable to process appointment.'); } } else if (data.type === 'error') { addBotMessage(data.content || 'An error occurred. Please try again.'); } else { addBotMessage(data.content || 'I received your message.'); } } function addUserMessage(message) { const chatMessages = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = 'message user-message'; messageDiv.innerHTML = ` `; chatMessages.appendChild(messageDiv); scrollToBottom(); } function addBotMessage(message) { const chatMessages = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = 'message bot-message'; messageDiv.innerHTML = ` `; chatMessages.appendChild(messageDiv); scrollToBottom(); } function addAppointmentCard(details) { const chatMessages = document.getElementById('chatMessages'); const messageDiv = document.createElement('div'); messageDiv.className = 'message bot-message'; // Format date for display (YYYY-MM-DD to DD-MM-YYYY) const displayDate = formatDateForDisplay(details.date); const displayTime = formatTimeForDisplay(details.time); messageDiv.innerHTML = ` `; chatMessages.appendChild(messageDiv); scrollToBottom(); // Refresh time slots only if booking modal is open and date matches const appointmentModal = document.getElementById('appointmentModal'); const dateInput = document.getElementById('date'); if (appointmentModal && appointmentModal.style.display === 'block' && dateInput && dateInput.value === details.date) { updateTimeSlots(); } } // Format date from YYYY-MM-DD to DD-MM-YYYY for display function formatDateForDisplay(dateStr) { try { const parts = dateStr.split('-'); if (parts.length === 3 && parts[0].length === 4) { // YYYY-MM-DD format return `${parts[2]}-${parts[1]}-${parts[0]}`; } return dateStr; // Return as-is if already in different format } catch { return dateStr; } } // Format time from 24-hour to 12-hour with AM/PM function formatTimeForDisplay(timeStr) { try { const [hours] = timeStr.split(':').map(Number); return formatTime12Hour(hours); } catch { return timeStr; } } function showTypingIndicator() { document.getElementById('typingIndicator').style.display = 'flex'; } function hideTypingIndicator() { document.getElementById('typingIndicator').style.display = 'none'; } function scrollToBottom() { const chatMessages = document.getElementById('chatMessages'); chatMessages.scrollTop = chatMessages.scrollHeight; } function getCurrentTime() { const now = new Date(); return now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function handleKeyPress(event) { if (event.key === 'Enter') { sendMessage(); } } // Product quick links function askAboutProduct(productName) { const input = document.getElementById('messageInput'); input.value = `Tell me about ${productName} series`; sendMessage(); } // Appointment Modal Functions function showAppointmentForm() { document.getElementById('appointmentModal').style.display = 'block'; // Initialize date and time slots const dateInput = document.getElementById('date'); if (dateInput) { const today = new Date(); const currentHour = today.getHours(); // If it's past business hours (after 6 PM), default to tomorrow if (currentHour >= 18) { const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); dateInput.value = tomorrow.toISOString().split('T')[0]; } else { dateInput.value = today.toISOString().split('T')[0]; } updateTimeSlots(); } } function closeModal() { document.getElementById('appointmentModal').style.display = 'none'; document.getElementById('appointmentForm').reset(); } function submitAppointment(event) { event.preventDefault(); const name = document.getElementById('name').value; const address = document.getElementById('address').value; const date = document.getElementById('date').value; const time = document.getElementById('time').value; // Check if time slot is already booked fetch('/api/appointments') .then(response => response.json()) .then(data => { const isSlotTaken = data.appointments.some(apt => apt.date === date && apt.time === time ); if (isSlotTaken) { alert('⚠️ This time slot is already booked! Please select another time.'); return; } // Proceed with booking return fetch('/api/appointments/book', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name, address, date, time }) }); }) .then(response => { if (!response) return; // Slot was taken // Check if response is error (400) if (!response.ok) { return response.json().then(err => { throw new Error(err.message || 'Booking failed'); }); } return response.json(); }) .then(data => { if (!data) return; // Slot was taken closeModal(); addAppointmentCard(data); }) .catch(error => { console.error('Error:', error); alert(error.message || 'Failed to book appointment. Please try again.'); }); } // Appointments List Functions function showAppointments() { fetch('/api/appointments') .then(response => response.json()) .then(data => { displayAppointments(data.appointments); document.getElementById('appointmentsModal').style.display = 'block'; }) .catch(error => { console.error('Error:', error); alert('Failed to load appointments.'); }); } function displayAppointments(appointments) { const appointmentsList = document.getElementById('appointmentsList'); if (appointments.length === 0) { appointmentsList.innerHTML = `No appointments found
Name: ${escapeHtml(apt.name)}
Address: ${escapeHtml(apt.address)}
Date: ${displayDate}
Time: ${displayTime}