FREEAPI / index.html
ArushBuilds's picture
Update index.html
1e1c513 verified
Raw
History Blame Contribute Delete
47.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Qwopus</title>
<meta name="description" content="Qwopus by jackrong">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<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">
<!-- FontAwesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Prism.js (light theme for Claude-style) -->
<link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" rel="stylesheet">
<!-- marked.js -->
<script src="https://cdn.jsdelivr.net/npm/marked@9.1.6/marked.min.js"></script>
<!-- Prism.js core + languages -->
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-python.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-javascript.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-bash.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-cpp.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-java.min.js"></script>
<!-- MathJax -->
<script>
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
processEscapes: true
},
options: { skipHtmlTags: ['script','noscript','style','textarea','pre','code'] }
};
</script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<style>
/* ─── Design Tokens ─────────────────────────────────────────────── */
:root {
/* WeiboAI brand colors extracted from hummingbird logo */
--brand-deep: #C0310A; /* deep crimson-red */
--brand-mid: #E04015; /* primary orange-red */
--brand-bright: #F26027; /* vivid orange */
--brand-light: #F5874F; /* soft orange */
--brand-pale: #FEF0EB; /* blush tint */
/* Claude-style neutrals */
--bg: #FFFFFF;
--bg-sidebar: #F7F6F3;
--bg-hover: #EFEDE8;
--bg-input: #F9F8F6;
--border: #E8E6E1;
--border-light: #F0EEE9;
--text-primary: #1A1A18;
--text-secondary:#6B6560;
--text-muted: #9B9590;
--text-on-brand: #FFFFFF;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.10);
--sidebar-w: 260px;
--radius: 12px;
--radius-sm: 8px;
--radius-xs: 6px;
}
/* ─── Reset ─────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
font-family: 'Inter', system-ui, sans-serif;
font-size: 15px;
color: var(--text-primary);
background: var(--bg);
-webkit-font-smoothing: antialiased;
}
/* ─── Layout ─────────────────────────────────────────────────────── */
.app {
display: flex;
height: 100vh;
overflow: hidden;
}
/* ─── Sidebar ────────────────────────────────────────────────────── */
.sidebar {
width: var(--sidebar-w);
background: var(--bg-sidebar);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
transition: width 0.22s ease;
}
.sidebar.collapsed { width: 0; border-right: none; }
/* Brand / Logo area */
.sidebar-header {
padding: 20px 16px 14px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
}
.brand-logo {
width: 34px;
height: 34px;
border-radius: 8px;
object-fit: cover;
flex-shrink: 0;
}
.brand-name {
font-size: 15px;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.3px;
}
.brand-tag {
display: inline-block;
font-size: 10px;
font-weight: 600;
background: var(--brand-pale);
color: var(--brand-mid);
border: 1px solid rgba(224,64,21,0.2);
padding: 1px 6px;
border-radius: 20px;
letter-spacing: 0.2px;
}
/* Sidebar body / sections */
.sidebar-body {
flex: 1;
overflow-y: auto;
padding: 12px 10px;
}
.sidebar-body::-webkit-scrollbar { width: 4px; }
.sidebar-body::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.sidebar-section-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
padding: 8px 6px 6px;
}
/* Parameter controls */
.param-group { margin-bottom: 14px; padding: 0 4px; }
.param-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.param-label { font-size: 12.5px; color: var(--text-secondary); font-weight: 500; }
.param-val {
font-size: 12px;
font-weight: 600;
color: var(--brand-mid);
min-width: 36px;
text-align: right;
}
input[type="range"] {
width: 100%;
height: 4px;
background: var(--border);
border-radius: 2px;
outline: none;
-webkit-appearance: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--brand-mid);
box-shadow: 0 0 0 3px rgba(224,64,21,0.15);
transition: transform 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.15); }
.system-prompt-textarea {
width: 100%;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-xs);
color: var(--text-primary);
font-family: inherit;
font-size: 12px;
line-height: 1.5;
padding: 8px 10px;
resize: vertical;
min-height: 74px;
outline: none;
transition: border-color 0.15s;
}
.system-prompt-textarea:focus { border-color: var(--brand-light); }
/* Preset buttons */
.preset-btn {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
padding: 8px 10px;
border-radius: var(--radius-xs);
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 12.5px;
font-family: inherit;
cursor: pointer;
text-align: left;
transition: background 0.12s, color 0.12s;
}
.preset-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
.preset-btn .preset-icon {
width: 22px; height: 22px;
background: var(--brand-pale);
border-radius: 5px;
display: flex; align-items: center; justify-content: center;
font-size: 11px;
color: var(--brand-mid);
flex-shrink: 0;
}
.sidebar-footer {
padding: 12px 14px;
border-top: 1px solid var(--border);
display: flex;
flex-direction: column;
gap: 6px;
}
.footer-link {
display: flex;
align-items: center;
gap: 7px;
font-size: 12px;
color: var(--text-muted);
text-decoration: none;
padding: 4px 2px;
border-radius: 4px;
transition: color 0.12s;
}
.footer-link:hover { color: var(--brand-mid); }
.footer-link i { width: 14px; text-align: center; }
/* ─── Main Panel ─────────────────────────────────────────────────── */
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
background: var(--bg);
}
/* Top bar */
.topbar {
height: 56px;
border-bottom: 1px solid var(--border);
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
background: var(--bg);
}
.topbar-left { display: flex; align-items: center; gap: 12px; }
.toggle-sidebar-btn {
width: 32px; height: 32px;
border: 1px solid var(--border);
border-radius: var(--radius-xs);
background: transparent;
color: var(--text-secondary);
display: flex; align-items: center; justify-content: center;
cursor: pointer;
font-size: 14px;
transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.toggle-sidebar-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--border);
}
.model-pill {
display: flex;
align-items: center;
gap: 7px;
padding: 5px 11px;
border: 1px solid var(--border);
border-radius: 20px;
background: var(--bg);
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
cursor: default;
}
.model-dot {
width: 7px; height: 7px;
border-radius: 50%;
background: var(--brand-mid);
flex-shrink: 0;
}
.model-dot.busy {
animation: pulse-dot 1.2s ease-in-out infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.4; transform: scale(0.7); }
}
.topbar-right { display: flex; align-items: center; gap: 8px; }
.topbar-action-btn {
width: 32px; height: 32px;
border: 1px solid var(--border);
border-radius: var(--radius-xs);
background: transparent;
color: var(--text-secondary);
display: flex; align-items: center; justify-content: center;
cursor: pointer;
font-size: 13px;
transition: background 0.12s, color 0.12s;
text-decoration: none;
}
.topbar-action-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
/* ─── Chat area ──────────────────────────────────────────────────── */
.chat-scroll {
flex: 1;
overflow-y: auto;
scroll-behavior: smooth;
}
.chat-scroll::-webkit-scrollbar { width: 5px; }
.chat-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
.chat-inner {
max-width: 740px;
margin: 0 auto;
padding: 32px 24px 16px;
}
/* ─── Welcome screen ─────────────────────────────────────────────── */
.welcome {
padding: 48px 0 32px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 20px;
}
.welcome-logo {
width: 64px;
height: 64px;
border-radius: 16px;
object-fit: cover;
box-shadow: var(--shadow-md);
}
.welcome-title {
font-size: 26px;
font-weight: 700;
letter-spacing: -0.5px;
color: var(--text-primary);
}
.welcome-sub {
font-size: 14.5px;
color: var(--text-secondary);
max-width: 520px;
line-height: 1.65;
}
.welcome-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-top: 4px;
}
.chip {
display: flex;
align-items: center;
gap: 6px;
padding: 7px 13px;
border: 1px solid var(--border);
border-radius: 20px;
font-size: 13px;
color: var(--text-secondary);
background: var(--bg);
cursor: pointer;
transition: all 0.14s;
}
.chip:hover {
border-color: var(--brand-light);
color: var(--brand-mid);
background: var(--brand-pale);
}
.chip i { font-size: 12px; }
.welcome-cards {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
width: 100%;
margin-top: 8px;
}
.welcome-card {
padding: 16px;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg);
text-align: left;
transition: border-color 0.14s, box-shadow 0.14s;
}
.welcome-card:hover { border-color: var(--brand-light); box-shadow: var(--shadow-sm); }
.wc-icon {
width: 28px; height: 28px;
background: var(--brand-pale);
border-radius: 7px;
display: flex; align-items: center; justify-content: center;
font-size: 13px;
color: var(--brand-mid);
margin-bottom: 10px;
}
.wc-title { font-size: 13px; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
.wc-desc { font-size: 12px; color: var(--text-muted); line-height: 1.5; }
/* ─── Messages ───────────────────────────────────────────────────── */
.message-group {
margin-bottom: 24px;
}
/* User message */
.user-row {
display: flex;
justify-content: flex-end;
margin-bottom: 4px;
}
.user-bubble {
max-width: 72%;
background: var(--brand-pale);
border: 1px solid rgba(224,64,21,0.12);
border-radius: 18px 18px 4px 18px;
padding: 11px 16px;
font-size: 14.5px;
line-height: 1.6;
color: var(--text-primary);
word-wrap: break-word;
}
/* Assistant message */
.assistant-row {
display: flex;
align-items: flex-start;
gap: 12px;
}
.assistant-avatar {
width: 28px;
height: 28px;
border-radius: 7px;
object-fit: cover;
flex-shrink: 0;
margin-top: 2px;
box-shadow: var(--shadow-sm);
}
.assistant-content { flex: 1; min-width: 0; }
.assistant-name {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: 6px;
}
/* Thinking accordion */
.think-box {
border: 1px solid #F0C4B4;
border-radius: var(--radius-sm);
background: #FFF7F4;
margin-bottom: 12px;
overflow: hidden;
}
.think-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 9px 13px;
cursor: pointer;
user-select: none;
gap: 8px;
}
.think-header:hover { background: rgba(224,64,21,0.04); }
.think-header-left {
display: flex;
align-items: center;
gap: 7px;
font-size: 12.5px;
font-weight: 600;
color: var(--brand-mid);
}
.think-spinner {
font-size: 11px;
color: var(--brand-mid);
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.think-check { color: #22A757; font-size: 13px; }
.think-timer {
font-size: 11px;
color: var(--text-muted);
font-weight: 400;
}
.think-body {
padding: 10px 13px 12px;
border-top: 1px solid #F0C4B4;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
line-height: 1.65;
color: #7A5040;
white-space: pre-wrap;
max-height: 220px;
overflow-y: auto;
background: #FFFAF8;
}
/* Response body markdown */
.response-body {
font-size: 14.5px;
line-height: 1.7;
color: var(--text-primary);
}
.response-body p { margin-bottom: 10px; }
.response-body p:last-child { margin-bottom: 0; }
.response-body h1, .response-body h2, .response-body h3 {
font-weight: 600;
margin-top: 18px;
margin-bottom: 6px;
color: var(--text-primary);
}
.response-body h1 { font-size: 18px; }
.response-body h2 { font-size: 16px; }
.response-body h3 { font-size: 14.5px; }
.response-body ul, .response-body ol {
padding-left: 20px;
margin-bottom: 10px;
}
.response-body li { margin-bottom: 3px; }
.response-body strong { font-weight: 600; }
.response-body em { font-style: italic; }
.response-body code {
font-family: 'JetBrains Mono', monospace;
font-size: 12.5px;
background: #F4F3F0;
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
}
.response-body pre {
margin: 12px 0;
border-radius: var(--radius-sm);
overflow: hidden;
border: 1px solid var(--border);
box-shadow: var(--shadow-sm);
}
.response-body pre code {
background: none;
border: none;
padding: 0;
font-size: 13px;
border-radius: 0;
}
.response-body table {
width: 100%;
border-collapse: collapse;
margin: 12px 0;
font-size: 13.5px;
}
.response-body th, .response-body td {
border: 1px solid var(--border);
padding: 7px 11px;
text-align: left;
}
.response-body th {
background: var(--bg-sidebar);
font-weight: 600;
}
/* Code block header */
.code-block-wrap { position: relative; }
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 12px;
background: #F4F3F0;
border-bottom: 1px solid var(--border);
font-size: 11.5px;
color: var(--text-muted);
font-family: 'JetBrains Mono', monospace;
}
.code-copy-btn {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 11.5px;
display: flex; align-items: center; gap: 4px;
padding: 2px 6px;
border-radius: 4px;
transition: background 0.12s, color 0.12s;
}
.code-copy-btn:hover { background: var(--border); color: var(--text-primary); }
/* Typing dots */
.typing { display: flex; gap: 4px; padding: 4px 0; }
.typing-dot {
width: 7px; height: 7px;
background: var(--brand-light);
border-radius: 50%;
animation: bounce-dot 1.3s ease-in-out infinite both;
}
.typing-dot:nth-child(1) { animation-delay: -0.3s; }
.typing-dot:nth-child(2) { animation-delay: -0.15s; }
@keyframes bounce-dot {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
/* ─── Input area ─────────────────────────────────────────────────── */
.input-area {
padding: 16px 24px 24px;
flex-shrink: 0;
background: var(--bg);
}
.input-inner {
max-width: 740px;
margin: 0 auto;
}
.input-box {
border: 1.5px solid var(--border);
border-radius: 14px;
background: var(--bg-input);
display: flex;
flex-direction: column;
gap: 0;
transition: border-color 0.15s, box-shadow 0.15s;
box-shadow: var(--shadow-sm);
}
.input-box:focus-within {
border-color: var(--brand-light);
box-shadow: 0 0 0 3px rgba(224,64,21,0.08), var(--shadow-sm);
}
.input-textarea {
width: 100%;
background: transparent;
border: none;
outline: none;
font-family: inherit;
font-size: 14.5px;
color: var(--text-primary);
line-height: 1.55;
padding: 13px 16px 4px;
resize: none;
min-height: 50px;
max-height: 220px;
}
.input-textarea::placeholder { color: var(--text-muted); }
.input-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 10px 8px;
}
.input-left-actions { display: flex; gap: 4px; align-items: center; }
.tool-btn {
width: 30px; height: 30px;
border: none;
border-radius: 7px;
background: transparent;
color: var(--text-muted);
cursor: pointer;
display: flex; align-items: center; justify-content: center;
font-size: 13px;
transition: background 0.12s, color 0.12s;
}
.tool-btn:hover { background: var(--bg-hover); color: var(--text-secondary); }
.send-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 7px 15px;
background: var(--brand-mid);
color: #fff;
border: none;
border-radius: 9px;
font-family: inherit;
font-size: 13.5px;
font-weight: 600;
cursor: pointer;
transition: background 0.14s, transform 0.08s;
}
.send-btn:hover { background: var(--brand-deep); }
.send-btn:active { transform: scale(0.97); }
.send-btn.stop { background: #DC2626; }
.send-btn.stop:hover { background: #B91C1C; }
.input-hint {
text-align: center;
font-size: 11.5px;
color: var(--text-muted);
margin-top: 9px;
}
/* ─── Error state ─────────────────────────────────────────────────── */
.error-msg {
display: flex;
align-items: center;
gap: 7px;
font-size: 13.5px;
color: #C0310A;
background: #FEF2EE;
border: 1px solid #FCCDB8;
border-radius: 8px;
padding: 9px 13px;
}
/* ─── Responsive ─────────────────────────────────────────────────── */
@media (max-width: 640px) {
.sidebar { position: absolute; z-index: 20; height: 100%; box-shadow: var(--shadow-lg); }
.sidebar.collapsed { width: 0; }
.welcome-cards { grid-template-columns: 1fr; }
}
/* Scrollbar for prism code blocks */
.response-body pre::-webkit-scrollbar { height: 4px; }
.response-body pre::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
</style>
</head>
<body>
<div class="app">
<!-- ═══ SIDEBAR ═══════════════════════════════════════════════════════ -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
alt="VibeThinker" class="brand-logo">
<div>
<div class="brand-name">VibeThinker</div>
<span class="brand-tag">3B Β· WeiboAI</span>
</div>
</div>
<div class="sidebar-body">
<!-- Parameters -->
<div class="sidebar-section-label">Parameters</div>
<div class="param-group">
<div class="param-row">
<span class="param-label">Temperature</span>
<span class="param-val" id="val-temp">1.0</span>
</div>
<input type="range" id="param-temp" min="0" max="1.5" step="0.05" value="1.0">
</div>
<div class="param-group">
<div class="param-row">
<span class="param-label">Top-P</span>
<span class="param-val" id="val-topp">0.95</span>
</div>
<input type="range" id="param-topp" min="0" max="1" step="0.01" value="0.95">
</div>
<div class="param-group">
<div class="param-row">
<span class="param-label">Max Tokens</span>
<span class="param-val" id="val-tokens">4096</span>
</div>
<input type="range" id="param-tokens" min="256" max="32768" step="256" value="4096">
</div>
<div class="param-group">
<div class="param-label" style="margin-bottom:6px">System Prompt</div>
<textarea class="system-prompt-textarea" id="param-system">You are VibeThinker, a helpful and harmless AI assistant specialized in reasoning. You solve complex problems step-by-step to verify correctness.</textarea>
</div>
<!-- Presets -->
<div class="sidebar-section-label" style="margin-top:4px">Try a prompt</div>
<button class="preset-btn" onclick="loadPreset('card')">
<span class="preset-icon"><i class="fa-solid fa-dice"></i></span>
Probability β€” card deck
</button>
<button class="preset-btn" onclick="loadPreset('palindrome')">
<span class="preset-icon"><i class="fa-solid fa-code"></i></span>
Algorithm β€” longest palindrome
</button>
<button class="preset-btn" onclick="loadPreset('logic')">
<span class="preset-icon"><i class="fa-solid fa-box"></i></span>
Logic β€” labeled fruit boxes
</button>
</div><!-- /sidebar-body -->
<div class="sidebar-footer">
<a class="footer-link" href="https://huggingface.co/KyleHessling1/Qwopus3.6-27B-Fusion-GGUF" target="_blank">
<i class="fa-solid fa-cube"></i> Model Card
</a>
<a class="footer-link" href="https://arxiv.org/abs/2606.16140" target="_blank">
<i class="fa-regular fa-file-lines"></i> Research Paper
</a>
<span class="footer-link" style="cursor:default">
<i class="fa-solid fa-scale-balanced"></i> MIT License Β· WeiboAI 2026
</span>
</div>
</aside><!-- /sidebar -->
<!-- ═══ MAIN ══════════════════════════════════════════════════════════ -->
<div class="main">
<!-- Top bar -->
<div class="topbar">
<div class="topbar-left">
<button class="toggle-sidebar-btn" id="toggle-sidebar-btn" onclick="toggleSidebar()" title="Toggle sidebar">
<i class="fa-solid fa-sidebar"></i>
</button>
<div class="model-pill">
<div class="model-dot" id="status-dot"></div>
<span id="status-text">VibeThinker-3B</span>
</div>
</div>
<div class="topbar-right">
<button class="topbar-action-btn" title="Clear chat" onclick="clearChat()">
<i class="fa-regular fa-trash-can"></i>
</button>
<a class="topbar-action-btn" href="https://huggingface.co/WeiboAI/VibeThinker-3B" target="_blank" title="Model on HuggingFace">
πŸ€—
</a>
</div>
</div>
<!-- Chat scroll -->
<div class="chat-scroll" id="chat-scroll">
<div class="chat-inner" id="chat-inner">
<!-- Welcome -->
<div class="welcome" id="welcome-screen">
<img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
alt="VibeThinker" class="welcome-logo">
<h1 class="welcome-title">Qwopus-27b</h1>
<p class="welcome-sub">
Qwopus works!</p>
<div class="welcome-chips">
<div class="chip" onclick="loadPreset('palindrome')"><i class="fa-solid fa-code"></i> Coding</div>
<div class="chip" onclick="loadPreset('card')"><i class="fa-solid fa-calculator"></i> Math</div>
<div class="chip" onclick="loadPreset('logic')"><i class="fa-solid fa-puzzle-piece"></i> Logic</div>
</div>
<div class="welcome-cards">
<div class="welcome-card">
<div class="wc-icon"><i class="fa-solid fa-brain"></i></div>
<div class="wc-title">Chain-of-Thought</div>
<div class="wc-desc">Reasoning steps rendered live inside <code>&lt;think&gt;</code> blocks before the final answer.</div>
</div>
<div class="welcome-card">
<div class="wc-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="wc-title">Frontier Accuracy</div>
<div class="wc-desc">76.4% on IMO-AnswerBench β€” matching models 200Γ— larger.</div>
</div>
<div class="welcome-card">
<div class="wc-icon"><i class="fa-solid fa-bolt"></i></div>
<div class="wc-title">Token Streaming</div>
<div class="wc-desc">Gradio Server + ZeroGPU stream every token in real time.</div>
</div>
<div class="welcome-card">
<div class="wc-icon"><i class="fa-brands fa-python"></i></div>
<div class="wc-title">96.1% LeetCode</div>
<div class="wc-desc">123/128 first-attempt submissions on recent weekly contests.</div>
</div>
</div>
</div>
</div><!-- /chat-inner -->
</div><!-- /chat-scroll -->
<!-- Input -->
<div class="input-area">
<div class="input-inner">
<div class="input-box">
<textarea class="input-textarea" id="chat-input"
placeholder="Ask a math, logic, or coding question…"
rows="1"
oninput="autoResize(this)"></textarea>
<div class="input-toolbar">
<div class="input-left-actions">
<button class="tool-btn" title="Clear chat" onclick="clearChat()">
<i class="fa-regular fa-trash-can"></i>
</button>
<button class="tool-btn" title="Parameters" onclick="toggleSidebar()">
<i class="fa-solid fa-sliders"></i>
</button>
</div>
<button class="send-btn" id="send-btn" onclick="handleSubmit()">
<i class="fa-solid fa-paper-plane" id="send-icon"></i>
<span id="send-text">Send</span>
</button>
</div>
</div>
<div class="input-hint">Qwopus can make mistakes. Verify important results.</div>
</div>
</div>
</div><!-- /main -->
</div><!-- /app -->
<!-- ═══ SCRIPT ════════════════════════════════════════════════════════ -->
<script type="module">
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
let client = null;
let activeJob = null;
let isGenerating = false;
let currentMsgId = null;
let timerInterval = null;
let sidebarOpen = true;
// ── Init ──────────────────────────────────────────────────────────────
window.addEventListener("DOMContentLoaded", () => {
setupSliders();
initClient();
});
async function initClient() {
try {
setStatus("connecting", "Connecting…");
client = await Client.connect(window.location.origin);
setStatus("ready", "VibeThinker-3B");
} catch (e) {
console.error("Client connect failed:", e);
setStatus("error", "Connection error");
}
}
// ── Sliders ───────────────────────────────────────────────────────────
function setupSliders() {
[["param-temp","val-temp"],["param-topp","val-topp"],["param-tokens","val-tokens"]]
.forEach(([inputId, valId]) => {
const el = document.getElementById(inputId);
const vl = document.getElementById(valId);
el.addEventListener("input", () => { vl.textContent = el.value; });
});
}
// ── Sidebar toggle ────────────────────────────────────────────────────
window.toggleSidebar = function() {
const sidebar = document.getElementById("sidebar");
sidebarOpen = !sidebarOpen;
sidebar.classList.toggle("collapsed", !sidebarOpen);
};
// ── Presets ───────────────────────────────────────────────────────────
window.loadPreset = function(type) {
const ta = document.getElementById("chat-input");
const map = {
palindrome: "Write a Python function to solve the Longest Palindromic Substring problem, with full complexity analysis.",
card: "If a card is drawn from a standard deck, what is the probability it is a spade or a face card?",
logic: "Three boxes are labeled 'Apples', 'Oranges', and 'Mixed'. All labels are wrong. Draw one fruit from one box β€” how do you correctly label all three boxes?"
};
ta.value = map[type] || "";
autoResize(ta);
ta.focus();
if (window.innerWidth <= 640) document.getElementById("sidebar").classList.add("collapsed");
};
// ── Auto-resize textarea ──────────────────────────────────────────────
window.autoResize = function(el) {
el.style.height = "auto";
el.style.height = Math.min(el.scrollHeight, 220) + "px";
};
// ── Status indicator ──────────────────────────────────────────────────
function setStatus(state, label) {
const dot = document.getElementById("status-dot");
const txt = document.getElementById("status-text");
txt.textContent = label;
dot.className = "model-dot";
if (state === "connecting" || state === "generating") dot.classList.add("busy");
if (state === "error") dot.style.background = "#DC2626";
else dot.style.background = "";
}
// ── Submit ────────────────────────────────────────────────────────────
window.handleSubmit = async function() {
if (isGenerating) {
if (activeJob) { try { activeJob.cancel(); } catch(_) {} }
isGenerating = false;
clearInterval(timerInterval);
resetBtn();
setStatus("ready", "VibeThinker-3B");
return;
}
const ta = document.getElementById("chat-input");
const prompt = ta.value.trim();
if (!prompt) return;
if (!client) { alert("Server not connected. Please wait…"); initClient(); return; }
// Hide welcome
const ws = document.getElementById("welcome-screen");
if (ws) ws.remove();
ta.value = "";
autoResize(ta);
appendUserMsg(prompt);
currentMsgId = appendAssistantMsg();
setToStop();
isGenerating = true;
setStatus("generating", "Thinking…");
const temp = parseFloat(document.getElementById("param-temp").value);
const topp = parseFloat(document.getElementById("param-topp").value);
const tokens = parseInt(document.getElementById("param-tokens").value);
const system = document.getElementById("param-system").value;
// Start elapsed timer
let elapsed = 0;
const timerEl = document.getElementById("think-timer-" + currentMsgId);
if (timerEl) {
timerInterval = setInterval(() => {
elapsed += 0.1;
timerEl.textContent = `${elapsed.toFixed(1)}s`;
}, 100);
}
try {
activeJob = client.submit("/predict", {
message: prompt,
system_prompt: system,
temperature: temp,
top_p: topp,
max_tokens: tokens
});
(async () => {
try {
for await (const msg of activeJob) {
if (msg && msg.data) {
const text = msg.data[0];
if (typeof text === "string") updateStream(currentMsgId, text);
}
}
finalizeStream(currentMsgId);
} catch (err) {
console.error("Stream error:", err);
clearInterval(timerInterval);
showError(currentMsgId, "An error occurred during generation. Please try again.");
isGenerating = false;
resetBtn();
setStatus("ready", "VibeThinker-3B");
}
})();
} catch (err) {
console.error("Submit failed:", err);
clearInterval(timerInterval);
showError(currentMsgId, "Failed to submit request.");
isGenerating = false;
resetBtn();
setStatus("ready", "VibeThinker-3B");
}
};
// ── Button states ─────────────────────────────────────────────────────
function setToStop() {
document.getElementById("send-btn").className = "send-btn stop";
document.getElementById("send-icon").className = "fa-solid fa-stop";
document.getElementById("send-text").textContent = "Stop";
}
function resetBtn() {
document.getElementById("send-btn").className = "send-btn";
document.getElementById("send-icon").className = "fa-solid fa-paper-plane";
document.getElementById("send-text").textContent = "Send";
}
// ── Append messages ───────────────────────────────────────────────────
function appendUserMsg(text) {
const inner = document.getElementById("chat-inner");
const g = document.createElement("div");
g.className = "message-group";
g.innerHTML = `
<div class="user-row">
<div class="user-bubble">${escHtml(text)}</div>
</div>`;
inner.appendChild(g);
scrollBottom();
}
function appendAssistantMsg() {
const inner = document.getElementById("chat-inner");
const id = "msg-" + Date.now();
const g = document.createElement("div");
g.className = "message-group";
g.id = id;
g.innerHTML = `
<div class="assistant-row">
<img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
alt="VibeThinker" class="assistant-avatar">
<div class="assistant-content">
<div class="assistant-name">VibeThinker-3B</div>
<!-- think box (hidden until we see <think>) -->
<div class="think-box" id="think-box-${id}" style="display:none">
<div class="think-header" onclick="toggleThink('${id}')">
<div class="think-header-left">
<i class="fa-solid fa-spinner think-spinner" id="think-spin-${id}"></i>
<span id="think-label-${id}">Thinking…</span>
</div>
<span class="think-timer" id="think-timer-${id}">0.0s</span>
</div>
<div class="think-body" id="think-body-${id}" style="display:none"></div>
</div>
<!-- response body -->
<div class="response-body" id="resp-body-${id}">
<div class="typing">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
</div>
</div>
</div>`;
inner.appendChild(g);
scrollBottom();
return id;
}
// ── Stream update ─────────────────────────────────────────────────────
function updateStream(id, fullText) {
const { thinking, response, thinkDone } = parseStream(fullText);
const thinkBox = document.getElementById("think-box-" + id);
const thinkBody = document.getElementById("think-body-" + id);
const thinkSpin = document.getElementById("think-spin-" + id);
const thinkLbl = document.getElementById("think-label-" + id);
const timerEl = document.getElementById("think-timer-" + id);
const respBody = document.getElementById("resp-body-" + id);
if (thinking) {
thinkBox.style.display = "block";
thinkBody.textContent = thinking;
}
if (thinkDone) {
clearInterval(timerInterval);
thinkSpin.className = "fa-solid fa-check think-check";
thinkLbl.textContent = "Reasoning complete";
// Auto-collapse body once done
if (!thinkBody.dataset.collapsed) {
thinkBody.style.display = "none";
thinkBody.dataset.collapsed = "true";
}
}
if (response) {
respBody.innerHTML = marked.parse(response);
setupCodeHeaders(respBody);
Prism.highlightAllUnder(respBody);
} else if (!thinkDone) {
// Still thinking β€” keep typing dots if no response yet
}
scrollBottom();
debounceMath();
}
function finalizeStream(id) {
clearInterval(timerInterval);
isGenerating = false;
resetBtn();
setStatus("ready", "VibeThinker-3B");
if (window.MathJax?.typesetPromise) window.MathJax.typesetPromise();
}
function showError(id, msg) {
const respBody = document.getElementById("resp-body-" + id);
if (respBody) {
respBody.innerHTML = `<div class="error-msg"><i class="fa-solid fa-triangle-exclamation"></i>${escHtml(msg)}</div>`;
}
}
// ── Parse <think>…</think> ────────────────────────────────────────────
function parseStream(text) {
if (!text || typeof text !== "string") return { thinking: "", response: "", thinkDone: false };
if (!text.includes("<think>")) return { thinking: "", response: text, thinkDone: false };
const start = text.indexOf("<think>") + 7;
if (text.includes("</think>")) {
const end = text.indexOf("</think>");
return {
thinking: text.substring(start, end).trim(),
response: text.substring(end + 8).trim(),
thinkDone: true
};
}
return { thinking: text.substring(start), response: "", thinkDone: false };
}
// ── Think accordion toggle ─────────────────────────────────────────────
window.toggleThink = function(id) {
const body = document.getElementById("think-body-" + id);
body.style.display = body.style.display === "none" ? "block" : "none";
};
// ── Code block copy buttons ───────────────────────────────────────────
function setupCodeHeaders(container) {
container.querySelectorAll("pre").forEach(pre => {
if (pre.querySelector(".code-header")) return;
const code = pre.querySelector("code");
let lang = "";
(code?.className || "").split(" ").forEach(c => {
if (c.startsWith("language-")) lang = c.replace("language-", "");
});
const header = document.createElement("div");
header.className = "code-header";
header.innerHTML = `
<span>${lang.toUpperCase() || "CODE"}</span>
<button class="code-copy-btn" onclick="copyCode(this)">
<i class="fa-regular fa-copy"></i> Copy
</button>`;
pre.insertBefore(header, code);
});
}
window.copyCode = function(btn) {
const code = btn.closest("pre").querySelector("code")?.textContent || "";
navigator.clipboard.writeText(code).then(() => {
btn.innerHTML = '<i class="fa-solid fa-check" style="color:#22A757"></i> Copied';
setTimeout(() => { btn.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; }, 2000);
});
};
// ── Math debounce ─────────────────────────────────────────────────────
let mathTimer = null;
function debounceMath() {
clearTimeout(mathTimer);
mathTimer = setTimeout(() => {
if (window.MathJax?.typesetPromise) window.MathJax.typesetPromise();
}, 600);
}
// ── Clear chat ────────────────────────────────────────────────────────
window.clearChat = function() {
if (isGenerating) {
try { activeJob?.cancel(); } catch(_) {}
isGenerating = false;
clearInterval(timerInterval);
resetBtn();
setStatus("ready", "VibeThinker-3B");
}
const inner = document.getElementById("chat-inner");
inner.innerHTML = `
<div class="welcome" id="welcome-screen">
<img src="https://cdn-avatars.huggingface.co/v1/production/uploads/64d1faaa1ed6649d70d1fa2f/lZVm6Yuiif9cdr5KsnfZr.png"
alt="VibeThinker" class="welcome-logo">
<h1 class="welcome-title">VibeThinker-3B</h1>
<p class="welcome-sub">
A 3B-parameter reasoning model by WeiboAI, achieving frontier-level performance on mathematics, coding, and STEM β€” with transparent chain-of-thought.
</p>
<div class="welcome-chips">
<div class="chip" onclick="loadPreset('palindrome')"><i class="fa-solid fa-code"></i> Coding</div>
<div class="chip" onclick="loadPreset('card')"><i class="fa-solid fa-calculator"></i> Math</div>
<div class="chip" onclick="loadPreset('logic')"><i class="fa-solid fa-puzzle-piece"></i> Logic</div>
</div>
<div class="welcome-cards">
<div class="welcome-card">
<div class="wc-icon"><i class="fa-solid fa-brain"></i></div>
<div class="wc-title">Chain-of-Thought</div>
<div class="wc-desc">Reasoning rendered live in <code>&lt;think&gt;</code> blocks.</div>
</div>
<div class="welcome-card">
<div class="wc-icon"><i class="fa-solid fa-trophy"></i></div>
<div class="wc-title">Frontier Accuracy</div>
<div class="wc-desc">76.4% on IMO-AnswerBench β€” matching models 200Γ— larger.</div>
</div>
<div class="welcome-card">
<div class="wc-icon"><i class="fa-solid fa-bolt"></i></div>
<div class="wc-title">Token Streaming</div>
<div class="wc-desc">Gradio Server + ZeroGPU stream every token live.</div>
</div>
<div class="welcome-card">
<div class="wc-icon"><i class="fa-brands fa-python"></i></div>
<div class="wc-title">96.1% LeetCode</div>
<div class="wc-desc">123/128 first-attempt contest submissions.</div>
</div>
</div>
</div>`;
};
// ── Helpers ───────────────────────────────────────────────────────────
function scrollBottom() {
const sc = document.getElementById("chat-scroll");
sc.scrollTop = sc.scrollHeight;
}
function escHtml(s) {
return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")
.replace(/"/g,"&quot;").replace(/'/g,"&#39;");
}
// Enter to submit, Shift+Enter for newline
document.getElementById("chat-input").addEventListener("keydown", e => {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); }
});
</script>
</body>
</html>