Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Supra 50M Instruct</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| background: #0f0f13; | |
| color: #e4e4e7; | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| header { | |
| padding: 16px 24px; | |
| border-bottom: 1px solid #27272a; | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| flex-shrink: 0; | |
| } | |
| header h1 { | |
| font-size: 18px; | |
| font-weight: 600; | |
| } | |
| .badge { | |
| background: #3b0764; | |
| color: #c084fc; | |
| font-size: 11px; | |
| padding: 2px 8px; | |
| border-radius: 9999px; | |
| font-weight: 500; | |
| } | |
| #status-bar { | |
| padding: 8px 24px; | |
| font-size: 13px; | |
| color: #a1a1aa; | |
| border-bottom: 1px solid #27272a; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| flex-shrink: 0; | |
| } | |
| #progress-bar { | |
| flex: 1; | |
| max-width: 300px; | |
| height: 4px; | |
| background: #27272a; | |
| border-radius: 2px; | |
| overflow: hidden; | |
| display: none; | |
| } | |
| #progress-fill { | |
| height: 100%; | |
| background: #8b5cf6; | |
| width: 0%; | |
| transition: width 0.2s; | |
| } | |
| #chat { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 24px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 16px; | |
| } | |
| .message { | |
| max-width: 720px; | |
| width: 100%; | |
| margin: 0 auto; | |
| } | |
| .message-user { | |
| background: #18181b; | |
| border: 1px solid #27272a; | |
| border-radius: 12px; | |
| padding: 12px 16px; | |
| } | |
| .message-user .label { | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| color: #8b5cf6; | |
| margin-bottom: 4px; | |
| font-weight: 600; | |
| } | |
| .message-assistant { | |
| background: transparent; | |
| padding: 4px 0; | |
| } | |
| .message-assistant .label { | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| color: #22c55e; | |
| margin-bottom: 4px; | |
| font-weight: 600; | |
| } | |
| .message-text { | |
| font-size: 15px; | |
| line-height: 1.6; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| .cursor { | |
| display: inline-block; | |
| width: 2px; | |
| height: 1em; | |
| background: #8b5cf6; | |
| margin-left: 2px; | |
| vertical-align: text-bottom; | |
| animation: blink 0.8s infinite; | |
| } | |
| @keyframes blink { | |
| 0%, 50% { opacity: 1; } | |
| 51%, 100% { opacity: 0; } | |
| } | |
| #input-area { | |
| padding: 16px 24px; | |
| border-top: 1px solid #27272a; | |
| flex-shrink: 0; | |
| } | |
| #input-row { | |
| max-width: 720px; | |
| margin: 0 auto; | |
| display: flex; | |
| gap: 8px; | |
| } | |
| #prompt-input { | |
| flex: 1; | |
| background: #18181b; | |
| border: 1px solid #27272a; | |
| border-radius: 8px; | |
| padding: 10px 14px; | |
| color: #e4e4e7; | |
| font-size: 15px; | |
| font-family: inherit; | |
| resize: none; | |
| outline: none; | |
| min-height: 44px; | |
| max-height: 120px; | |
| } | |
| #prompt-input:focus { border-color: #8b5cf6; } | |
| #prompt-input::placeholder { color: #52525b; } | |
| #send-btn { | |
| background: #7c3aed; | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| padding: 10px 20px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| white-space: nowrap; | |
| align-self: flex-end; | |
| } | |
| #send-btn:hover { background: #6d28d9; } | |
| #send-btn:disabled { background: #3f3f46; color: #71717a; cursor: not-allowed; } | |
| #stop-btn { | |
| background: #dc2626; | |
| color: white; | |
| border: none; | |
| border-radius: 8px; | |
| padding: 10px 16px; | |
| font-size: 14px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| white-space: nowrap; | |
| align-self: flex-end; | |
| display: none; | |
| } | |
| #stop-btn:hover { background: #b91c1c; } | |
| #settings-toggle { | |
| background: none; | |
| border: 1px solid #27272a; | |
| color: #a1a1aa; | |
| border-radius: 8px; | |
| padding: 10px 12px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| align-self: flex-end; | |
| } | |
| #settings-toggle:hover { border-color: #8b5cf6; color: #e4e4e7; } | |
| #settings-panel { | |
| display: none; | |
| max-width: 720px; | |
| margin: 8px auto 0; | |
| padding: 12px 16px; | |
| background: #18181b; | |
| border: 1px solid #27272a; | |
| border-radius: 8px; | |
| } | |
| #settings-panel.open { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } | |
| .setting { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .setting label { | |
| font-size: 12px; | |
| color: #a1a1aa; | |
| } | |
| .setting input { | |
| background: #0f0f13; | |
| border: 1px solid #27272a; | |
| border-radius: 4px; | |
| padding: 6px 8px; | |
| color: #e4e4e7; | |
| font-size: 13px; | |
| width: 100%; | |
| } | |
| .welcome { | |
| text-align: center; | |
| margin: auto; | |
| max-width: 480px; | |
| } | |
| .welcome h2 { | |
| font-size: 24px; | |
| margin-bottom: 8px; | |
| } | |
| .welcome p { | |
| font-size: 14px; | |
| color: #71717a; | |
| line-height: 1.5; | |
| } | |
| footer { | |
| padding: 8px; | |
| text-align: center; | |
| font-size: 11px; | |
| color: #52525b; | |
| flex-shrink: 0; | |
| } | |
| footer a { color: #8b5cf6; text-decoration: none; } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <h1>Supra 50M Instruct</h1> | |
| <span class="badge">ONNX / In-Browser</span> | |
| </header> | |
| <div id="status-bar"> | |
| <span id="status-text">Initializing...</span> | |
| <div id="progress-bar"><div id="progress-fill"></div></div> | |
| </div> | |
| <div id="chat"> | |
| <div class="welcome"> | |
| <h2>Supra 50M</h2> | |
| <p>A tiny instruction-tuned LLM running entirely in your browser via ONNX Runtime + Transformers.js. No server needed.</p> | |
| </div> | |
| </div> | |
| <div id="input-area"> | |
| <div id="input-row"> | |
| <textarea id="prompt-input" placeholder="Ask something..." rows="1" disabled></textarea> | |
| <button id="settings-toggle" title="Settings">⚙</button> | |
| <button id="send-btn" disabled>Send</button> | |
| <button id="stop-btn">Stop</button> | |
| </div> | |
| <div id="settings-panel"> | |
| <div class="setting"> | |
| <label>Max tokens</label> | |
| <input type="number" id="param-max-tokens" value="256" min="1" max="512" /> | |
| </div> | |
| <div class="setting"> | |
| <label>Temperature</label> | |
| <input type="number" id="param-temperature" value="0.7" min="0" max="2" step="0.1" /> | |
| </div> | |
| <div class="setting"> | |
| <label>Top-K</label> | |
| <input type="number" id="param-top-k" value="50" min="1" max="200" /> | |
| </div> | |
| <div class="setting"> | |
| <label>Repetition penalty</label> | |
| <input type="number" id="param-rep-penalty" value="1.15" min="1" max="2" step="0.05" /> | |
| </div> | |
| </div> | |
| </div> | |
| <footer> | |
| Powered by <a href="https://huggingface.co/docs/transformers.js" target="_blank">Transformers.js</a> | |
| · Model: <a href="https://huggingface.co/SupraLabs/Supra-50M-Instruct" target="_blank">SupraLabs/Supra-50M-Instruct</a> | |
| </footer> | |
| <script type="module"> | |
| const worker = new Worker("worker.js", { type: "module" }); | |
| const chatEl = document.getElementById("chat"); | |
| const inputEl = document.getElementById("prompt-input"); | |
| const sendBtn = document.getElementById("send-btn"); | |
| const statusText = document.getElementById("status-text"); | |
| const progressBar = document.getElementById("progress-bar"); | |
| const progressFill = document.getElementById("progress-fill"); | |
| const stopBtn = document.getElementById("stop-btn"); | |
| const settingsToggle = document.getElementById("settings-toggle"); | |
| const settingsPanel = document.getElementById("settings-panel"); | |
| let isGenerating = false; | |
| let currentResponseEl = null; | |
| settingsToggle.addEventListener("click", () => { | |
| settingsPanel.classList.toggle("open"); | |
| }); | |
| function getParams() { | |
| return { | |
| max_new_tokens: parseInt(document.getElementById("param-max-tokens").value) || 256, | |
| temperature: parseFloat(document.getElementById("param-temperature").value) || 0.7, | |
| top_k: parseInt(document.getElementById("param-top-k").value) || 50, | |
| top_p: 0.9, | |
| repetition_penalty: parseFloat(document.getElementById("param-rep-penalty").value) || 1.15, | |
| }; | |
| } | |
| function addMessage(role, text) { | |
| const welcome = chatEl.querySelector(".welcome"); | |
| if (welcome) welcome.remove(); | |
| const msg = document.createElement("div"); | |
| msg.className = `message message-${role}`; | |
| const label = document.createElement("div"); | |
| label.className = "label"; | |
| label.textContent = role === "user" ? "You" : "Supra 50M"; | |
| const content = document.createElement("div"); | |
| content.className = "message-text"; | |
| content.textContent = text || ""; | |
| msg.appendChild(label); | |
| msg.appendChild(content); | |
| chatEl.appendChild(msg); | |
| chatEl.scrollTop = chatEl.scrollHeight; | |
| return content; | |
| } | |
| function send() { | |
| const text = inputEl.value.trim(); | |
| if (!text || isGenerating) return; | |
| addMessage("user", text); | |
| currentResponseEl = addMessage("assistant", ""); | |
| const cursor = document.createElement("span"); | |
| cursor.className = "cursor"; | |
| currentResponseEl.appendChild(cursor); | |
| isGenerating = true; | |
| sendBtn.style.display = "none"; | |
| stopBtn.style.display = "block"; | |
| inputEl.value = ""; | |
| inputEl.style.height = "auto"; | |
| worker.postMessage({ | |
| type: "generate", | |
| instruction: text, | |
| params: getParams(), | |
| }); | |
| } | |
| sendBtn.addEventListener("click", send); | |
| stopBtn.addEventListener("click", () => { | |
| worker.postMessage({ type: "stop" }); | |
| }); | |
| inputEl.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter" && !e.shiftKey) { | |
| e.preventDefault(); | |
| send(); | |
| } | |
| }); | |
| inputEl.addEventListener("input", () => { | |
| inputEl.style.height = "auto"; | |
| inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + "px"; | |
| }); | |
| worker.onmessage = (e) => { | |
| const { type } = e.data; | |
| if (type === "status") { | |
| statusText.textContent = e.data.message; | |
| } else if (type === "progress") { | |
| progressBar.style.display = "block"; | |
| progressFill.style.width = e.data.percent.toFixed(0) + "%"; | |
| statusText.textContent = `Loading model... ${e.data.percent.toFixed(0)}%`; | |
| } else if (type === "ready") { | |
| progressBar.style.display = "none"; | |
| statusText.textContent = "Ready"; | |
| inputEl.disabled = false; | |
| sendBtn.disabled = false; | |
| inputEl.focus(); | |
| } else if (type === "token") { | |
| if (currentResponseEl) { | |
| const cursor = currentResponseEl.querySelector(".cursor"); | |
| if (cursor) cursor.remove(); | |
| currentResponseEl.textContent += e.data.text; | |
| const newCursor = document.createElement("span"); | |
| newCursor.className = "cursor"; | |
| currentResponseEl.appendChild(newCursor); | |
| chatEl.scrollTop = chatEl.scrollHeight; | |
| } | |
| } else if (type === "done") { | |
| if (currentResponseEl) { | |
| const cursor = currentResponseEl.querySelector(".cursor"); | |
| if (cursor) cursor.remove(); | |
| } | |
| isGenerating = false; | |
| stopBtn.style.display = "none"; | |
| sendBtn.style.display = "block"; | |
| sendBtn.disabled = false; | |
| inputEl.focus(); | |
| } else if (type === "error") { | |
| if (currentResponseEl) { | |
| const cursor = currentResponseEl.querySelector(".cursor"); | |
| if (cursor) cursor.remove(); | |
| currentResponseEl.textContent += "\n[Error: " + e.data.message + "]"; | |
| } | |
| isGenerating = false; | |
| stopBtn.style.display = "none"; | |
| sendBtn.style.display = "block"; | |
| sendBtn.disabled = false; | |
| } | |
| }; | |
| worker.postMessage({ type: "load" }); | |
| </script> | |
| </body> | |
| </html> | |