|
|
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>AI Phone Assistant</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> |
| |
| |
| let businessSettings = { |
| maxRingTime: 30, |
| smsResponseTime: 15, |
| responseLanguage: "en", |
| personalGreeting: "", |
| businessHours: { |
| open: "08:00", |
| close: "18:00" |
| }, |
| responses: { |
| missedCall: { |
| en: "Hello, this is Jay's Mobile Wash. We missed your call. Please leave a message and we'll return your call as soon as possible.", |
| es: "Hola, es el lavado móvil de Jay. Perdimos su llamada. Por favor deje un mensaje y le devolveremos la llamada lo antes posible." |
| }, |
| afterHours: { |
| en: "Thank you for contacting Jay's Mobile Wash. Our business hours are [hours]. We will respond when we reopen.", |
| es: "Gracias por contactar el lavado móvil de Jay. Nuestro horario es de [hours]. Responderemos cuando volvamos a abrir." |
| } |
| } |
| }; |
| |
| function loadSettings() { |
| const saved = localStorage.getItem('JMW_Settings'); |
| if (saved) { |
| businessSettings = JSON.parse(saved); |
| |
| if (!businessSettings.maxRingTime) businessSettings.maxRingTime = 30; |
| if (!businessSettings.smsResponseTime) businessSettings.smsResponseTime = 15; |
| |
| document.getElementById('maxRingTime').value = businessSettings.maxRingTime; |
| document.getElementById('smsResponseTime').value = businessSettings.smsResponseTime; |
| document.getElementById('openTime').value = businessSettings.businessHours.open; |
| document.getElementById('closeTime').value = businessSettings.businessHours.close; |
| document.getElementById('responseLanguage').value = businessSettings.responseLanguage; |
| document.getElementById('personalGreeting').value = businessSettings.personalGreeting; |
| document.getElementById('missedCallResponse').value = businessSettings.responses.missedCall.en; |
| document.getElementById('missedCallResponseEs').value = businessSettings.responses.missedCall.es; |
| document.getElementById('afterHoursResponse').value = businessSettings.responses.afterHours.en; |
| document.getElementById('afterHoursResponseEs').value = businessSettings.responses.afterHours.es; |
| } |
| } |
| |
| function saveSettings(showNotification = true) { |
| businessSettings = { |
| maxRingTime: parseInt(document.getElementById('maxRingTime').value), |
| smsResponseTime: parseInt(document.getElementById('smsResponseTime').value), |
| responseLanguage: document.getElementById('responseLanguage').value, |
| personalGreeting: document.getElementById('personalGreeting').value, |
| businessHours: { |
| open: document.getElementById('openTime').value, |
| close: document.getElementById('closeTime').value |
| }, |
| responses: { |
| missedCall: { |
| en: document.getElementById('missedCallResponse').value, |
| es: document.getElementById('missedCallResponseEs').value |
| }, |
| afterHours: { |
| en: document.getElementById('afterHoursResponse').value, |
| es: document.getElementById('afterHoursResponseEs').value |
| } |
| } |
| }; |
| |
| localStorage.setItem('JMW_Settings', JSON.stringify(businessSettings)); |
| |
| |
| const notification = document.createElement('div'); |
| notification.className = 'fixed top-40 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-6 py-3 rounded-xl animate-fadeIn z-50 shadow-lg'; |
| notification.innerHTML = 'Settings saved successfully!'; |
| document.body.appendChild(notification); |
| setTimeout(() => notification.remove(), 3000); |
| } |
| |
| |
| function answerCall() { |
| const currentTime = new Date(); |
| const openTime = new Date(); |
| const closeTime = new Date(); |
| const [openHours, openMins] = businessSettings.businessHours.open.split(':'); |
| const [closeHours, closeMins] = businessSettings.businessHours.close.split(':'); |
| |
| openTime.setHours(openHours, openMins); |
| closeTime.setHours(closeHours, closeMins); |
| |
| if (currentTime < openTime || currentTime > closeTime) { |
| const fab = document.querySelector('.fab'); |
| const icon = fab.querySelector('i'); |
| icon.classList.remove('fa-phone'); |
| icon.classList.add('fa-clock'); |
| |
| showResponseMessage(businessSettings.responses.afterHours.replace('[hours]', |
| `${businessSettings.businessHours.open} - ${businessSettings.businessHours.close}`)); |
| |
| setTimeout(() => { |
| icon.classList.remove('fa-clock'); |
| icon.classList.add('fa-phone'); |
| }, 1000); |
| return; |
| } |
| |
| } |
| |
| function showResponseMessage(message) { |
| const responseContainer = document.querySelector('#callResponseContainer'); |
| if (!responseContainer) { |
| const container = document.createElement('div'); |
| container.id = 'callResponseContainer'; |
| container.className = 'fixed top-16 left-1/2 transform -translate-x-1/2 bg-black bg-opacity-80 text-white px-4 py-2 rounded-lg z-50'; |
| container.textContent = message; |
| document.body.appendChild(container); |
| setTimeout(() => container.remove(), 3000); |
| } |
| } |
| |
| function exportConfiguration() { |
| const data = { |
| trainingData: trainingData, |
| timestamp: new Date().toISOString(), |
| version: "1.0", |
| aiProfile: "Grok-Style Learning Assistant" |
| }; |
| |
| const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'}); |
| const url = URL.createObjectURL(blob); |
| |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = `AI-Call-Assistant-${new Date().toISOString().split('T')[0]}.json`; |
| document.body.appendChild(a); |
| a.click(); |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| |
| alert("Configuration exported successfully!"); |
| } |
| |
| |
| tailwind.config = { |
| theme: { |
| extend: { |
| colors: { |
| iosbg: '#f2f2f7', |
| iosdark: '#1c1c1e', |
| accent: '#0a84ff', |
| accent2: '#5e5ce6', |
| } |
| } |
| } |
| } |
| |
| |
| function handleSMS(message, sender, isiMessage) { |
| const responseLanguage = businessSettings.responseLanguage; |
| const greeting = businessSettings.personalGreeting; |
| |
| let response; |
| |
| |
| const spanishKeywords = ['hola', 'gracias', 'servicio', 'lavado']; |
| const isSpanish = spanishKeywords.some(word => |
| message.toLowerCase().includes(word)); |
| |
| |
| const useSpanish = responseLanguage === 'es' || isSpanish; |
| |
| if (message.toLowerCase().includes('appointment') || |
| message.toLowerCase().includes('cita')) { |
| response = useSpanish ? |
| `Gracias por su mensaje. Nuestros horarios para citas son ${businessSettings.businessHours.open} a ${businessSettings.businessHours.close}.` : |
| `Thanks for your message. Our appointment hours are ${businessSettings.businessHours.open} to ${businessSettings.businessHours.close}.`; |
| } else if (greeting && !isiMessage) { |
| |
| response = greeting; |
| } else { |
| response = useSpanish ? |
| "Gracias por su mensaje. ¿En qué puedo ayudarle hoy?" : |
| "Thanks for your message. How can I help you today?"; |
| } |
| |
| return response; |
| } |
| |
| |
| function answerCallWithSpeech(callerNumber) { |
| const synth = window.speechSynthesis; |
| const utterance = new SpeechSynthesisUtterance(); |
| |
| const greeting = businessSettings.personalGreeting || |
| (businessSettings.responseLanguage === 'es' ? |
| "Buenos días, habla el asistente de Jay's Mobile Wash. ¿En qué puedo ayudarle?" : |
| "Hello, this is Jay's Mobile Wash assistant. How can I help you?"); |
| |
| utterance.text = greeting; |
| utterance.lang = businessSettings.responseLanguage === 'es' ? 'es-ES' : 'en-US'; |
| |
| |
| synth.speak(utterance); |
| |
| |
| if ('webkitSpeechRecognition' in window) { |
| const recognition = new webkitSpeechRecognition(); |
| recognition.lang = businessSettings.responseLanguage === 'es' ? 'es-ES' : 'en-US'; |
| recognition.interimResults = false; |
| |
| recognition.onresult = function(event) { |
| const transcript = event.results[0][0].transcript; |
| |
| processCallerSpeech(transcript, recognition); |
| }; |
| |
| recognition.start(); |
| } |
| } |
| |
| function processCallerSpeech(transcript, recognition) { |
| const synth = window.speechSynthesis; |
| const utterance = new SpeechSynthesisUtterance(); |
| utterance.lang = businessSettings.responseLanguage === 'es' ? 'es-ES' : 'en-US'; |
| |
| |
| if (transcript.toLowerCase().includes('appointment') || |
| transcript.toLowerCase().includes('cita')) { |
| utterance.text = businessSettings.responseLanguage === 'es' ? |
| `Las citas están disponibles de ${businessSettings.businessHours.open} a ${businessSettings.businessHours.close}.` : |
| `Appointments are available from ${businessSettings.businessHours.open} to ${businessSettings.businessHours.close}.`; |
| } else { |
| utterance.text = businessSettings.responseLanguage === 'es' ? |
| "Por favor visite nuestro sitio web jaysmobilewash.com para más información. ¿Algo más en lo que pueda ayudarle?" : |
| "Please visit our website jaysmobilewash.com for more information. Is there anything else I can help with?"; |
| } |
| |
| synth.speak(utterance); |
| |
| |
| setTimeout(() => recognition.start(), 2000); |
| } |
| </script> |
| <style> |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
| } |
| |
| .screen { |
| display: none; |
| } |
| |
| .screen.active { |
| display: block; |
| } |
| |
| |
| .toggle-switch { |
| position: relative; |
| display: inline-block; |
| width: 60px; |
| height: 34px; |
| } |
| |
| .toggle-switch input { |
| opacity: 0; |
| width: 0; |
| height: 0; |
| } |
| |
| .slider { |
| position: absolute; |
| cursor: pointer; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: #ccc; |
| transition: .4s; |
| border-radius: 34px; |
| } |
| |
| .slider:before { |
| position: absolute; |
| content: ""; |
| height: 26px; |
| width: 26px; |
| left: 4px; |
| bottom: 4px; |
| background-color: white; |
| transition: .4s; |
| border-radius: 50%; |
| } |
| |
| input:checked + .slider { |
| background-color: #2196F3; |
| } |
| |
| input:checked + .slider:before { |
| transform: translateX(26px); |
| } |
| |
| |
| .ai-bubble { |
| background: #f1f1f1; |
| border-radius: 5px 15px 15px 5px; |
| padding: 10px 15px; |
| max-width: 85%; |
| margin: 5px 0; |
| } |
| |
| .user-bubble { |
| background: #2196F3; |
| color: white; |
| border-radius: 15px 5px 5px 15px; |
| padding: 10px 15px; |
| max-width: 85%; |
| margin: 5px 0 5px auto; |
| } |
| |
| |
| .simple-btn { |
| background: #2196F3; |
| color: white; |
| border: none; |
| padding: 10px 15px; |
| border-radius: 5px; |
| cursor: pointer; |
| margin: 5px; |
| } |
| |
| |
| .action-btn { |
| background: #4CAF50; |
| color: white; |
| border: none; |
| padding: 10px 15px; |
| border-radius: 5px; |
| cursor: pointer; |
| margin: 5px; |
| } |
| </style> |
| </head> |
| <body class="bg-iosbg dark:bg-iosdark text-gray-900 dark:text-gray-200 min-h-screen"> |
| |
| <div class="fixed top-0 left-0 right-0 h-12 flex items-center px-4 z-50 bg-iosbg dark:bg-iosdark"> |
| <div class="text-left text-sm w-20">9:41</div> |
| <div class="flex-1 flex justify-center"> |
| <i class="fas fa-signal mr-2"></i> |
| <i class="fas fa-wifi mr-2"></i> |
| <i class="fas fa-battery-three-quarters"></i> |
| </div> |
| <div class="w-20 text-right text-xs">100%</div> |
| </div> |
|
|
| |
| <div class="relative pt-12 max-w-md mx-auto h-screen overflow-hidden"> |
| |
| <div id="homeScreen" class="screen active px-4 pt-4 h-full flex flex-col"> |
| <div class="mt-2"> |
| <h1 class="text-3xl font-bold">Jay's Mobile Wash</h1> |
| <p class="text-gray-500 dark:text-gray-400 mt-1">AI Call Assistant Dashboard</p> |
| </div> |
| |
| |
| <div class="mt-8 grid grid-cols-2 gap-4"> |
| <button onclick="showScreen('repliesScreen')" class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex flex-col items-center shadow-sm"> |
| <div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center mb-3"> |
| <i class="fas fa-comments text-blue-500 text-xl"></i> |
| </div> |
| <h3 class="font-medium">Response Templates</h3> |
| <p class="text-xs text-gray-500 mt-1 text-center">Pre-set replies for common calls</p> |
| </button> |
| |
| <button onclick="showScreen('trainingScreen')" class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex flex-col items-center shadow-sm"> |
| <div class="w-12 h-12 rounded-full bg-purple-100 flex items-center justify-center mb-3"> |
| <i class="fas fa-graduation-cap text-purple-500 text-xl"></i> |
| </div> |
| <h3 class="font-medium">Train AI</h3> |
| <p class="text-xs text-gray-500 mt-1 text-center">Improve call responses</p> |
| </button> |
| |
| <button onclick="showScreen('settingsScreen')" class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex flex-col items-center shadow-sm"> |
| <div class="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center mb-3"> |
| <i class="fas fa-cog text-green-500 text-xl"></i> |
| </div> |
| <h3 class="font-medium">Settings</h3> |
| <p class="text-xs text-gray-500 mt-1 text-center">Configure call handling</p> |
| </button> |
| |
| <button onclick="answerCall(true)" class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex flex-col items-center shadow-sm"> |
| <div class="w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mb-3"> |
| <i class="fas fa-phone text-red-500 text-xl"></i> |
| </div> |
| <h3 class="font-medium">Test Call</h3> |
| <p class="text-xs text-gray-500 mt-1 text-center">Practice AI responses</p> |
| </button> |
| </div> |
|
|
| |
| <div class="mt-8 bg-white dark:bg-gray-800 rounded-2xl p-5"> |
| <h2 class="font-bold text-lg mb-3">Today's Summary</h2> |
| <div class="grid grid-cols-3 gap-4 text-center"> |
| <div class="bg-blue-50 dark:bg-blue-900/30 p-3 rounded-xl"> |
| <div class="text-2xl font-bold">12</div> |
| <div class="text-xs">Calls</div> |
| </div> |
| <div class="bg-green-50 dark:bg-green-900/30 p-3 rounded-xl"> |
| <div class="text-2xl font-bold">8</div> |
| <div class="text-xs">Answered</div> |
| </div> |
| <div class="bg-yellow-50 dark:bg-yellow-900/30 p-3 rounded-xl"> |
| <div class="text-2xl font-bold">4</div> |
| <div class="text-xs">Missed</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="mt-4"> |
| <div class="flex justify-between items-center mb-2"> |
| <h3 class="font-medium">Recent Calls</h3> |
| <button class="text-sm text-blue-500">See All</button> |
| </div> |
| <div class="space-y-2"> |
| <div class="flex items-center justify-between p-3 bg-white dark:bg-gray-800 rounded-xl"> |
| <div class="flex items-center"> |
| <div class="w-8 h-8 rounded-full bg-green-100 flex items-center justify-center mr-3"> |
| <i class="fas fa-check text-green-500 text-xs"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium">(562) 228-9429</p> |
| <p class="text-xs text-gray-500">AI Answered - 2:15 PM</p> |
| </div> |
| </div> |
| <i class="fas fa-chevron-right text-gray-400"></i> |
| </div> |
| <div class="flex items-center justify-between p-3 bg-white dark:bg-gray-800 rounded-xl"> |
| <div class="flex items-center"> |
| <div class="w-8 h-8 rounded-full bg-red-100 flex items-center justify-center mr-3"> |
| <i class="fas fa-times text-red-500 text-xs"></i> |
| </div> |
| <div> |
| <p class="text-sm font-medium">Unknown Caller</p> |
| <p class="text-xs text-gray-500">Missed - 10:45 AM</p> |
| </div> |
| </div> |
| <i class="fas fa-chevron-right text-gray-400"></i> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="repliesScreen" class="screen h-full flex flex-col"> |
| <div class="px-4 pt-4"> |
| <div class="flex items-center"> |
| <button class="p-2 rounded-full" onclick="showScreen('homeScreen')"> |
| <i class="fas fa-arrow-left"></i> |
| </button> |
| <h2 class="text-xl font-bold ml-2">Smart Replies</h2> |
| </div> |
| <p class="text-gray-500 dark:text-gray-400 mt-1 ml-12">Customize how AI answers calls</p> |
| </div> |
| |
| <div class="mt-4 px-4 flex-1 overflow-y-auto"> |
| <div class="bg-white dark:bg-gray-800 rounded-2xl p-5 mb-4"> |
| <div class="flex items-center"> |
| <div class="w-12 h-12 rounded-full bg-gradient-to-br from-accent to-accent2 flex items-center justify-center"> |
| <i class="fas fa-brain text-white text-xl"></i> |
| </div> |
| <div class="ml-3"> |
| <h3 class="font-medium">AI Learning Mode</h3> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">Improves responses over time</p> |
| </div> |
| </div> |
| |
| <div class="mt-4 flex items-center justify-between"> |
| <span>Learning from interactions</span> |
| <label class="ios-switch"> |
| <input type="checkbox" checked> |
| <span class="ios-slider"></span> |
| </label> |
| </div> |
| </div> |
| |
| <div class="bg-white dark:bg-gray-800 rounded-2xl overflow-hidden"> |
| <div class="px-5 pt-4"> |
| <h3 class="font-medium">Custom Response Templates</h3> |
| <p class="text-gray-500 dark:text-gray-400 text-sm mt-1">Set predefined responses</p> |
| </div> |
| |
| <div class="mt-4 space-y-2"> |
| <div class="flex items-center justify-between p-4 hover:bg-gray-100 dark:hover:bg-gray-750 cursor-pointer"> |
| <div> |
| <h4 class="font-medium">Business Calls</h4> |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">"Hello, this is [Your Name]'s assistant..."</p> |
| </div> |
| <i class="fas fa-chevron-right text-gray-400"></i> |
| </div> |
| |
| <div class="flex items-center justify-between p-4 hover:bg-gray-100 dark:hover:bg-gray-750 cursor-pointer"> |
| <div> |
| <h4 class="font-medium">Personal Calls</h4> |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">"Hi, this is [Name]'s phone..."</p> |
| </div> |
| <i class="fas fa-chevron-right text-gray-400"></i> |
| </div> |
| |
| <div class="flex items-center justify-between p-4 hover:bg-gray-100 dark:hover:bg-gray-750 cursor-pointer"> |
| <div> |
| <h4 class="font-medium">Spam Protection</h4> |
| <p class="text-sm text-gray-500 dark:text-gray-400 mt-1">"Sorry, this number is not accepting calls..."</p> |
| </div> |
| <i class="fas fa-chevron-right text-gray-400"></i> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-white dark:bg-gray-800 rounded-2xl mt-4 p-5"> |
| <h3 class="font-medium">Response Style</h3> |
| <div class="mt-4 space-y-4"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <h4 class="font-medium">Formal Tone</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">Professional business language</p> |
| </div> |
| <label class="ios-switch"> |
| <input type="checkbox" checked> |
| <span class="ios-slider"></span> |
| </label> |
| </div> |
| |
| <div class="flex items-center justify-between"> |
| <div> |
| <h4 class="font-medium">Friendly Tone</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">Casual conversation style</p> |
| </div> |
| <label class="ios-switch"> |
| <input type="checkbox"> |
| <span class="ios-slider"></span> |
| </label> |
| </div> |
| |
| <div class="flex items-center justify-between"> |
| <div> |
| <h4 class="font-medium">Use My Name</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">"This is [Your Name]'s phone"</p> |
| </div> |
| <label class="ios-switch"> |
| <input type="checkbox" checked> |
| <span class="ios-slider"></span> |
| </label> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="trainingScreen" class="screen h-full flex flex-col"> |
| <div class="px-4 pt-4"> |
| <div class="flex items-center justify-between"> |
| <div class="flex items-center"> |
| <button class="p-2 rounded-full" onclick="showScreen('homeScreen')"> |
| <i class="fas fa-arrow-left"></i> |
| </button> |
| <div class="ml-2"> |
| <h2 class="text-xl font-bold">Train AI</h2> |
| <p class="text-gray-500 dark:text-gray-400 text-sm -mt-1">Linked to (562) 228-9429</p> |
| </div> |
| </div> |
| <button class="text-accent" id="saveTraining">Save</button> |
| </div> |
| </div> |
| |
| <div class="mt-4 px-4 flex-1 overflow-hidden flex flex-col"> |
| <div class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex-1 overflow-hidden flex flex-col"> |
| <div class="flex-1 overflow-y-auto pb-4"> |
| <div class="ai-bubble"> |
| <p>How would you like me to respond to calls from your family?</p> |
| </div> |
| |
| <div class="user-bubble mt-4"> |
| <p>Always transfer calls from Mom and Dad to me</p> |
| </div> |
| |
| <div class="ai-bubble mt-4"> |
| <p>Noted! I'll transfer calls from Mom and Dad immediately.</p> |
| <p class="mt-2">For other family members, how should I respond?</p> |
| </div> |
| |
| <div class="user-bubble mt-4"> |
| <p>Ask them for the reason of calling and text me if it's important</p> |
| </div> |
| |
| <div class="ai-bubble mt-4"> |
| <p>Got it. Here's the response I created based on your feedback:</p> |
| <div class="mt-2 bg-blue-50 dark:bg-blue-900 rounded-lg p-3"> |
| <p>"Hello, this is Alex's assistant. Could you let me know what you're calling about? I'll make sure they get your message."</p> |
| </div> |
| <p class="mt-2">Does this work?</p> |
| </div> |
| </div> |
| |
| <div class="mt-auto pt-4 border-t border-gray-200 dark:border-gray-700"> |
| <div class="flex gap-2"> |
| <input id="trainingInput" type="text" class="ios-input flex-1" placeholder="Teach your assistant..." onkeypress="handleTrainingKeyPress(event)"> |
| <div class="flex gap-2"> |
| <button class="w-12 h-12 rounded-xl bg-blue-500 flex items-center justify-center text-white" onclick="submitTextTraining()"> |
| <i class="fas fa-keyboard"></i> |
| </button> |
| <button class="w-12 h-12 rounded-xl bg-purple-500 flex items-center justify-center text-white" onclick="showScreen('voiceTrainingScreen')"> |
| <i class="fas fa-microphone"></i> |
| </button> |
| </div> |
| </div> |
| <p class="text-xs text-gray-500 dark:text-gray-400 mt-2 text-center">The AI learns from every interaction</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="tab-bar fixed inset-x-0 bottom-0 bg-white dark:bg-gray-800 shadow-lg p-2 flex justify-around items-center border-t border-gray-200 dark:border-gray-700"> |
| <button class="flex flex-col items-center text-xs" onclick="showScreen('homeScreen')"> |
| <i class="fas fa-home mb-1"></i> |
| <span>Home</span> |
| </button> |
| <button class="flex flex-col items-center text-xs" onclick="showScreen('repliesScreen')"> |
| <i class="fas fa-comments mb-1"></i> |
| <span>Replies</span> |
| </button> |
| <button class="flex flex-col items-center text-xs" onclick="showScreen('trainingScreen')"> |
| <i class="fas fa-graduation-cap mb-1"></i> |
| <span>Train</span> |
| </button> |
| <button class="flex flex-col items-center text-xs" onclick="showScreen('settingsScreen')"> |
| <i class="fas fa-cog mb-1"></i> |
| <span>Settings</span> |
| </button> |
| </div> |
| |
| |
| <div id="voiceTrainingScreen" class="screen h-full flex flex-col"> |
| <div class="px-4 pt-4"> |
| <div class="flex items-center justify-between"> |
| <div class="flex items-center"> |
| <button class="p-2 rounded-full" onclick="showScreen('trainingScreen')"> |
| <i class="fas fa-arrow-left"></i> |
| </button> |
| <div class="ml-2"> |
| <h2 class="text-xl font-bold">Voice Training</h2> |
| <p class="text-gray-500 dark:text-gray-400 text-sm -mt-1">Train vocal responses</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mt-4 px-4 flex-1 overflow-hidden flex flex-col"> |
| <div class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex-1 overflow-hidden flex flex-col"> |
| <div id="voiceFeedback" class="flex-1 overflow-y-auto pb-4"> |
| <div class="ai-bubble"> |
| <p>Let's train your AI's verbal responses. Start by saying sample phrases you'd like it to use.</p> |
| </div> |
| </div> |
| |
| <div class="mt-auto pt-4 border-t border-gray-200 dark:border-gray-700"> |
| <div class="flex flex-col gap-3"> |
| <div class="flex gap-2"> |
| <button id="startRecording" class="flex-1 h-12 rounded-xl bg-red-500 text-white" onclick="startVoiceTraining()"> |
| <i class="fas fa-microphone mr-2"></i> Record Response |
| </button> |
| <button class="w-12 h-12 rounded-xl bg-gray-200 flex items-center justify-center" onclick="playLastResponse()"> |
| <i class="fas fa-play"></i> |
| </button> |
| </div> |
| <div class="text-center"> |
| <p class="text-xs text-gray-500 dark:text-gray-400">Analyzing: Tone, Clarity, Pace</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="chatScreen" class="screen h-full flex flex-col"> |
| <div class="px-4 pt-4"> |
| <div class="flex items-center"> |
| <button class="p-2 rounded-full" onclick="showScreen('homeScreen')"> |
| <i class="fas fa-arrow-left"></i> |
| </button> |
| <h2 class="text-xl font-bold ml-2">AI Chat Test</h2> |
| </div> |
| <p class="text-gray-500 dark:text-gray-400 mt-1 ml-12">Test and train the AI assistant</p> |
| </div> |
| |
| <div class="mt-4 px-4 flex-1 overflow-hidden flex flex-col"> |
| <div class="bg-white dark:bg-gray-800 rounded-2xl p-5 flex-1 overflow-hidden flex flex-col"> |
| <div id="chatMessages" class="flex-1 overflow-y-auto pb-4 space-y-4"> |
| <div class="ai-bubble animate-fadeIn"> |
| <p>Hi! I'm your AI assistant for Jay's Mobile Wash. I can answer questions about our detailing services and learn from our conversations.</p> |
| <p class="mt-2">Would you like to test my knowledge or teach me something new?</p> |
| </div> |
| </div> |
| |
| <div class="mt-auto pt-4 border-t border-gray-200 dark:border-gray-700"> |
| <div class="flex gap-2"> |
| <input id="chatInput" type="text" class="ios-input flex-1" placeholder="Ask or teach me..." onkeypress="handleChatKeyPress(event)"> |
| <button class="w-12 h-12 rounded-xl bg-accent flex items-center justify-center text-white" onclick="submitChatMessage()"> |
| <i class="fas fa-paper-plane"></i> |
| </button> |
| </div> |
| <p class="text-xs text-gray-500 dark:text-gray-400 mt-2 text-center">I'll learn from every interaction</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div id="settingsScreen" class="screen h-full flex flex-col"> |
| <div class="px-4 pt-4"> |
| <div class="flex items-center"> |
| <button class="p-2 rounded-full" onclick="showScreen('homeScreen')"> |
| <i class="fas fa-arrow-left"></i> |
| </button> |
| <h2 class="text-xl font-bold ml-2">Jay's Mobile Wash Settings</h2> |
| </div> |
| <p class="text-gray-500 dark:text-gray-400 mt-1 ml-12">Configure AI response behavior</p> |
| </div> |
| |
| <div class="mt-4 px-4 flex-1 overflow-y-auto"> |
| <div class="bg-white dark:bg-gray-800 rounded-2xl p-5 mb-4"> |
| <h3 class="font-medium mb-4">Call Response Timing</h3> |
| |
| <div class="space-y-4"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <h4>Max Ring Time (seconds)</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">If you don't answer by this time, AI will answer</p> |
| </div> |
| <input type="number" id="maxRingTime" value="30" min="5" max="60" class="w-20 px-2 py-1 rounded-lg border border-gray-300"> |
| </div> |
| |
| |
| <div class="flex items-center justify-between"> |
| <div> |
| <h4>SMS Auto-Reply Time (minutes)</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">If no reply to SMS by this time</p> |
| </div> |
| <input type="number" id="smsResponseTime" value="15" min="1" max="120" class="w-20 px-2 py-1 rounded-lg border border-gray-300"> |
| </div> |
| |
| <div class="border-t border-gray-200 dark:border-gray-700 pt-4"> |
| <h3 class="font-medium mb-2">Business Hours</h3> |
| <div class="grid grid-cols-2 gap-3"> |
| <div> |
| <label class="block text-sm text-gray-500 dark:text-gray-400 mb-1">Open Time</label> |
| <input type="time" id="openTime" value="08:00" class="w-full px-2 py-1 rounded-lg border border-gray-300"> |
| </div> |
| <div> |
| <label class="block text-sm text-gray-500 dark:text-gray-400 mb-1">Close Time</label> |
| <input type="time" id="closeTime" value="18:00" class="w-full px-2 py-1 rounded-lg border border-gray-300"> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="bg-white dark:bg-gray-800 rounded-2xl p-5"> |
| <h3 class="font-medium mb-4">Call Handling Configuration</h3> |
| |
| <div class="flex items-center justify-between mb-4"> |
| <div> |
| <h4>Answer Incoming Calls</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">Enable AI to answer phone calls</p> |
| </div> |
| <label class="ios-switch"> |
| <input type="checkbox" id="answerCallsToggle" checked> |
| <span class="ios-slider"></span> |
| </label> |
| </div> |
| |
| <div class="flex items-center justify-between mb-4"> |
| <div> |
| <h4>Live Call Training Mode</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">Ask me questions during calls to learn</p> |
| </div> |
| <label class="ios-switch"> |
| <input type="checkbox" id="callTrainingToggle"> |
| <span class="ios-slider"></span> |
| </label> |
| </div> |
| |
| <h3 class="font-medium mb-4 mt-6">Response Messages</h3> |
| |
| <div class="space-y-4"> |
| <div class="flex items-center justify-between"> |
| <div> |
| <h4>Language</h4> |
| <p class="text-gray-500 dark:text-gray-400 text-sm">Default response language</p> |
| </div> |
| <select id="responseLanguage" class="px-2 py-1 rounded-lg border border-gray-300"> |
| <option value="en">English</option> |
| <option value="es">Español</option> |
| </select> |
| </div> |
|
|
| <div> |
| <label class="block text-sm mb-1">Personal Greeting</label> |
| <textarea id="personalGreeting" rows="2" class="w-full px-3 py-2 rounded-lg border border-gray-300" placeholder="Custom greeting when answering calls..."></textarea> |
| </div> |
| |
| <div> |
| <label class="block text-sm mb-1">Missed Call Response (English)</label> |
| <textarea id="missedCallResponse" rows="2" class="w-full px-3 py-2 rounded-lg border border-gray-300">Hello, this is Jay's Mobile Wash. We missed your call. Please leave a message and we'll return your call as soon as possible.</textarea> |
| </div> |
| <div> |
| <label class="block text-sm mb-1">Respuesta Llamada Perdida (Español)</label> |
| <textarea id="missedCallResponseEs" rows="2" class="w-full px-3 py-2 rounded-lg border border-gray-300">Hola, es el lavado móvil de Jay. Perdimos su llamada. Por favor deje un mensaje y le devolveremos la llamada lo antes posible.</textarea> |
| </div> |
| |
| <div> |
| <label class="block text-sm mb-1">After Hours Response (English)</label> |
| <textarea id="afterHoursResponse" rows="2" class="w-full px-3 py-2 rounded-lg border border-gray-300">Thank you for contacting Jay's Mobile Wash. Our business hours are [hours]. We will respond when we reopen.</textarea> |
| </div> |
| <div> |
| <label class="block text-sm mb-1">Respuesta Fuera Horario (Español)</label> |
| <textarea id="afterHoursResponseEs" rows="2" class="w-full px-3 py-2 rounded-lg border border-gray-300">Gracias por contactar el lavado móvil de Jay. Nuestro horario es de [hours]. Responderemos cuando volvamos a abrir.</textarea> |
| </div> |
| </div> |
| |
| <button onclick="saveCallTimings()" class="mt-6 w-full bg-green-500 text-white py-2 rounded-lg font-medium">Save Call Timing Settings</button> |
| <button onclick="document.getElementById('directPhoneLink').click()" class="mt-4 w-full bg-blue-500 text-white py-2 rounded-lg font-medium flex items-center justify-center"> |
| <i class="fas fa-phone mr-2"></i> Test Phone Connection |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="fab" onclick="answerCall()"> |
| <i class="fas fa-phone"></i> |
| </div> |
| </div> |
|
|
| <script> |
| |
| let trainingData = { |
| |
| textTraining: { |
| responseTemplates: {}, |
| patternMatching: {}, |
| contextRules: {}, |
| learningMetrics: { |
| responseAccuracy: 0.85, |
| confidenceScores: {} |
| } |
| }, |
| |
| |
| voiceTraining: { |
| speechProfiles: {}, |
| toneAnalysis: {}, |
| voiceResponses: [], |
| pronunciation: {} |
| }, |
| customResponses: { |
| business: '"Hello, this is [Your Name]\'s assistant. How can I help you today?"', |
| personal: '"Hi, this is [Name] calling. How can I help you?"', |
| spam: '"Sorry, this number is not accepting unsolicited calls at this time."', |
| urgent: '"This is urgent, please connect me immediately"' |
| }, |
| |
| humorProfiles: {}, |
| sarcasmDetection: {}, |
| emotionalContext: {}, |
| savedTemplates: [] |
| }; |
| |
| |
| function storeTrainingPattern(pattern, response) { |
| trainingData.trainedPatterns[pattern.toLowerCase()] = response; |
| localStorage.setItem('AI_Training', JSON.stringify(trainingData)); |
| } |
| |
| |
| function showScreen(screenId) { |
| const screens = document.querySelectorAll('.screen'); |
| screens.forEach(screen => { |
| screen.classList.remove('active'); |
| }); |
| |
| const targetScreen = document.getElementById(screenId); |
| if (targetScreen) { |
| targetScreen.classList.add('active'); |
| |
| if (screenId === 'settingsScreen') { |
| loadSettings(); |
| } else if (screenId === 'trainingScreen') { |
| populateTrainingHistory(); |
| document.getElementById('trainingInput').focus(); |
| } |
| } |
| |
| |
| const tabLabels = { |
| 'homeScreen': 0, |
| 'repliesScreen': 1, |
| 'trainingScreen': 2, |
| 'settingsScreen': 3 |
| }; |
| |
| const tabs = document.querySelectorAll('.tab-bar button'); |
| const tabIndex = tabLabels[screenId] || 0; |
| |
| tabs.forEach((tab, index) => { |
| tab.classList.toggle('text-accent', index === tabIndex); |
| tab.classList.toggle('text-gray-500', index !== tabIndex); |
| tab.classList.toggle('font-medium', index === tabIndex); |
| }); |
| |
| |
| switch(screenId) { |
| case 'settingsScreen': |
| loadSettings(); |
| break; |
| case 'trainingScreen': |
| initTraining(); |
| break; |
| case 'voiceTrainingScreen': |
| initVoiceTraining(); |
| break; |
| } |
| |
| |
| localStorage.setItem('lastScreen', screenId); |
| } |
| |
| |
| function saveCallTimings() { |
| businessSettings.maxRingTime = parseInt(document.getElementById('maxRingTime').value); |
| businessSettings.smsResponseTime = parseInt(document.getElementById('smsResponseTime').value); |
| businessSettings.businessHours = { |
| open: document.getElementById('openTime').value, |
| close: document.getElementById('closeTime').value |
| }; |
| |
| localStorage.setItem('JMW_Settings', JSON.stringify(businessSettings)); |
| |
| |
| const notification = document.createElement('div'); |
| notification.className = 'fixed top-40 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-6 py-3 rounded-xl animate-fadeIn z-50 shadow-lg'; |
| notification.innerHTML = 'Call timings saved successfully!'; |
| document.body.appendChild(notification); |
| setTimeout(() => notification.remove(), 3000); |
| } |
| |
| |
| function simulateAIProgress() { |
| const progressBars = document.querySelectorAll('.progress'); |
| progressBars.forEach(bar => { |
| const width = 70 + Math.floor(Math.random() * 30); |
| bar.style.width = `${width}%`; |
| }); |
| } |
| |
| |
| function toggleRecording() { |
| const fab = document.querySelector('.fab'); |
| const icon = fab.querySelector('i'); |
| |
| if (icon.classList.contains('fa-microphone')) { |
| icon.classList.remove('fa-microphone'); |
| icon.classList.add('fa-stop'); |
| fab.style.background = 'linear-gradient(135deg, #ff375f, #ff2d55)'; |
| |
| |
| const notification = document.createElement('div'); |
| notification.innerHTML = ` |
| <div class="fixed top-16 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-full animate-fadeIn"> |
| Recording started |
| </div> |
| `; |
| document.body.appendChild(notification); |
| setTimeout(() => { |
| notification.remove(); |
| }, 2000); |
| } else { |
| icon.classList.remove('fa-stop'); |
| icon.classList.add('fa-microphone'); |
| fab.style.background = 'linear-gradient(135deg, #0a84ff, #5e5ce6)'; |
| |
| |
| simulateAIProgress(); |
| } |
| } |
| |
| |
| function answerCall(isTest = false) { |
| const fab = document.querySelector('.fab'); |
| const icon = fab.querySelector('i'); |
| |
| if (icon.classList.contains('fa-phone')) { |
| icon.classList.remove('fa-phone'); |
| icon.classList.add('fa-stop'); |
| fab.style.background = 'linear-gradient(135deg, #32d74b, #30d158)'; |
| |
| showResponseMessage(`Call ringing (will answer in ${businessSettings.maxRingTime} seconds if no reply)...`); |
| |
| setTimeout(() => { |
| if (icon.classList.contains('fa-stop')) { |
| showResponseMessage(businessSettings.personalGreeting || |
| (businessSettings.responseLanguage === 'es' ? |
| "Hola, habla el asistente de Jay's Mobile Wash" : |
| "Hello, this is Jay's Mobile Wash assistant")); |
| } |
| }, businessSettings.maxRingTime * 1000); |
| } else { |
| icon.classList.remove('fa-stop'); |
| icon.classList.add('fa-phone'); |
| fab.style.background = 'linear-gradient(135deg, #0a84ff, #5e5ce6)'; |
| } |
| |
| |
| const callLog = { |
| timestamp: new Date().toISOString(), |
| number: customNumber || (isTest ? 'Test Caller' : 'Unknown'), |
| scenario, |
| duration: businessSettings.maxRingTime, |
| response: businessSettings.personalGreeting, |
| outcome: 'answered' |
| }; |
| |
| trainingData.callHistory = trainingData.callHistory || []; |
| trainingData.callHistory.push(callLog); |
| const fab = document.querySelector('.fab'); |
| const icon = fab.querySelector('i'); |
| |
| if (icon.classList.contains('fa-phone')) { |
| icon.classList.remove('fa-phone'); |
| icon.classList.add('fa-stop'); |
| fab.style.background = 'linear-gradient(135deg, #32d74b, #30d158)'; |
| |
| |
| showResponseMessage(`Call ringing (will answer in ${businessSettings.maxRingTime} seconds if no reply)...`); |
| |
| |
| setTimeout(() => { |
| if (icon.classList.contains('fa-stop')) { |
| |
| const homeScreen = document.getElementById('homeScreen'); |
| const callCard = document.createElement('div'); |
| callCard.className = 'bg-white dark:bg-gray-800 rounded-2xl p-4 mt-4 animate-fadeIn'; |
| |
| const caller = isTest ? 'Test Caller' : 'Unknown (New number)'; |
| callCard.innerHTML = ` |
| <div class="flex items-center justify-between"> |
| <div> |
| <h3 class="font-medium">AI answered call</h3> |
| <p class="text-sm text-gray-500 dark:text-gray-400">From: ${caller}</p> |
| </div> |
| <div class="text-green-500"> |
| <i class="fas fa-check-circle text-xl"></i> |
| </div> |
| </div> |
| <div class="mt-3 text-sm"> |
| <p>AI answered after ${businessSettings.maxRingTime} seconds</p> |
| </div> |
| `; |
| homeScreen.insertBefore(callCard, homeScreen.children[3]); |
| |
| showResponseMessage(businessSettings.personalGreeting || |
| (businessSettings.responseLanguage === 'es' ? |
| "Hola, habla el asistente de Jay's Mobile Wash" : |
| "Hello, this is Jay's Mobile Wash assistant")); |
| } |
| }, businessSettings.maxRingTime * 1000); |
| |
| } else { |
| |
| icon.classList.remove('fa-stop'); |
| icon.classList.add('fa-phone'); |
| fab.style.background = 'linear-gradient(135deg, #0a84ff, #5e5ce6)'; |
| } |
| } |
| |
| function handleSMS(message, sender, isiMessage) { |
| const responseLanguage = businessSettings.responseLanguage; |
| const greeting = businessSettings.personalGreeting; |
| |
| |
| setTimeout(() => { |
| if (!message.includes('RE:')) { |
| const response = greeting || |
| (responseLanguage === 'es' ? |
| "Gracias por su mensaje. ¿En qué puedo ayudarle hoy?" : |
| "Thanks for your message. How can I help you today?"); |
| |
| showResponseMessage(`Auto-reply sent after ${businessSettings.smsResponseTime} mins: ${response}`); |
| } |
| }, businessSettings.smsResponseTime * 60 * 1000); |
| |
| return handleSMSResponse(message, sender, isiMessage); |
| } |
| |
| |
| function saveTraining() { |
| localStorage.setItem('AI_Training', JSON.stringify(trainingData)); |
| |
| |
| const notification = document.createElement('div'); |
| notification.className = 'fixed top-40 left-1/2 transform -translate-x-1/2 bg-gradient-to-r from-green-400 to-blue-500 text-white px-6 py-3 rounded-xl animate-fadeIn z-50 shadow-lg'; |
| notification.innerHTML = ` |
| <div class="flex items-center"> |
| <i class="fas fa-check-circle mr-2"></i> |
| <span>AI training saved! New responses will be active immediately.</span> |
| </div> |
| `; |
| document.body.appendChild(notification); |
| |
| setTimeout(() => { |
| notification.remove(); |
| showScreen('homeScreen'); |
| }, 3000); |
| |
| |
| const statsElement = document.querySelector('#homeScreen .w-10.h-10 + div p'); |
| statsElement.textContent = `Trained on ${Object.keys(trainingData.trainedPatterns).length} patterns`; |
| } |
| |
| |
| function addAppearAnimations() { |
| document.querySelectorAll('.animate-fadeIn').forEach((el, index) => { |
| el.style.animationDelay = `${index * 0.1}s`; |
| }); |
| } |
| |
| |
| function submitTraining() { |
| const input = document.getElementById('trainingInput'); |
| if (input.value.trim() !== '') { |
| trainingData.responses.push(input.value); |
| |
| |
| const chatContainer = document.querySelector('#trainingScreen .flex-1.overflow-y-auto'); |
| const userBubble = document.createElement('div'); |
| userBubble.className = 'user-bubble mt-4 animate-fadeIn'; |
| userBubble.innerHTML = `<p>${input.value}</p>`; |
| chatContainer.appendChild(userBubble); |
| |
| |
| const processed = processTrainingInput(input.value); |
| |
| |
| setTimeout(() => { |
| const aiBubble = document.createElement('div'); |
| aiBubble.className = 'ai-bubble mt-4 animate-fadeIn'; |
| |
| if (processed.success) { |
| aiBubble.innerHTML = ` |
| <p>Understood! I'll handle calls that way now.</p> |
| <div class="mt-2 bg-green-50 dark:bg-green-900 rounded-lg p-3"> |
| <h4 class="font-medium">New Response Pattern:</h4> |
| <p class="mt-1"><strong>When caller says:</strong> ${processed.triggerPhrases.join(' OR ')}</p> |
| <p class="mt-1"><strong>I will:</strong> ${processed.response}</p> |
| </div> |
| <p class="mt-2">Would you like to review any other scenarios?</p> |
| `; |
| storeTrainingPattern(processed.triggerPhrases[0], processed.response); |
| } else { |
| aiBubble.innerHTML = ` |
| <p>I think you want me to handle calls differently when:</p> |
| <div class="mt-2 bg-blue-50 dark:bg-blue-900 rounded-lg p-3"> |
| <p>${processed.response}</p> |
| </div> |
| <p class="mt-2">Can you confirm or clarify how you'd like me to respond?</p> |
| `; |
| } |
| |
| chatContainer.appendChild(aiBubble); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| }, 800); |
| |
| input.value = ''; |
| } |
| } |
| |
| function handleTrainingKeyPress(e) { |
| if (e.key === 'Enter') { |
| submitTextTraining(); |
| } |
| } |
| |
| function submitTextTraining() { |
| const input = document.getElementById('trainingInput'); |
| if (input.value.trim()) { |
| const chatContainer = document.querySelector('#trainingScreen .flex-1.overflow-y-auto'); |
| const userBubble = document.createElement('div'); |
| userBubble.className = 'user-bubble mt-4 animate-fadeIn'; |
| userBubble.innerHTML = `<p>${input.value}</p>`; |
| chatContainer.appendChild(userBubble); |
| |
| setTimeout(() => { |
| const aiBubble = document.createElement('div'); |
| aiBubble.className = 'ai-bubble mt-4 animate-fadeIn'; |
| aiBubble.innerHTML = `<p>Understood! I'll handle calls with: "${input.value}"</p>`; |
| chatContainer.appendChild(aiBubble); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| |
| trainingData.textTraining.responseTemplates[Date.now()] = input.value; |
| localStorage.setItem('AI_Training', JSON.stringify(trainingData)); |
| }, 500); |
| |
| input.value = ''; |
| } |
| } |
| |
| |
| function processTrainingInput(inputText) { |
| |
| const tokens = inputText.toLowerCase().split(/\s+/); |
| const intent = detectIntent(inputText); |
| const context = buildContext(inputText); |
| |
| |
| const reasoningChain = buildReasoningChain(intent, context); |
| |
| |
| const responseOptions = generateResponseVariants(intent, context); |
| |
| return { |
| success: true, |
| intent: intent, |
| reasoning: reasoningChain, |
| responseVariants: responseOptions, |
| confidence: 0.92, |
| suggestedActions: generateFollowUpQuestions(intent) |
| }; |
| } |
| |
| function detectIntent(text) { |
| |
| const normalized = text.toLowerCase(); |
| |
| |
| if (/business|client|customer|service/i.test(normalized)) { |
| return 'business_call'; |
| } else if (/family|friend|personal/i.test(normalized)) { |
| return 'personal_call'; |
| } |
| |
| |
| if (/transfer|connect|put through/i.test(normalized)) { |
| return 'call_transfer'; |
| } else if (/message|text|notify/i.test(normalized)) { |
| return 'message_relay'; |
| } |
| |
| |
| return 'new_response_pattern'; |
| } |
| |
| function buildReasoningChain(intent, context) { |
| |
| const chains = { |
| 'business_call': [ |
| "Detected business context → Using formal tone", |
| "Verified working hours → Will mention availability", |
| "Analyzed keywords → Focus on professional responses" |
| ], |
| 'personal_call': [ |
| "Identified personal connection → Using friendly tone", |
| "Checked relationship markers → Adjusting familiarity level", |
| "Assessed urgency → Determining response priority" |
| ] |
| }; |
| |
| return chains[intent] || [ |
| "No specific pattern found → Learning new behavior", |
| "Analyzing word frequency → Building response map", |
| "Cross-referencing with existing knowledge → Creating adaptive response" |
| ]; |
| } |
| |
| analyzeDiction(inputText); |
| |
| |
| const context = getCurrentContext(); |
| const patterns = [ |
| |
| /(when|if) (.+?) (then|do|respond with) (.+)/i, |
| |
| /(for|when) (\w+) calls?,? (say|respond) "(.+?)"/i, |
| |
| /(treat|handle) (.+?) (as|like) (\w+)/i, |
| |
| /(make|be) (more|less) (\w+) (when|for) (\w+)/i |
| ]; |
| |
| for (const pattern of patterns) { |
| const match = inputText.match(pattern); |
| if (match) { |
| const [, triggerType, trigger, action, response] = match; |
| storeBehaviorPattern(trigger, response, context); |
| |
| return { |
| success: true, |
| triggerPhrases: [trigger], |
| response: enhanceResponse(response, context), |
| context: context |
| }; |
| } |
| } |
| |
| |
| return adaptiveLearning(inputText); |
| } |
| |
| |
| |
| let voiceRecording = false; |
| let mediaRecorder; |
| let audioChunks = []; |
| |
| function startVoiceTraining() { |
| const button = document.getElementById('startRecording'); |
| |
| if (!voiceRecording) { |
| button.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Recording'; |
| button.classList.add('bg-red-600'); |
| voiceRecording = true; |
| |
| |
| setTimeout(() => { |
| button.innerHTML = '<i class="fas fa-microphone mr-2"></i> Record Response'; |
| button.classList.remove('bg-red-600'); |
| voiceRecording = false; |
| processVoiceRecording(); |
| }, 2000); |
| } |
| const button = document.getElementById('startRecording'); |
| |
| if (!voiceRecording) { |
| |
| button.innerHTML = '<i class="fas fa-stop mr-2"></i> Stop Recording'; |
| button.classList.add('bg-red-600'); |
| |
| navigator.mediaDevices.getUserMedia({ audio: true }) |
| .then(stream => { |
| mediaRecorder = new MediaRecorder(stream); |
| mediaRecorder.start(); |
| voiceRecording = true; |
| |
| mediaRecorder.ondataavailable = e => { |
| audioChunks.push(e.data); |
| }; |
| |
| mediaRecorder.onstop = () => { |
| processVoiceRecording(); |
| }; |
| }); |
| } else { |
| |
| button.innerHTML = '<i class="fas fa-microphone mr-2"></i> Record Response'; |
| button.classList.remove('bg-red-600'); |
| |
| mediaRecorder.stop(); |
| voiceRecording = false; |
| } |
| } |
| |
| function processVoiceRecording() { |
| const audioBlob = new Blob(audioChunks); |
| audioChunks = []; |
| |
| |
| const feedback = document.getElementById('voiceFeedback'); |
| const bubble = document.createElement('div'); |
| bubble.className = 'ai-bubble mt-4 animate-fadeIn'; |
| bubble.innerHTML = ` |
| <p>Recording received! Analysis:</p> |
| <ul class="ml-5 mt-2 list-disc"> |
| <li>Tone: Professional</li> |
| <li>Clarity: 85%</li> |
| <li>Recommended pacing adjustment: Slightly slower</li> |
| </ul> |
| <p class="mt-2">Would you like to save this as a standard response?</p> |
| `; |
| feedback.appendChild(bubble); |
| |
| |
| trainingData.voiceTraining.voiceResponses.push({ |
| timestamp: new Date().toISOString(), |
| blob: URL.createObjectURL(audioBlob), |
| analysis: { |
| tone: 'professional', |
| clarity: 0.85, |
| pace: 0.72 |
| } |
| }); |
| |
| localStorage.setItem('AI_Training', JSON.stringify(trainingData)); |
| } |
| const words = text.toLowerCase().split(/\s+/); |
| const contractions = text.match(/\w+'?\w*/g) || []; |
| |
| |
| if (!trainingData.dictionProfiles.user) { |
| trainingData.dictionProfiles.user = { |
| wordUsage: {}, |
| contractionRate: 0, |
| formalityScore: 0, |
| complexityScore: 0 |
| }; |
| } |
| |
| contractions.forEach(word => { |
| if (word.includes("'")) { |
| trainingData.dictionProfiles.user.contractionRate++; |
| } |
| }); |
| |
| |
| words.forEach(word => { |
| trainingData.dictionProfiles.user.wordUsage[word] = |
| (trainingData.dictionProfiles.user.wordUsage[word] || 0) + 1; |
| }); |
| |
| |
| localStorage.setItem('AI_DictionProfile', JSON.stringify(trainingData.dictionProfiles)); |
| } |
| |
| function interpretTrainingIntent(text) { |
| const lower = text.toLowerCase(); |
| |
| if (lower.includes('family') || lower.includes('mom') || lower.includes('dad')) { |
| return trainingData.customResponses.personal; |
| } |
| else if (lower.includes('work') || lower.includes('business') || lower.includes('colleague')) { |
| return trainingData.customResponses.business; |
| } |
| else if (lower.includes('appointment') || lower.includes('schedule')) { |
| return trainingData.customResponses.appointments; |
| } |
| else if (lower.includes('urgent') || lower.includes('emergency')) { |
| return trainingData.customResponses.urgent; |
| } |
| else if (lower.includes('callback') || lower.includes('call back')) { |
| return trainingData.customResponses.callbacks; |
| } |
| else if (lower.includes('spam') || lower.includes('scam') || lower.includes('block')) { |
| return trainingData.customResponses.spam; |
| } |
| else { |
| return detectNewPattern(text); |
| } |
| } |
| |
| function detectNewPattern(text) { |
| |
| const keywords = text.match(/\b(\w{4,})\b/g) || []; |
| const commands = ['transfer', 'ask', 'tell', 'say', 'respond', 'record']; |
| |
| const action = commands.find(cmd => text.toLowerCase().includes(cmd)) || 'handle'; |
| const context = keywords.filter(w => |
| !commands.includes(w.toLowerCase()) && |
| w.length > 3 |
| ).join(', '); |
| |
| return `When call relates to ${context || "this situation"}, I will ${action} accordingly.`; |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| |
| if (localStorage.getItem('JMW_Settings')) { |
| businessSettings = JSON.parse(localStorage.getItem('JMW_Settings')); |
| loadSettings(); |
| } |
| |
| const savedData = localStorage.getItem('AI_Training'); |
| if (savedData) { |
| trainingData = JSON.parse(savedData); |
| } |
| |
| document.getElementById('saveTraining').addEventListener('click', saveTraining); |
| addAppearAnimations(); |
| simulateAIProgress(); |
| |
| |
| showScreen('homeScreen'); |
| |
| |
| |
| document.querySelectorAll('.tab-bar button').forEach((tab, index) => { |
| tab.addEventListener('click', function() { |
| const screens = ['homeScreen', 'repliesScreen', 'trainingScreen', 'settingsScreen']; |
| showScreen(screens[index]); |
| }); |
| }); |
| |
| |
| document.querySelector('.fab').addEventListener('click', function() { |
| answerCall(); |
| }); |
| |
| document.getElementById('saveTraining').addEventListener('click', saveTraining); |
| |
| |
| const phoneLink = document.createElement('a'); |
| phoneLink.href = 'tel:5622289429'; |
| phoneLink.id = 'directPhoneLink'; |
| document.body.appendChild(phoneLink); |
| }); |
| |
| |
| function saveSettings() { |
| businessSettings = { |
| maxRingTime: parseInt(document.getElementById('maxRingTime').value) || 30, |
| smsResponseTime: parseInt(document.getElementById('smsResponseTime').value) || 15, |
| responseLanguage: document.getElementById('responseLanguage').value || 'en', |
| personalGreeting: document.getElementById('personalGreeting').value || '', |
| businessHours: { |
| open: document.getElementById('openTime').value || '08:00', |
| close: document.getElementById('closeTime').value || '18:00' |
| }, |
| responses: { |
| missedCall: { |
| en: document.getElementById('missedCallResponse').value || "Hello, this is Jay's Mobile Wash...", |
| es: document.getElementById('missedCallResponseEs').value || "Hola, es el lavado móvil de Jay..." |
| }, |
| afterHours: { |
| en: document.getElementById('afterHoursResponse').value || "Thank you for contacting...", |
| es: document.getElementById('afterHoursResponseEs').value || "Gracias por contactar..." |
| } |
| } |
| }; |
| |
| localStorage.setItem('JMW_Settings', JSON.stringify(businessSettings)); |
| |
| |
| const notification = document.createElement('div'); |
| notification.className = 'fixed top-40 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-6 py-3 rounded-xl animate-fadeIn z-50 shadow-lg'; |
| notification.innerHTML = '<i class="fas fa-check-circle mr-2"></i> Settings saved successfully!'; |
| document.body.appendChild(notification); |
| setTimeout(() => notification.remove(), 3000); |
| |
| |
| document.getElementById('directPhoneLink').click(); |
| } |
| |
| |
| if (!trainingData) { |
| trainingData = { |
| textTraining: {}, |
| voiceTraining: {}, |
| customResponses: {}, |
| detailingKnowledge: { |
| website: "https://www.jaysmobilewash.com", |
| pricing: { |
| basic: { car: 60, suv: 70 }, |
| luxury: { car: 130, suv: 140 }, |
| max: { car: 200, suv: 210 } |
| } |
| } |
| }; |
| } |
| |
| |
| const detailingKnowledge = { |
| website: "https://www.jaysmobilewash.com", |
| pricing: { |
| basic: { car: 60, suv: 70 }, |
| luxury: { car: 130, suv: 140 }, |
| max: { car: 200, suv: 210 } |
| }, |
| packages: { |
| basic: "2-Step Hand Wash, Tornador Blast, Rim Cleaning, Interior Wipe-Down", |
| luxury: "Basic + Ceramic Wax, Dust Repellent, Vinyl Restoration", |
| max: "Luxury + Graphene Wax, Steam Sanitization, Leather Conditioning" |
| }, |
| features: [ |
| "Eco-Friendly Self-Sufficient Service", |
| "Deionized Spot-Free Water", |
| "Tornador Z-007 Compressed Air Cleaning", |
| "Customizable Packages" |
| ] |
| }; |
| |
| function handleChatKeyPress(e) { |
| if (e.key === 'Enter') { |
| submitChatMessage(); |
| } |
| } |
| |
| function submitChatMessage() { |
| const input = document.getElementById('chatInput'); |
| if (input.value.trim() !== '') { |
| |
| const chatContainer = document.getElementById('chatMessages'); |
| const userBubble = document.createElement('div'); |
| userBubble.className = 'user-bubble mt-4 animate-fadeIn'; |
| userBubble.innerHTML = `<p>${input.value}</p>`; |
| chatContainer.appendChild(userBubble); |
| |
| |
| setTimeout(() => { |
| const aiResponse = generateAIResponse(input.value); |
| const aiBubble = document.createElement('div'); |
| aiBubble.className = 'ai-bubble mt-4 animate-fadeIn'; |
| aiBubble.innerHTML = `<p>${aiResponse}</p>`; |
| chatContainer.appendChild(aiBubble); |
| chatContainer.scrollTop = chatContainer.scrollHeight; |
| |
| |
| learnFromChat(input.value, aiResponse); |
| }, 500); |
| |
| input.value = ''; |
| } |
| } |
| |
| function generateAIResponse(message) { |
| const lowerMsg = message.toLowerCase(); |
| |
| |
| if (lowerMsg.includes('price') || lowerMsg.includes('cost') || lowerMsg.includes('how much')) { |
| if (lowerMsg.includes('basic')) { |
| return `Our Basic package is ${detailingKnowledge.pricing.basic.car} for cars and ${detailingKnowledge.pricing.basic.suv} for SUVs. It includes: ${detailingKnowledge.packages.basic}`; |
| } else if (lowerMsg.includes('luxury')) { |
| return `Our Luxury package is ${detailingKnowledge.pricing.luxury.car} for cars and ${detailingKnowledge.pricing.luxury.suv} for SUVs. Includes: ${detailingKnowledge.packages.luxury}`; |
| } else if (lowerMsg.includes('max')) { |
| return `Our Max package is ${detailingKnowledge.pricing.max.car} for cars and ${detailingKnowledge.pricing.max.suv} for SUVs. Everything in: ${detailingKnowledge.packages.max}`; |
| } |
| return `We offer three main packages:\nBasic: ${detailingKnowledge.pricing.basic.car}-${detailingKnowledge.pricing.basic.suv}\nLuxury: ${detailingKnowledge.pricing.luxury.car}-${detailingKnowledge.pricing.luxury.suv}\nMax: ${detailingKnowledge.pricing.max.car}-${detailingKnowledge.pricing.max.suv}`; |
| } |
| |
| |
| if (lowerMsg.includes('service') || lowerMsg.includes('include') || lowerMsg.includes('wash')) { |
| return `Our services include:\n- ${detailingKnowledge.features.join('\n- ')}\n\nWould you like details on a specific package?`; |
| } |
| |
| |
| if (lowerMsg.includes('book') || lowerMsg.includes('schedule') || lowerMsg.includes('appointment')) { |
| return `To schedule an appointment, please call or text us at (562) 228-9429 with your preferred date, time, and package selection.`; |
| } |
| |
| |
| if (lowerMsg.includes('website') || lowerMsg.includes('online')) { |
| return `You can find more information on our website: ${detailingKnowledge.website}`; |
| } |
| |
| |
| return `I'm still learning about Jay's Mobile Wash. Could you clarify your question or teach me the correct response? For example:\n- "For basic washes, say 'Our Basic package starts at $60'"\n\nYou can also visit our website: ${detailingKnowledge.website}`; |
| } |
| |
| function learnFromChat(message, response) { |
| |
| if (!trainingData.detailingKnowledge) { |
| trainingData.detailingKnowledge = []; |
| } |
| |
| trainingData.detailingKnowledge.push({ |
| question: message, |
| answer: response, |
| timestamp: new Date().toISOString() |
| }); |
| |
| localStorage.setItem('AI_Training', JSON.stringify(trainingData)); |
| } |
| |
| function populateTrainingHistory() { |
| const chatContainer = document.querySelector('#trainingScreen .flex-1.overflow-y-auto'); |
| chatContainer.innerHTML = ''; |
| |
| if (trainingData.responses.length === 0) { |
| const welcomeBubble = document.createElement('div'); |
| welcomeBubble.className = 'ai-bubble animate-fadeIn'; |
| welcomeBubble.innerHTML = ` |
| <p>Hi! I'm your call assistant. Teach me how to handle calls by:</p> |
| <ol class="list-decimal pl-5 mt-2 space-y-1"> |
| <li>Setting response templates</li> |
| <li>Training me on specific call types</li> |
| <li>Testing responses in real calls</li> |
| </ol> |
| <p class="mt-2">Try saying something like: "If caller says 'urgent', mark as important"</p> |
| `; |
| chatContainer.appendChild(welcomeBubble); |
| } else { |
| trainingData.responses.forEach(item => { |
| const userBubble = document.createElement('div'); |
| userBubble.className = 'user-bubble mt-4 animate-fadeIn'; |
| userBubble.innerHTML = `<p>${item}</p>`; |
| chatContainer.appendChild(userBubble); |
| }); |
| } |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=jjmandog/jaysx" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |