User:Jono Bean/common.js: Difference between revisions

From DQWiki
Jump to navigationJump to search
mNo edit summary
m (update to more robust script)
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* Lorekeeper Standalone Terminal */
/* Lorekeeper Widget Loader - v1.1.4 (MediaWiki Hardened Loader) */
(function() {
(function () {
     if (document.getElementById('lore-terminal-btn')) return;
     if (window.__lorekeeperWidgetLoaded) return;
    window.__lorekeeperWidgetLoaded = true;


     // 1. Create Floating Button
     var APP_ORIGIN = 'https://npc-forge--npc-forge-qh4gc.asia-southeast1.hosted.app';
    var btn = document.createElement('div');
     var APP_WIDGET_URL = APP_ORIGIN + '/widget';
     btn.id = 'lore-terminal-btn';
     var MOBILE_BREAKPOINT = 768;
     btn.innerHTML = '?';
     var CONTAINER_ID = 'lorekeeper-widget-container';
     btn.style.cssText = 'position:fixed;bottom:30px;right:30px;width:60px;height:60px;background:#8b5cf6;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:30px;cursor:pointer;box-shadow:0 10px 25px rgba(139,92,246,0.5);transition:0.3s;z-index:999999;border:2px solid rgba(255,255,255,0.2);';
     var IFRAME_ID = 'lorekeeper-widget-frame';
     btn.onmouseover = function() { this.style.transform = 'scale(1.1) rotate(15deg)'; };
    btn.onmouseout = function() { this.style.transform = 'scale(1) rotate(0deg)'; };


     // 2. Create Chat Box
     function isMobileViewport() {
     var box = document.createElement('div');
        return window.innerWidth < MOBILE_BREAKPOINT;
    box.id = 'lore-terminal-box';
     }
     box.style.cssText = 'position:fixed;bottom:100px;right:30px;width:400px;height:550px;background:rgba(15,15,20,0.95);backdrop-filter:blur(15px);border:1px solid rgba(139,92,246,0.3);border-radius:24px;display:none;flex-direction:column;box-shadow:0 25px 50px rgba(0,0,0,0.5);overflow:hidden;z-index:999999;color:#fff;font-family:system-ui,-apple-system,sans-serif;';
 
      
    function getUsername() {
     box.innerHTML = `
        try {
         <div style="padding:20px;background:rgba(139,92,246,0.1);border-bottom:1px solid rgba(139,92,246,0.2);display:flex;justify-content:space-between;align-items:center;">
            return (mw && mw.config && mw.config.get('wgUserName')) || '';
            <div style="font-weight:bold;color:#a78bfa;">Archives of the Western Kingdom</div>
        } catch (err) {
            <div id="lore-close" style="cursor:pointer;opacity:0.6;">?</div>
            return '';
         </div>
        }
         <div id="lore-chat" style="flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:15px;font-size:14px;scroll-behavior:smooth;">
     }
            <div style="background:rgba(255,255,255,0.05);padding:12px;border-radius:12px;border-left:3px solid #8b5cf6;">Greetings, Traveler. What secrets of the archives do you seek?</div>
 
         </div>
    function buildIframeUrl() {
         <div style="padding:20px;border-top:1px solid rgba(255,255,255,0.1);display:flex;gap:10px;">
        var params = [];
            <input id="lore-input" placeholder="Ask about the Iron Guild..." style="flex:1;background:rgba(255,255,255,0.05);border:1px solid rgba(139,92,246,0.2);padding:10px 15px;border-radius:12px;color:#fff;outline:none;">
        var username = getUsername();
            <button id="lore-send" style="background:#8b5cf6;border:none;color:#fff;padding:0 15px;border-radius:10px;cursor:pointer;font-weight:bold;">Ask</button>
 
         </div>
        if (username) {
     `;
            params.push('user=' + encodeURIComponent(username));
        } else {
            params.push('guest=' + encodeURIComponent('Traveler'));
        }
 
        if (isMobileViewport()) {
            params.push('isMobile=true');
        }
 
        params.push('parentOrigin=' + encodeURIComponent(window.location.origin));
 
        return APP_WIDGET_URL + '?' + params.join('&');
     }
 
     function setCollapsedSize(container) {
        if (isMobileViewport()) {
            container.style.width = '80px';
            container.style.height = '80px';
         } else {
            container.style.width = '300px';
            container.style.height = '120px';
        }
    }
 
    function ensureContainer() {
        var existing = document.getElementById(CONTAINER_ID);
        if (existing) return existing;
 
        var container = document.createElement('div');
        container.id = CONTAINER_ID;
        container.style.position = 'fixed';
        container.style.bottom = '0';
        container.style.right = '0';
         container.style.zIndex = '99999';
         container.style.pointerEvents = 'none';
        container.style.overflow = 'hidden';
        container.style.background = 'transparent';
        container.style.transition = 'width 0.3s ease, height 0.3s ease';
        setCollapsedSize(container);
 
        return container;
    }
 
    function ensureIframe() {
        var existing = document.getElementById(IFRAME_ID);
        if (existing) return existing;
 
        var iframe = document.createElement('iframe');
         iframe.id = IFRAME_ID;
         iframe.src = buildIframeUrl();
        iframe.style.width = '100%';
        iframe.style.height = '100%';
        iframe.style.border = 'none';
        iframe.style.background = 'transparent';
        iframe.style.pointerEvents = 'auto';
        iframe.setAttribute('allow', 'clipboard-write');
        iframe.setAttribute('loading', 'lazy');
        iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
 
        return iframe;
    }
 
    function mountWidget() {
        if (!document.body) return false;
 
        var container = ensureContainer();
        var iframe = ensureIframe();
 
        if (!container.parentNode) {
            container.appendChild(iframe);
            document.body.appendChild(container);
        } else if (!iframe.parentNode) {
            container.appendChild(iframe);
         }
 
        return true;
     }


     document.body.appendChild(btn);
     function applyResize(container, width, height) {
    document.body.appendChild(box);
        if (!container) return;


    // 3. Logic
        if (typeof width === 'number') {
    var chat = box.querySelector('#lore-chat');
            container.style.width = width + 'px';
    var input = box.querySelector('#lore-input');
        } else if (typeof width === 'string' && width) {
    var send = box.querySelector('#lore-send');
            container.style.width = width;
    var convId = 'anon-' + Math.random().toString(36).substring(7);
        }


    btn.onclick = function() { box.style.display = box.style.display === 'none' ? 'flex' : 'none'; };
        if (typeof height === 'number') {
    box.querySelector('#lore-close').onclick = function() { box.style.display = 'none'; };
            container.style.height = height + 'px';
        } else if (typeof height === 'string' && height) {
            container.style.height = height;
        }
    }


     async function handleAsk() {
     function handleMessage(event) {
         var text = input.value.trim();
         if (event.origin !== APP_ORIGIN) return;
         if (!text) return;
         if (!event.data || event.data.type !== 'lorekeeper-resize') return;
        input.value = '';


        // Add User Message
         var container = document.getElementById(CONTAINER_ID);
         var uMsg = document.createElement('div');
         if (!container) return;
         uMsg.style.cssText = 'align-self:flex-end;background:#8b5cf6;padding:10px 15px;border-radius:12px;max-width:80%;';
        uMsg.innerText = text;
        chat.appendChild(uMsg);
        chat.scrollTop = chat.scrollHeight;


         // Add AI Message Placeholder
         applyResize(container, event.data.width, event.data.height);
        var aMsg = document.createElement('div');
    }
        aMsg.style.cssText = 'background:rgba(255,255,255,0.05);padding:12px;border-radius:12px;max-width:90%;border-left:3px solid #8b5cf6;';
        aMsg.innerHTML = '<i>The scribe is searching...</i>';
        chat.appendChild(aMsg);


         try {
    function handleWindowResize() {
            const response = await fetch('https://npc-forge--npc-forge-qh4gc.asia-southeast1.hosted.app/api/lore/stream', {
         var container = document.getElementById(CONTAINER_ID);
                method: 'POST',
        var iframe = document.getElementById(IFRAME_ID);
                headers: { 'Content-Type': 'application/json' },
        if (!container || !iframe) return;
                body: JSON.stringify({ question: text, conversationId: convId })
 
             });
        var wasMobile = iframe.getAttribute('data-is-mobile') === 'true';
        var nowMobile = isMobileViewport();
 
        if (wasMobile !== nowMobile) {
            iframe.setAttribute('data-is-mobile', nowMobile ? 'true' : 'false');
            iframe.src = buildIframeUrl();
            setCollapsedSize(container);
            return;
        }
 
        if (
            container.style.width === '80px' ||
            container.style.width === '300px' ||
            container.style.width === '100vw'
        ) {
             setCollapsedSize(container);
        }
    }


            const reader = response.body.getReader();
    function init() {
            const decoder = new TextDecoder();
        if (!mountWidget()) {
             aMsg.innerHTML = '';
             window.setTimeout(init, 50);
             var fullContent = '';
             return;
        }


            while (true) {
        var iframe = document.getElementById(IFRAME_ID);
                const { done, value } = await reader.read();
        if (iframe) {
                if (done) break;
            iframe.setAttribute('data-is-mobile', isMobileViewport() ? 'true' : 'false');
                const chunk = decoder.decode(value);
                fullContent += chunk;
                aMsg.innerText = fullContent; // Typewriter effect
                chat.scrollTop = chat.scrollHeight;
            }
        } catch (err) {
            aMsg.innerHTML = '<span style="color:#ef4444;">The archives are unreachable at the moment.</span>';
         }
         }
        window.addEventListener('message', handleMessage, false);
        window.addEventListener('resize', handleWindowResize, false);
     }
     }


     send.onclick = handleAsk;
     if (document.readyState === 'loading') {
    input.onkeypress = function(e) { if (e.key === 'Enter') handleAsk(); };
        document.addEventListener('DOMContentLoaded', init, { once: true });
    } else {
        init();
    }
})();
})();

Latest revision as of 04:59, 3 April 2026

/* Lorekeeper Widget Loader - v1.1.4 (MediaWiki Hardened Loader) */
(function () {
    if (window.__lorekeeperWidgetLoaded) return;
    window.__lorekeeperWidgetLoaded = true;

    var APP_ORIGIN = 'https://npc-forge--npc-forge-qh4gc.asia-southeast1.hosted.app';
    var APP_WIDGET_URL = APP_ORIGIN + '/widget';
    var MOBILE_BREAKPOINT = 768;
    var CONTAINER_ID = 'lorekeeper-widget-container';
    var IFRAME_ID = 'lorekeeper-widget-frame';

    function isMobileViewport() {
        return window.innerWidth < MOBILE_BREAKPOINT;
    }

    function getUsername() {
        try {
            return (mw && mw.config && mw.config.get('wgUserName')) || '';
        } catch (err) {
            return '';
        }
    }

    function buildIframeUrl() {
        var params = [];
        var username = getUsername();

        if (username) {
            params.push('user=' + encodeURIComponent(username));
        } else {
            params.push('guest=' + encodeURIComponent('Traveler'));
        }

        if (isMobileViewport()) {
            params.push('isMobile=true');
        }

        params.push('parentOrigin=' + encodeURIComponent(window.location.origin));

        return APP_WIDGET_URL + '?' + params.join('&');
    }

    function setCollapsedSize(container) {
        if (isMobileViewport()) {
            container.style.width = '80px';
            container.style.height = '80px';
        } else {
            container.style.width = '300px';
            container.style.height = '120px';
        }
    }

    function ensureContainer() {
        var existing = document.getElementById(CONTAINER_ID);
        if (existing) return existing;

        var container = document.createElement('div');
        container.id = CONTAINER_ID;
        container.style.position = 'fixed';
        container.style.bottom = '0';
        container.style.right = '0';
        container.style.zIndex = '99999';
        container.style.pointerEvents = 'none';
        container.style.overflow = 'hidden';
        container.style.background = 'transparent';
        container.style.transition = 'width 0.3s ease, height 0.3s ease';
        setCollapsedSize(container);

        return container;
    }

    function ensureIframe() {
        var existing = document.getElementById(IFRAME_ID);
        if (existing) return existing;

        var iframe = document.createElement('iframe');
        iframe.id = IFRAME_ID;
        iframe.src = buildIframeUrl();
        iframe.style.width = '100%';
        iframe.style.height = '100%';
        iframe.style.border = 'none';
        iframe.style.background = 'transparent';
        iframe.style.pointerEvents = 'auto';
        iframe.setAttribute('allow', 'clipboard-write');
        iframe.setAttribute('loading', 'lazy');
        iframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');

        return iframe;
    }

    function mountWidget() {
        if (!document.body) return false;

        var container = ensureContainer();
        var iframe = ensureIframe();

        if (!container.parentNode) {
            container.appendChild(iframe);
            document.body.appendChild(container);
        } else if (!iframe.parentNode) {
            container.appendChild(iframe);
        }

        return true;
    }

    function applyResize(container, width, height) {
        if (!container) return;

        if (typeof width === 'number') {
            container.style.width = width + 'px';
        } else if (typeof width === 'string' && width) {
            container.style.width = width;
        }

        if (typeof height === 'number') {
            container.style.height = height + 'px';
        } else if (typeof height === 'string' && height) {
            container.style.height = height;
        }
    }

    function handleMessage(event) {
        if (event.origin !== APP_ORIGIN) return;
        if (!event.data || event.data.type !== 'lorekeeper-resize') return;

        var container = document.getElementById(CONTAINER_ID);
        if (!container) return;

        applyResize(container, event.data.width, event.data.height);
    }

    function handleWindowResize() {
        var container = document.getElementById(CONTAINER_ID);
        var iframe = document.getElementById(IFRAME_ID);
        if (!container || !iframe) return;

        var wasMobile = iframe.getAttribute('data-is-mobile') === 'true';
        var nowMobile = isMobileViewport();

        if (wasMobile !== nowMobile) {
            iframe.setAttribute('data-is-mobile', nowMobile ? 'true' : 'false');
            iframe.src = buildIframeUrl();
            setCollapsedSize(container);
            return;
        }

        if (
            container.style.width === '80px' ||
            container.style.width === '300px' ||
            container.style.width === '100vw'
        ) {
            setCollapsedSize(container);
        }
    }

    function init() {
        if (!mountWidget()) {
            window.setTimeout(init, 50);
            return;
        }

        var iframe = document.getElementById(IFRAME_ID);
        if (iframe) {
            iframe.setAttribute('data-is-mobile', isMobileViewport() ? 'true' : 'false');
        }

        window.addEventListener('message', handleMessage, false);
        window.addEventListener('resize', handleWindowResize, false);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init, { once: true });
    } else {
        init();
    }
})();