Ctrl K
ComunidadGuías…

chattermate-test/index.html

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ChatterMate Widget Test</title>
    <style>
        * {
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
            background: #ffffff;
            color: #333;
        }

        h1 {
            color: #333;
            border-bottom: 2px solid #f34611;
            padding-bottom: 10px;
            font-size: clamp(1.5rem, 4vw, 2.5rem);
            margin: 0 0 20px 0;
        }

        h2 {
            color: #444;
            font-size: clamp(1.25rem, 3vw, 1.5rem);
            margin: 20px 0 15px 0;
        }

        .content {
            background: #f9f9f9;
            padding: 20px;
            border-radius: 8px;
            margin: 20px 0;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }

        code {
            background: #f5f5f5;
            padding: 2px 6px;
            border-radius: 4px;
            font-family: 'Courier New', Courier, monospace;
            font-size: 0.9em;
            word-break: break-word;
        }

        pre {
            overflow-x: auto;
            background: #f5f5f5;
            padding: 15px;
            border-radius: 4px;
            margin: 15px 0;
        }

        pre code {
            background: none;
            padding: 0;
            font-size: 0.85em;
            white-space: pre;
        }

        .demo-section {
            margin: 40px 0;
        }

        .widget-controls {
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
            align-items: center;
            margin-top: 15px;
        }

        .widget-input {
            flex: 1;
            min-width: 200px;
            padding: 12px;
            border-radius: 4px;
            border: 1px solid #ccc;
            font-size: 16px;
            transition: border-color 0.3s ease;
        }

        .widget-input:focus {
            outline: none;
            border-color: #f34611;
            box-shadow: 0 0 0 2px rgba(243, 70, 17, 0.1);
        }

        .demo-button {
            background: #f34611;
            color: white;
            border: none;
            padding: 12px 20px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            font-weight: 500;
            transition: all 0.3s ease;
            white-space: nowrap;
            min-height: 44px;
        }

        .demo-button:hover {
            background: #d93d0c;
            transform: translateY(-1px);
            box-shadow: 0 2px 8px rgba(243, 70, 17, 0.3);
        }

        .demo-button:active {
            transform: translateY(0);
        }

        .demo-button.clear-button {
            background: #dc3545;
        }

        .demo-button.clear-button:hover {
            background: #c82333;
            box-shadow: 0 2px 8px rgba(220, 53, 69, 0.3);
        }

        .demo-button.auth-button {
            background: #28a745;
        }

        .demo-button.auth-button:hover {
            background: #218838;
            box-shadow: 0 2px 8px rgba(40, 167, 69, 0.3);
        }

        .auth-section {
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid #ddd;
        }

        .auth-section h3 {
            font-size: 1.1rem;
            color: #555;
            margin: 0 0 10px 0;
        }

        .auth-section p {
            font-size: 0.9rem;
            color: #666;
            margin: 0 0 15px 0;
        }

        .auth-status {
            font-size: 0.9rem;
            padding: 8px 12px;
            border-radius: 4px;
            display: inline-block;
        }

        .auth-status.success {
            background: #d4edda;
            color: #155724;
        }

        .auth-status.error {
            background: #f8d7da;
            color: #721c24;
        }

        .auth-status.loading {
            background: #fff3cd;
            color: #856404;
        }

        /* Responsive styles */
        @media (max-width: 768px) {
            body {
                padding: 15px;
            }

            .content {
                padding: 15px;
                margin: 15px 0;
            }

            .demo-section {
                margin: 30px 0;
            }

            .widget-controls {
                flex-direction: column;
                align-items: stretch;
            }

            .widget-input {
                min-width: 100%;
                margin-bottom: 10px;
            }

            .demo-button {
                width: 100%;
                margin: 5px 0;
            }

            pre {
                padding: 10px;
                font-size: 0.8em;
            }

            h1 {
                text-align: center;
            }
        }

        @media (max-width: 480px) {
            body {
                padding: 10px;
            }

            .content {
                padding: 12px;
            }

            .demo-button {
                padding: 14px 16px;
                font-size: 16px;
            }

            .widget-input {
                padding: 14px 12px;
                font-size: 16px;
            }
        }

        /* Dark mode support */
        @media (prefers-color-scheme: dark) {
            body {
                background: #1a1a1a;
                color: #e0e0e0;
            }

            h1, h2 {
                color: #f0f0f0;
            }

            .content {
                background: #2d2d2d;
                color: #e0e0e0;
            }

            code, pre {
                background: #333;
                color: #e0e0e0;
            }

            .widget-input {
                background: #333;
                border-color: #555;
                color: #e0e0e0;
            }

            .widget-input:focus {
                border-color: #f34611;
            }

            .auth-section {
                border-top-color: #444;
            }

            .auth-section h3 {
                color: #ccc;
            }

            .auth-section p {
                color: #aaa;
            }

            .auth-status.success {
                background: #1e4620;
                color: #8fdf94;
            }

            .auth-status.error {
                background: #4a1c1c;
                color: #f5a5a5;
            }

            .auth-status.loading {
                background: #4a3c00;
                color: #ffd966;
            }
        }

        /* Accessibility improvements */
        @media (prefers-reduced-motion: reduce) {
            * {
                animation-duration: 0.01ms !important;
                animation-iteration-count: 1 !important;
                transition-duration: 0.01ms !important;
            }
        }
    </style>
</head>

<body>
    <h1>ChatterMate Widget Test Page</h1>

    <div class="content">
        <h2>About ChatterMate</h2>
        <p>
            This is a test page demonstrating the ChatterMate chat widget integration.
            The widget provides real-time chat functionality with AI-powered responses
            and seamless human handoff capabilities.
        </p>
    </div>

    <div class="demo-section">
        <h2>Widget Controls</h2>
        <p>Enter your widget ID and click initialize to load the chat widget.</p>
        <div class="widget-controls">
            <input
                type="text"
                id="widgetIdInput"
                placeholder="Enter Widget ID"
                value=""
                class="widget-input"
            >
            <button class="demo-button" id="widgetButton" onclick="initializeWidget()">
                Initialize Widget
            </button>
            <button class="demo-button clear-button" id="clearButton" onclick="clearWidget()">
                Clear Widget
            </button>
        </div>

        <!-- API Key Authentication Section -->
        <div class="auth-section">
            <h3>Token Authentication (Optional)</h3>
            <p>For agents with "Require Token Auth" enabled, provide an API key to authenticate.</p>
            <div class="widget-controls">
                <input
                    type="password"
                    id="apiKeyInput"
                    placeholder="Enter API Key (from Widget Apps)"
                    class="widget-input"
                >
                <input
                    type="email"
                    id="customerEmailInput"
                    placeholder="Customer Email (optional)"
                    class="widget-input"
                >
            </div>
            <div class="widget-controls" style="margin-top: 10px;">
                <button class="demo-button auth-button" id="authButton" onclick="authenticateAndInitialize()">
                    Authenticate & Initialize
                </button>
                <span id="authStatus" class="auth-status"></span>
            </div>
        </div>
    </div>

    <div class="content">
        <h2>Integration Code</h2>
        <p>To add the ChatterMate widget to your website, add the following code before the closing
            <code>&lt;/body&gt;</code> tag:
        </p>

        <h3 style="margin-top: 20px; font-size: 1rem;">Basic (No Authentication)</h3>
        <pre><code>
&lt;script&gt;
    window.chattermateId = 'YOUR_WIDGET_ID';
    // Required for local testing — without it the widget uses the API URL baked
    // into the build (the cloud API for a production build).
    window.chattermateBaseUrl = 'http://localhost:8000/api/v1';
&lt;/script&gt;
&lt;script src="http://localhost:5173/webclient/chattermate.min.js"&gt;&lt;/script&gt;
        </code></pre>

        <h3 style="margin-top: 20px; font-size: 1rem;">With Token Authentication</h3>
        <p style="font-size: 0.9em; color: #666;">For agents with "Require Token Auth" enabled, generate a token server-side:</p>
        <pre><code>
// Server-side: Generate token using your API key
const response = await fetch('http://localhost:8000/api/v1/generate-token', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_API_KEY'
    },
    body: JSON.stringify({
        widget_id: 'YOUR_WIDGET_ID',
        customer_email: 'user@example.com',  // optional
        ttl_seconds: 3600
    })
});
const { data } = await response.json();
// Pass data.token to frontend

// Client-side: Initialize widget with token
&lt;script&gt;
    window.chattermateId = 'YOUR_WIDGET_ID';
    window.chattermateToken = 'TOKEN_FROM_SERVER';
    window.chattermateBaseUrl = 'http://localhost:8000/api/v1';
&lt;/script&gt;
&lt;script src="http://localhost:5173/webclient/chattermate.min.js"&gt;&lt;/script&gt;
        </code></pre>
    </div>

    <!-- ChatterMate Widget Integration -->
    <script>
        // Constants for localStorage keys
        const WIDGET_ID_KEY = 'chattermate_widget_id';
        const WIDGET_INITIALIZED_KEY = 'chattermate_initialized';

        // Where the widget talks to. WITHOUT this the loader falls back to the URL
        // baked in at build time — which for a :prod build is the cloud API, so a
        // local test would silently chat with api.chattermate.chat.
        // Override in the console: localStorage.setItem('chattermate_api_base', '...')
        const CM_API_BASE = localStorage.getItem('chattermate_api_base') || 'http://localhost:8000/api/v1';
        // The loader is served by the FRONTEND (vite in dev, the dashboard origin in
        // production) — never copied into this folder, where it would go stale.
        // The backend does not serve it. Rebuild after loader changes:
        //   cd ../frontend && npm run build:webclient:prod
        // Follow the page's own scheme rather than hardcoding http://: served over
        // https this page would otherwise pull executable code over an unencrypted
        // connection, which the browser blocks as mixed content anyway.
        const CM_LOADER_URL = localStorage.getItem('chattermate_loader_url')
            || `${window.location.protocol}//localhost:5173/webclient/chattermate.min.js`;
        
        // Load saved widget ID and initialize on page load
        document.addEventListener('DOMContentLoaded', function() {
            loadSavedWidgetId();
            autoInitializeWidget();
        });
        
        function loadSavedWidgetId() {
            const savedWidgetId = localStorage.getItem(WIDGET_ID_KEY);
            const widgetInput = document.getElementById('widgetIdInput');
            const widgetButton = document.getElementById('widgetButton');
            
            if (savedWidgetId) {
                widgetInput.value = savedWidgetId;
                widgetButton.textContent = 'Re-initialize Widget';
                updateButtonStates(true);
            } else {
                // Set default widget ID if none saved
                widgetInput.value = 'd63483f1-3954-4a94-a3f7-bb5039dda633';
                updateButtonStates(false);
            }
        }
        
        function autoInitializeWidget() {
            const savedWidgetId = localStorage.getItem(WIDGET_ID_KEY);
            const wasInitialized = localStorage.getItem(WIDGET_INITIALIZED_KEY) === 'true';
            const hasApiKey = localStorage.getItem('chattermate_api_key');

            // Don't auto-initialize if we have an API key saved (requires token auth)
            if (hasApiKey) {
                console.log('Skipping auto-initialize: API key detected, use Authenticate & Initialize button');
                return;
            }

            if (savedWidgetId && wasInitialized) {
                console.log('Auto-initializing widget with saved ID:', savedWidgetId);
                performWidgetInitialization(savedWidgetId);
            }
        }
        
        function initializeWidget() {
            const widgetId = document.getElementById('widgetIdInput').value;
            if (!widgetId) {
                alert('Please enter a widget ID');
                return;
            }
            
            // Save widget ID to localStorage
            localStorage.setItem(WIDGET_ID_KEY, widgetId);
            localStorage.setItem(WIDGET_INITIALIZED_KEY, 'true');
            
            performWidgetInitialization(widgetId);
            updateButtonStates(true);
        }
        
        function performWidgetInitialization(widgetId) {
            // Set the widget ID
            window.chattermateId = widgetId;
            window.chattermateBaseUrl = CM_API_BASE;

            // Remove existing script if any
            const existingScript = document.getElementById('chattermate-script');
            if (existingScript) {
                existingScript.remove();
            }

            // Wait a moment for cleanup
            setTimeout(() => {
                // Create and add new script
                const script = document.createElement('script');
                script.id = 'chattermate-script';
                script.src = CM_LOADER_URL;
                script.onload = function() {
                    // Initialize the widget once script loads
                    if (window.ChatterMate) {
                        window.ChatterMate.init({ id: widgetId });
                        console.log('Widget initialized with ID:', widgetId);
                    }
                };
                document.body.appendChild(script);
            }, 100);
        }
        
        function clearWidget() {
            // Remove widget script first to prevent it from recreating storage items
            const existingScript = document.getElementById('chattermate-script');
            if (existingScript) {
                existingScript.remove();
            }
            
            // Reset window properties to disable widget functionality
            if (window.chattermateId) {
                delete window.chattermateId;
            }
            if (window.ChatterMate) {
                // Call destroy method if it exists
                if (typeof window.ChatterMate.destroy === 'function') {
                    window.ChatterMate.destroy();
                }
                delete window.ChatterMate;
            }
            
            // Function to clear storage items with specific patterns
            function clearStorageItems(storage, storageType) {
                const keysToRemove = [];
                
                // Collect all keys that match our patterns
                for (let i = 0; i < storage.length; i++) {
                    const key = storage.key(i);
                    if (key && (
                        key.includes('chattermate') || 
                        key.includes('widget') || 
                        key === 'ctid' ||
                        key.includes('chat') ||
                        key.startsWith('cm_') ||
                        key.includes('conversation') ||
                        key.includes('session')
                    )) {
                        keysToRemove.push(key);
                    }
                }
                
                // Remove all collected keys
                keysToRemove.forEach(key => {
                    storage.removeItem(key);
                    console.log(`Removed ${storageType} key:`, key);
                });
                
                return keysToRemove.length;
            }
            
            // Clear localStorage
            const localKeysRemoved = clearStorageItems(localStorage, 'localStorage');
            
            // Clear sessionStorage  
            const sessionKeysRemoved = clearStorageItems(sessionStorage, 'sessionStorage');
            
            // Also remove our specific keys (backup)
            localStorage.removeItem(WIDGET_ID_KEY);
            localStorage.removeItem(WIDGET_INITIALIZED_KEY);
            localStorage.removeItem('ctid');
            sessionStorage.removeItem('ctid');
            
            // Clear any widget-related cookies
            let cookiesCleared = 0;
            document.cookie.split(";").forEach(function(c) { 
                const cookie = c.trim();
                if (cookie.includes('chattermate') || 
                    cookie.includes('widget') || 
                    cookie.includes('ctid') ||
                    cookie.includes('chat')) {
                    const eqPos = cookie.indexOf("=");
                    const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
                    document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/";
                    document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;domain=" + window.location.hostname;
                    cookiesCleared++;
                    console.log('Removed cookie:', name);
                }
            });
            
            // Clear input field
            document.getElementById('widgetIdInput').value = '';
            
            // Force a small delay and check again for any recreated items
            setTimeout(() => {
                localStorage.removeItem('ctid');
                sessionStorage.removeItem('ctid');
                console.log('Final cleanup: Removed any remaining ctid keys');
            }, 100);
            
            updateButtonStates(false);
            console.log(`Widget cleared completely:
- LocalStorage keys removed: ${localKeysRemoved}
- SessionStorage keys removed: ${sessionKeysRemoved}  
- Cookies cleared: ${cookiesCleared}
- Widget script removed and window properties cleaned`);
        }
        
        function updateButtonStates(isInitialized) {
            const widgetButton = document.getElementById('widgetButton');
            const clearButton = document.getElementById('clearButton');
            
            if (isInitialized) {
                widgetButton.textContent = 'Re-initialize Widget';
                clearButton.style.display = 'inline-block';
            } else {
                widgetButton.textContent = 'Initialize Widget';
                clearButton.style.display = 'none';
            }
        }
        
        // Update button state when input changes
        document.addEventListener('DOMContentLoaded', function() {
            const widgetInput = document.getElementById('widgetIdInput');
            widgetInput.addEventListener('input', function() {
                const savedWidgetId = localStorage.getItem(WIDGET_ID_KEY);
                const currentValue = this.value;
                const widgetButton = document.getElementById('widgetButton');

                if (savedWidgetId && currentValue === savedWidgetId) {
                    widgetButton.textContent = 'Re-initialize Widget';
                } else {
                    widgetButton.textContent = 'Initialize Widget';
                }
            });

            // Load saved API key
            const savedApiKey = localStorage.getItem('chattermate_api_key');
            if (savedApiKey) {
                document.getElementById('apiKeyInput').value = savedApiKey;
            }
        });

        // Authenticate with API key and initialize widget
        async function authenticateAndInitialize() {
            const widgetId = document.getElementById('widgetIdInput').value;
            const apiKey = document.getElementById('apiKeyInput').value;
            const customerEmail = document.getElementById('customerEmailInput').value;
            const authStatus = document.getElementById('authStatus');

            if (!widgetId) {
                alert('Please enter a widget ID');
                return;
            }

            if (!apiKey) {
                alert('Please enter an API key');
                return;
            }

            // Show loading state
            authStatus.textContent = 'Authenticating...';
            authStatus.className = 'auth-status loading';

            try {
                // Call the generate-token endpoint
                const response = await fetch(`${CM_API_BASE}/generate-token`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': `Bearer ${apiKey}`
                    },
                    body: JSON.stringify({
                        widget_id: widgetId,
                        customer_email: customerEmail || undefined,
                        ttl_seconds: 3600
                    })
                });

                if (!response.ok) {
                    const errorData = await response.json().catch(() => ({}));
                    throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`);
                }

                const data = await response.json();

                if (data.success && data.data?.token) {
                    // Save API key for future use
                    localStorage.setItem('chattermate_api_key', apiKey);
                    localStorage.setItem(WIDGET_ID_KEY, widgetId);
                    localStorage.setItem(WIDGET_INITIALIZED_KEY, 'true');

                    // Set the token for the widget
                    window.chattermateToken = data.data.token;
                    window.chattermateId = widgetId;

                    // Show success
                    authStatus.textContent = `Authenticated! Token expires: ${new Date(data.data.expires_at).toLocaleString()}`;
                    authStatus.className = 'auth-status success';

                    console.log('Token generated successfully:', {
                        widget_id: data.data.widget_id,
                        expires_at: data.data.expires_at,
                        expires_in: data.data.expires_in
                    });

                    // Initialize the widget with the token
                    performAuthenticatedWidgetInitialization(widgetId, data.data.token);
                    updateButtonStates(true);
                } else {
                    throw new Error('Invalid response from server');
                }
            } catch (error) {
                console.error('Authentication error:', error);
                authStatus.textContent = `Error: ${error.message}`;
                authStatus.className = 'auth-status error';
            }
        }

        function performAuthenticatedWidgetInitialization(widgetId, token) {
            // Set widget configuration - widget loader checks window.chattermateToken first
            window.chattermateId = widgetId;
            window.chattermateToken = token;
            window.chattermateBaseUrl = CM_API_BASE;

            // Remove existing script if any
            const existingScript = document.getElementById('chattermate-script');
            if (existingScript) {
                existingScript.remove();
            }

            // Also remove any existing widget elements to ensure fresh initialization
            const existingContainer = document.getElementById('chattermate-container');
            const existingButton = document.getElementById('chattermate-button');
            const existingClose = document.getElementById('chattermate-mobile-close');
            const existingTopbar = document.getElementById('chattermate-mobile-topbar');
            if (existingContainer) existingContainer.remove();
            if (existingButton) existingButton.remove();
            if (existingClose) existingClose.remove();
            if (existingTopbar) existingTopbar.remove();

            // Clear ChatterMate instance
            if (window.ChatterMate) {
                delete window.ChatterMate;
            }

            // Wait a moment for cleanup
            setTimeout(() => {
                // Create and add new script
                const script = document.createElement('script');
                script.id = 'chattermate-script';
                script.src = CM_LOADER_URL;

                script.onload = function() {
                    if (window.ChatterMate) {
                        window.ChatterMate.init({
                            id: widgetId
                        });
                    }
                };
                document.body.appendChild(script);
            }, 200);
        }
    </script>
</body>

</html>