|
d mm<!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 src="https://cdn.jsdelivr.net/npm/@rvcjs/core@latest/dist/rvc.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/@okada-tts/web@latest/dist/okada.min.js"></script> |
|
<script> |
|
tailwind.config = { |
|
theme: { |
|
extend: { |
|
colors: { |
|
iosbg: '#f2f2f7', |
|
iosdark: '#1c1c1e', |
|
accent: '#0a84ff', |
|
accent2: '#5e5ce6', |
|
} |
|
} |
|
} |
|
} |
|
</script> |
|
<style> |
|
|
|
body { |
|
-webkit-tap-highlight-color: transparent; |
|
-webkit-overflow-scrolling: touch; |
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
|
} |
|
|
|
.screen { |
|
display: none; |
|
opacity: 0; |
|
transition: opacity 0.3s ease; |
|
} |
|
|
|
.screen.active { |
|
display: block; |
|
opacity: 1; |
|
} |
|
|
|
|
|
::-webkit-scrollbar { |
|
width: 6px; |
|
} |
|
|
|
::-webkit-scrollbar-track { |
|
background: transparent; |
|
} |
|
|
|
::-webkit-scrollbar-thumb { |
|
background: rgba(0,0,0,0.15); |
|
border-radius: 3px; |
|
} |
|
|
|
|
|
.ios-switch { |
|
position: relative; |
|
display: inline-block; |
|
width: 52px; |
|
height: 32px; |
|
} |
|
|
|
.ios-switch input { |
|
opacity: 0; |
|
width: 0; |
|
height: 0; |
|
} |
|
|
|
.ios-slider { |
|
position: absolute; |
|
cursor: pointer; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background-color: #e9e9ea; |
|
transition: .4s; |
|
border-radius: 16px; |
|
} |
|
|
|
.ios-slider:before { |
|
position: absolute; |
|
content: ""; |
|
height: 28px; |
|
width: 28px; |
|
left: 2px; |
|
bottom: 2px; |
|
background-color: white; |
|
transition: .4s; |
|
border-radius: 50%; |
|
box-shadow: 0 1px 3px rgba(0,0,0,0.3); |
|
} |
|
|
|
input:checked + .ios-slider { |
|
background-color: #32d74b; |
|
} |
|
|
|
input:checked + .ios-slider:before { |
|
transform: translateX(20px); |
|
} |
|
|
|
|
|
.fab { |
|
position: fixed; |
|
right: 20px; |
|
bottom: 20px; |
|
width: 60px; |
|
height: 60px; |
|
background: linear-gradient(135deg, #0a84ff, #5e5ce6); |
|
border-radius: 50%; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
color: white; |
|
font-size: 22px; |
|
box-shadow: 0 4px 10px rgba(0,0,0,0.25); |
|
z-index: 50; |
|
cursor: pointer; |
|
} |
|
|
|
|
|
.ios-input { |
|
background: rgba(118,118,128,0.12); |
|
border-radius: 10px; |
|
padding: 8px 12px; |
|
font-size: 16px; |
|
width: 100%; |
|
transition: background 0.2s; |
|
} |
|
|
|
.ios-input:focus { |
|
background: rgba(118,118,128,0.18); |
|
outline: none; |
|
} |
|
|
|
|
|
.ai-bubble { |
|
background: rgba(118,118,128,0.12); |
|
border-top-left-radius: 18px; |
|
border-top-right-radius: 18px; |
|
border-bottom-right-radius: 18px; |
|
padding: 12px 16px; |
|
max-width: 85%; |
|
align-self: flex-start; |
|
} |
|
|
|
.user-bubble { |
|
background: #0a84ff; |
|
color: white; |
|
border-top-left-radius: 18px; |
|
border-top-right-radius: 18px; |
|
border-bottom-left-radius: 18px; |
|
padding: 12px 16px; |
|
max-width: 85%; |
|
align-self: flex-end; |
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; transform: translateY(10px); } |
|
to { opacity: 1; transform: translateY(0); } |
|
} |
|
|
|
.animate-fadeIn { |
|
animation: fadeIn 0.3s ease-out forwards; |
|
} |
|
</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">Call Assistant</h1> |
|
<p class="text-gray-500 dark:text-gray-400 mt-1">AI that answers your calls and learns over time</p> |
|
</div> |
|
|
|
|
|
<div class="mt-6 bg-white dark:bg-gray-800 rounded-2xl p-5 shadow-sm"> |
|
<div class="flex items-center justify-between mb-4"> |
|
<div> |
|
<h2 class="font-medium">Current Status</h2> |
|
<p class="text-gray-500 dark:text-gray-400 text-sm mt-1">Assistant is active</p> |
|
</div> |
|
<label class="ios-switch"> |
|
<input type="checkbox" checked> |
|
<span class="ios-slider"></span> |
|
</label> |
|
</div> |
|
|
|
<div class="border-t border-gray-200 dark:border-gray-700 pt-4"> |
|
<h3 class="font-medium flex items-center"> |
|
<i class="fas fa-phone mr-2"></i> Connected Number |
|
</h3> |
|
<div class="mt-2 flex items-center justify-between"> |
|
<span class="text-gray-500 dark:text-gray-400">+1 (562) 228-9429</span> |
|
<button class="text-accent text-sm">Change</button> |
|
</div> |
|
</div> |
|
|
|
<div class="mt-4 flex items-center"> |
|
<div class="w-10 h-10 rounded-full bg-accent flex items-center justify-center"> |
|
<i class="fas fa-robot text-white"></i> |
|
</div> |
|
<div class="ml-3"> |
|
<h3 class="font-medium">Today's Stats</h3> |
|
<p class="text-gray-500 dark:text-gray-400 text-sm">Answered 5 calls, 12 min talk time</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mt-4 flex gap-3"> |
|
<div class="flex-1 bg-gradient-to-br from-accent to-accent2 rounded-2xl p-5 text-white"> |
|
<i class="fas fa-comment-alt text-2xl"></i> |
|
<h3 class="font-medium mt-3">Smart Replies</h3> |
|
<p class="text-white text-opacity-80 text-sm mt-1">Teach the AI how to respond</p> |
|
</div> |
|
<div class="flex-1 bg-gray-800 dark:bg-gray-700 rounded-2xl p-5 text-white"> |
|
<i class="fas fa-history text-2xl"></i> |
|
<h3 class="font-medium mt-3">Call History</h3> |
|
<p class="text-gray-300 text-sm mt-1">Review previous conversations</p> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mt-4 bg-white dark:bg-gray-800 rounded-2xl p-5 flex-1 overflow-hidden flex flex-col"> |
|
<div class="flex items-center justify-between"> |
|
<h2 class="font-medium">Recent Activity</h2> |
|
<button class="text-accent text-sm">See All</button> |
|
</div> |
|
|
|
<div class="mt-3 flex-1 overflow-y-auto space-y-4"> |
|
<div class="flex items-start animate-fadeIn"> |
|
<div class="w-10 h-10 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center"> |
|
<i class="fas fa-phone-alt text-green-600 dark:text-green-400"></i> |
|
</div> |
|
<div class="ml-3 flex-1"> |
|
<div class="flex justify-between"> |
|
<h3 class="font-medium">Michael (Work)</h3> |
|
<span class="text-xs text-gray-500 dark:text-gray-400">12:45 PM</span> |
|
</div> |
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">AI Assistant handled call: "Meeting confirmed for tomorrow"</p> |
|
</div> |
|
</div> |
|
|
|
<div class="flex items-start animate-fadeIn"> |
|
<div class="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900 flex items-center justify-center"> |
|
<i class="fas fa-phone-alt text-purple-600 dark:text-purple-400"></i> |
|
</div> |
|
<div class="ml-3 flex-1"> |
|
<div class="flex justify-between"> |
|
<h3 class="font-medium">Sarah (Spam)</h3> |
|
<span class="text-xs text-gray-500 dark:text-gray-400">11:30 AM</span> |
|
</div> |
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">AI blocked suspected spam call</p> |
|
</div> |
|
</div> |
|
|
|
<div class="flex items-start animate-fadeIn"> |
|
<div class="w-10 h-10 rounded-full bg-blue-100 dark:bg-blue-900 flex items-center justify-center"> |
|
<i class="fas fa-phone-alt text-blue-600 dark:text-blue-400"></i> |
|
</div> |
|
<div class="ml-3 flex-1"> |
|
<div class="flex justify-between"> |
|
<h3 class="font-medium">Mom</h3> |
|
<span class="text-xs text-gray-500 dark:text-gray-400">10:15 AM</span> |
|
</div> |
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">AI transferred call to you after screening</p> |
|
</div> |
|
</div> |
|
|
|
<div class="flex items-start animate-fadeIn"> |
|
<div class="w-10 h-10 rounded-full bg-amber-100 dark:bg-amber-900 flex items-center justify-center"> |
|
<i class="fas fa-phone-alt text-amber-600 dark:text-amber-400"></i> |
|
</div> |
|
<div class="ml-3 flex-1"> |
|
<div class="flex justify-between"> |
|
<h3 class="font-medium">Dr. Smith Clinic</h3> |
|
<span class="text-xs text-gray-500 dark:text-gray-400">9:20 AM</span> |
|
</div> |
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">AI scheduled your appointment for next Monday</p> |
|
</div> |
|
</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"> |
|
<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">Train AI</h2> |
|
</div> |
|
<p class="text-gray-500 dark:text-gray-400 mt-1 ml-12">Help your assistant learn</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 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)"> |
|
<button class="w-12 h-12 rounded-xl bg-accent flex items-center justify-center text-white" onclick="submitTraining()"> |
|
<i class="fas fa-paper-plane"></i> |
|
</button> |
|
</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="fixed bottom-0 left-0 right-0 bg-white dark:bg-iosdark border-t border-gray-200 dark:border-gray-800 max-w-md mx-auto"> |
|
<div class="flex justify-around py-2"> |
|
<button class="py-2 px-4 rounded-xl flex flex-col items-center text-accent" onclick="showScreen('homeScreen')"> |
|
<i class="fas fa-home text-lg"></i> |
|
<span class="text-xs mt-1">Home</span> |
|
</button> |
|
<button class="py-2 px-4 rounded-xl flex flex-col items-center text-gray-500" onclick="showScreen('repliesScreen')"> |
|
<i class="fas fa-comment-alt text-lg"></i> |
|
<span class="text-xs mt-1">Replies</span> |
|
</button> |
|
<button class="py-2 px-4 rounded-xl flex flex-col items-center text-gray-500" onclick="showScreen('trainingScreen')"> |
|
<i class="fas fa-graduation-cap text-lg"></i> |
|
<span class="text-xs mt-1">Train</span> |
|
</button> |
|
<button class="py-2 px-4 rounded-xl flex flex-col items-center text-gray-500" onclick="showScreen('settingsScreen')"> |
|
<i class="fas fa-cog text-lg"></i> |
|
<span class="text-xs mt-1">Settings</span> |
|
</button> |
|
</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">Voice Settings</h2> |
|
</div> |
|
<p class="text-gray-500 dark:text-gray-400 mt-1 ml-12">Customize voice personality</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"> |
|
<h3 class="font-medium">Voice Personality</h3> |
|
|
|
<div class="mt-4 space-y-4"> |
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<h4 class="font-medium">Voice Model</h4> |
|
<p class="text-gray-500 dark:text-gray-400 text-sm">RVC-based human voice</p> |
|
</div> |
|
<select id="voiceModel" class="ios-input"> |
|
<option value="okada-female">OKADA Female</option> |
|
<option value="okada-male">OKADA Male</option> |
|
<option value="custom-rvc">Custom RVC</option> |
|
</select> |
|
</div> |
|
|
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<h4 class="font-medium">Emotional Tone</h4> |
|
<p class="text-gray-500 dark:text-gray-400 text-sm">Adjust voice emotions</p> |
|
</div> |
|
<select id="voiceTone" class="ios-input"> |
|
<option value="neutral">Neutral</option> |
|
<option value="friendly">Friendly</option> |
|
<option value="professional">Professional</option> |
|
<option value="empathic">Empathic</option> |
|
</select> |
|
</div> |
|
|
|
<div class="flex items-center justify-between"> |
|
<div> |
|
<h4 class="font-medium">Speed</h4> |
|
<p class="text-gray-500 dark:text-gray-400 text-sm">Speech rate</p> |
|
</div> |
|
<select id="voiceSpeed" class="ios-input"> |
|
<option value="1.0">Normal</option> |
|
<option value="0.8">Slow</option> |
|
<option value="1.2">Fast</option> |
|
</select> |
|
</div> |
|
|
|
<div class="mt-6"> |
|
<button class="w-full py-3 bg-accent text-white rounded-xl" onclick="testVoiceSettings()"> |
|
Test Voice Settings |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-2xl mt-4 p-5"> |
|
<h3 class="font-medium">Advanced RVC Settings</h3> |
|
<div class="mt-4 space-y-2"> |
|
<div> |
|
<label class="text-sm text-gray-500 dark:text-gray-400">Pitch Shift</label> |
|
<input type="range" id="pitchShift" min="-12" max="12" value="0" step="1" class="w-full mt-1"> |
|
<div class="flex justify-between text-xs text-gray-500 mt-1"> |
|
<span>Lower</span> |
|
<span>Normal</span> |
|
<span>Higher</span> |
|
</div> |
|
</div> |
|
|
|
<div class="mt-4"> |
|
<div class="flex justify-between"> |
|
<label class="text-sm">RVC Similarity</label> |
|
<span id="similarityValue" class="text-sm">85%</span> |
|
</div> |
|
<input type="range" id="similarity" min="50" max="100" value="85" step="1" class="w-full mt-1"> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="fab" onclick="toggleRecording()"> |
|
<i class="fas fa-microphone"></i> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
let trainingData = { |
|
learnedResponses: { |
|
"business": [ |
|
{ |
|
"input": "car detailing", |
|
"response": "Thank you for calling [Your Business Name], premium auto detailing specialists. How can we help you today?", |
|
"tokens": ["car", "detailing"], |
|
"lastUsed": "2023-11-15T00:00:00.000Z" |
|
}, |
|
{ |
|
"input": "appointment", |
|
"response": "For appointments, we have availability this week. Would you prefer a wash & wax ($75), full detail ($150) or ceramic coating ($400)?", |
|
"tokens": ["appointment"], |
|
"lastUsed": "2023-11-15T00:00:00.000Z" |
|
}, |
|
{ |
|
"input": "hours", |
|
"response": "Our detailing center is open Monday-Friday 8am-6pm, Saturday 9am-4pm. We're closed Sundays.", |
|
"tokens": ["hours"], |
|
"lastUsed": "2023-11-15T00:00:00.000Z" |
|
} |
|
] |
|
}, |
|
customResponses: { |
|
"business": "Hello, you've reached [Your Business Name] auto detailing. Ask me about our services: basic wash ($35), premium detailing ($150), or ceramic coatings ($400+).", |
|
"spam": "We don't accept sales calls. Please email us at info@yourbusiness.com for business inquiries." |
|
}, |
|
phoneNumber: "+1 (562) 228-9429", |
|
callHandling: { |
|
transferContacts: ["Mom", "Dad"], |
|
screenContacts: true, |
|
spamDetection: true |
|
}, |
|
personality: { |
|
tone: "professional", |
|
useName: true, |
|
responseSpeed: "normal", |
|
businessName: "Shine On Auto Detailing", |
|
services: [ |
|
{name: "Basic Wash", price: "$35", duration: "30 mins"}, |
|
{name: "Deluxe Detail", price: "$150", duration: "3 hours"}, |
|
{name: "Ceramic Coating", price: "$400+", duration: "1-2 days"} |
|
] |
|
} |
|
}; |
|
|
|
|
|
const nlp = { |
|
processInput: function(text) { |
|
|
|
text = text.toLowerCase().trim(); |
|
|
|
|
|
const tokens = text |
|
.replace(/[^\w\s]/g, '') |
|
.split(/\s+/) |
|
.filter(token => token.length > 2); |
|
|
|
|
|
const contextScores = { |
|
business: ['work', 'job', 'meeting', 'service', 'price', 'appointment', 'business', 'company'] |
|
.filter(word => text.includes(word)).length, |
|
personal: ['family', 'friend', 'mom', 'dad', 'home', 'personal'] |
|
.filter(word => text.includes(word)).length, |
|
spam: ['spam', 'block', 'unwanted', 'telemarketer', 'sales', 'offer'] |
|
.filter(word => text.includes(word)).length * 2, |
|
question: ['what', 'when', 'where', 'how', 'why', 'can you', 'would you', '?'] |
|
.filter(word => text.includes(word)).length |
|
}; |
|
|
|
|
|
let primaryContext = 'general'; |
|
let maxScore = 0; |
|
for (const [context, score] of Object.entries(contextScores)) { |
|
if (score > maxScore) { |
|
maxScore = score; |
|
primaryContext = context; |
|
} |
|
} |
|
|
|
return { |
|
text, |
|
tokens, |
|
context: primaryContext, |
|
isQuestion: contextScores.question > 0 |
|
}; |
|
}, |
|
learnResponse: function(inputText, preferredResponse) { |
|
|
|
const tokens = inputText.toLowerCase().split(/\s+/); |
|
const context = this.determineContext(inputText); |
|
|
|
if (!trainingData.learnedResponses[context]) { |
|
trainingData.learnedResponses[context] = []; |
|
} |
|
|
|
trainingData.learnedResponses[context].push({ |
|
input: inputText, |
|
response: preferredResponse, |
|
tokens: tokens, |
|
lastUsed: new Date() |
|
}); |
|
|
|
this.saveToLocalStorage(); |
|
return true; |
|
}, |
|
determineContext: function(text) { |
|
text = text.toLowerCase(); |
|
if (text.includes('work') || text.includes('business') || text.includes('job')) return 'business'; |
|
if (text.includes('family') || text.includes('mom') || text.includes('dad')) return 'personal'; |
|
if (text.includes('spam') || text.includes('block')) return 'spam'; |
|
return 'general'; |
|
}, |
|
getBestResponse: function(inputText) { |
|
const context = this.determineContext(inputText); |
|
const possibleResponses = trainingData.learnedResponses[context] || []; |
|
|
|
if (possibleResponses.length === 0) { |
|
return this.generateDefaultResponse(context); |
|
} |
|
|
|
|
|
const inputTokens = inputText.toLowerCase().split(/\s+/); |
|
let bestMatch = {score: 0, response: possibleResponses[0]}; |
|
|
|
possibleResponses.forEach(item => { |
|
let score = 0; |
|
inputTokens.forEach(token => { |
|
if (item.tokens.includes(token)) score++; |
|
}); |
|
if (score > bestMatch.score) { |
|
bestMatch = {score, response: item}; |
|
} |
|
}); |
|
|
|
return bestMatch.response.response || this.generateDefaultResponse(context); |
|
}, |
|
generateDefaultResponse: function(context) { |
|
const defaults = { |
|
business: "Hello! Thank you for calling our auto detailing service. We offer car washes starting at $35 and full detailing for $150. How can we assist you?", |
|
personal: "Hi, this is [Name]'s phone. I'll get your message to them if it's important.", |
|
spam: "We don't accept sales calls. Please email us at info@yourbusiness.com for business inquiries.", |
|
general: "I'll connect you with our detailing team. Are you calling about an appointment or would you like service information?" |
|
}; |
|
return defaults[context] || defaults.general; |
|
}, |
|
saveToLocalStorage: function() { |
|
localStorage.setItem('aiCallAssistantTraining', JSON.stringify(trainingData)); |
|
}, |
|
loadFromLocalStorage: function() { |
|
const savedData = localStorage.getItem('aiCallAssistantTraining'); |
|
if (savedData) { |
|
trainingData = JSON.parse(savedData); |
|
} |
|
} |
|
}; |
|
|
|
|
|
function showScreen(screenId) { |
|
document.querySelectorAll('.screen').forEach(screen => { |
|
screen.classList.remove('active'); |
|
}); |
|
document.getElementById(screenId).classList.add('active'); |
|
|
|
|
|
const tabs = document.querySelectorAll('.fab ~ .fixed button'); |
|
tabs.forEach(tab => { |
|
const icon = tab.querySelector('i'); |
|
const span = tab.querySelector('span'); |
|
if (screenId === 'homeScreen' && icon.classList.contains('fa-home')) { |
|
tab.classList.add('text-accent'); |
|
} else if (screenId === 'repliesScreen' && icon.classList.contains('fa-comment-alt')) { |
|
tab.classList.add('text-accent'); |
|
} else if (screenId === 'trainingScreen' && icon.classList.contains('fa-graduation-cap')) { |
|
tab.classList.add('text-accent'); |
|
} else { |
|
tab.classList.remove('text-accent'); |
|
tab.classList.add('text-gray-500'); |
|
} |
|
}); |
|
} |
|
|
|
|
|
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 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() !== '') { |
|
const userMessage = 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>${userMessage}</p>`; |
|
chatContainer.appendChild(userBubble); |
|
|
|
|
|
setTimeout(() => { |
|
let aiResponse; |
|
const isTeachingExample = /how should i respond|please reply with|respond with/i.test(userMessage); |
|
|
|
if (isTeachingExample) { |
|
const teachingMatch = userMessage.match(/(how should i respond to|please reply to|respond to) (.*?) (with|by saying) (.*)/i); |
|
if (teachingMatch) { |
|
const question = teachingMatch[2]; |
|
const answer = teachingMatch[4]; |
|
nlp.learnResponse(question, answer); |
|
|
|
aiResponse = ` |
|
<p>Got it! I've learned this response:</p> |
|
<div class="mt-2 bg-blue-50 dark:bg-blue-900 rounded-lg p-3"> |
|
<p><strong>When asked:</strong> ${question}</p> |
|
<p><strong>I'll respond:</strong> ${answer}</p> |
|
</div> |
|
<p class="mt-2">I'll use this response pattern for similar questions.</p> |
|
`; |
|
} else { |
|
aiResponse = ` |
|
<div class="ai-bubble mt-4 animate-fadeIn"> |
|
<p>I can learn how to respond! Try phrasing like:</p> |
|
<div class="mt-2 bg-blue-50 dark:bg-blue-900 rounded-lg p-3"> |
|
<p>"How should I respond to questions about pricing?"</p> |
|
<p>"Please reply to appointment requests with: 'Would you like morning or afternoon?'"</p> |
|
</div> |
|
</div> |
|
`; |
|
} |
|
} else { |
|
const response = nlp.getBestResponse(userMessage); |
|
aiResponse = ` |
|
<p>Based on my training, here's how I would respond:</p> |
|
<div class="mt-2 bg-blue-50 dark:bg-blue-900 rounded-lg p-3"> |
|
<p>${response}</p> |
|
</div> |
|
<p class="mt-2">Would you like to:</p> |
|
`; |
|
} |
|
|
|
const aiBubble = document.createElement('div'); |
|
aiBubble.className = 'ai-bubble mt-4 animate-fadeIn'; |
|
aiBubble.innerHTML = aiResponse; |
|
|
|
if (!aiResponse.includes('Try phrasing')) { |
|
aiBubble.innerHTML += ` |
|
<div class="mt-3 flex gap-2"> |
|
<button class="flex-1 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition" |
|
onclick="acceptResponse(this)"> |
|
Accept <i class="fas fa-check ml-1"></i> |
|
</button> |
|
<button class="flex-1 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition" |
|
onclick="editResponse(this)"> |
|
Edit <i class="fas fa-edit ml-1"></i> |
|
</button> |
|
<button class="px-3 py-2 bg-accent text-white rounded-lg hover:bg-blue-600 transition" |
|
onclick="playResponse(this)"> |
|
<i class="fas fa-volume-up"></i> |
|
</button> |
|
</div> |
|
`; |
|
} |
|
|
|
chatContainer.appendChild(aiBubble); |
|
chatContainer.scrollTop = chatContainer.scrollHeight; |
|
}, 500); |
|
|
|
input.value = ''; |
|
} |
|
} |
|
|
|
function acceptResponse(button) { |
|
try { |
|
const responseDiv = button.closest('.ai-bubble').querySelector('div.bg-blue-50'); |
|
if (!responseDiv) throw new Error('Response not found'); |
|
|
|
let responseText = ''; |
|
|
|
const pTags = responseDiv.querySelectorAll('p'); |
|
if (pTags.length > 1) { |
|
responseText = Array.from(pTags) |
|
.filter(p => !p.innerHTML.includes('<strong>')) |
|
.map(p => p.textContent) |
|
.join('\n'); |
|
} else { |
|
responseText = pTags[0].textContent; |
|
} |
|
|
|
const context = nlp.determineContext(responseText); |
|
|
|
|
|
const strongTags = responseDiv.querySelectorAll('strong'); |
|
if (strongTags.length >= 2) { |
|
const question = strongTags[0].nextSibling.textContent.trim(); |
|
const answer = strongTags[1].nextSibling.textContent.trim(); |
|
nlp.learnResponse(question, answer); |
|
} else { |
|
trainingData.customResponses[context] = responseText; |
|
} |
|
|
|
nlp.saveToLocalStorage(); |
|
|
|
button.innerHTML = '<i class="fas fa-check-circle"></i> Saved'; |
|
button.classList.remove('bg-green-500'); |
|
button.classList.add('bg-emerald-600'); |
|
button.disabled = true; |
|
|
|
|
|
const buttons = button.closest('.flex').querySelectorAll('button'); |
|
buttons.forEach(btn => { |
|
if (btn !== button) { |
|
btn.classList.add('opacity-50', 'cursor-not-allowed'); |
|
btn.disabled = true; |
|
} |
|
}); |
|
} catch (error) { |
|
console.error('Error accepting response:', error); |
|
alert('There was an error saving this response. Please try again.'); |
|
} |
|
} |
|
|
|
function editResponse(button) { |
|
const bubble = button.closest('.ai-bubble'); |
|
const responseDiv = bubble.querySelector('div.bg-blue-50'); |
|
const pTags = responseDiv.querySelectorAll('p'); |
|
|
|
let currentText = ''; |
|
if (pTags.length > 1) { |
|
|
|
currentText = Array.from(pTags) |
|
.filter(p => !p.innerHTML.includes('<strong>')) |
|
.map(p => p.textContent) |
|
.join('\n\n'); |
|
} else { |
|
currentText = pTags[0].textContent; |
|
} |
|
|
|
responseDiv.innerHTML = ` |
|
<textarea class="w-full p-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700" |
|
rows="4">${currentText}</textarea> |
|
<div class="mt-2 flex justify-end gap-2"> |
|
<button onclick="cancelEdit(this)" |
|
class="px-4 py-1 bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded"> |
|
Cancel |
|
</button> |
|
<button onclick="saveEditedResponse(this)" |
|
class="px-4 py-1 bg-accent text-white rounded hover:bg-blue-600 transition"> |
|
Save Changes |
|
</button> |
|
</div> |
|
`; |
|
} |
|
|
|
function cancelEdit(button) { |
|
const bubble = button.closest('.ai-bubble'); |
|
const responseDiv = button.closest('.bg-blue-50'); |
|
const originalText = responseDiv.querySelector('textarea').value; |
|
|
|
|
|
if (originalText.includes('\n\n')) { |
|
responseDiv.innerHTML = originalText.split('\n\n') |
|
.map(text => `<p>${text}</p>`) |
|
.join(''); |
|
} else { |
|
responseDiv.innerHTML = `<p>${originalText}</p>`; |
|
} |
|
} |
|
|
|
function saveEditedResponse(button) { |
|
try { |
|
const newText = button.closest('.bg-blue-50').querySelector('textarea').value; |
|
const bubble = button.closest('.ai-bubble'); |
|
const responseDiv = button.closest('.bg-blue-50'); |
|
|
|
|
|
const wasTeachingExample = bubble.innerHTML.includes('<strong>When asked:</strong>'); |
|
|
|
if (wasTeachingExample) { |
|
|
|
const lines = newText.split('\n'); |
|
if (lines.length >= 2) { |
|
const question = lines[0].replace('When asked:', '').trim(); |
|
const answer = lines[1].replace('I\'ll respond:', '').trim(); |
|
nlp.learnResponse(question, answer); |
|
} |
|
} else { |
|
|
|
const context = nlp.determineContext(newText); |
|
trainingData.customResponses[context] = newText; |
|
} |
|
|
|
nlp.saveToLocalStorage(); |
|
|
|
|
|
if (newText.includes('\n')) { |
|
responseDiv.innerHTML = newText.split('\n') |
|
.map(text => `<p>${text}</p>`) |
|
.join(''); |
|
} else { |
|
responseDiv.innerHTML = `<p>${newText}</p>`; |
|
} |
|
|
|
|
|
const actionDiv = document.createElement('div'); |
|
actionDiv.className = 'mt-3 flex gap-2'; |
|
actionDiv.innerHTML = ` |
|
<button class="flex-1 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 transition" |
|
onclick="acceptResponse(this)"> |
|
Accept <i class="fas fa-check ml-1"></i> |
|
</button> |
|
<button class="flex-1 py-2 bg-red-500 text-white rounded-lg hover:bg-red-600 transition" |
|
onclick="editResponse(this)"> |
|
Edit <i class="fas fa-edit ml-1"></i> |
|
</button> |
|
<button class="px-3 py-2 bg-accent text-white rounded-lg hover:bg-blue-600 transition" |
|
onclick="playResponse(this)"> |
|
<i class="fas fa-volume-up"></i> |
|
</button> |
|
`; |
|
responseDiv.insertAdjacentElement('afterend', actionDiv); |
|
} catch (error) { |
|
console.error('Error saving edits:', error); |
|
alert('There was an error saving your changes. Please try again.'); |
|
} |
|
} |
|
|
|
function playResponse(button) { |
|
try { |
|
const responseDiv = button.closest('.ai-bubble').querySelector('div.bg-blue-50'); |
|
let responseText = ''; |
|
|
|
if (responseDiv.querySelector('strong')) { |
|
|
|
const pTags = responseDiv.querySelectorAll('p'); |
|
responseText = Array.from(pTags) |
|
.filter(p => !p.innerHTML.includes('<strong>')) |
|
.map(p => p.textContent) |
|
.join(' '); |
|
} else { |
|
|
|
responseText = responseDiv.textContent; |
|
} |
|
|
|
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; |
|
button.disabled = true; |
|
|
|
setTimeout(() => { |
|
button.innerHTML = '<i class="fas fa-volume-up"></i>'; |
|
button.disabled = false; |
|
console.log('Would play:', responseText); |
|
|
|
|
|
|
|
const bubble = button.closest('.ai-bubble'); |
|
bubble.classList.add('ring-2', 'ring-accent'); |
|
setTimeout(() => { |
|
bubble.classList.remove('ring-2', 'ring-accent'); |
|
}, 1000); |
|
}, 1500); |
|
} catch (error) { |
|
console.error('Error playing response:', error); |
|
button.innerHTML = '<i class="fas fa-volume-up"></i>'; |
|
button.disabled = false; |
|
} |
|
} |
|
|
|
function handleTrainingKeyPress(e) { |
|
if (e.key === 'Enter') { |
|
submitTraining(); |
|
} |
|
} |
|
|
|
|
|
let voiceProcessor = { |
|
model: 'okada-female', |
|
tone: 'neutral', |
|
speed: 1.0, |
|
pitch: 0, |
|
similarity: 0.85, |
|
|
|
init: function() { |
|
|
|
this.tts = new OkadaTTS({ |
|
voice: this.model, |
|
speed: this.speed, |
|
pitch: this.pitch |
|
}); |
|
|
|
|
|
this.rvc = new RVC({ |
|
similarity: this.similarity, |
|
pitchShift: this.pitch |
|
}); |
|
}, |
|
|
|
speak: function(text) { |
|
|
|
const ttsResult = this.tts.synthesize(text, this.tone); |
|
|
|
|
|
return this.rvc.convert(ttsResult); |
|
}, |
|
|
|
updateSettings: function() { |
|
this.model = document.getElementById('voiceModel').value; |
|
this.tone = document.getElementById('voiceTone').value; |
|
this.speed = parseFloat(document.getElementById('voiceSpeed').value); |
|
this.pitch = parseInt(document.getElementById('pitchShift').value); |
|
this.similarity = parseInt(document.getElementById('similarity').value) / 100; |
|
|
|
this.init(); |
|
} |
|
}; |
|
|
|
|
|
function showCreateTemplateModal() { |
|
document.getElementById('templateModal').classList.remove('hidden'); |
|
document.getElementById('templateName').focus(); |
|
} |
|
|
|
function hideCreateTemplateModal() { |
|
document.getElementById('templateModal').classList.add('hidden'); |
|
} |
|
|
|
function saveNewTemplate() { |
|
const name = document.getElementById('templateName').value.trim(); |
|
const triggers = document.getElementById('templateTriggers').value.trim(); |
|
const response = document.getElementById('templateResponse').value.trim(); |
|
|
|
if (!name || !response) { |
|
alert('Please provide both a template name and response text'); |
|
return; |
|
} |
|
|
|
|
|
if (!trainingData.learnedResponses.business) { |
|
trainingData.learnedResponses.business = []; |
|
} |
|
|
|
trainingData.learnedResponses.business.push({ |
|
input: name, |
|
response: response, |
|
tokens: triggers.split(',').map(t => t.trim()), |
|
lastUsed: new Date() |
|
}); |
|
|
|
nlp.saveToLocalStorage(); |
|
hideCreateTemplateModal(); |
|
showScreen('repliesScreen'); |
|
|
|
|
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed top-16 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-4 py-2 rounded-full animate-fadeIn z-50'; |
|
notification.textContent = 'Template created successfully!'; |
|
document.body.appendChild(notification); |
|
setTimeout(() => notification.remove(), 2000); |
|
|
|
|
|
document.getElementById('templateName').value = ''; |
|
document.getElementById('templateTriggers').value = ''; |
|
document.getElementById('templateResponse').value = ''; |
|
} |
|
|
|
function testVoiceSettings() { |
|
voiceProcessor.updateSettings(); |
|
const audio = voiceProcessor.speak("Hello, this is your AI call assistant with new voice settings."); |
|
audio.play(); |
|
} |
|
|
|
|
|
document.getElementById('similarity').addEventListener('input', function() { |
|
document.getElementById('similarityValue').textContent = this.value + '%'; |
|
}); |
|
|
|
function playResponse(button) { |
|
const responseText = button.parentElement.querySelector('div p').textContent; |
|
const audio = voiceProcessor.speak(responseText); |
|
audio.play(); |
|
} |
|
|
|
|
|
const phoneHandler = { |
|
client: null, |
|
isCallActive: false, |
|
currentCall: null, |
|
|
|
init: function() { |
|
|
|
nlp.loadFromLocalStorage(); |
|
|
|
|
|
this.client = { |
|
onIncomingCall: (call) => this.handleIncomingCall(call), |
|
answerCall: (call) => this.answerCall(call), |
|
endCall: () => this.endCall() |
|
}; |
|
|
|
|
|
this.checkActiveCall(); |
|
|
|
|
|
voiceProcessor.init(); |
|
addAppearAnimations(); |
|
}, |
|
|
|
handleIncomingCall: function(call) { |
|
this.isCallActive = true; |
|
this.currentCall = call; |
|
|
|
|
|
showScreen('homeScreen'); |
|
document.querySelector('.fab i').classList.remove('fa-microphone'); |
|
document.querySelector('.fab i').classList.add('fa-phone-alt'); |
|
document.querySelector('.fab').style.background = '#ff375f'; |
|
|
|
|
|
const callerInfo = call.callerID || 'Unknown Caller'; |
|
const notification = document.createElement('div'); |
|
notification.className = 'fixed top-16 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-full animate-fadeIn z-50'; |
|
notification.innerHTML = ` |
|
Incoming call from: ${callerInfo} |
|
<div class="mt-2 flex justify-center gap-4"> |
|
<button onclick="phoneHandler.answerCall()" class="bg-green-500 w-12 h-12 rounded-full flex items-center justify-center"> |
|
<i class="fas fa-phone"></i> |
|
</button> |
|
<button onclick="phoneHandler.endCall()" class="bg-red-500 w-12 h-12 rounded-full flex items-center justify-center"> |
|
<i class="fas fa-phone-slash"></i> |
|
</button> |
|
</div> |
|
`; |
|
document.body.appendChild(notification); |
|
|
|
|
|
if (trainingData.callHandling.screenContacts || |
|
(!callerInfo && trainingData.callHandling.spamDetection)) { |
|
setTimeout(() => this.screenCall(), 2000); |
|
} |
|
}, |
|
|
|
screenCall: function() { |
|
const callerInfo = this.currentCall.callerID || 'Unknown Caller'; |
|
const isSpam = callerInfo === 'Unknown Caller' && trainingData.callHandling.spamDetection; |
|
const isTransfer = trainingData.callHandling.transferContacts.some(name => |
|
callerInfo.includes(name)); |
|
|
|
let response; |
|
if (isTransfer) { |
|
response = `Transferring call from ${callerInfo} to you...`; |
|
this.currentCall.transfer(); |
|
} else if (isSpam) { |
|
response = trainingData.customResponses.spam || nlp.generateDefaultResponse('spam'); |
|
this.currentCall.reject(); |
|
} else { |
|
response = nlp.getBestResponse(callerInfo); |
|
this.currentCall.speakResponse(response); |
|
} |
|
|
|
|
|
this.logInteraction(callerInfo, response, isSpam ? 'blocked' : 'screened'); |
|
}, |
|
|
|
answerCall: function() { |
|
if (!this.isCallActive) return; |
|
|
|
const callerInfo = this.currentCall.callerID || 'Unknown Caller'; |
|
this.currentCall.answer(); |
|
|
|
|
|
const greeting = nlp.getBestResponse(`call from ${callerInfo}`); |
|
this.currentCall.speakResponse(greeting); |
|
|
|
|
|
this.currentCall.startVoiceRecognition((text) => { |
|
const response = nlp.getBestResponse(text); |
|
this.currentCall.speakResponse(response); |
|
|
|
|
|
if (response.includes('transferring') || response.includes('connecting you')) { |
|
this.currentCall.transfer(); |
|
} |
|
}); |
|
|
|
|
|
this.logInteraction(callerInfo, greeting, 'answered'); |
|
}, |
|
|
|
endCall: function() { |
|
if (!this.isCallActive) return; |
|
this.currentCall.hangup(); |
|
this.isCallActive = false; |
|
this.currentCall = null; |
|
|
|
|
|
document.querySelector('.fab i').classList.remove('fa-phone-alt'); |
|
document.querySelector('.fab i').classList.add('fa-microphone'); |
|
document.querySelector('.fab').style.background = 'linear-gradient(135deg, #0a84ff, #5e5ce6)'; |
|
}, |
|
|
|
logInteraction: function(caller, response, type) { |
|
const logEntry = { |
|
date: new Date(), |
|
caller, |
|
response, |
|
type, |
|
duration: type === 'answered' ? Math.floor(Math.random() * 120) + 5 : 0 |
|
}; |
|
|
|
|
|
const activityElement = document.createElement('div'); |
|
activityElement.className = 'flex items-start animate-fadeIn'; |
|
activityElement.innerHTML = ` |
|
<div class="w-10 h-10 rounded-full ${type === 'blocked' ? 'bg-purple-100 dark:bg-purple-900' : 'bg-green-100 dark:bg-green-900'} flex items-center justify-center"> |
|
<i class="fas fa-phone-alt ${type === 'blocked' ? 'text-purple-600 dark:text-purple-400' : 'text-green-600 dark:text-green-400'}"></i> |
|
</div> |
|
<div class="ml-3 flex-1"> |
|
<div class="flex justify-between"> |
|
<h3 class="font-medium">${caller}</h3> |
|
<span class="text-xs text-gray-500 dark:text-gray-400">${logEntry.date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span> |
|
</div> |
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1"> |
|
AI ${type} call: "${response.substring(0, 40)}${response.length > 40 ? '...' : ''}" |
|
</p> |
|
</div> |
|
`; |
|
|
|
|
|
const activityContainer = document.querySelector('#homeScreen .space-y-4'); |
|
if (activityContainer.firstChild) { |
|
activityContainer.insertBefore(activityElement, activityContainer.firstChild); |
|
} else { |
|
activityContainer.appendChild(activityElement); |
|
} |
|
}, |
|
|
|
checkActiveCall: function() { |
|
|
|
const activeCall = false; |
|
if (activeCall) { |
|
this.handleIncomingCall({ |
|
callerID: 'Incoming call', |
|
answer: () => console.log('Call answered'), |
|
speakResponse: (text) => voiceProcessor.speak(text).play() |
|
}); |
|
} |
|
} |
|
}; |
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
phoneHandler.init(); |
|
|
|
|
|
document.querySelectorAll('[onclick^="showScreen"]').forEach(btn => { |
|
btn.addEventListener('click', function() { |
|
if (this.getAttribute('onclick').includes('trainingScreen')) { |
|
setTimeout(() => { |
|
document.getElementById('trainingInput').focus(); |
|
}, 300); |
|
} |
|
}); |
|
}); |
|
|
|
|
|
window.simulateCall = function(callerID) { |
|
phoneHandler.handleIncomingCall({ |
|
callerID: callerID || 'Unknown Caller', |
|
answer: function() { |
|
console.log('Call answered'); |
|
const audio = voiceProcessor.speak("Hello, this call has been answered by the AI assistant."); |
|
audio.play(); |
|
}, |
|
speakResponse: function(text) { |
|
const audio = voiceProcessor.speak(text); |
|
audio.play(); |
|
}, |
|
reject: function() { console.log('Call rejected'); }, |
|
hangup: function() { console.log('Call ended'); }, |
|
transfer: function() { console.log('Call transferred'); } |
|
}); |
|
}; |
|
}); |
|
</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/ccx" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |