@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
JavaScript
(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();
})();