def generate_widget_js(app_url: str) -> str: """Generate the embeddable widget JavaScript with app_url baked in.""" return f""" (function() {{ var APP_URL = "{app_url}"; // Find script tag to get chatbot ID var scripts = document.querySelectorAll('script[data-chatbot]'); var chatbotId = null; if (scripts.length > 0) {{ chatbotId = scripts[scripts.length - 1].getAttribute('data-chatbot'); }} if (!chatbotId) {{ console.warn('[Contexta] No data-chatbot attribute found on script tag'); return; }} // Styles var style = document.createElement('style'); style.textContent = ` .contexta-btn {{ position: fixed; bottom: 24px; right: 24px; width: 56px; height: 56px; border-radius: 50%; background: #6366f1; border: none; cursor: pointer; box-shadow: 0 4px 12px rgba(0,0,0,0.2); display: flex; align-items: center; justify-content: center; z-index: 999998; transition: transform 0.2s, box-shadow 0.2s; }} .contexta-btn:hover {{ transform: scale(1.08); box-shadow: 0 6px 20px rgba(0,0,0,0.25); }} .contexta-btn svg {{ width: 26px; height: 26px; fill: white; }} .contexta-container {{ position: fixed; bottom: 92px; right: 24px; width: 380px; height: 580px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.15); z-index: 999999; overflow: hidden; display: none; flex-direction: column; border: 1px solid #e5e7eb; }} .contexta-container.open {{ display: flex; }} .contexta-iframe {{ width: 100%; height: 100%; border: none; flex: 1; }} .contexta-close {{ position: absolute; top: 8px; right: 8px; background: rgba(0,0,0,0.3); border: none; color: white; border-radius: 50%; width: 24px; height: 24px; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; z-index: 1; }} @media (max-width: 480px) {{ .contexta-container {{ bottom: 0; right: 0; width: 100vw; height: 100vh; border-radius: 0; }} }} `; document.head.appendChild(style); // Button var btn = document.createElement('button'); btn.className = 'contexta-btn'; btn.setAttribute('aria-label', 'Open chat'); btn.innerHTML = ''; document.body.appendChild(btn); // Container with iframe var container = document.createElement('div'); container.className = 'contexta-container'; var closeBtn = document.createElement('button'); closeBtn.className = 'contexta-close'; closeBtn.innerHTML = '×'; closeBtn.setAttribute('aria-label', 'Close chat'); container.appendChild(closeBtn); var iframe = document.createElement('iframe'); iframe.className = 'contexta-iframe'; iframe.src = APP_URL + '/chat/' + chatbotId; iframe.setAttribute('allow', 'microphone'); container.appendChild(iframe); document.body.appendChild(container); // Toggle logic var isOpen = false; function toggle() {{ isOpen = !isOpen; if (isOpen) {{ container.classList.add('open'); btn.innerHTML = ''; }} else {{ container.classList.remove('open'); btn.innerHTML = ''; }} }} btn.addEventListener('click', toggle); closeBtn.addEventListener('click', toggle); }})(); """