Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Laguna XS.2 — Poolside AI</title> | |
| <meta name="description" content="Chat with Poolside's Laguna-XS.2 reasoning model"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| :root{ | |
| --purple-50:#faf5ff;--purple-100:#f3e8ff;--purple-200:#e9d5ff; | |
| --purple-300:#d8b4fe;--purple-400:#c084fc;--purple-500:#a855f7; | |
| --purple-600:#9333ea;--purple-700:#7e22ce;--purple-800:#6b21a8; | |
| --purple-900:#581c87;--purple-950:#3b0764; | |
| --bg:#0c0612; | |
| --surface:rgba(20,12,30,0.65); | |
| --surface-solid:#140c1e; | |
| --surface2:rgba(30,18,45,0.55); | |
| --glass:rgba(255,255,255,0.04); | |
| --glass-border:rgba(255,255,255,0.08); | |
| --glass-hover:rgba(255,255,255,0.07); | |
| --text:#f0eaf8;--text-secondary:#a89bc2;--text-dim:#6b5f80; | |
| --accent:#a855f7;--accent-light:#c084fc;--accent-glow:rgba(168,85,247,0.15); | |
| --accent-hover:#b66dfa; | |
| --think-bg:rgba(88,28,135,0.12);--think-border:rgba(168,85,247,0.15); | |
| --success:#34d399;--error:#f87171; | |
| --radius-lg:24px;--radius-xl:32px;--radius-full:9999px; | |
| } | |
| html,body{ | |
| height:100%;font-family:'Inter',system-ui,sans-serif; | |
| background:var(--bg);color:var(--text);overflow:hidden; | |
| } | |
| /* Animated gradient background */ | |
| body::before{ | |
| content:'';position:fixed;inset:0;z-index:0; | |
| background: | |
| radial-gradient(ellipse 80% 60% at 10% 90%, rgba(147,51,234,0.18) 0%, transparent 60%), | |
| radial-gradient(ellipse 60% 50% at 85% 20%, rgba(168,85,247,0.12) 0%, transparent 55%), | |
| radial-gradient(ellipse 50% 40% at 50% 50%, rgba(192,132,252,0.06) 0%, transparent 50%); | |
| animation:bgShift 20s ease-in-out infinite alternate; | |
| } | |
| @keyframes bgShift{ | |
| 0%{filter:hue-rotate(0deg)} | |
| 50%{filter:hue-rotate(-8deg)} | |
| 100%{filter:hue-rotate(5deg)} | |
| } | |
| /* App container */ | |
| .app{ | |
| position:relative;z-index:1;display:flex;flex-direction:column; | |
| height:100vh;max-width:900px;margin:0 auto; | |
| } | |
| /* Header */ | |
| .header{ | |
| padding:16px 28px;display:flex;align-items:center;justify-content:space-between; | |
| flex-shrink:0; | |
| border-bottom:1px solid var(--glass-border); | |
| background:var(--surface); | |
| backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px); | |
| } | |
| .header-brand{display:flex;align-items:center;gap:12px} | |
| .header-logo{ | |
| width:36px;height:36px;border-radius:10px; | |
| object-fit:cover; | |
| box-shadow:0 0 20px rgba(168,85,247,0.25); | |
| } | |
| .header-text h1{ | |
| font-size:1.05rem;font-weight:600;letter-spacing:-.01em; | |
| color:var(--text); | |
| } | |
| .header-text span{ | |
| font-size:.7rem;color:var(--text-dim);font-weight:400;letter-spacing:.02em; | |
| } | |
| .header-actions{display:flex;gap:8px;align-items:center} | |
| /* Pill buttons */ | |
| .pill-btn{ | |
| display:inline-flex;align-items:center;gap:6px; | |
| padding:6px 14px;border-radius:var(--radius-full); | |
| border:1px solid var(--glass-border);background:var(--glass); | |
| color:var(--text-secondary);font-size:.75rem;font-weight:500; | |
| cursor:pointer;transition:all .2s ease;font-family:inherit; | |
| backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px); | |
| } | |
| .pill-btn:hover{background:var(--glass-hover);border-color:rgba(255,255,255,0.15);color:var(--text)} | |
| .pill-btn.active{background:var(--accent-glow);border-color:rgba(168,85,247,0.3);color:var(--accent-light)} | |
| .pill-btn svg{width:14px;height:14px;opacity:.7} | |
| .pill-btn.danger:hover{border-color:rgba(248,113,113,0.4);color:var(--error);background:rgba(248,113,113,0.08)} | |
| /* Controls bar */ | |
| .controls{ | |
| display:flex;gap:8px;padding:10px 28px;flex-shrink:0;align-items:center;flex-wrap:wrap; | |
| border-bottom:1px solid var(--glass-border); | |
| background:rgba(20,12,30,0.35); | |
| backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px); | |
| } | |
| .control-chip{ | |
| display:flex;align-items:center;gap:6px; | |
| padding:4px 12px;border-radius:var(--radius-full); | |
| background:var(--glass);border:1px solid var(--glass-border); | |
| font-size:.72rem;color:var(--text-secondary); | |
| } | |
| .control-chip label{cursor:pointer;white-space:nowrap} | |
| .control-chip input[type=range]{ | |
| width:70px;height:4px;accent-color:var(--accent); | |
| -webkit-appearance:none;appearance:none;background:rgba(255,255,255,0.1); | |
| border-radius:2px;outline:none; | |
| } | |
| .control-chip input[type=range]::-webkit-slider-thumb{ | |
| -webkit-appearance:none;width:12px;height:12px;border-radius:50%; | |
| background:var(--accent);cursor:pointer;border:2px solid var(--bg); | |
| } | |
| .control-chip span{color:var(--accent-light);font-weight:600;min-width:28px;text-align:right} | |
| /* Toggle switch */ | |
| .switch{position:relative;width:32px;height:18px;cursor:pointer;display:inline-block;vertical-align:middle} | |
| .switch input{opacity:0;width:0;height:0} | |
| .switch .track{ | |
| position:absolute;inset:0;background:rgba(255,255,255,0.1); | |
| border-radius:var(--radius-full);transition:.3s ease; | |
| } | |
| .switch .track::before{ | |
| content:'';position:absolute;left:2px;top:2px; | |
| width:14px;height:14px;background:var(--text-dim); | |
| border-radius:50%;transition:.3s ease; | |
| } | |
| .switch input:checked+.track{background:var(--accent)} | |
| .switch input:checked+.track::before{transform:translateX(14px);background:#fff} | |
| /* Messages area */ | |
| .messages{ | |
| flex:1;overflow-y:auto;padding:28px;display:flex;flex-direction:column; | |
| gap:4px;scroll-behavior:smooth; | |
| } | |
| .messages::-webkit-scrollbar{width:5px} | |
| .messages::-webkit-scrollbar-track{background:transparent} | |
| .messages::-webkit-scrollbar-thumb{background:rgba(168,85,247,0.2);border-radius:3px} | |
| .messages::-webkit-scrollbar-thumb:hover{background:rgba(168,85,247,0.35)} | |
| /* Message rows */ | |
| .msg{animation:msgIn .35s cubic-bezier(.16,1,.3,1)} | |
| @keyframes msgIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}} | |
| .msg-row{display:flex;gap:12px;padding:6px 0} | |
| .msg-row.user{justify-content:flex-end} | |
| .msg-row.assistant{justify-content:flex-start} | |
| .msg-avatar{ | |
| width:30px;height:30px;border-radius:10px;flex-shrink:0; | |
| display:flex;align-items:center;justify-content:center; | |
| font-size:.75rem;font-weight:700;margin-top:2px; | |
| } | |
| .msg-row.user .msg-avatar{ | |
| background:linear-gradient(135deg,var(--purple-600),var(--purple-400)); | |
| color:#fff;order:2; | |
| } | |
| .msg-row.assistant .msg-avatar{ | |
| background:var(--glass);border:1px solid var(--glass-border); | |
| overflow:hidden; | |
| } | |
| .msg-row.assistant .msg-avatar img{width:100%;height:100%;object-fit:cover} | |
| .msg-bubble{ | |
| max-width:72%;padding:14px 18px;font-size:.88rem;line-height:1.7; | |
| word-wrap:break-word;overflow-wrap:break-word; | |
| } | |
| .msg-row.user .msg-bubble{ | |
| background:linear-gradient(135deg,var(--purple-700),var(--purple-600)); | |
| color:#fff;border-radius:var(--radius-lg) var(--radius-lg) 6px var(--radius-lg); | |
| box-shadow:0 4px 24px rgba(168,85,247,0.15); | |
| } | |
| .msg-row.assistant .msg-bubble{ | |
| background:var(--surface); | |
| border:1px solid var(--glass-border); | |
| border-radius:var(--radius-lg) var(--radius-lg) var(--radius-lg) 6px; | |
| backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px); | |
| } | |
| /* Thinking block */ | |
| .think-block{ | |
| background:var(--think-bg);border:1px solid var(--think-border); | |
| border-radius:14px;padding:10px 14px;margin-bottom:10px; | |
| } | |
| .think-toggle{ | |
| cursor:pointer;display:flex;align-items:center;gap:8px; | |
| font-size:.72rem;color:var(--text-dim);user-select:none; | |
| font-weight:500;letter-spacing:.02em;text-transform:uppercase; | |
| } | |
| .think-toggle .think-icon{ | |
| width:18px;height:18px;border-radius:50%; | |
| background:rgba(168,85,247,0.15); | |
| display:flex;align-items:center;justify-content:center; | |
| font-size:.6rem; | |
| } | |
| .think-content{ | |
| margin-top:8px;white-space:pre-wrap;display:none; | |
| max-height:280px;overflow-y:auto;font-size:.8rem; | |
| color:var(--text-secondary);line-height:1.6; | |
| padding-right:8px; | |
| } | |
| .think-content::-webkit-scrollbar{width:3px} | |
| .think-content::-webkit-scrollbar-thumb{background:var(--think-border);border-radius:2px} | |
| .think-content.open{display:block} | |
| /* Code formatting */ | |
| .msg-bubble pre{ | |
| background:rgba(0,0,0,0.35);border:1px solid var(--glass-border); | |
| border-radius:14px;padding:14px 16px;margin:10px 0;overflow-x:auto; | |
| font-family:'JetBrains Mono',monospace;font-size:.8rem;line-height:1.6; | |
| } | |
| .msg-bubble code{font-family:'JetBrains Mono',monospace;font-size:.83em; | |
| background:rgba(168,85,247,0.1);padding:1px 5px;border-radius:5px;} | |
| .msg-bubble pre code{background:none;padding:0;border-radius:0} | |
| .msg-bubble p{margin:5px 0} | |
| .msg-bubble ul,.msg-bubble ol{margin:6px 0;padding-left:20px} | |
| .msg-bubble li{margin:3px 0} | |
| /* Typing indicator */ | |
| .typing-dots{display:flex;gap:5px;padding:6px 0} | |
| .typing-dots span{ | |
| width:7px;height:7px;background:var(--accent);opacity:.4; | |
| border-radius:50%;animation:dotPulse 1.4s infinite; | |
| } | |
| .typing-dots span:nth-child(2){animation-delay:.2s} | |
| .typing-dots span:nth-child(3){animation-delay:.4s} | |
| @keyframes dotPulse{0%,80%,100%{transform:scale(.5);opacity:.3}40%{transform:scale(1);opacity:.8}} | |
| /* Input area */ | |
| .input-area{ | |
| padding:16px 28px 28px;flex-shrink:0; | |
| } | |
| .input-container{ | |
| background:var(--surface); | |
| border:1px solid var(--glass-border); | |
| border-radius:var(--radius-xl); | |
| padding:8px 8px 8px 22px; | |
| display:flex;align-items:flex-end;gap:10px; | |
| transition:all .3s ease; | |
| backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px); | |
| box-shadow:0 8px 40px rgba(0,0,0,0.25); | |
| } | |
| .input-container:focus-within{ | |
| border-color:rgba(168,85,247,0.35); | |
| box-shadow:0 8px 40px rgba(0,0,0,0.25),0 0 0 3px var(--accent-glow); | |
| } | |
| #userInput{ | |
| flex:1;background:none;border:none;color:var(--text); | |
| font-size:.9rem;font-family:'Inter',system-ui,sans-serif; | |
| resize:none;outline:none;max-height:150px;min-height:26px; | |
| line-height:1.55;padding:8px 0; | |
| } | |
| #userInput::placeholder{color:var(--text-dim)} | |
| #sendBtn{ | |
| width:42px;height:42px;border-radius:var(--radius-full); | |
| border:none;background:linear-gradient(135deg,var(--purple-600),var(--purple-500)); | |
| color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center; | |
| transition:all .25s ease;flex-shrink:0; | |
| box-shadow:0 4px 16px rgba(168,85,247,0.3); | |
| } | |
| #sendBtn:hover{transform:scale(1.06);box-shadow:0 6px 24px rgba(168,85,247,0.4)} | |
| #sendBtn:active{transform:scale(0.96)} | |
| #sendBtn:disabled{opacity:.35;cursor:not-allowed;transform:none;box-shadow:none} | |
| #sendBtn svg{width:18px;height:18px} | |
| /* Welcome / Empty state */ | |
| .welcome{ | |
| display:flex;flex-direction:column;align-items:center;justify-content:center; | |
| flex:1;gap:16px;padding:60px 40px;text-align:center; | |
| } | |
| .welcome-logo{ | |
| width:64px;height:64px;border-radius:18px; | |
| box-shadow:0 0 40px rgba(168,85,247,0.25); | |
| animation:logoPulse 3s ease-in-out infinite; | |
| } | |
| @keyframes logoPulse{ | |
| 0%,100%{box-shadow:0 0 30px rgba(168,85,247,0.2)} | |
| 50%{box-shadow:0 0 50px rgba(168,85,247,0.35)} | |
| } | |
| .welcome h2{ | |
| font-size:1.3rem;font-weight:600; | |
| background:linear-gradient(135deg,var(--purple-300),var(--purple-500)); | |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent; | |
| background-clip:text; | |
| } | |
| .welcome p{ | |
| font-size:.85rem;color:var(--text-dim);max-width:420px;line-height:1.7; | |
| } | |
| .welcome-suggestions{ | |
| display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:8px;max-width:520px; | |
| } | |
| .suggestion{ | |
| padding:8px 16px;border-radius:var(--radius-full); | |
| background:var(--glass);border:1px solid var(--glass-border); | |
| color:var(--text-secondary);font-size:.78rem;cursor:pointer; | |
| transition:all .2s ease;font-family:inherit; | |
| } | |
| .suggestion:hover{ | |
| background:var(--accent-glow);border-color:rgba(168,85,247,0.3); | |
| color:var(--accent-light);transform:translateY(-1px); | |
| } | |
| /* Footer hint */ | |
| .input-hint{ | |
| text-align:center;padding-top:8px; | |
| font-size:.68rem;color:var(--text-dim); | |
| } | |
| /* Responsive */ | |
| @media(max-width:640px){ | |
| .app{max-width:100%} | |
| .msg-bubble{max-width:88%} | |
| .controls{padding:8px 16px} | |
| .messages{padding:20px 16px} | |
| .input-area{padding:12px 16px 20px} | |
| .header{padding:12px 16px} | |
| .welcome-suggestions{flex-direction:column;align-items:center} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <!-- Header --> | |
| <div class="header"> | |
| <div class="header-brand"> | |
| <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/699484cbe85a4b61cbc5ee0f/GpYWuz-CovEFgbPOW21dZ.png" alt="Poolside" class="header-logo"> | |
| <div class="header-text"> | |
| <h1>Laguna XS.2</h1> | |
| <span>by Poolside AI</span> | |
| </div> | |
| </div> | |
| <div class="header-actions"> | |
| <button class="pill-btn danger" id="clearBtn"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 6h18"/><path d="M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></svg> | |
| Clear | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Controls --> | |
| <div class="controls"> | |
| <div class="control-chip"> | |
| <label class="switch"><input type="checkbox" id="thinkToggle" checked><span class="track"></span></label> | |
| <label for="thinkToggle">Thinking</label> | |
| </div> | |
| <div class="control-chip"> | |
| <label>Temp</label> | |
| <input type="range" id="tempSlider" min="0.1" max="1.5" step="0.1" value="0.7"> | |
| <span id="tempVal">0.7</span> | |
| </div> | |
| <div class="control-chip"> | |
| <label>Tokens</label> | |
| <input type="range" id="tokenSlider" min="256" max="4096" step="256" value="1024"> | |
| <span id="tokenVal">1024</span> | |
| </div> | |
| </div> | |
| <!-- Messages --> | |
| <div class="messages" id="messages"> | |
| <div class="welcome"> | |
| <img src="https://cdn-avatars.huggingface.co/v1/production/uploads/699484cbe85a4b61cbc5ee0f/GpYWuz-CovEFgbPOW21dZ.png" alt="Poolside" class="welcome-logo"> | |
| <h2>What can I help you build?</h2> | |
| <p>Laguna XS.2 is a reasoning-capable code model by Poolside AI. Ask me to write code, debug problems, or explain complex concepts.</p> | |
| <div class="welcome-suggestions"> | |
| <button class="suggestion" data-prompt="Write a Python FastAPI server with CRUD endpoints">Build a FastAPI server</button> | |
| <button class="suggestion" data-prompt="Explain how transformers work in machine learning">Explain transformers</button> | |
| <button class="suggestion" data-prompt="Write a React component for a dark mode toggle with animation">React dark mode toggle</button> | |
| <button class="suggestion" data-prompt="Debug this: why does my async function return a pending promise?">Debug async/await</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Input --> | |
| <div class="input-area"> | |
| <div class="input-container"> | |
| <textarea id="userInput" rows="1" placeholder="Ask Laguna anything..." autofocus></textarea> | |
| <button id="sendBtn"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M5 12h14"/> | |
| <path d="M12 5l7 7-7 7"/> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="input-hint">Laguna XS.2 can make mistakes. Verify important information.</div> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; | |
| const messagesEl = document.getElementById('messages'); | |
| const userInput = document.getElementById('userInput'); | |
| const sendBtn = document.getElementById('sendBtn'); | |
| const thinkToggle = document.getElementById('thinkToggle'); | |
| const tempSlider = document.getElementById('tempSlider'); | |
| const tempVal = document.getElementById('tempVal'); | |
| const tokenSlider = document.getElementById('tokenSlider'); | |
| const tokenVal = document.getElementById('tokenVal'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| let history = []; | |
| let client = null; | |
| let isGenerating = false; | |
| const LOGO_URL = 'https://cdn-avatars.huggingface.co/v1/production/uploads/699484cbe85a4b61cbc5ee0f/GpYWuz-CovEFgbPOW21dZ.png'; | |
| const WELCOME_HTML = `<div class="welcome"><img src="${LOGO_URL}" alt="Poolside" class="welcome-logo"><h2>What can I help you build?</h2><p>Laguna XS.2 is a reasoning-capable code model by Poolside AI. Ask me to write code, debug problems, or explain complex concepts.</p><div class="welcome-suggestions"><button class="suggestion" data-prompt="Write a Python FastAPI server with CRUD endpoints">Build a FastAPI server</button><button class="suggestion" data-prompt="Explain how transformers work in machine learning">Explain transformers</button><button class="suggestion" data-prompt="Write a React component for a dark mode toggle with animation">React dark mode toggle</button><button class="suggestion" data-prompt="Debug this: why does my async function return a pending promise?">Debug async/await</button></div></div>`; | |
| async function initClient() { | |
| client = await Client.connect(window.location.origin); | |
| } | |
| initClient(); | |
| tempSlider.oninput = () => tempVal.textContent = tempSlider.value; | |
| tokenSlider.oninput = () => tokenVal.textContent = tokenSlider.value; | |
| clearBtn.onclick = () => { | |
| history = []; | |
| messagesEl.innerHTML = WELCOME_HTML; | |
| bindSuggestions(); | |
| }; | |
| // Suggestion chips | |
| function bindSuggestions() { | |
| document.querySelectorAll('.suggestion').forEach(btn => { | |
| btn.onclick = () => { | |
| userInput.value = btn.dataset.prompt; | |
| sendMessage(); | |
| }; | |
| }); | |
| } | |
| bindSuggestions(); | |
| userInput.addEventListener('input', () => { | |
| userInput.style.height = 'auto'; | |
| userInput.style.height = Math.min(userInput.scrollHeight, 150) + 'px'; | |
| }); | |
| userInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } | |
| }); | |
| sendBtn.onclick = sendMessage; | |
| function addMessage(role, content, reasoning) { | |
| const welcome = messagesEl.querySelector('.welcome'); | |
| if (welcome) welcome.remove(); | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = 'msg'; | |
| const div = document.createElement('div'); | |
| div.className = `msg-row ${role}`; | |
| let avatarHTML; | |
| if (role === 'user') { | |
| avatarHTML = `<div class="msg-avatar">U</div>`; | |
| } else { | |
| avatarHTML = `<div class="msg-avatar"><img src="${LOGO_URL}" alt="AI"></div>`; | |
| } | |
| div.innerHTML = `${avatarHTML}<div class="msg-bubble">${formatContent(content, role, reasoning)}</div>`; | |
| wrapper.appendChild(div); | |
| messagesEl.appendChild(wrapper); | |
| messagesEl.scrollTop = messagesEl.scrollHeight; | |
| return wrapper; | |
| } | |
| function addTypingIndicator() { | |
| const wrapper = document.createElement('div'); | |
| wrapper.className = 'msg'; | |
| wrapper.id = 'typing'; | |
| const div = document.createElement('div'); | |
| div.className = 'msg-row assistant'; | |
| div.innerHTML = `<div class="msg-avatar"><img src="${LOGO_URL}" alt="AI"></div><div class="msg-bubble"><div class="typing-dots"><span></span><span></span><span></span></div></div>`; | |
| wrapper.appendChild(div); | |
| messagesEl.appendChild(wrapper); | |
| messagesEl.scrollTop = messagesEl.scrollHeight; | |
| } | |
| function removeTypingIndicator() { | |
| const el = document.getElementById('typing'); | |
| if (el) el.remove(); | |
| } | |
| function escapeHtml(text) { | |
| const d = document.createElement('div'); | |
| d.textContent = text; | |
| return d.innerHTML; | |
| } | |
| function formatContent(text, role, reasoning) { | |
| if (role === 'user') return escapeHtml(text); | |
| let html = ''; | |
| if (reasoning) { | |
| html += `<div class="think-block"><div class="think-toggle" onclick="this.nextElementSibling.classList.toggle('open');this.querySelector('span').textContent=this.nextElementSibling.classList.contains('open')?'Hide reasoning':'Show reasoning'"><div class="think-icon">💭</div><span>Show reasoning</span></div><div class="think-content">${escapeHtml(reasoning)}</div></div>`; | |
| } | |
| let formatted = escapeHtml(text); | |
| formatted = formatted.replace(/```(\w*)\n?([\s\S]*?)```/g, '<pre><code>$2</code></pre>'); | |
| formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>'); | |
| formatted = formatted.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>'); | |
| formatted = formatted.replace(/\n/g, '<br>'); | |
| html += formatted; | |
| return html; | |
| } | |
| async function sendMessage() { | |
| const text = userInput.value.trim(); | |
| if (!text || isGenerating || !client) return; | |
| isGenerating = true; | |
| sendBtn.disabled = true; | |
| userInput.value = ''; | |
| userInput.style.height = 'auto'; | |
| addMessage('user', text); | |
| addTypingIndicator(); | |
| try { | |
| const result = await client.predict("/chat", { | |
| message: text, | |
| history: history, | |
| enable_thinking: thinkToggle.checked, | |
| max_tokens: parseInt(tokenSlider.value), | |
| temperature: parseFloat(tempSlider.value), | |
| }); | |
| removeTypingIndicator(); | |
| const response = result.data[0]; | |
| let reasoningText = ''; | |
| if (response.reasoning_details && Array.isArray(response.reasoning_details)) { | |
| reasoningText = response.reasoning_details | |
| .filter(d => d.type === 'text') | |
| .map(d => d.text) | |
| .join('\n'); | |
| } | |
| addMessage('assistant', response.content || '', reasoningText); | |
| history.push({ role: "user", content: text }); | |
| history.push({ | |
| role: "assistant", | |
| content: response.content || '', | |
| reasoning_details: response.reasoning_details || null, | |
| }); | |
| } catch (err) { | |
| removeTypingIndicator(); | |
| addMessage('assistant', `Error: ${err.message}`); | |
| } | |
| isGenerating = false; | |
| sendBtn.disabled = false; | |
| userInput.focus(); | |
| } | |
| </script> | |
| </body> | |
| </html> | |