ZAYA1-8B / index.html
akhaliq's picture
akhaliq HF Staff
feat: implement support for aborting active chat generation jobs
a79d5cf
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ZAYA1-8B — Zyphra Reasoning Model</title>
<meta name="description" content="Chat with ZAYA1-8B, a highly efficient reasoning model by Zyphra with 760M active parameters.">
<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{
--bg:#0a0a0f;--surface:#12121a;--surface2:#1a1a28;--surface3:#22223a;
--border:#2a2a40;--border-glow:rgba(139,92,246,.25);
--text:#e4e4ef;--text2:#9898b0;--text3:#6a6a80;
--accent:#8b5cf6;--accent2:#a78bfa;--accent-glow:rgba(139,92,246,.15);
--green:#34d399;--orange:#fb923c;--red:#f87171;
--user-bg:linear-gradient(135deg,#8b5cf6 0%,#6d28d9 100%);
--radius:16px;--radius-sm:10px;
}
html,body{height:100%;font-family:'Inter',sans-serif;background:var(--bg);color:var(--text);overflow:hidden}
/* ── Layout ── */
.app{display:flex;height:100vh;width:100vw}
.sidebar{width:300px;background:var(--surface);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0;transition:transform .3s}
.main{flex:1;display:flex;flex-direction:column;min-width:0}
/* ── Sidebar ── */
.sidebar-header{padding:20px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px}
.logo{width:36px;height:36px;border-radius:10px;background:var(--user-bg);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;color:#fff}
.brand h1{font-size:16px;font-weight:700;background:linear-gradient(135deg,var(--accent2),var(--accent));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.brand span{font-size:11px;color:var(--text3);font-weight:500}
.sidebar-body{flex:1;overflow-y:auto;padding:12px}
.new-chat-btn{width:100%;padding:12px;border-radius:var(--radius-sm);border:1px dashed var(--border);background:transparent;color:var(--text2);cursor:pointer;font-size:13px;font-family:inherit;transition:all .2s;margin-bottom:8px}
.new-chat-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-glow)}
.chat-item{padding:10px 12px;border-radius:var(--radius-sm);cursor:pointer;font-size:13px;color:var(--text2);transition:all .15s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px}
.chat-item:hover,.chat-item.active{background:var(--surface2);color:var(--text)}
.sidebar-footer{padding:16px;border-top:1px solid var(--border)}
.model-badge{display:flex;align-items:center;gap:8px;padding:10px 12px;border-radius:var(--radius-sm);background:var(--surface2);font-size:12px;color:var(--text2)}
.model-badge .dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green)}
/* ── Chat Area ── */
.chat-header{padding:16px 24px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:between;gap:12px;background:rgba(18,18,26,.8);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}
.chat-header h2{font-size:15px;font-weight:600;flex:1}
.header-controls{display:flex;gap:8px}
.icon-btn{width:36px;height:36px;border-radius:var(--radius-sm);border:1px solid var(--border);background:transparent;color:var(--text2);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;font-size:16px}
.icon-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-glow)}
.messages{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:20px;scroll-behavior:smooth}
/* ── Welcome ── */
.welcome{display:flex;flex-direction:column;align-items:center;justify-content:center;flex:1;text-align:center;gap:16px;padding:40px;animation:fadeUp .6s ease}
.welcome-icon{width:72px;height:72px;border-radius:20px;background:var(--user-bg);display:flex;align-items:center;justify-content:center;font-size:28px;box-shadow:0 8px 32px rgba(139,92,246,.3)}
.welcome h2{font-size:24px;font-weight:700;background:linear-gradient(135deg,#fff,var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.welcome p{color:var(--text3);font-size:14px;max-width:480px;line-height:1.6}
.suggestions{display:grid;grid-template-columns:1fr 1fr;gap:10px;max-width:520px;width:100%;margin-top:8px}
.suggestion{padding:14px 16px;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--surface);cursor:pointer;text-align:left;font-size:13px;color:var(--text2);transition:all .2s;font-family:inherit;line-height:1.4}
.suggestion:hover{border-color:var(--accent);background:var(--accent-glow);color:var(--text);transform:translateY(-1px)}
.suggestion strong{display:block;color:var(--text);margin-bottom:4px;font-size:12px;font-weight:600}
/* ── Messages ── */
.msg{display:flex;gap:12px;max-width:800px;width:100%;margin:0 auto;animation:fadeUp .3s ease}
.msg.user{flex-direction:row-reverse}
.avatar{width:32px;height:32px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:14px;font-weight:600}
.msg.user .avatar{background:var(--user-bg);color:#fff}
.msg.assistant .avatar{background:var(--surface3);color:var(--accent2);border:1px solid var(--border)}
.bubble{padding:14px 18px;border-radius:var(--radius);font-size:14px;line-height:1.7;max-width:75%;word-wrap:break-word}
.msg.user .bubble{background:var(--surface2);border:1px solid var(--border);color:var(--text)}
.msg.assistant .bubble{background:var(--surface);border:1px solid var(--border);color:var(--text)}
.msg.assistant .bubble p{margin-bottom:8px}
.msg.assistant .bubble p:last-child{margin-bottom:0}
/* Thinking block */
.think-block{background:var(--surface2);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--radius-sm);padding:12px 16px;margin-bottom:10px;font-size:13px;color:var(--text2);line-height:1.6}
.think-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;font-weight:600;color:var(--accent2);margin-bottom:6px;user-select:none}
.think-toggle svg{transition:transform .2s}
.think-toggle.collapsed svg{transform:rotate(-90deg)}
.think-content{overflow:hidden;transition:max-height .3s}
.think-content.hidden{max-height:0!important;margin:0;padding:0}
/* Code blocks */
.bubble pre{background:var(--surface2);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px 16px;overflow-x:auto;margin:8px 0;font-size:13px;line-height:1.5}
.bubble code{font-family:'JetBrains Mono',monospace;font-size:13px}
.bubble :not(pre)>code{background:var(--surface2);padding:2px 6px;border-radius:4px;font-size:12px;border:1px solid var(--border)}
/* ── Typing Indicator ── */
.typing{display:flex;gap:4px;padding:8px 0}
.typing span{width:6px;height:6px;border-radius:50%;background:var(--accent2);animation:bounce .6s infinite alternate}
.typing span:nth-child(2){animation-delay:.15s}
.typing span:nth-child(3){animation-delay:.3s}
/* ── Input Area ── */
.input-area{padding:16px 24px 20px;border-top:1px solid var(--border);background:rgba(18,18,26,.8);backdrop-filter:blur(20px)}
.input-wrap{max-width:800px;margin:0 auto;display:flex;gap:10px;align-items:flex-end}
.input-box{flex:1;position:relative}
#userInput{width:100%;resize:none;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);color:var(--text);padding:14px 18px;font-size:14px;font-family:'Inter',sans-serif;line-height:1.5;outline:none;min-height:52px;max-height:200px;transition:border-color .2s}
#userInput:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-glow)}
#userInput::placeholder{color:var(--text3)}
#sendBtn{width:48px;height:48px;border-radius:var(--radius-sm);border:none;background:var(--user-bg);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;flex-shrink:0}
#sendBtn:hover{transform:scale(1.05);box-shadow:0 4px 16px rgba(139,92,246,.4)}
#sendBtn:disabled{opacity:.4;cursor:not-allowed;transform:none;box-shadow:none}
.input-hint{text-align:center;font-size:11px;color:var(--text3);margin-top:8px}
/* ── Settings Panel ── */
.settings-panel{display:none;position:absolute;top:60px;right:24px;width:320px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:20px;z-index:100;box-shadow:0 16px 48px rgba(0,0,0,.5);animation:fadeUp .2s ease}
.settings-panel.show{display:block}
.settings-panel h3{font-size:14px;font-weight:600;margin-bottom:16px;color:var(--text)}
.setting-row{margin-bottom:14px}
.setting-row label{display:flex;justify-content:space-between;font-size:12px;color:var(--text2);margin-bottom:6px;font-weight:500}
.setting-row input[type=range]{width:100%;accent-color:var(--accent);height:4px;-webkit-appearance:none;background:var(--surface3);border-radius:2px;outline:none}
.setting-row input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);cursor:pointer;border:2px solid var(--surface)}
.setting-row textarea{width:100%;min-height:80px;border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--surface2);color:var(--text);padding:10px;font-size:12px;font-family:'Inter',sans-serif;resize:vertical;outline:none}
.setting-row textarea:focus{border-color:var(--accent)}
/* ── Mobile ── */
.menu-btn{display:none;position:fixed;top:12px;left:12px;z-index:200}
@media(max-width:768px){
.sidebar{position:fixed;left:0;top:0;bottom:0;z-index:150;transform:translateX(-100%)}
.sidebar.open{transform:translateX(0)}
.menu-btn{display:flex}
.suggestions{grid-template-columns:1fr}
.bubble{max-width:90%}
}
/* ── Animations ── */
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
@keyframes bounce{to{transform:translateY(-6px);opacity:.4}}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
/* Scrollbar */
::-webkit-scrollbar{width:6px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--surface3);border-radius:3px}
::-webkit-scrollbar-thumb:hover{background:var(--border)}
</style>
</head>
<body>
<div class="app">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">Z</div>
<div class="brand"><h1>ZAYA1-8B</h1><span>by Zyphra</span></div>
</div>
<div class="sidebar-body">
<button class="new-chat-btn" onclick="newChat()">+ New Chat</button>
<div id="chatList"></div>
</div>
<div class="sidebar-footer">
<div class="model-badge"><span class="dot"></span> ZAYA1-8B · 760M active · MoE</div>
</div>
</aside>
<!-- Main -->
<div class="main">
<button class="icon-btn menu-btn" onclick="document.getElementById('sidebar').classList.toggle('open')"></button>
<div class="chat-header">
<h2 id="chatTitle">New Chat</h2>
<div class="header-controls">
<button class="icon-btn" id="settingsBtn" onclick="toggleSettings()" title="Settings"></button>
</div>
<div class="settings-panel" id="settingsPanel">
<h3>Generation Settings</h3>
<div class="setting-row"><label>Temperature <span id="tempVal">1.0</span></label><input type="range" id="temperature" min="0.1" max="2" step="0.05" value="1.0" oninput="document.getElementById('tempVal').textContent=this.value"></div>
<div class="setting-row"><label>Top-P <span id="topPVal">0.95</span></label><input type="range" id="topP" min="0.1" max="1" step="0.05" value="0.95" oninput="document.getElementById('topPVal').textContent=this.value"></div>
<div class="setting-row"><label>Max Tokens <span id="maxTVal">2048</span></label><input type="range" id="maxTokens" min="256" max="4096" step="256" value="2048" oninput="document.getElementById('maxTVal').textContent=this.value"></div>
<div class="setting-row"><label>System Prompt</label><textarea id="systemPrompt">You are ZAYA1-8B, a highly capable reasoning assistant built by Zyphra. You excel at detailed long-form reasoning, mathematics, and coding. Think step by step when solving complex problems.</textarea></div>
</div>
</div>
<div class="messages" id="messages">
<div class="welcome" id="welcome">
<div class="welcome-icon">Z</div>
<h2>ZAYA1-8B</h2>
<p>A highly efficient reasoning model with 760M active parameters. Excels at mathematics, coding, and complex reasoning tasks.</p>
<div class="suggestions">
<button class="suggestion" onclick="useSuggestion(this)"><strong>🧮 Math</strong>Prove that √2 is irrational using proof by contradiction.</button>
<button class="suggestion" onclick="useSuggestion(this)"><strong>💻 Coding</strong>Write a Python function for merge sort with detailed comments.</button>
<button class="suggestion" onclick="useSuggestion(this)"><strong>🧠 Reasoning</strong>Explain the Monty Hall problem and why switching is optimal.</button>
<button class="suggestion" onclick="useSuggestion(this)"><strong>📐 Logic</strong>Solve: If all A are B, and some B are C, what can we conclude?</button>
</div>
</div>
</div>
<div class="input-area">
<div class="input-wrap">
<div class="input-box"><textarea id="userInput" placeholder="Ask ZAYA1 anything..." rows="1"></textarea></div>
<button id="sendBtn" onclick="sendMessage()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/></svg>
</button>
</div>
<div class="input-hint">ZAYA1-8B can make mistakes. Verify important information.</div>
</div>
</div>
</div>
<script type="module">
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
// ── Gradio Client ──
let gradioClient = null;
async function getClient() {
if (!gradioClient) {
gradioClient = await Client.connect(window.location.origin);
}
return gradioClient;
}
let conversations = [];
let currentConv = null;
let isGenerating = false;
let currentJob = null;
// ── Init ──
function init() {
newChat();
document.getElementById('userInput').addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
});
document.getElementById('userInput').addEventListener('input', autoResize);
document.addEventListener('click', e => {
const sp = document.getElementById('settingsPanel');
if (sp.classList.contains('show') && !sp.contains(e.target) && e.target.id !== 'settingsBtn') sp.classList.remove('show');
});
}
function autoResize() {
const ta = document.getElementById('userInput');
ta.style.height = 'auto';
ta.style.height = Math.min(ta.scrollHeight, 200) + 'px';
}
// ── Chat Management ──
function newChat() {
if (isGenerating && currentJob) {
try { currentJob.destroy(); } catch(e) {}
}
isGenerating = false;
currentJob = null;
document.getElementById('sendBtn').disabled = false;
updateSendBtnIcon(false);
const conv = { id: Date.now(), title: 'New Chat', messages: [] };
conversations.unshift(conv);
currentConv = conv;
renderChatList();
renderMessages();
document.getElementById('chatTitle').textContent = 'New Chat';
}
function switchChat(id) {
if (isGenerating && currentJob) {
try { currentJob.destroy(); } catch(e) {}
}
isGenerating = false;
currentJob = null;
document.getElementById('sendBtn').disabled = false;
updateSendBtnIcon(false);
currentConv = conversations.find(c => c.id === id);
renderChatList();
renderMessages();
document.getElementById('chatTitle').textContent = currentConv.title;
document.getElementById('sidebar').classList.remove('open');
}
function renderChatList() {
const el = document.getElementById('chatList');
el.innerHTML = conversations.map(c =>
`<div class="chat-item ${c.id === currentConv?.id ? 'active' : ''}" onclick="switchChat(${c.id})">${c.title}</div>`
).join('');
}
// ── Render Messages ──
function renderMessages() {
const el = document.getElementById('messages');
if (!currentConv || currentConv.messages.length === 0) {
el.innerHTML = `<div class="welcome" id="welcome">
<div class="welcome-icon">Z</div><h2>ZAYA1-8B</h2>
<p>A highly efficient reasoning model with 760M active parameters. Excels at mathematics, coding, and complex reasoning tasks.</p>
<div class="suggestions">
<button class="suggestion" onclick="useSuggestion(this)"><strong>🧮 Math</strong>Prove that √2 is irrational using proof by contradiction.</button>
<button class="suggestion" onclick="useSuggestion(this)"><strong>💻 Coding</strong>Write a Python function for merge sort with detailed comments.</button>
<button class="suggestion" onclick="useSuggestion(this)"><strong>🧠 Reasoning</strong>Explain the Monty Hall problem and why switching is optimal.</button>
<button class="suggestion" onclick="useSuggestion(this)"><strong>📐 Logic</strong>Solve: If all A are B, and some B are C, what can we conclude?</button>
</div></div>`;
return;
}
el.innerHTML = currentConv.messages.map(m => msgHTML(m.role, m.content)).join('');
el.scrollTop = el.scrollHeight;
}
function msgHTML(role, content) {
const avatar = role === 'user' ? 'U' : 'Z';
const rendered = role === 'assistant' ? formatResponse(content) : escapeHTML(content);
return `<div class="msg ${role}"><div class="avatar">${avatar}</div><div class="bubble">${rendered}</div></div>`;
}
// ── Format response with thinking blocks, code, markdown ──
function formatResponse(text) {
let html = text;
// Handle <think> blocks
html = html.replace(/<think>([\s\S]*?)<\/think>/g, (_, inner) => {
const id = 'think_' + Math.random().toString(36).substr(2, 6);
return `<div class="think-block"><div class="think-toggle" onclick="toggleThink('${id}')">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
Reasoning</div><div class="think-content" id="${id}">${escapeHTML(inner.trim())}</div></div>`;
});
// Code blocks
html = html.replace(/```(\w*)\n?([\s\S]*?)```/g, (_, lang, code) =>
`<pre><code class="language-${lang}">${escapeHTML(code.trim())}</code></pre>`);
// Inline code
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
// Bold
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
// Italic
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
// Line breaks
html = html.replace(/\n/g, '<br>');
return html;
}
function escapeHTML(s) {
const d = document.createElement('div'); d.textContent = s; return d.innerHTML;
}
// ── Send Message (uses Gradio JS Client) ──
// ── Send Message (uses Gradio JS Client) ──
async function sendMessage() {
if (isGenerating) {
if (currentJob) {
try { currentJob.destroy(); } catch(e) {}
}
isGenerating = false;
currentJob = null;
document.getElementById('sendBtn').disabled = false;
updateSendBtnIcon(false);
return;
}
const input = document.getElementById('userInput');
const text = input.value.trim();
if (!text) return;
// Hide welcome
const welcome = document.getElementById('welcome');
if (welcome) welcome.remove();
// Add user message
currentConv.messages.push({ role: 'user', content: text });
if (currentConv.messages.length === 1) {
currentConv.title = text.slice(0, 40) + (text.length > 40 ? '…' : '');
document.getElementById('chatTitle').textContent = currentConv.title;
renderChatList();
}
const messagesEl = document.getElementById('messages');
messagesEl.innerHTML += msgHTML('user', text);
input.value = '';
input.style.height = 'auto';
isGenerating = true;
updateSendBtnIcon(true);
// Add assistant placeholder with typing indicator
const assistantDiv = document.createElement('div');
assistantDiv.className = 'msg assistant';
assistantDiv.innerHTML = `<div class="avatar">Z</div><div class="bubble"><div class="typing"><span></span><span></span><span></span></div></div>`;
messagesEl.appendChild(assistantDiv);
messagesEl.scrollTop = messagesEl.scrollHeight;
const bubble = assistantDiv.querySelector('.bubble');
let fullText = '';
try {
const client = await getClient();
const history = currentConv.messages.slice(0, -1);
currentJob = client.submit("/generate", {
message: text,
history: JSON.stringify(history),
system_prompt: document.getElementById('systemPrompt').value,
temperature: parseFloat(document.getElementById('temperature').value),
top_p: parseFloat(document.getElementById('topP').value),
max_new_tokens: parseInt(document.getElementById('maxTokens').value),
});
await new Promise((resolve, reject) => {
currentJob.on("data", (event) => {
if (event.data && event.data[0]) {
fullText = event.data[0];
bubble.innerHTML = formatResponse(fullText);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
});
currentJob.on("status", (status) => {
if (status.stage === "complete") resolve();
if (status.stage === "error") reject(new Error(status.message || "Generation failed"));
});
});
} catch (err) {
console.error("Generation error:", err);
if (!fullText) {
fullText = 'Sorry, an error occurred. Please try again.';
bubble.innerHTML = `<span style="color:var(--red)">${escapeHTML(err.message || fullText)}</span>`;
}
} finally {
isGenerating = false;
currentJob = null;
document.getElementById('sendBtn').disabled = false;
updateSendBtnIcon(false);
if (fullText) {
currentConv.messages.push({ role: 'assistant', content: fullText });
}
document.getElementById('userInput').focus();
}
}
function updateSendBtnIcon(loading) {
const btn = document.getElementById('sendBtn');
if (loading) {
btn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="6" width="12" height="12"/></svg>`;
btn.title = "Stop generating";
} else {
btn.innerHTML = `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/></svg>`;
btn.title = "Send message";
}
}
// ── Globals for onclick handlers ──
window.newChat = newChat;
window.switchChat = switchChat;
window.sendMessage = sendMessage;
window.toggleSettings = () => document.getElementById('settingsPanel').classList.toggle('show');
window.toggleThink = (id) => {
const el = document.getElementById(id);
el.classList.toggle('hidden');
el.previousElementSibling.classList.toggle('collapsed');
};
window.useSuggestion = (btn) => {
const text = btn.textContent.replace(btn.querySelector('strong').textContent, '').trim();
document.getElementById('userInput').value = text;
sendMessage();
};
init();
</script>
</body>
</html>