Spaces:
Running on Zero
Running on Zero
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Muse Glimmer 30B — Meta AI</title> | |
| <style> | |
| :root { | |
| --blue: #1877F2; | |
| --blue-dark: #1458b8; | |
| --bg: #0a1128; | |
| --bg2: #0d1b3e; | |
| --card: #111d3a; | |
| --border: #233460; | |
| --text: #ffffff; | |
| --muted: #9aa8c7; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background: radial-gradient(ellipse at 50% 30%, #14245c 0%, var(--bg2) 40%, var(--bg) 100%); | |
| color: var(--text); | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* Header */ | |
| header { | |
| display: flex; align-items: center; gap: 32px; | |
| padding: 14px 28px; | |
| border-bottom: 1px solid rgba(255,255,255,.06); | |
| } | |
| header .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; font-size: 17px; } | |
| header nav { display: flex; gap: 28px; font-size: 14px; font-weight: 500; } | |
| header nav a { color: #dfe6f5; text-decoration: none; } | |
| header nav a:hover { color: #fff; } | |
| header .cta { | |
| margin-left: auto; background: var(--blue); color: #fff; border: none; | |
| padding: 9px 20px; border-radius: 999px; font-weight: 600; font-size: 14px; cursor: pointer; | |
| } | |
| header .cta:hover { background: var(--blue-dark); } | |
| @media (max-width: 760px) { header nav { display: none; } } | |
| /* Orb */ | |
| .orb { display: flex; justify-content: center; padding: 36px 0 8px; } | |
| .orb svg { width: 130px; height: 130px; animation: spin 24s linear infinite; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| /* Hero */ | |
| .hero { text-align: center; padding: 12px 20px 20px; } | |
| .hero h1 { font-size: clamp(34px, 6vw, 58px); font-weight: 700; letter-spacing: -1px; } | |
| .hero p { color: #c6d0e8; font-size: 17px; margin-top: 14px; line-height: 1.5; } | |
| /* Chat */ | |
| #chat { | |
| flex: 1; overflow-y: auto; padding: 10px 20px 20px; | |
| display: flex; flex-direction: column; gap: 12px; | |
| max-width: 800px; width: 100%; margin: 0 auto; | |
| } | |
| .msg { max-width: 78%; padding: 12px 16px; border-radius: 20px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; font-size: 15px; } | |
| .user { align-self: flex-end; background: var(--blue); color: #fff; border-bottom-right-radius: 6px; } | |
| .bot { align-self: flex-start; background: var(--card); border: 1px solid var(--border); color: #eef2fb; border-bottom-left-radius: 6px; } | |
| .msg img, .msg video { max-width: 240px; border-radius: 12px; display: block; margin-bottom: 8px; } | |
| .sys { align-self: center; color: var(--muted); font-size: 12px; background: none; } | |
| .reasoning { margin-bottom: 8px; border-left: 2px solid var(--border); padding-left: 10px; } | |
| .reasoning summary { cursor: pointer; color: #8ab4ff; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .5px; } | |
| .reasoning-body { color: var(--muted); font-size: 13px; font-style: italic; margin-top: 6px; white-space: pre-wrap; } | |
| .bot.typing span::after { content: "▊"; animation: blink 1s steps(1) infinite; color: #8ab4ff; } | |
| @keyframes blink { 50% { opacity: 0; } } | |
| /* Controls */ | |
| #controls { | |
| max-width: 800px; width: 100%; margin: 0 auto; padding: 0 20px 10px; | |
| display: flex; gap: 18px; flex-wrap: wrap; font-size: 12px; color: var(--muted); align-items: center; | |
| } | |
| #controls label { display: flex; align-items: center; gap: 8px; } | |
| select { | |
| background: var(--card); color: var(--text); border: 1px solid var(--border); | |
| border-radius: 8px; padding: 4px 8px; font-size: 12px; | |
| } | |
| input[type=range] { accent-color: var(--blue); } | |
| #controls b { color: #dfe6f5; } | |
| #examples { max-width: 800px; width: 100%; margin: 0 auto; padding: 4px 20px 14px; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; } | |
| .chip { | |
| background: var(--card); border: 1px solid var(--border); color: #dfe6f5; | |
| padding: 8px 16px; border-radius: 999px; font-size: 13px; font-weight: 500; cursor: pointer; | |
| } | |
| .chip:hover { border-color: var(--blue); color: #fff; background: #16244a; } | |
| /* Composer */ | |
| #composer { padding: 12px 20px 18px; } | |
| #composer .row { | |
| max-width: 800px; margin: 0 auto; display: flex; gap: 10px; align-items: flex-end; | |
| background: var(--card); border: 1px solid var(--border); | |
| border-radius: 24px; padding: 8px 10px; | |
| } | |
| #composer .row:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px rgba(24,119,242,.25); } | |
| #input { | |
| flex: 1; resize: none; border: none; background: transparent; color: var(--text); | |
| padding: 8px 10px; font-size: 15px; font-family: inherit; max-height: 140px; | |
| } | |
| #input:focus { outline: none; } | |
| #input::placeholder { color: var(--muted); } | |
| button { | |
| background: var(--blue); border: none; color: #fff; font-weight: 600; | |
| padding: 10px 18px; border-radius: 999px; cursor: pointer; font-size: 14px; | |
| } | |
| button:disabled { opacity: .5; cursor: default; } | |
| button:hover:not(:disabled) { background: var(--blue-dark); } | |
| #attach { background: transparent; color: var(--muted); padding: 10px 12px; font-size: 17px; } | |
| #attach:hover:not(:disabled) { background: rgba(255,255,255,.08); color: #fff; } | |
| #stop { background: #e0245e; } | |
| #preview { max-width: 800px; margin: 0 auto 10px; display: none; position: relative; width: fit-content; } | |
| #preview img { height: 64px; border-radius: 12px; border: 1px solid var(--border); } | |
| #preview .x { | |
| position: absolute; top: -8px; right: -8px; background: #e0245e; color: #fff; | |
| border-radius: 50%; width: 20px; height: 20px; font-size: 11px; | |
| display: flex; align-items: center; justify-content: center; cursor: pointer; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <svg width="24" height="16" viewBox="0 0 36 24" fill="none"> | |
| <path d="M6 24C2.7 24 0 21.3 0 18c0-1.5.6-3 1.5-4.5C3.5 10.5 6.7 6 9.9 3.2 11.5 1.8 13.2 1 14.7 1c2.4 0 4 1.6 5.1 3.7.4.9.9 2.1 1.4 3.5.5-1.4 1-2.6 1.4-3.5C23.7 2.6 25.4 1 27.8 1c1.5 0 3.2.8 4.8 2.2 3.2 2.8 6.4 7.3 8.4 10.3.9 1.5 1.5 3 1.5 4.5 0 3.3-2.7 6-6 6-2 0-3.7-1-5-2.8-1.3-1.8-2.6-4.4-3.9-7.2-.4-1-.9-2-1.3-3.1-.4 1-.9 2.1-1.3 3.1-1.3 2.8-2.6 5.4-3.9 7.2C19.7 23 18 24 16 24h-10z" fill="#1877F2" transform="scale(0.8)"/> | |
| </svg> | |
| Muse Glimmer | |
| </div> | |
| <nav> | |
| <a href="https://huggingface.co/meta-models/Muse-Glimmer-30B" target="_blank">Model Card</a> | |
| </nav> | |
| </header> | |
| <div class="orb"> | |
| <svg viewBox="0 0 100 100"> | |
| <defs> | |
| <linearGradient id="g1" x1="0" y1="0" x2="1" y2="1"> | |
| <stop offset="0%" stop-color="#c084fc"/><stop offset="100%" stop-color="#7c3aed"/> | |
| </linearGradient> | |
| </defs> | |
| <g> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(0 50 50)"/> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(51 50 50)"/> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(103 50 50)"/> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(154 50 50)"/> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(206 50 50)"/> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(257 50 50)"/> | |
| <ellipse cx="50" cy="14" rx="9" ry="14" fill="url(#g1)" transform="rotate(309 50 50)"/> | |
| </g> | |
| </svg> | |
| </div> | |
| <div class="hero" id="hero"> | |
| <h1>Do more with Muse Glimmer</h1> | |
| <p>A 30B multimodal agentic model — answering questions, reasoning over images and video,<br>and completing tasks, right here on ZeroGPU.</p> | |
| </div> | |
| <div id="examples"> | |
| <button class="chip" data-text="Write a short joke about saving RAM.">😂 Joke about RAM</button> | |
| <button class="chip" data-text="Write a Python function to compute Fibonacci numbers with memoization.">🐍 Fibonacci in Python</button> | |
| <button class="chip" data-text="Explain how speculative decoding works in 3 sentences.">⚡ Speculative decoding</button> | |
| <button class="chip" data-text="What is shown in this image?" data-url="https://huggingface.co/datasets/merve/vl-test-suite/resolve/main/SF.png">🌁 What's in this image?</button> | |
| <button class="chip" data-text="Detect the bridge. Return only the detection in the model's native object-detection format, with no explanation." data-url="https://huggingface.co/datasets/merve/vl-test-suite/resolve/main/SF.png">🌉 Detect the bridge</button> | |
| <button class="chip" data-text="Describe what happens in this video." data-url="https://huggingface.co/datasets/merve/vl-test-suite/resolve/main/IMG_8137.mp4">🎬 Describe this video</button> | |
| </div> | |
| <div id="chat"></div> | |
| <div id="controls"> | |
| <label>Reasoning | |
| <select id="reasoning"> | |
| <option>low</option><option>medium</option> | |
| <option selected>high</option><option>xhigh</option> | |
| </select> | |
| </label> | |
| <label>Queue <span id="queue">—</span></label> | |
| </div> | |
| <div id="composer"> | |
| <div id="preview"><img id="pimg"><div class="x" onclick="clearImage()">✕</div></div> | |
| <div class="row"> | |
| <button id="attach" title="Attach image">📎</button> | |
| <input type="file" id="file" accept="image/*,video/*" hidden> | |
| <textarea id="input" rows="1" placeholder="Message Muse Glimmer…"></textarea> | |
| <button id="send">Send</button> | |
| <button id="stop" style="display:none">Stop</button> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; | |
| const chatEl = document.getElementById("chat"); | |
| const hero = document.getElementById("hero"); | |
| const input = document.getElementById("input"); | |
| const sendBtn = document.getElementById("send"); | |
| const stopBtn = document.getElementById("stop"); | |
| const fileInput = document.getElementById("file"); | |
| const preview = document.getElementById("preview"); | |
| const pimg = document.getElementById("pimg"); | |
| const queueEl = document.getElementById("queue"); | |
| let attachedFile = null; | |
| let history = []; | |
| let currentJob = null; | |
| const client = await Client.connect(window.location.origin, { events: ["data", "status"] }); | |
| document.getElementById("attach").onclick = () => fileInput.click(); | |
| fileInput.onchange = () => { | |
| attachedFile = fileInput.files[0] || null; | |
| if (attachedFile) { | |
| const url = URL.createObjectURL(attachedFile); | |
| if (attachedFile.type.startsWith("video/")) { | |
| preview.innerHTML = `<video src="${url}" style="height:64px;border-radius:12px;border:1px solid var(--border)" muted></video><div class="x" onclick="clearImage()">✕</div>`; | |
| } else { | |
| preview.innerHTML = `<img src="${url}" style="height:64px;border-radius:12px;border:1px solid var(--border)"><div class="x" onclick="clearImage()">✕</div>`; | |
| } | |
| preview.style.display = "block"; | |
| } else preview.style.display = "none"; | |
| }; | |
| window.clearImage = () => { attachedFile = null; fileInput.value = ""; preview.style.display = "none"; }; | |
| function addMsg(role, text, mediaUrl, isVideo) { | |
| const div = document.createElement("div"); | |
| div.className = "msg " + role; | |
| if (mediaUrl) { | |
| const im = document.createElement(isVideo ? "video" : "img"); | |
| im.src = mediaUrl; | |
| if (isVideo) { im.controls = true; im.muted = true; } | |
| div.appendChild(im); | |
| } | |
| const t = document.createElement("span"); t.textContent = text; div.appendChild(t); | |
| chatEl.appendChild(div); | |
| chatEl.scrollTop = chatEl.scrollHeight; | |
| return t; | |
| } | |
| // Render a bot message, splitting the model's reasoning preamble | |
| // (everything before the "to=user" marker) into a collapsible block. | |
| function renderBot(div, raw) { | |
| div.innerHTML = ""; | |
| let reasoning = "", final = raw; | |
| if (raw.includes("to=user")) { | |
| const idx = raw.lastIndexOf("to=user"); | |
| reasoning = raw.slice(0, idx).replace(/\.?assistant\s*$/, "").trim(); | |
| final = raw.slice(idx + "to=user".length).trim(); | |
| } else { | |
| reasoning = raw.trim(); | |
| final = ""; | |
| } | |
| if (reasoning) { | |
| const det = document.createElement("details"); | |
| det.className = "reasoning"; | |
| det.open = !final; // expand while still thinking, collapse once answer starts | |
| const sum = document.createElement("summary"); | |
| sum.textContent = final ? "Reasoning" : "Thinking…"; | |
| det.appendChild(sum); | |
| const body = document.createElement("div"); | |
| body.className = "reasoning-body"; | |
| body.textContent = reasoning; | |
| det.appendChild(body); | |
| div.appendChild(det); | |
| } | |
| const t = document.createElement("span"); | |
| t.textContent = final || "💭 thinking…"; | |
| div.appendChild(t); | |
| } | |
| function setBusy(b) { | |
| sendBtn.style.display = b ? "none" : ""; | |
| stopBtn.style.display = b ? "" : "none"; | |
| input.disabled = b; | |
| } | |
| async function send(mediaUrl = null) { | |
| const text = input.value.trim(); | |
| if (!text && !attachedFile && !mediaUrl) return; | |
| hero.style.display = "none"; | |
| document.getElementById("examples").style.display = "none"; | |
| document.querySelector(".orb").style.padding = "12px 0 4px"; | |
| const isVideo = attachedFile ? attachedFile.type.startsWith("video/") | |
| : (mediaUrl && /\.(mp4|mov|webm|mkv|avi)(\?|$)/i.test(mediaUrl)); | |
| const previewUrl = attachedFile ? URL.createObjectURL(attachedFile) : mediaUrl; | |
| addMsg("user", text || (isVideo ? "[video]" : "[image]"), previewUrl, isVideo); | |
| input.value = ""; | |
| input.style.height = "auto"; | |
| setBusy(true); | |
| const botDiv = addMsg("bot", "").parentElement; | |
| botDiv.classList.add("typing"); | |
| try { | |
| currentJob = client.submit("/chat", { | |
| message: text, | |
| media: attachedFile ? handle_file(attachedFile) : null, | |
| media_url: attachedFile ? "" : (mediaUrl || ""), | |
| history: history, | |
| reasoning: document.getElementById("reasoning").value, | |
| }); | |
| let finalText = ""; | |
| for await (const event of currentJob) { | |
| if (event.type === "status") { | |
| queueEl.textContent = event.stage + (event.position != null ? ` #${event.position}` : ""); | |
| } | |
| if (event.type === "data") { | |
| finalText = event.data[0]; | |
| renderBot(botDiv, finalText); | |
| chatEl.scrollTop = chatEl.scrollHeight; | |
| } | |
| } | |
| history.push({ role: "user", content: text || (isVideo ? "[video]" : "[image]") }); | |
| history.push({ role: "assistant", content: finalText }); | |
| } catch (e) { | |
| botDiv.textContent = "⚠️ " + (e.message || e); | |
| } finally { | |
| botDiv.classList.remove("typing"); | |
| setBusy(false); | |
| clearImage(); | |
| queueEl.textContent = "—"; | |
| currentJob = null; | |
| input.focus(); | |
| } | |
| } | |
| sendBtn.onclick = send; | |
| stopBtn.onclick = () => { if (currentJob) currentJob.cancel(); }; | |
| input.onkeydown = e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } }; | |
| input.oninput = () => { input.style.height = "auto"; input.style.height = Math.min(input.scrollHeight, 140) + "px"; }; | |
| document.querySelectorAll(".chip").forEach(chip => { | |
| chip.onclick = () => { | |
| input.value = chip.dataset.text; | |
| input.dispatchEvent(new Event("input")); | |
| send(chip.dataset.url || null); | |
| }; | |
| }); | |
| </script> | |
| </body> | |
| </html> | |