AI Chat Widget (BETA)

The long-awaited chat widget native to the platform. We wanted to go simple with a lot of functionality and a clean interface. The widget is able to book, call tools, and converse using all of your assistant’s configuration (find & replace, tools, etc.). This is also the first instance of a decoupled framework from us meaning this widget and the code behind it is completely separate from our main server so it will run even if we go down for whatever reason. This widget also – as long as your oauth is up-to-date and valid – will save all messages as a guest contact and show in your GoHighLevel conversation logs.

 

Install Guide:

  1. Copy the code and put it in a sheet or element where you can edit it.

  2. Fill in your assistant ID and your GoHighLevel location ID for your assistant and oauth to populate.

  3. Replace the highlighted areas with your number and leadconnector inbound webhook url

  4. Configure the inputs and settings as you please.

  5. Copy the code and paste it in a javascript / html element on the page OR in the page header.

  6. You’re ready to go!

 

Copy Code:

Make sure to fill in your Assistant ID for the field that says "data-assistant-id"  and fill in your your location ID for the field that says "data-account-id" number and leadconnector inbound webhook url. Check all highlighted areas in code. This will allow for OAuth access and your assistant to populate correctly.

 

 

<script

      src="https://botdisplay.com/chat-widget.js"

      data-color="#44008a"   

   data-assistant-id="172…00"

      data-account-id="j…O"

      data-position="bottom-right"

      data-theme="light"

      data-show-prompt="true"

      data-prompt-message="Ask me about voice AI 🚀"

      data-startup-message="How can I help you today?"

      data-assistant-name="Halie | LeadIndicator.ai"

      data-button-icon="chat"

      data-greeting-message="👋 Hey there! My name is {assistant_name} and I'm here to find out why you aren't making money with voice ai."

      data-prompts='[{"text": "What is Leadindicator.ai?"}, {"text": "How do I get started?"}, {"text": "What does it cost?"}, {"text": "How does it work?"}]'

    ></script>


<!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',

                mode: 'no-cors', // This bypasses CORS but we won't get response details

                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

Was this article helpful?