ccx / index.html
jjmandog's picture
I can’t create my own responses to customers only the ones you show ! Please fix this - Follow Up Deployment
24754bc verified
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>
/* iOS-like transitions and scrolling */
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;
}
/* Custom iOS-like scrollbars */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.15);
border-radius: 3px;
}
/* iOS-like switch */
.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);
}
/* Floating action button */
.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;
}
/* Custom inputs */
.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;
}
/* Chat bubbles */
.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;
}
/* Dynamic animations */
@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">
<!-- Status Bar -->
<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>
<!-- Main App Container -->
<div class="relative pt-12 max-w-md mx-auto h-screen overflow-hidden">
<!-- Home Screen -->
<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>
<!-- Status Card -->
<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>
<!-- Action Cards -->
<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>
<!-- Recent Activity -->
<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>
<!-- Configure Replies Screen -->
<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>
<!-- Call Training Screen -->
<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>
<!-- Tab Bar -->
<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>
<!-- Settings Screen -->
<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>
<!-- Floating Action Button -->
<div class="fab" onclick="toggleRecording()">
<i class="fas fa-microphone"></i>
</div>
</div>
<script>
// Enhanced AI Training Data with Natural Language Learning
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"}
]
}
};
// Initialize Natural Language Processing
const nlp = {
processInput: function(text) {
// Enhanced NLP processing
text = text.toLowerCase().trim();
// Tokenize and clean input
const tokens = text
.replace(/[^\w\s]/g, '')
.split(/\s+/)
.filter(token => token.length > 2);
// Context detection with weights
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, // Higher weight
question: ['what', 'when', 'where', 'how', 'why', 'can you', 'would you', '?']
.filter(word => text.includes(word)).length
};
// Determine primary context
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) {
// Tokenize input and learn patterns
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);
}
// Find the most relevant response based on keyword matching
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);
}
}
};
// Screen navigation
function showScreen(screenId) {
document.querySelectorAll('.screen').forEach(screen => {
screen.classList.remove('active');
});
document.getElementById(screenId).classList.add('active');
// Update tab bar active state
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');
}
});
}
// Simulate AI learning progress
function simulateAIProgress() {
const progressBars = document.querySelectorAll('.progress');
progressBars.forEach(bar => {
const width = 70 + Math.floor(Math.random() * 30);
bar.style.width = `${width}%`;
});
}
// Toggle call recording function
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)';
// Show a notification
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)';
// Simulate AI learning after recording
simulateAIProgress();
}
}
// Add animation to elements when they appear
function addAppearAnimations() {
document.querySelectorAll('.animate-fadeIn').forEach((el, index) => {
el.style.animationDelay = `${index * 0.1}s`;
});
}
// Enhanced Training Functions
function submitTraining() {
const input = document.getElementById('trainingInput');
if (input.value.trim() !== '') {
const userMessage = input.value;
// Add user message to chat
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);
// Process user input and generate AI response
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 = '';
// Handle both single p and multi-line responses
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);
// If it's a teaching example, find the question too
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;
// Disable other buttons
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) {
// For teaching examples, combine response
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;
// Re-create the original response display
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');
// Determine if this was a teaching example
const wasTeachingExample = bubble.innerHTML.includes('<strong>When asked:</strong>');
if (wasTeachingExample) {
// Parse the edited teaching example
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 {
// Update regular response
const context = nlp.determineContext(newText);
trainingData.customResponses[context] = newText;
}
nlp.saveToLocalStorage();
// Update display
if (newText.includes('\n')) {
responseDiv.innerHTML = newText.split('\n')
.map(text => `<p>${text}</p>`)
.join('');
} else {
responseDiv.innerHTML = `<p>${newText}</p>`;
}
// Reset action buttons
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')) {
// For teaching examples
const pTags = responseDiv.querySelectorAll('p');
responseText = Array.from(pTags)
.filter(p => !p.innerHTML.includes('<strong>'))
.map(p => p.textContent)
.join(' ');
} else {
// For regular responses
responseText = responseDiv.textContent;
}
// Simulate voice playback
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);
// In a real app: voiceProcessor.speak(responseText).play();
// Show visual feedback
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();
}
}
// Voice Processing Functions
let voiceProcessor = {
model: 'okada-female',
tone: 'neutral',
speed: 1.0,
pitch: 0,
similarity: 0.85,
init: function() {
// Initialize OKADA TTS
this.tts = new OkadaTTS({
voice: this.model,
speed: this.speed,
pitch: this.pitch
});
// Initialize RVC
this.rvc = new RVC({
similarity: this.similarity,
pitchShift: this.pitch
});
},
speak: function(text) {
// Process text with OKADA TTS
const ttsResult = this.tts.synthesize(text, this.tone);
// Apply RVC voice conversion
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();
}
};
// Template creation functions
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;
}
// Add to training data
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');
// Show success message
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);
// Clear form
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();
}
// Update similarity display
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();
}
// Phone Integration with WebRTC
const phoneHandler = {
client: null,
isCallActive: false,
currentCall: null,
init: function() {
// Load saved training data
nlp.loadFromLocalStorage();
// Initialize WebRTC client (simplified)
this.client = {
onIncomingCall: (call) => this.handleIncomingCall(call),
answerCall: (call) => this.answerCall(call),
endCall: () => this.endCall()
};
// Check for active call on page load
this.checkActiveCall();
// Initialize voice processor
voiceProcessor.init();
addAppearAnimations();
},
handleIncomingCall: function(call) {
this.isCallActive = true;
this.currentCall = call;
// Update UI to show active 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';
// Show call notification
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);
// Auto-screen call if enabled
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);
}
// Log the interaction
this.logInteraction(callerInfo, response, isSpam ? 'blocked' : 'screened');
},
answerCall: function() {
if (!this.isCallActive) return;
const callerInfo = this.currentCall.callerID || 'Unknown Caller';
this.currentCall.answer();
// Generate smart greeting
const greeting = nlp.getBestResponse(`call from ${callerInfo}`);
this.currentCall.speakResponse(greeting);
// Setup voice recognition for conversation
this.currentCall.startVoiceRecognition((text) => {
const response = nlp.getBestResponse(text);
this.currentCall.speakResponse(response);
// If the response includes transferring, initiate transfer
if (response.includes('transferring') || response.includes('connecting you')) {
this.currentCall.transfer();
}
});
// Log the answered call
this.logInteraction(callerInfo, greeting, 'answered');
},
endCall: function() {
if (!this.isCallActive) return;
this.currentCall.hangup();
this.isCallActive = false;
this.currentCall = null;
// Reset UI
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
};
// Add to recent activity
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>
`;
// Add to top of recent activity
const activityContainer = document.querySelector('#homeScreen .space-y-4');
if (activityContainer.firstChild) {
activityContainer.insertBefore(activityElement, activityContainer.firstChild);
} else {
activityContainer.appendChild(activityElement);
}
},
checkActiveCall: function() {
// Simulated check in demo - in real app would check with telephony provider
const activeCall = false;
if (activeCall) {
this.handleIncomingCall({
callerID: 'Incoming call',
answer: () => console.log('Call answered'),
speakResponse: (text) => voiceProcessor.speak(text).play()
});
}
}
};
// Initialize when page loads
document.addEventListener('DOMContentLoaded', () => {
phoneHandler.init();
// Focus input when training screen opens
document.querySelectorAll('[onclick^="showScreen"]').forEach(btn => {
btn.addEventListener('click', function() {
if (this.getAttribute('onclick').includes('trainingScreen')) {
setTimeout(() => {
document.getElementById('trainingInput').focus();
}, 300);
}
});
});
// Simulate incoming call for demo purposes
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>