Hospital Management System
Booking Confirmed!
Your appointment has been successfully stored in the hospital records.
APT-VOICE-0000
MESSAGES Just Now
Hospital Management System Alert: Your appointment is confirmed.
Offline

AI Receptionist Bot

Aria - Virtual Assistant

AI CRM Live Extractor

No active call
Patient Name Waiting...
Caller Phone Waiting...
Suffering / Symptoms Waiting...
Specialist Doctor Waiting...
Department Waiting...
Appointment Date Waiting...
Appointment Time Waiting...
Urgency Temperature Waiting...
Call Reference ID Waiting...

Call Dialogue Transcript

Call not connected. Click phone button to start speaking.

Guidelines to test AI Agent:

  • Step 1: Start call. The AI Receptionist will greet you and ask for details.
  • Step 2: State your name and reason for calling (e.g. "I am Sarah Carter, I have child with high fever").
  • Step 3: The AI extracts symptoms, matches specialized doctor (Pediatrician) and suggests date/time.
  • Step 4: Agree or customize slot (e.g. "Yes, tomorrow morning works" or "I want Friday at 2:00 PM").
  • Step 5: Confirm the booking when AI asks to complete the reservation!
// Config const API_BASE = window.location.origin; // State Variables let isCallActive = false; let callSessionId = ''; let currentMode = 'voice'; // 'voice' or 'text' let speakVoices = []; let recognition = null; let isBotSpeaking = false; let isWaitingForResponse = false; let currentCallerPhone = ''; // Elements const btnToggleCall = document.getElementById('btn-toggle-call'); const badgeCallStatus = document.getElementById('call-status-badge'); const phoneAvatar = document.getElementById('phone-avatar-glow'); const soundEqualizer = document.getElementById('sound-equalizer'); const textInputConsole = document.getElementById('text-input-console'); const patientTextInput = document.getElementById('patient-text-input'); const btnSendText = document.getElementById('btn-send-text'); const chatTranscript = document.getElementById('chat-transcript'); const emptyTranscriptMsg = document.getElementById('empty-transcript-msg'); const phoneDisplayTitle = document.getElementById('phone-display-title'); const phoneDisplaySub = document.getElementById('phone-display-sub'); // Mode Buttons const btnModeVoice = document.getElementById('btn-mode-voice'); const btnModeText = document.getElementById('btn-mode-text'); // Extractor Elements const labelSession = document.getElementById('extractor-session'); const extName = document.getElementById('ext-name'); const extPhone = document.getElementById('ext-phone'); const extSymptoms = document.getElementById('ext-symptoms'); const extDoctor = document.getElementById('ext-doctor'); const extDept = document.getElementById('ext-dept'); const extDate = document.getElementById('ext-date'); const extTime = document.getElementById('ext-time'); const extUrgency = document.getElementById('ext-urgency'); const extRef = document.getElementById('ext-ref'); // Booking popup elements const bookingPopup = document.getElementById('booking-popup'); const bookingMsg = document.getElementById('booking-msg'); const bookingRef = document.getElementById('booking-ref'); const btnRestartCall = document.getElementById('btn-restart-call'); // Generate random 10-digit phone for session simulating real call From twilio function generateRandomPhone() { const parts = [ Math.floor(200 + Math.random() * 800), Math.floor(200 + Math.random() * 800), Math.floor(1000 + Math.random() * 9000) ]; return `+1 (${parts[0]}) ${parts[1]}-${parts[2]}`; } // Initialize voices list function initSpeechSynthesis() { if ('speechSynthesis' in window) { speakVoices = window.speechSynthesis.getVoices(); window.speechSynthesis.onvoiceschanged = () => { speakVoices = window.speechSynthesis.getVoices(); }; } } initSpeechSynthesis(); let currentAudioPlayback = null; // Speak Text aloud using High-Fidelity Audio / SpeechSynthesis function speakText(text, callback) { if (!text) { if (callback) callback(); return; } const selectedLanguage = document.getElementById('lang-pref-select').value || 'en'; if (currentAudioPlayback) { try { currentAudioPlayback.pause(); currentAudioPlayback.currentTime = 0; } catch(e) {} currentAudioPlayback = null; } if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); } // If Telugu is selected, use high-fidelity natural human Telugu voice stream if (selectedLanguage === 'te') { const audioUrl = 'https://translate.google.com/translate_tts?ie=UTF-8&tl=te&client=tw-ob&q=' + encodeURIComponent(text); const audio = new Audio(audioUrl); currentAudioPlayback = audio; let hasFinished = false; const done = () => { if (!hasFinished) { hasFinished = true; isBotSpeaking = false; animateEqualizer(false); currentAudioPlayback = null; if (callback) callback(); } }; audio.onplay = () => { isBotSpeaking = true; animateEqualizer(true); }; audio.onended = () => { done(); }; audio.onerror = () => { console.warn('Online Telugu TTS failed, falling back to Web Speech Synthesis.'); fallbackSpeechSynthesis(text, selectedLanguage, callback); }; audio.play().catch(err => { console.warn('Audio play restricted or failed, falling back to SpeechSynthesis:', err); fallbackSpeechSynthesis(text, selectedLanguage, callback); }); return; } fallbackSpeechSynthesis(text, selectedLanguage, callback); } function fallbackSpeechSynthesis(text, selectedLanguage, callback) { if (!('speechSynthesis' in window)) { if (callback) callback(); return; } window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.lang = selectedLanguage === 'te' ? 'te-IN' : 'en-US'; let selectedVoice = null; if (selectedLanguage === 'te') { selectedVoice = speakVoices.find(v => v.lang.startsWith('te') || v.name.includes('Telugu') || v.name.includes('India')); } else { selectedVoice = speakVoices.find(v => v.name.includes('Google US English') || v.name.includes('Zira') || v.name.includes('Samantha') || v.name.includes('Moira')); } if (!selectedVoice && speakVoices.length > 0 && selectedLanguage !== 'te') { selectedVoice = speakVoices.find(v => v.lang.startsWith('en')) || speakVoices[0]; } if (selectedVoice) utterance.voice = selectedVoice; utterance.rate = 0.95; utterance.pitch = 1.0; utterance.onstart = () => { isBotSpeaking = true; animateEqualizer(true); }; utterance.onend = () => { isBotSpeaking = false; animateEqualizer(false); if (callback) callback(); }; utterance.onerror = (e) => { console.error('SpeechSynthesis error:', e); isBotSpeaking = false; animateEqualizer(false); if (callback) callback(); }; window.speechSynthesis.speak(utterance); } // Initialize Web Speech Recognition function initSpeechRecognition() { const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { console.warn('SpeechRecognition API not supported in this browser.'); return; } recognition = new SpeechRecognition(); recognition.continuous = false; recognition.interimResults = false; recognition.lang = 'en-US'; recognition.onstart = () => { badgeCallStatus.textContent = "Listening..."; badgeCallStatus.className = "phone-status active"; phoneAvatar.classList.add('active'); animateEqualizer(true); }; recognition.onresult = (event) => { const text = event.results[0][0].transcript; console.log('Speech result:', text); if (text && text.trim() !== '') { addTranscriptBubble(text, 'patient'); sendInputToBackend(text); } }; recognition.onerror = (event) => { console.error('SpeechRecognition error:', event.error); if (event.error !== 'no-speech') { badgeCallStatus.textContent = "Voice Active"; } }; recognition.onend = () => { animateEqualizer(false); phoneAvatar.classList.remove('active'); // Restart listening if call is active, bot is not talking, and we are not waiting for response if (isCallActive && !isBotSpeaking && !isWaitingForResponse && currentMode === 'voice') { setTimeout(() => { if (isCallActive && !isBotSpeaking && !isWaitingForResponse && currentMode === 'voice') { startRecognition(); } }, 800); } }; } initSpeechRecognition(); function startRecognition() { if (recognition) { const selectedLanguage = document.getElementById('lang-pref-select').value || 'en'; recognition.lang = selectedLanguage === 'te' ? 'te-IN' : 'en-US'; try { recognition.start(); } catch(e){} } } // UI Helpers function animateEqualizer(active) { if (active) { soundEqualizer.classList.add('active'); } else { soundEqualizer.classList.remove('active'); } } function addTranscriptBubble(text, sender) { if (emptyTranscriptMsg) { emptyTranscriptMsg.style.display = 'none'; } const bubble = document.createElement('div'); bubble.className = `bubble ${sender}`; const senderLabel = document.createElement('div'); senderLabel.className = 'bubble-sender'; senderLabel.textContent = sender === 'bot' ? 'Aria (AI)' : 'Patient (You)'; const textContent = document.createElement('div'); textContent.textContent = text; bubble.appendChild(senderLabel); bubble.appendChild(textContent); chatTranscript.appendChild(bubble); // Scroll to bottom chatTranscript.scrollTop = chatTranscript.scrollHeight; } function updateExtractorFields(session, callRef, aptId) { if (!session) return; extName.textContent = session.patientName || 'Waiting...'; extName.className = session.patientName ? 'ext-val' : 'ext-val empty'; extPhone.textContent = session.callerPhone || 'Waiting...'; extPhone.className = session.callerPhone ? 'ext-val' : 'ext-val empty'; extSymptoms.textContent = session.symptoms || 'Waiting...'; extSymptoms.className = session.symptoms ? 'ext-val' : 'ext-val empty'; extDoctor.textContent = session.doctorAssigned || 'Waiting...'; extDoctor.className = session.doctorAssigned ? 'ext-val' : 'ext-val empty'; extDept.textContent = session.department || 'Waiting...'; extDept.className = session.department ? 'ext-val' : 'ext-val empty'; extDate.textContent = session.preferredDate || 'Waiting...'; extDate.className = session.preferredDate ? 'ext-val' : 'ext-val empty'; extTime.textContent = session.preferredTime || 'Waiting...'; extTime.className = session.preferredTime ? 'ext-val' : 'ext-val empty'; if (session.urgency) { extUrgency.innerHTML = `${session.urgency}`; } else { extUrgency.textContent = 'Waiting...'; extUrgency.className = 'ext-val empty'; } extRef.textContent = aptId || 'Waiting...'; extRef.className = aptId ? 'ext-val' : 'ext-val empty'; } function clearExtractorFields() { extName.textContent = 'Waiting...'; extName.className = 'ext-val empty'; extPhone.textContent = 'Waiting...'; extPhone.className = 'ext-val empty'; extSymptoms.textContent = 'Waiting...'; extSymptoms.className = 'ext-val empty'; extDoctor.textContent = 'Waiting...'; extDoctor.className = 'ext-val empty'; extDept.textContent = 'Waiting...'; extDept.className = 'ext-val empty'; extDate.textContent = 'Waiting...'; extDate.className = 'ext-val empty'; extTime.textContent = 'Waiting...'; extTime.className = 'ext-val empty'; extUrgency.textContent = 'Waiting...'; extUrgency.className = 'ext-val empty'; extRef.textContent = 'Waiting...'; extRef.className = 'ext-val empty'; } // Toggle call controls btnToggleCall.addEventListener('click', () => { if (!isCallActive) { startCall(); } else { hangUpCall(); } }); // Start simulated call function startCall() { isCallActive = true; callSessionId = 'vses_' + Date.now() + '_' + Math.random().toString(36).substring(2, 7); currentCallerPhone = generateRandomPhone(); btnToggleCall.className = "btn-phone hangup"; btnToggleCall.innerHTML = ''; btnToggleCall.title = "Hang Up"; badgeCallStatus.textContent = "Connecting..."; badgeCallStatus.className = "phone-status ringing"; phoneDisplayTitle.textContent = "Calling AI Receptionist..."; labelSession.textContent = `Live Session: ${callSessionId.substring(0, 10)}...`; clearExtractorFields(); chatTranscript.innerHTML = ''; if (emptyTranscriptMsg) emptyTranscriptMsg.style.display = 'none'; // Simulate network delay for calling connection setTimeout(() => { if (!isCallActive) return; badgeCallStatus.textContent = "Call Connected"; badgeCallStatus.className = "phone-status active"; phoneDisplayTitle.textContent = "Aria (AI Voice Bot)"; phoneDisplaySub.textContent = `Call Active: ${currentCallerPhone}`; // Initialize with greeting sendInputToBackend(''); }, 1200); } // Hangup call function hangUpCall() { isCallActive = false; if (currentAudioPlayback) { try { currentAudioPlayback.pause(); currentAudioPlayback.currentTime = 0; } catch(e){} currentAudioPlayback = null; } if (recognition) { try { recognition.stop(); } catch(e){} } if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); } btnToggleCall.className = "btn-phone call"; btnToggleCall.innerHTML = ''; btnToggleCall.title = "Start Call"; badgeCallStatus.textContent = "Offline"; badgeCallStatus.className = "phone-status ended"; phoneDisplayTitle.textContent = "AI Receptionist Bot"; phoneDisplaySub.textContent = "Aria - Virtual Assistant"; animateEqualizer(false); phoneAvatar.classList.remove('active'); } // Mode toggle functionality btnModeVoice.addEventListener('click', () => { currentMode = 'voice'; btnModeVoice.classList.add('active'); btnModeText.classList.remove('active'); textInputConsole.classList.remove('active'); if (isCallActive && !isBotSpeaking && recognition) { try { recognition.start(); } catch(e){} } }); btnModeText.addEventListener('click', () => { currentMode = 'text'; btnModeText.classList.add('active'); btnModeVoice.classList.remove('active'); textInputConsole.classList.add('active'); if (recognition) { try { recognition.stop(); } catch(e){} } }); // Handle Text Send btnSendText.addEventListener('click', () => { handleTextSubmission(); }); patientTextInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleTextSubmission(); } }); function handleTextSubmission() { const text = patientTextInput.value.trim(); if (text === '') return; addTranscriptBubble(text, 'patient'); patientTextInput.value = ''; sendInputToBackend(text); } // Backend Connection async function sendInputToBackend(userInput) { if (!isCallActive) return; isWaitingForResponse = true; if (recognition) { try { recognition.stop(); } catch(e){} } try { const response = await fetch(`${API_BASE}/api/voice-call/process`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sessionId: callSessionId, userInput: userInput, callerPhone: currentCallerPhone, language: document.getElementById('lang-pref-select').value || 'en' }) }); const data = await response.json(); if (data.success) { // Display bot reply addTranscriptBubble(data.voiceResponse, 'bot'); // Update extractor board updateExtractorFields(data.session, data.callReference, data.appointmentId); // Speak reply speakText(data.voiceResponse, () => { isWaitingForResponse = false; // End call if finished or emergency, otherwise continue listening if (!isCallActive) return; if (data.isBooked) { setTimeout(() => { showBookingConfirmation(data.voiceResponse, data.appointmentId); }, 1000); } else if (data.isEmergency) { hangUpCall(); } else if (currentMode === 'voice') { startRecognition(); } }); } else { isWaitingForResponse = false; console.error('API Error:', data.error); } } catch (err) { isWaitingForResponse = false; console.error('Fetch error:', err); addTranscriptBubble("I apologize, but we are experiencing system connectivity issues. Please try again.", 'bot'); speakText("I apologize, but we are experiencing system connectivity issues. Please try again.", () => { if (isCallActive && currentMode === 'voice') { startRecognition(); } }); } } // Booking confirmed popup function showBookingConfirmation(aiMsg, aptId) { bookingRef.textContent = aptId; bookingMsg.textContent = `Aria successfully confirmed your slot! Details have been synced into Salesforce and hospital records. Call details saved under Ref: ${aptId}`; bookingPopup.classList.add('active'); // Trigger simulated mobile phone SMS toast notification const smsToast = document.getElementById('sms-toast-overlay'); const smsToastBody = document.getElementById('sms-toast-body'); const smsToastTime = document.getElementById('sms-toast-time'); if (smsToast && smsToastBody && smsToastTime) { const timeStr = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); smsToastTime.textContent = timeStr; smsToastBody.textContent = `Hospital Management System Alert: Your appointment has been booked successfully. Code: ${aptId}. Specialist details synced to CRM dashboard.`; smsToast.classList.add('show'); // Auto-hide the SMS toast after 8 seconds setTimeout(() => { smsToast.classList.remove('show'); }, 8000); } } btnRestartCall.addEventListener('click', () => { bookingPopup.classList.remove('active'); const smsToast = document.getElementById('sms-toast-overlay'); if (smsToast) smsToast.classList.remove('show'); hangUpCall(); startCall(); });