UNPKG

@xuda.io/runtime-bundle

Version:

The Xuda Runtime Bundle refers to a collection of scripts and libraries packaged together to provide the necessary runtime environment for executing plugins or components in the Xuda platform.

101 lines (95 loc) 5.47 kB
(function () { var current = document.currentScript; if (!current) { var scripts = document.getElementsByTagName('script'); for (var i = scripts.length - 1; i >= 0; i--) { if ((scripts[i].src || '').indexOf('chat-widget-loader.js') !== -1) { current = scripts[i]; break; } } } if (!current) return; var profileId = current.getAttribute('data-profile'); if (!profileId) return; if (document.getElementById('xuda-chat-widget-iframe')) return; var position = current.getAttribute('data-position') || 'br'; var origin = (current.src || '').replace(/\/dist\/runtime\/js\/chat-widget-loader\.js.*$/, ''); // The iframe origin can be overridden so the loader can live on one host // (e.g. dev.xuda.io for previewing new sizing/CSS) while the chat widget // content + persona profile lookups stay on the production master // (master.xuda.ai). Without this override the iframe inherits the loader's // origin, which 404s when the dev DB doesn't have the persona profile. var iframeOrigin = current.getAttribute('data-iframe-origin') || origin; // Floating launcher button var btn = document.createElement('button'); btn.id = 'xuda-chat-widget-launcher'; btn.type = 'button'; btn.setAttribute('aria-label', 'Open chat'); btn.innerHTML = '<svg width="26" height="26" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 5C4 3.9 4.9 3 6 3H18C19.1 3 20 3.9 20 5V14C20 15.1 19.1 16 18 16H8L4 20V5Z" fill="#fff"/></svg>'; var btnPosStyle = ''; if (position === 'br') btnPosStyle = 'right:24px;bottom:24px;'; else if (position === 'bl') btnPosStyle = 'left:24px;bottom:24px;'; else if (position === 'tr') btnPosStyle = 'right:24px;top:24px;'; else if (position === 'tl') btnPosStyle = 'left:24px;top:24px;'; else btnPosStyle = 'right:24px;bottom:24px;'; btn.style.cssText = 'position:fixed;' + btnPosStyle + 'width:56px;height:56px;border-radius:50%;border:0;background:#1a3557;color:#fff;cursor:pointer;box-shadow:0 8px 24px rgba(0,0,0,.18);z-index:2147483646;display:flex;align-items:center;justify-content:center;'; // Iframe container (hidden initially) var frame = document.createElement('iframe'); frame.id = 'xuda-chat-widget-iframe'; // Detect the visitor's preferred language from the browser. This lets the // backend render the greeting in their language and instruct the AI agent // to reply in the same. Examples: "ja-JP", "ko-KR", "ar-SA", "es-AR", "en". // The server reduces to the base subtag (ja/ko/ar/etc.) when picking from // its translation map, so we send the full tag and let the server decide. var visitorLang = ''; try { visitorLang = (navigator.languages && navigator.languages[0]) || navigator.language || ''; } catch (e) {} var qs = visitorLang ? ('?lang=' + encodeURIComponent(visitorLang)) : ''; frame.src = iframeOrigin + '/chat-widget/' + encodeURIComponent(profileId) + qs; frame.title = 'Chat'; frame.allow = 'clipboard-write'; function sizeFrame() { // Mobile (any phone in portrait or small tablet up to iPad-mini) goes // edge-to-edge — the previous 480px breakpoint left landscape phones + // small tablets stuck on the desktop float, which felt cramped because // the float was smaller than the viewport it was sitting on. var w = window.innerWidth; var narrow = w < 768; var basePos = btnPosStyle.replace(/bottom:24px/, 'bottom:96px').replace(/top:24px/, 'top:96px'); if (narrow) { // Mobile: near-fullscreen but with a small margin so the visitor can // still see they're on a webpage (not a separate app) and the // launcher button stays visible at the bottom. Anchored bottom so the // greeting + form sit close to the launcher tap target rather than // floating at the top. Fullscreen (inset:0) felt too brutal; the // original 380×600 felt cramped. ~92vw × 78vh hits the middle. frame.setAttribute('width', ''); frame.setAttribute('height', ''); frame.style.cssText = 'position:fixed;left:8px;right:8px;bottom:80px;width:auto;height:78vh;max-height:calc(100vh - 96px);border:0;border-radius:14px;box-shadow:0 12px 32px rgba(0,0,0,.22);z-index:2147483647;background:#fff;display:' + (open ? 'block' : 'none') + ';'; } else { // Desktop default 380x600. Larger sizes (we tried 420x680) read as // intrusive on a normal monitor — most chat widgets sit at ~360-400 // wide. max-* keeps it from running off-screen on shorter windows. frame.style.cssText = 'position:fixed;' + basePos + 'width:380px;height:600px;max-width:calc(100vw - 32px);max-height:calc(100vh - 120px);border:0;border-radius:14px;box-shadow:0 12px 32px rgba(0,0,0,.22);z-index:2147483647;background:#fff;display:' + (open ? 'block' : 'none') + ';'; } } var open = false; btn.addEventListener('click', function () { open = !open; sizeFrame(); // Tell the widget it was opened so it can lazily adopt a site-level login // (e.g. xuda.network's Google sign-in) on first open instead of prompting. if (open && frame.contentWindow) { try { frame.contentWindow.postMessage({ type: 'xuda:widget-open' }, iframeOrigin); } catch (e) {} } }); window.addEventListener('resize', sizeFrame); sizeFrame(); function mount() { if (!document.body) return setTimeout(mount, 30); document.body.appendChild(frame); document.body.appendChild(btn); } mount(); })();