|
|
| Line 1: |
Line 1: |
| /* Lorekeeper Standalone Terminal */
| | alert("The Lorekeeper is awake!"); |
| (function() {
| |
| if (document.getElementById('lore-terminal-btn')) return;
| |
| | |
| // 1. Create Floating Button
| |
| var btn = document.createElement('div');
| |
| btn.id = 'lore-terminal-btn';
| |
| btn.innerHTML = '?';
| |
| 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);';
| |
| 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
| |
| var box = document.createElement('div');
| |
| 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;';
| |
|
| |
| box.innerHTML = `
| |
| <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;">
| |
| <div style="font-weight:bold;color:#a78bfa;">Archives of the Western Kingdom</div>
| |
| <div id="lore-close" style="cursor:pointer;opacity:0.6;">?</div>
| |
| </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>
| |
| <div style="padding:20px;border-top:1px solid rgba(255,255,255,0.1);display:flex;gap:10px;">
| |
| <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;">
| |
| <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>
| |
| `;
| |
| | |
| document.body.appendChild(btn);
| |
| document.body.appendChild(box);
| |
| | |
| // 3. Logic
| |
| var chat = box.querySelector('#lore-chat');
| |
| var input = box.querySelector('#lore-input');
| |
| var send = box.querySelector('#lore-send');
| |
| var convId = 'anon-' + Math.random().toString(36).substring(7);
| |
| | |
| btn.onclick = function() { box.style.display = box.style.display === 'none' ? 'flex' : 'none'; };
| |
| box.querySelector('#lore-close').onclick = function() { box.style.display = 'none'; };
| |
| | |
| async function handleAsk() {
| |
| var text = input.value.trim();
| |
| if (!text) return;
| |
| input.value = '';
| |
| | |
| // Add User Message
| |
| var uMsg = document.createElement('div');
| |
| 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
| |
| 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 {
| |
| const response = await fetch('https://npc-forge--npc-forge-qh4gc.asia-southeast1.hosted.app/api/lore/stream', {
| |
| method: 'POST',
| |
| headers: { 'Content-Type': 'application/json' },
| |
| body: JSON.stringify({ question: text, conversationId: convId })
| |
| });
| |
| | |
| const reader = response.body.getReader();
| |
| const decoder = new TextDecoder();
| |
| aMsg.innerHTML = '';
| |
| var fullContent = '';
| |
| | |
| while (true) {
| |
| const { done, value } = await reader.read();
| |
| if (done) break;
| |
| 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>';
| |
| }
| |
| }
| |
| | |
| send.onclick = handleAsk;
| |
| input.onkeypress = function(e) { if (e.key === 'Enter') handleAsk(); };
| |
| })();
| |