AI Voice Widget (BETA) (Call You/Call Me)
Install Guide:
-
Lines to change
Line 327: Number in pretty format (678) 931-7883
Line 330: Number in raw format: +16789317883
Line 416: GHL Inbound Webhook
Line 523: Number without country code - 6789317883
Copy Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Call Interface</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
}
.call-container {
max-width: 500px;
margin: 0 auto;
background: white;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(0, 0, 0, 0.05);
position: relative;
}
.phone-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 30px 30px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.phone-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="circles" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse"><circle cx="10" cy="10" r="1" fill="white" opacity="0.1"/></pattern></defs><rect x="0" y="0" width="100" height="100" fill="url(%23circles)"/></svg>');
}
.ai-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
margin: 0 auto 20px;
border: 3px solid rgba(255, 255, 255, 0.3);
position: relative;
z-index: 1;
transition: all 0.3s ease;
}
.ai-avatar.talking {
animation: pulse 1.5s infinite;
box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7);
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); }
70% { transform: scale(1.05); box-shadow: 0 0 0 10px rgba(255, 255, 255, 0); }
100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
.header-title {
color: white;
font-size: 1.3rem;
font-weight: 600;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
.header-subtitle {
color: rgba(255, 255, 255, 0.8);
font-size: 0.9rem;
position: relative;
z-index: 1;
}
.mode-selector {
display: flex;
background: #f1f5f9;
margin: 30px;
border-radius: 16px;
padding: 6px;
position: relative;
}
.mode-option {
flex: 1;
padding: 12px 20px;
text-align: center;
border-radius: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
z-index: 2;
}
.mode-option.active {
background: white;
color: #667eea;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.call-interface {
padding: 0 30px 30px;
}
.call-section {
display: none;
animation: slideIn 0.3s ease-out;
}
.call-section.active {
display: block;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.phone-display {
background: #f8fafc;
border-radius: 16px;
padding: 30px;
text-align: center;
margin-bottom: 30px;
border: 2px solid #e2e8f0;
}
.phone-number {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 10px;
letter-spacing: 2px;
}
.phone-subtitle {
color: #64748b;
font-size: 0.9rem;
}
.call-button {
width: 100%;
background: linear-gradient(135deg, #10b981, #059669);
color: white;
border: none;
padding: 18px;
border-radius: 16px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
position: relative;
overflow: hidden;
text-decoration: none;
}
.call-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s ease;
}
.call-button:hover::before {
left: 100%;
}
.call-button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(16, 185, 129, 0.3);
}
.input-group {
margin-bottom: 20px;
}
.input-label {
display: block;
color: #374151;
font-weight: 500;
margin-bottom: 8px;
font-size: 0.9rem;
}
.phone-input {
width: 100%;
padding: 16px 20px;
border: 2px solid #e5e7eb;
border-radius: 12px;
font-size: 1.1rem;
transition: all 0.3s ease;
background: white;
}
.phone-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.callback-button {
width: 100%;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
padding: 18px;
border-radius: 16px;
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
position: relative;
overflow: hidden;
}
.callback-button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
}
.callback-button.requesting {
background: linear-gradient(135deg, #f59e0b, #d97706);
animation: pulse 1s infinite;
}
.status-message {
text-align: center;
padding: 20px;
background: #f0fdf4;
border-radius: 12px;
margin-top: 20px;
border: 1px solid #bbf7d0;
color: #15803d;
font-weight: 500;
display: none;
}
.status-message.show {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.call-icon {
font-size: 1.2rem;
}
/* Responsive */
@media (max-width: 480px) {
.call-container {
margin: 0 10px;
}
.phone-header {
padding: 25px 20px 35px;
}
.call-interface {
padding: 0 20px 20px;
}
.mode-selector {
margin: 20px;
}
.phone-number {
font-size: 1.6rem;
}
}
</style>
</head>
<body>
<div class="call-container">
<div class="phone-header">
<div class="ai-avatar" id="aiAvatar">π€</div>
<h2 class="header-title">Talk to Our AI Agent</h2>
<p class="header-subtitle">Available 24/7 • Average response time: < 2 seconds</p>
</div>
<div class="mode-selector">
<div class="mode-option active" onclick="switchMode('inbound')">
π Call AI
</div>
<div class="mode-option" onclick="switchMode('outbound')">
π² AI Calls You
</div>
</div>
<div class="call-interface">
<!-- Inbound Call Section -->
<div class="call-section active" id="inbound">
<div class="phone-display">
<div class="phone-number">(678) 931-7883</div>
<p class="phone-subtitle">Tap to copy • Click call to connect instantly</p>
</div>
<a href="tel:+16789317883" class="call-button" id="callButton">
<span class="call-icon">π</span>
<span id="callText">Call Now - Free Demo</span>
</a>
</div>
<!-- Outbound Call Section -->
<div class="call-section" id="outbound">
<div class="input-group">
<label class="input-label">Your Phone Number</label>
<input type="tel" class="phone-input" id="phoneInput" placeholder="(555) 123-4567" />
</div>
<button class="callback-button" id="callbackButton" onclick="requestCallback()">
<span class="call-icon">π²</span>
<span id="callbackText">Have AI Call Me in 30 Seconds</span>
</button>
</div>
<div class="status-message" id="statusMessage"></div>
</div>
</div>
<script>
let currentMode = 'inbound';
function switchMode(mode) {
currentMode = mode;
// Update active mode selector
document.querySelectorAll('.mode-option').forEach(option => {
option.classList.remove('active');
});
event.target.classList.add('active');
// Show/hide sections
document.querySelectorAll('.call-section').forEach(section => {
section.classList.remove('active');
});
document.getElementById(mode).classList.add('active');
// Update header
const avatar = document.getElementById('aiAvatar');
const title = document.querySelector('.header-title');
const subtitle = document.querySelector('.header-subtitle');
if (mode === 'inbound') {
title.textContent = 'Talk to Our AI Agent';
subtitle.textContent = 'Available 24/7 • Average response time: < 2 seconds';
avatar.textContent = 'π€';
} else {
title.textContent = 'AI Will Call You';
subtitle.textContent = 'Enter your number • AI calls in 30 seconds';
avatar.textContent = 'π';
}
// Hide status message
document.getElementById('statusMessage').classList.remove('show');
}
function requestCallback() {
const phoneInput = document.getElementById('phoneInput');
const button = document.getElementById('callbackButton');
const avatar = document.getElementById('aiAvatar');
const text = document.getElementById('callbackText');
// Check if phone number has at least 10 digits
const digitsOnly = phoneInput.value.replace(/\D/g, '');
if (digitsOnly.length < 10) {
phoneInput.style.borderColor = '#ef4444';
phoneInput.focus();
showStatus('β οΈ Please enter a valid 10-digit phone number');
return;
}
// Reset border color
phoneInput.style.borderColor = '#e5e7eb';
// Start requesting state
button.classList.add('requesting');
avatar.classList.add('talking');
text.textContent = 'Setting up call...';
console.log('Attempting to call webhook with number:', digitsOnly);
// Make the webhook call
fetch('https://services.leadconnecto…c94', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: digitsOnly
})
})
.then(response => {
console.log('Webhook response:', response);
text.textContent = 'Call scheduled successfully!';
showStatus(`π Perfect! Our AI will call ${phoneInput.value} shortly. Please keep your phone nearby and answer when it rings!`);
})
.catch(error => {
console.error('Webhook error:', error);
// Even if there's a CORS error, the webhook might still work
text.textContent = 'Call scheduled!';
showStatus(`π Great! Our AI will call ${phoneInput.value} shortly. Please keep your phone nearby!`);
})
.finally(() => {
setTimeout(() => {
// Reset after 5 seconds
button.classList.remove('requesting');
avatar.classList.remove('talking');
text.textContent = 'Have AI Call Me in 30 Seconds';
phoneInput.value = '';
}, 5000);
});
}
function showStatus(message) {
const statusDiv = document.getElementById('statusMessage');
statusDiv.textContent = message;
statusDiv.classList.add('show');
setTimeout(() => {
statusDiv.classList.remove('show');
}, 8000);
}
// Phone number formatting - improved version
document.getElementById('phoneInput').addEventListener('input', function(e) {
let input = e.target;
let value = input.value.replace(/\D/g, ''); // Remove all non-digits
let cursorPosition = input.selectionStart;
let oldLength = input.value.length;
// Format the number
let formattedValue = '';
if (value.length > 0) {
if (value.length <= 3) {
formattedValue = `(${value}`;
} else if (value.length <= 6) {
formattedValue = `(${value.slice(0, 3)}) ${value.slice(3)}`;
} else {
formattedValue = `(${value.slice(0, 3)}) ${value.slice(3, 6)}-${value.slice(6, 10)}`;
}
}
// Update the input value
input.value = formattedValue;
// Adjust cursor position if needed
let newLength = input.value.length;
if (newLength > oldLength) {
cursorPosition += (newLength - oldLength);
}
// Set cursor position
input.setSelectionRange(cursorPosition, cursorPosition);
});
// Handle backspace properly
document.getElementById('phoneInput').addEventListener('keydown', function(e) {
if (e.key === 'Backspace') {
let input = e.target;
let value = input.value;
let cursorPos = input.selectionStart;
// If cursor is right after a formatting character, move it back
if (cursorPos > 0 && (value[cursorPos - 1] === ')' || value[cursorPos - 1] === ' ' || value[cursorPos - 1] === '-')) {
e.preventDefault();
input.setSelectionRange(cursorPos - 1, cursorPos - 1);
}
}
});
// Show validation on blur
document.getElementById('phoneInput').addEventListener('blur', function(e) {
const digitsOnly = e.target.value.replace(/\D/g, '');
if (digitsOnly.length === 10) {
e.target.style.borderColor = '#10b981';
} else if (digitsOnly.length > 0 && digitsOnly.length < 10) {
e.target.style.borderColor = '#ef4444';
} else {
e.target.style.borderColor = '#e5e7eb';
}
});
// Reset border on focus
document.getElementById('phoneInput').addEventListener('focus', function(e) {
e.target.style.borderColor = '#667eea';
});
// Copy phone number on click
document.querySelector('.phone-number').addEventListener('click', function() {
navigator.clipboard.writeText('6789317883').then(function() {
showStatus('π Phone number copied to clipboard!');
});
});
</script>
</body>
</html>
Inputs & Options:
-
data-color: The hex code of your choice that is responsible for the color of the widget.
-
data-assistant-id: Your assistant’s ID
-
data-account-id: Your GoHighLevel Location ID where the assistant is from
-
data-position: The position on the screen.
-
Choices (all lowercase):
-
bottom-right
-
bottom-left
-
top-right
-
top-left
-
-
-
data-theme: Dark or light theme.
-
Choices (all lowercase):
-
light
-
dark
-
-
-
data-show-prompt: true or false to show the message above the widget button on idle
-
Choices (all lowercase):
-
true
-
false
-
-
-
data-prompt-message: The message in the prompt popup (if visible)
-
data-assistant-name: The name displayed on the widget for the assistant
-
data-button-icon: The icon of the idle widget button to open the actual conversation window
-
Choices (all lowercase):
-
chat (default): Speech bubble icon
-
message: Message box icon
-
question: Question mark icon
-
headset: Support headset icon
-
-
-
data-greeting-message: The first message in the chat widget from the assistant (the pre-determined message that shows when the user opens the widget)
- data-prompts: Allows you to add prompts to the widget to get the user to click on pre-determined questions (that could be fine-tuned in your knowledge base) to start the conversation