Download index.html from ml-intern-explorers/MiniCoder-1-Inference: direct link, hf CLI and curl.
- Browser
- Download file 45.4 kB
-
https://huggingface.co/spaces/ml-intern-explorers/MiniCoder-1-Inference/resolve/main/index.html
- Command line
-
hf download hf://spaces/ml-intern-explorers/MiniCoder-1-Inference/index.html
-
curl -L -o index.html https://huggingface.co/spaces/ml-intern-explorers/MiniCoder-1-Inference/resolve/main/index.html
45.4 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>MiniCoder-1 Instruct Β· Inference Console</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet"> | |
| <style> | |
| /* ββ RESET & BASE βββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| :root { | |
| /* base surfaces β dim instrument-panel graphite */ | |
| --bg: #0a0c0b; | |
| --surface: #111512; | |
| --surface2: #171d19; | |
| --surface3: #1e2621; | |
| --border: #253028; | |
| --border-hi: #3a4a3f; | |
| /* signal colors β three token streams, each its own channel */ | |
| --sig-prompt: #5fd0d6; /* cyan β prompt tokens */ | |
| --sig-think: #ffb454; /* amber β reasoning tokens */ | |
| --sig-gen: #8b7bff; /* violet β response tokens */ | |
| --sig-free: #2a352d; | |
| --ok: #7ee787; | |
| --danger: #ff6b6b; | |
| --text: #e8ece6; | |
| --text-dim: #93a099; | |
| --text-faint: #56635b; | |
| --radius: 10px; | |
| --radius-sm: 6px; | |
| --font-display: 'Space Grotesk', sans-serif; | |
| --font-mono: 'JetBrains Mono', 'Fira Code', monospace; | |
| --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| } | |
| html, body { | |
| height: 100%; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: var(--font-body); | |
| font-size: 14px; | |
| line-height: 1.6; | |
| } | |
| /* faint fixed grain / vignette β sets the instrument-panel mood without being loud */ | |
| body::before { | |
| content: ""; | |
| position: fixed; | |
| inset: 0; | |
| pointer-events: none; | |
| z-index: 999; | |
| background: | |
| radial-gradient(ellipse at 50% -10%, rgba(255,255,255,0.035), transparent 55%); | |
| mix-blend-mode: screen; | |
| } | |
| /* ββ LAYOUT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .app { | |
| display: grid; | |
| grid-template-rows: auto 1fr; | |
| grid-template-columns: 320px 1fr; | |
| grid-template-areas: | |
| "header header" | |
| "sidebar main"; | |
| height: 100vh; | |
| overflow: hidden; | |
| } | |
| /* ββ HEADER βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| header { | |
| grid-area: header; | |
| display: flex; | |
| align-items: center; | |
| gap: 18px; | |
| padding: 0 22px; | |
| height: 60px; | |
| background: var(--surface); | |
| border-bottom: 1px solid var(--border); | |
| z-index: 10; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .logo-icon { | |
| width: 34px; | |
| height: 34px; | |
| background: var(--surface3); | |
| border: 1px solid var(--border-hi); | |
| border-radius: var(--radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: var(--font-mono); | |
| font-size: 12px; | |
| font-weight: 700; | |
| color: var(--sig-think); | |
| letter-spacing: -0.5px; | |
| flex-shrink: 0; | |
| position: relative; | |
| } | |
| .logo-icon::after { | |
| content: ""; | |
| position: absolute; | |
| inset: -1px; | |
| border-radius: inherit; | |
| box-shadow: 0 0 12px 0 rgba(255,180,84,0.25); | |
| pointer-events: none; | |
| } | |
| .logo-text h1 { | |
| font-family: var(--font-display); | |
| font-size: 15px; | |
| font-weight: 700; | |
| letter-spacing: 0.2px; | |
| color: var(--text); | |
| } | |
| .logo-text p { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| color: var(--text-faint); | |
| letter-spacing: 0.5px; | |
| margin-top: 1px; | |
| } | |
| .header-divider { | |
| width: 1px; | |
| height: 26px; | |
| background: var(--border); | |
| margin: 0 2px; | |
| } | |
| .header-stats { | |
| margin-left: auto; | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| } | |
| .stat-pill { | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 5px 12px; | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| color: var(--text-dim); | |
| display: flex; | |
| align-items: center; | |
| gap: 7px; | |
| letter-spacing: 0.2px; | |
| } | |
| .stat-pill .dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: var(--ok); | |
| box-shadow: 0 0 6px var(--ok); | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.35; } | |
| } | |
| /* ββ SIDEBAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .sidebar { | |
| grid-area: sidebar; | |
| background: var(--surface); | |
| border-right: 1px solid var(--border); | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| } | |
| .sidebar-section { | |
| padding: 18px 16px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .sidebar-section:last-child { border-bottom: none; } | |
| .section-label { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| letter-spacing: 1.4px; | |
| text-transform: uppercase; | |
| color: var(--text-faint); | |
| margin-bottom: 14px; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .section-label::before { | |
| content: ""; | |
| width: 3px; | |
| height: 3px; | |
| background: var(--text-faint); | |
| border-radius: 50%; | |
| } | |
| /* ββ CONTEXT GAUGE (signature element) βββββββββββββββββββββββββββββ */ | |
| .ctx-ring-wrap { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 14px; | |
| padding: 4px 0 2px; | |
| } | |
| .ctx-ring-svg { | |
| position: relative; | |
| width: 152px; | |
| height: 152px; | |
| } | |
| .ctx-ring-svg svg { | |
| width: 152px; | |
| height: 152px; | |
| } | |
| /* tick ring, decorative β analog gauge marks */ | |
| .ctx-ticks line { | |
| stroke: var(--border-hi); | |
| stroke-width: 1; | |
| } | |
| .ctx-ticks line.major { | |
| stroke: var(--text-faint); | |
| stroke-width: 1.5; | |
| } | |
| .ctx-track { | |
| fill: none; | |
| stroke: var(--surface3); | |
| stroke-width: 8; | |
| } | |
| .ctx-prompt { | |
| fill: none; | |
| stroke: var(--sig-prompt); | |
| stroke-width: 8; | |
| stroke-linecap: round; | |
| transition: stroke-dashoffset 0.3s ease; | |
| filter: drop-shadow(0 0 3px rgba(95,208,214,0.5)); | |
| } | |
| .ctx-generated { | |
| fill: none; | |
| stroke: var(--sig-gen); | |
| stroke-width: 8; | |
| stroke-linecap: round; | |
| transition: stroke-dashoffset 0.3s ease; | |
| filter: drop-shadow(0 0 3px rgba(139,123,255,0.5)); | |
| } | |
| .ctx-think { | |
| fill: none; | |
| stroke: var(--sig-think); | |
| stroke-width: 8; | |
| stroke-linecap: round; | |
| transition: stroke-dashoffset 0.3s ease; | |
| filter: drop-shadow(0 0 3px rgba(255,180,84,0.5)); | |
| } | |
| .ctx-ring-center { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| text-align: center; | |
| pointer-events: none; | |
| } | |
| .ctx-ring-center .ctx-num { | |
| font-family: var(--font-mono); | |
| font-size: 24px; | |
| font-weight: 700; | |
| color: var(--text); | |
| line-height: 1; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .ctx-ring-center .ctx-denom { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| color: var(--text-faint); | |
| margin-top: 3px; | |
| letter-spacing: 0.4px; | |
| } | |
| .ctx-legend { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 7px; | |
| width: 100%; | |
| } | |
| .legend-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| color: var(--text-dim); | |
| } | |
| .legend-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 2px; | |
| flex-shrink: 0; | |
| } | |
| .legend-row span:last-child { | |
| margin-left: auto; | |
| font-variant-numeric: tabular-nums; | |
| color: var(--text-faint); | |
| font-size: 11px; | |
| } | |
| /* ββ THINK BUDGET BAR βββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .think-budget { | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-left: 2px solid var(--sig-think); | |
| border-radius: var(--radius-sm); | |
| padding: 11px 13px; | |
| } | |
| .think-budget-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 9px; | |
| } | |
| .think-budget-label { | |
| color: var(--sig-think); | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.3px; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| text-transform: uppercase; | |
| } | |
| .think-budget-val { | |
| color: var(--text-dim); | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .think-bar-track { | |
| height: 5px; | |
| background: var(--surface3); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| } | |
| .think-bar-fill { | |
| height: 100%; | |
| background: var(--sig-think); | |
| box-shadow: 0 0 6px rgba(255,180,84,0.6); | |
| border-radius: 3px; | |
| transition: width 0.3s ease; | |
| width: 0%; | |
| } | |
| .think-hint { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| color: var(--text-faint); | |
| margin-top: 7px; | |
| } | |
| /* ββ CONTROLS βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| label { | |
| display: block; | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| font-weight: 600; | |
| color: var(--text-faint); | |
| margin-bottom: 7px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.8px; | |
| } | |
| .slider-wrap { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 16px; | |
| } | |
| input[type=range] { | |
| flex: 1; | |
| -webkit-appearance: none; | |
| height: 3px; | |
| border-radius: 2px; | |
| background: var(--surface3); | |
| outline: none; | |
| cursor: pointer; | |
| } | |
| input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 13px; | |
| height: 13px; | |
| border-radius: 3px; | |
| background: var(--text); | |
| border: 2px solid var(--bg); | |
| box-shadow: 0 0 0 1px var(--border-hi); | |
| cursor: pointer; | |
| transition: box-shadow 0.15s; | |
| } | |
| input[type=range]::-webkit-slider-thumb:hover { | |
| box-shadow: 0 0 0 1px var(--sig-prompt), 0 0 8px rgba(95,208,214,0.5); | |
| } | |
| input[type=range]::-moz-range-thumb { | |
| width: 13px; | |
| height: 13px; | |
| border-radius: 3px; | |
| background: var(--text); | |
| border: 2px solid var(--bg); | |
| box-shadow: 0 0 0 1px var(--border-hi); | |
| cursor: pointer; | |
| } | |
| .slider-val { | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| color: var(--text); | |
| font-variant-numeric: tabular-nums; | |
| min-width: 34px; | |
| text-align: right; | |
| font-weight: 600; | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 4px; | |
| padding: 2px 5px; | |
| } | |
| /* ββ EXAMPLES βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .examples-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .example-btn { | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| padding: 9px 11px; | |
| color: var(--text-dim); | |
| font-family: var(--font-body); | |
| font-size: 12px; | |
| cursor: pointer; | |
| text-align: left; | |
| transition: all 0.15s; | |
| line-height: 1.4; | |
| display: flex; | |
| align-items: center; | |
| gap: 9px; | |
| } | |
| .example-btn:hover { | |
| border-color: var(--sig-prompt); | |
| color: var(--text); | |
| background: var(--surface3); | |
| transform: translateX(2px); | |
| } | |
| /* ββ MAIN βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| main { | |
| grid-area: main; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| /* ββ STATUS BAR βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .status-bar { | |
| display: flex; | |
| gap: 20px; | |
| padding: 9px 20px; | |
| background: var(--surface); | |
| border-bottom: 1px solid var(--border); | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| color: var(--text-faint); | |
| font-variant-numeric: tabular-nums; | |
| flex-wrap: wrap; | |
| } | |
| .status-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .status-item .status-icon { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: var(--text-faint); | |
| flex-shrink: 0; | |
| } | |
| .status-item .status-icon.live { | |
| background: var(--ok); | |
| box-shadow: 0 0 5px var(--ok); | |
| animation: pulse 1.4s infinite; | |
| } | |
| .status-item .val { | |
| color: var(--text-dim); | |
| font-weight: 600; | |
| } | |
| /* ββ CHAT AREA ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .chat-area { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 26px 28px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 18px; | |
| } | |
| .chat-area::-webkit-scrollbar { width: 6px; } | |
| .chat-area::-webkit-scrollbar-track { background: transparent; } | |
| .chat-area::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } | |
| /* ββ MESSAGES βββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .msg { | |
| max-width: 800px; | |
| width: 100%; | |
| } | |
| .msg-user { | |
| align-self: flex-end; | |
| } | |
| .msg-assistant { | |
| align-self: flex-start; | |
| } | |
| .msg-bubble { | |
| border-radius: var(--radius); | |
| padding: 14px 18px; | |
| line-height: 1.7; | |
| font-size: 13.5px; | |
| } | |
| .msg-user .msg-bubble { | |
| background: var(--surface2); | |
| border: 1px solid var(--border-hi); | |
| border-right: 2px solid var(--sig-prompt); | |
| color: var(--text); | |
| } | |
| .msg-assistant .msg-bubble { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-left: 2px solid var(--sig-gen); | |
| } | |
| .msg-label { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| color: var(--text-faint); | |
| margin-bottom: 7px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .msg-user .msg-label { text-align: right; } | |
| /* ββ THINK BLOCK ββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .think-block { | |
| background: rgba(255, 180, 84, 0.05); | |
| border: 1px solid rgba(255, 180, 84, 0.22); | |
| border-radius: var(--radius-sm); | |
| margin-bottom: 12px; | |
| overflow: hidden; | |
| } | |
| .think-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 9px 12px; | |
| background: rgba(255, 180, 84, 0.07); | |
| border-bottom: 1px solid rgba(255, 180, 84, 0.15); | |
| cursor: pointer; | |
| user-select: none; | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--sig-think); | |
| letter-spacing: 0.3px; | |
| text-transform: uppercase; | |
| } | |
| .think-header:hover { background: rgba(255, 180, 84, 0.12); } | |
| .think-chevron { | |
| margin-left: auto; | |
| transition: transform 0.2s; | |
| font-size: 10px; | |
| } | |
| .think-header.collapsed .think-chevron { | |
| transform: rotate(-90deg); | |
| } | |
| .think-body { | |
| padding: 13px 15px; | |
| font-size: 12.5px; | |
| color: var(--text-dim); | |
| white-space: pre-wrap; | |
| font-family: var(--font-mono); | |
| line-height: 1.65; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .think-body.collapsed { display: none; } | |
| /* ββ CODE BLOCK βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .code-block { | |
| background: #0d100e; | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| overflow: hidden; | |
| margin: 8px 0; | |
| } | |
| .code-header { | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 7px 12px; | |
| background: var(--surface3); | |
| border-bottom: 1px solid var(--border); | |
| font-family: var(--font-mono); | |
| font-size: 10.5px; | |
| color: var(--text-faint); | |
| letter-spacing: 0.3px; | |
| text-transform: uppercase; | |
| } | |
| .copy-btn { | |
| background: none; | |
| border: 1px solid var(--border-hi); | |
| border-radius: 4px; | |
| padding: 2px 9px; | |
| color: var(--text-dim); | |
| font-family: var(--font-mono); | |
| font-size: 10.5px; | |
| cursor: pointer; | |
| transition: all 0.15s; | |
| } | |
| .copy-btn:hover { border-color: var(--sig-prompt); color: var(--sig-prompt); } | |
| pre { | |
| padding: 15px; | |
| overflow-x: auto; | |
| font-family: var(--font-mono); | |
| font-size: 12.5px; | |
| line-height: 1.65; | |
| color: var(--text); | |
| margin: 0; | |
| } | |
| /* ββ ANSWER TEXT ββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .answer-text { | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| /* ββ STREAMING CURSOR βββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .cursor { | |
| display: inline-block; | |
| width: 2px; | |
| height: 1em; | |
| background: var(--sig-gen); | |
| margin-left: 2px; | |
| vertical-align: text-bottom; | |
| animation: blink 0.8s steps(1) infinite; | |
| } | |
| @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } } | |
| /* ββ INPUT AREA βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .input-area { | |
| padding: 16px 26px 22px; | |
| background: var(--surface); | |
| border-top: 1px solid var(--border); | |
| } | |
| .input-wrap { | |
| display: flex; | |
| gap: 10px; | |
| align-items: flex-end; | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| padding: 11px 12px; | |
| transition: border-color 0.2s, box-shadow 0.2s; | |
| } | |
| .input-wrap:focus-within { | |
| border-color: var(--sig-prompt); | |
| box-shadow: 0 0 0 3px rgba(95,208,214,0.08); | |
| } | |
| textarea { | |
| flex: 1; | |
| background: none; | |
| border: none; | |
| outline: none; | |
| color: var(--text); | |
| font-size: 13.5px; | |
| font-family: var(--font-body); | |
| line-height: 1.6; | |
| resize: none; | |
| min-height: 44px; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| } | |
| textarea::placeholder { color: var(--text-faint); } | |
| .send-btn { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: var(--radius-sm); | |
| border: 1px solid var(--border-hi); | |
| background: var(--surface3); | |
| color: var(--sig-prompt); | |
| font-size: 15px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| transition: all 0.15s; | |
| } | |
| .send-btn:hover:not(:disabled) { | |
| border-color: var(--sig-prompt); | |
| box-shadow: 0 0 10px rgba(95,208,214,0.25); | |
| } | |
| .send-btn:disabled { opacity: 0.4; cursor: not-allowed; } | |
| .stop-btn { | |
| width: 40px; | |
| height: 40px; | |
| border-radius: var(--radius-sm); | |
| border: 1px solid var(--danger); | |
| background: rgba(255, 107, 107, 0.08); | |
| color: var(--danger); | |
| font-size: 13px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| transition: all 0.15s; | |
| } | |
| .stop-btn:hover { | |
| background: rgba(255, 107, 107, 0.18); | |
| } | |
| .stop-btn.hidden { display: none; } | |
| .input-hint { | |
| font-family: var(--font-mono); | |
| font-size: 10px; | |
| color: var(--text-faint); | |
| margin-top: 8px; | |
| padding-left: 2px; | |
| } | |
| .input-hint kbd { | |
| font-family: var(--font-mono); | |
| background: var(--surface2); | |
| border: 1px solid var(--border); | |
| border-radius: 3px; | |
| padding: 1px 5px; | |
| color: var(--text-dim); | |
| } | |
| /* ββ EMPTY STATE ββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .empty-state { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 14px; | |
| pointer-events: none; | |
| } | |
| .empty-icon { | |
| width: 56px; | |
| height: 56px; | |
| border: 1px solid var(--border-hi); | |
| border-radius: var(--radius); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-family: var(--font-mono); | |
| font-size: 20px; | |
| font-weight: 700; | |
| color: var(--sig-think); | |
| background: var(--surface2); | |
| } | |
| .empty-state h2 { | |
| font-family: var(--font-display); | |
| font-size: 19px; | |
| font-weight: 700; | |
| color: var(--text); | |
| } | |
| .empty-state p { | |
| font-size: 13px; | |
| color: var(--text-faint); | |
| text-align: center; | |
| max-width: 300px; | |
| line-height: 1.6; | |
| } | |
| /* ββ SCROLLBAR ββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| ::-webkit-scrollbar { width: 5px; height: 5px; } | |
| ::-webkit-scrollbar-track { background: transparent; } | |
| ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } | |
| /* ββ FOCUS VISIBILITY βββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| button:focus-visible, input:focus-visible, textarea:focus-visible { | |
| outline: 2px solid var(--sig-prompt); | |
| outline-offset: 2px; | |
| } | |
| /* ββ RESPONSIVE βββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| @media (max-width: 700px) { | |
| .app { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: auto auto 1fr; | |
| grid-template-areas: "header" "sidebar" "main"; | |
| } | |
| .sidebar { | |
| max-height: 280px; | |
| border-right: none; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| * { animation-duration: 0.001ms ; transition-duration: 0.001ms ; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <!-- ββ HEADER ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <header> | |
| <div class="logo"> | |
| <div class="logo-icon">M1</div> | |
| <div class="logo-text"> | |
| <h1>MiniCoder-1 Instruct</h1> | |
| <p>213M Β· SFT Β· LOCAL INFERENCE</p> | |
| </div> | |
| </div> | |
| <div class="header-divider"></div> | |
| <div class="header-stats"> | |
| <div class="stat-pill"> | |
| <div class="dot"></div> | |
| <span id="hdr-status">Ready</span> | |
| </div> | |
| <div class="stat-pill"><span id="hdr-tps">β</span> tok/s</div> | |
| <div class="stat-pill">213M params</div> | |
| </div> | |
| </header> | |
| <!-- ββ SIDEBAR βββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <aside class="sidebar"> | |
| <!-- Context Gauge --> | |
| <div class="sidebar-section"> | |
| <div class="section-label">Context Window</div> | |
| <div class="ctx-ring-wrap"> | |
| <div class="ctx-ring-svg"> | |
| <svg viewBox="0 0 152 152"> | |
| <!-- decorative tick ring --> | |
| <g class="ctx-ticks" transform="translate(76,76)" id="ctx-ticks"></g> | |
| <circle class="ctx-track" cx="76" cy="76" r="55" transform="rotate(-90 76 76)"/> | |
| <!-- Prompt arc (cyan) --> | |
| <circle class="ctx-prompt" id="ring-prompt" cx="76" cy="76" r="55" | |
| transform="rotate(-90 76 76)" | |
| stroke-dasharray="345.6" stroke-dashoffset="345.6"/> | |
| <!-- Think arc (amber) --> | |
| <circle class="ctx-think" id="ring-think" cx="76" cy="76" r="55" | |
| transform="rotate(-90 76 76)" | |
| stroke-dasharray="345.6" stroke-dashoffset="345.6"/> | |
| <!-- Generated arc (violet) --> | |
| <circle class="ctx-generated" id="ring-gen" cx="76" cy="76" r="55" | |
| transform="rotate(-90 76 76)" | |
| stroke-dasharray="345.6" stroke-dashoffset="345.6"/> | |
| </svg> | |
| <div class="ctx-ring-center"> | |
| <div class="ctx-num" id="ring-used">0</div> | |
| <div class="ctx-denom">/ 2048 TOK</div> | |
| </div> | |
| </div> | |
| <div class="ctx-legend"> | |
| <div class="legend-row"> | |
| <div class="legend-dot" style="background:var(--sig-prompt)"></div> | |
| <span>Prompt</span> | |
| <span id="leg-prompt">0 tok</span> | |
| </div> | |
| <div class="legend-row"> | |
| <div class="legend-dot" style="background:var(--sig-think)"></div> | |
| <span>Thinking</span> | |
| <span id="leg-think">0 tok</span> | |
| </div> | |
| <div class="legend-row"> | |
| <div class="legend-dot" style="background:var(--sig-gen)"></div> | |
| <span>Response</span> | |
| <span id="leg-gen">0 tok</span> | |
| </div> | |
| <div class="legend-row"> | |
| <div class="legend-dot" style="background:var(--sig-free)"></div> | |
| <span>Free</span> | |
| <span id="leg-free">2048 tok</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Think Budget --> | |
| <div class="sidebar-section"> | |
| <div class="section-label">Thinking Budget</div> | |
| <div class="think-budget"> | |
| <div class="think-budget-header"> | |
| <div class="think-budget-label"> | |
| Think tokens | |
| </div> | |
| <div class="think-budget-val"> | |
| <span id="think-used">0</span> / <span id="think-max">512</span> | |
| </div> | |
| </div> | |
| <div class="think-bar-track"> | |
| <div class="think-bar-fill" id="think-bar"></div> | |
| </div> | |
| <div class="think-hint">Amber = model reasoning tokens</div> | |
| </div> | |
| </div> | |
| <!-- Parameters --> | |
| <div class="sidebar-section"> | |
| <div class="section-label">Parameters</div> | |
| <label>Max new tokens</label> | |
| <div class="slider-wrap"> | |
| <input type="range" id="sl-max" min="32" max="512" step="16" value="256"> | |
| <span class="slider-val" id="vl-max">256</span> | |
| </div> | |
| <label>Temperature</label> | |
| <div class="slider-wrap"> | |
| <input type="range" id="sl-temp" min="0" max="1.0" step="0.05" value="0.3"> | |
| <span class="slider-val" id="vl-temp">0.3</span> | |
| </div> | |
| <label>Top-P</label> | |
| <div class="slider-wrap"> | |
| <input type="range" id="sl-topp" min="0.5" max="1.0" step="0.05" value="0.95"> | |
| <span class="slider-val" id="vl-topp">0.95</span> | |
| </div> | |
| <label>Top-K</label> | |
| <div class="slider-wrap"> | |
| <input type="range" id="sl-topk" min="1" max="100" step="1" value="50"> | |
| <span class="slider-val" id="vl-topk">50</span> | |
| </div> | |
| </div> | |
| <!-- Examples --> | |
| <div class="sidebar-section"> | |
| <div class="section-label">Examples</div> | |
| <div class="examples-list"> | |
| <button class="example-btn" onclick="setPrompt('Write a Python function to compute fibonacci numbers recursively with memoization')"> | |
| <span>π</span> Fibonacci with memoization | |
| </button> | |
| <button class="example-btn" onclick="setPrompt('Implement a binary search algorithm in Python with detailed comments')"> | |
| <span>π</span> Binary search | |
| </button> | |
| <button class="example-btn" onclick="setPrompt('Write a quicksort implementation and explain the time complexity')"> | |
| <span>β‘</span> Quicksort + complexity | |
| </button> | |
| <button class="example-btn" onclick="setPrompt('Create a Python class for a linked list with insert, delete, and search methods')"> | |
| <span>π</span> Linked list class | |
| </button> | |
| <button class="example-btn" onclick="setPrompt('How do I reverse a string in Python? Show multiple methods')"> | |
| <span>π</span> Reverse a string | |
| </button> | |
| <button class="example-btn" onclick="setPrompt('Explain what a decorator is in Python and write an example')"> | |
| <span>π¨</span> Python decorators | |
| </button> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- ββ MAIN ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ --> | |
| <main> | |
| <!-- Status bar --> | |
| <div class="status-bar" id="status-bar"> | |
| <div class="status-item"><span class="status-icon" id="st-icon"></span><span class="val" id="st-state">Idle</span></div> | |
| <div class="status-item">TIME <span class="val" id="st-time">β</span></div> | |
| <div class="status-item">SPEED <span class="val" id="st-tps">β</span> tok/s</div> | |
| <div class="status-item">TOKENS <span class="val" id="st-tokens">β</span></div> | |
| <div class="status-item">THINK <span class="val" id="st-think">no thinking</span></div> | |
| </div> | |
| <!-- Chat area --> | |
| <div class="chat-area" id="chat-area"> | |
| <div class="empty-state" id="empty-state"> | |
| <div class="empty-icon">M1</div> | |
| <h2>MiniCoder-1 Instruct</h2> | |
| <p>Instruction-tuned code assistant. Ask for code, an explanation, or a debug pass.</p> | |
| </div> | |
| </div> | |
| <!-- Input --> | |
| <div class="input-area"> | |
| <div class="input-wrap"> | |
| <textarea | |
| id="prompt-input" | |
| placeholder="Ask me to write code, explain concepts, debug..." | |
| rows="1" | |
| onkeydown="handleKey(event)" | |
| oninput="autoResize(this)" | |
| ></textarea> | |
| <button class="stop-btn hidden" id="stop-btn" onclick="stopGen()" title="Stop generation">β </button> | |
| <button class="send-btn" id="send-btn" onclick="sendMessage()" title="Send (Ctrl+Enter)">βΆ</button> | |
| </div> | |
| <div class="input-hint"><kbd>Ctrl</kbd> + <kbd>Enter</kbd> to send</div> | |
| </div> | |
| </main> | |
| </div> | |
| <script> | |
| // ββ STATE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| const MAX_CTX = 2048; | |
| const CIRC = 2 * Math.PI * 55; // circumference for r=55 | |
| let isStreaming = false; | |
| let thinkTokens = 0; | |
| let genTokens = 0; | |
| let promptTokens = 0; | |
| let currentBubbleEl = null; // tracks the live assistant bubble (ids repeat across turns, so we can't rely on getElementById) | |
| // ββ DECORATIVE TICK RING (signature gauge marks) ββββββββββββββββββββββββββ | |
| (function drawTicks() { | |
| const g = document.getElementById('ctx-ticks'); | |
| const rOuter = 68, rInnerMinor = 62, rInnerMajor = 59; | |
| const count = 40; | |
| let svgNS = "http://www.w3.org/2000/svg"; | |
| for (let i = 0; i < count; i++) { | |
| const angle = (i / count) * 2 * Math.PI - Math.PI / 2; | |
| const major = i % 5 === 0; | |
| const rInner = major ? rInnerMajor : rInnerMinor; | |
| const x1 = Math.cos(angle) * rOuter, y1 = Math.sin(angle) * rOuter; | |
| const x2 = Math.cos(angle) * rInner, y2 = Math.sin(angle) * rInner; | |
| const line = document.createElementNS(svgNS, 'line'); | |
| line.setAttribute('x1', x1.toFixed(2)); | |
| line.setAttribute('y1', y1.toFixed(2)); | |
| line.setAttribute('x2', x2.toFixed(2)); | |
| line.setAttribute('y2', y2.toFixed(2)); | |
| if (major) line.setAttribute('class', 'major'); | |
| g.appendChild(line); | |
| } | |
| })(); | |
| // ββ SLIDERS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function linkSlider(id, valId, decimals = 0) { | |
| const s = document.getElementById(id); | |
| const v = document.getElementById(valId); | |
| s.addEventListener('input', () => { | |
| v.textContent = parseFloat(s.value).toFixed(decimals); | |
| }); | |
| } | |
| linkSlider('sl-max', 'vl-max', 0); | |
| linkSlider('sl-temp', 'vl-temp', 2); | |
| linkSlider('sl-topp', 'vl-topp', 2); | |
| linkSlider('sl-topk', 'vl-topk', 0); | |
| // ββ TEXTAREA AUTO-RESIZE ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function autoResize(el) { | |
| el.style.height = 'auto'; | |
| el.style.height = Math.min(el.scrollHeight, 200) + 'px'; | |
| } | |
| function handleKey(e) { | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| } | |
| // ββ SET EXAMPLE PROMPT ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function setPrompt(text) { | |
| const ta = document.getElementById('prompt-input'); | |
| ta.value = text; | |
| autoResize(ta); | |
| ta.focus(); | |
| } | |
| // ββ CONTEXT RING UPDATE βββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function updateRing(pTok, thinkTok, genTok) { | |
| const total = Math.min(pTok + thinkTok + genTok, MAX_CTX); | |
| // Arc lengths | |
| const pArc = (pTok / MAX_CTX) * CIRC; | |
| const thinkArc = (thinkTok / MAX_CTX) * CIRC; | |
| const genArc = (genTok / MAX_CTX) * CIRC; | |
| // Prompt arc (starts at 0) | |
| const rp = document.getElementById('ring-prompt'); | |
| rp.style.strokeDashoffset = CIRC - pArc; | |
| // Think arc (starts after prompt) | |
| const rt = document.getElementById('ring-think'); | |
| rt.style.strokeDasharray = `${thinkArc} ${CIRC - thinkArc}`; | |
| rt.style.strokeDashoffset = CIRC - pArc; | |
| // Generated arc (starts after prompt + think) | |
| const rg = document.getElementById('ring-gen'); | |
| rg.style.strokeDasharray = `${genArc} ${CIRC - genArc}`; | |
| rg.style.strokeDashoffset = CIRC - pArc - thinkArc; | |
| // Center number | |
| document.getElementById('ring-used').textContent = total; | |
| // Legend | |
| document.getElementById('leg-prompt').textContent = pTok + ' tok'; | |
| document.getElementById('leg-think').textContent = thinkTok + ' tok'; | |
| document.getElementById('leg-gen').textContent = genTok + ' tok'; | |
| document.getElementById('leg-free').textContent = Math.max(0, MAX_CTX - total) + ' tok'; | |
| // Think budget bar | |
| const maxThink = parseInt(document.getElementById('sl-max').value); | |
| const thinkPct = Math.min(100, (thinkTok / maxThink) * 100); | |
| document.getElementById('think-bar').style.width = thinkPct + '%'; | |
| document.getElementById('think-used').textContent = thinkTok; | |
| document.getElementById('think-max').textContent = maxThink; | |
| } | |
| // ββ PARSE RESPONSE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Handles both <think> and <|think|> variants | |
| function parseResponse(raw) { | |
| // Normalize both think tag variants to same format | |
| let normalized = raw | |
| .replace(/<\|think\|>/g, '<THINK>') | |
| .replace(/<\|\/think\|>/g, '</THINK>') | |
| .replace(/<think>/gi, '<THINK>') | |
| .replace(/<\/think>/gi, '</THINK>'); | |
| // Clean up ChatML tokens | |
| normalized = normalized | |
| .replace(/<\|im_start\|>\w+\n?/g, '') | |
| .replace(/<\|im_end\|>/g, '') | |
| .replace(/<\|endoftext\|>/g, ''); | |
| const thinkMatch = normalized.match(/<THINK>([\s\S]*?)(<\/THINK>|$)/); | |
| let thinkContent = ''; | |
| let answerContent = normalized; | |
| if (thinkMatch) { | |
| thinkContent = thinkMatch[1].trim(); | |
| answerContent = normalized.replace(/<THINK>[\s\S]*?(<\/THINK>|$)/, '').trim(); | |
| } | |
| // Rough token estimate for think content | |
| const thinkTokEst = Math.round(thinkContent.split(/\s+/).length * 1.3); | |
| return { thinkContent, answerContent, thinkTokEst }; | |
| } | |
| // ββ RENDER ANSWER βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function renderAnswer(text, isStreaming) { | |
| // Split by code blocks | |
| const parts = text.split(/(```[\s\S]*?```|```[\s\S]*$)/g); | |
| let html = ''; | |
| for (const part of parts) { | |
| if (part.startsWith('```')) { | |
| const lines = part.replace(/^```/, '').split('\n'); | |
| const lang = lines[0].trim() || 'code'; | |
| const code = lines.slice(1).join('\n').replace(/```$/, '').trim(); | |
| const escaped = code.replace(/</g,'<').replace(/>/g,'>'); | |
| html += ` | |
| <div class="code-block"> | |
| <div class="code-header"> | |
| <span>${lang}</span> | |
| <button class="copy-btn" onclick="copyCode(this)">Copy</button> | |
| </div> | |
| <pre>${escaped}</pre> | |
| </div>`; | |
| } else if (part.trim()) { | |
| const escaped = part.replace(/</g,'<').replace(/>/g,'>'); | |
| html += `<div class="answer-text">${escaped}</div>`; | |
| } | |
| } | |
| if (isStreaming) html += '<span class="cursor"></span>'; | |
| return html; | |
| } | |
| // ββ COPY CODE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function copyCode(btn) { | |
| const code = btn.closest('.code-block').querySelector('pre').textContent; | |
| navigator.clipboard.writeText(code).then(() => { | |
| btn.textContent = 'Copied'; | |
| setTimeout(() => btn.textContent = 'Copy', 1500); | |
| }); | |
| } | |
| // ββ ADD USER MESSAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function addUserMsg(text) { | |
| document.getElementById('empty-state')?.remove(); | |
| const div = document.createElement('div'); | |
| div.className = 'msg msg-user'; | |
| div.innerHTML = ` | |
| <div class="msg-label">You</div> | |
| <div class="msg-bubble">${text.replace(/</g,'<').replace(/>/g,'>')}</div> | |
| `; | |
| document.getElementById('chat-area').appendChild(div); | |
| return div; | |
| } | |
| // ββ ADD ASSISTANT MESSAGE βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function addAssistantMsg() { | |
| const div = document.createElement('div'); | |
| div.className = 'msg msg-assistant'; | |
| div.innerHTML = ` | |
| <div class="msg-label">MiniCoder-1</div> | |
| <div class="msg-bubble"> | |
| <span class="cursor"></span> | |
| </div> | |
| `; | |
| document.getElementById('chat-area').appendChild(div); | |
| currentBubbleEl = div.querySelector('.msg-bubble'); // point future updates at THIS turn's bubble | |
| scrollBottom(); | |
| return div; | |
| } | |
| // ββ UPDATE ASSISTANT BUBBLE βββββββββββββββββββββββββββββββββββββββββββββββ | |
| function updateBubble(raw, streaming) { | |
| const bubble = currentBubbleEl; | |
| if (!bubble) return; | |
| const { thinkContent, answerContent, thinkTokEst } = parseResponse(raw); | |
| let html = ''; | |
| // Think block (collapsible) | |
| if (thinkContent) { | |
| html += ` | |
| <div class="think-block"> | |
| <div class="think-header" onclick="toggleThink(this)"> | |
| <span>Reasoning${streaming && !raw.includes('</THINK>') && !raw.includes('<|/think|>') ? '...' : ''}</span> | |
| <span>~${thinkTokEst} tok</span> | |
| <span class="think-chevron">βΌ</span> | |
| </div> | |
| <div class="think-body">${thinkContent.replace(/</g,'<').replace(/>/g,'>')}</div> | |
| </div>`; | |
| } | |
| // Answer | |
| html += renderAnswer(answerContent, streaming); | |
| bubble.innerHTML = html; | |
| } | |
| function toggleThink(header) { | |
| header.classList.toggle('collapsed'); | |
| header.nextElementSibling.classList.toggle('collapsed'); | |
| } | |
| // ββ SCROLL TO BOTTOM ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function scrollBottom() { | |
| const ca = document.getElementById('chat-area'); | |
| ca.scrollTop = ca.scrollHeight; | |
| } | |
| // ββ STATUS UPDATE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| function updateStatus(state, tps, elapsed, tokens, thinkTok) { | |
| document.getElementById('st-state').textContent = state; | |
| document.getElementById('st-time').textContent = elapsed ? elapsed + 's' : 'β'; | |
| document.getElementById('st-tps').textContent = tps ? tps : 'β'; | |
| document.getElementById('st-tokens').textContent = tokens ? tokens : 'β'; | |
| document.getElementById('st-think').textContent = thinkTok > 0 ? `${thinkTok} tok` : 'no thinking'; | |
| document.getElementById('hdr-status').textContent = state; | |
| document.getElementById('hdr-tps').textContent = tps || 'β'; | |
| const icon = document.getElementById('st-icon'); | |
| icon.classList.toggle('live', state === 'Generating...'); | |
| } | |
| // ββ SEND MESSAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function sendMessage() { | |
| if (isStreaming) return; | |
| const input = document.getElementById('prompt-input'); | |
| const prompt = input.value.trim(); | |
| if (!prompt) return; | |
| isStreaming = true; | |
| input.value = ''; | |
| autoResize(input); | |
| document.getElementById('send-btn').disabled = true; | |
| document.getElementById('stop-btn').classList.remove('hidden'); | |
| addUserMsg(prompt); | |
| addAssistantMsg(); | |
| const params = { | |
| prompt, | |
| max_new: parseInt(document.getElementById('sl-max').value), | |
| temperature: parseFloat(document.getElementById('sl-temp').value), | |
| top_p: parseFloat(document.getElementById('sl-topp').value), | |
| top_k: parseInt(document.getElementById('sl-topk').value), | |
| }; | |
| updateStatus('Generating...', null, null, null, 0); | |
| thinkTokens = 0; genTokens = 0; promptTokens = 0; | |
| try { | |
| const resp = await fetch('/generate', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(params), | |
| }); | |
| const reader = resp.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let buffer = ''; | |
| let rawAccum = ''; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| buffer += decoder.decode(value, { stream: true }); | |
| const lines = buffer.split('\n'); | |
| buffer = lines.pop(); | |
| for (const line of lines) { | |
| if (!line.startsWith('data: ')) continue; | |
| try { | |
| const data = JSON.parse(line.slice(6)); | |
| if (data.type === 'ctx') { | |
| promptTokens = data.prompt_tokens; | |
| updateRing(promptTokens, 0, 0); | |
| } | |
| else if (data.type === 'token') { | |
| rawAccum = data.raw; | |
| // Estimate think vs gen tokens from raw text | |
| const { thinkTokEst } = parseResponse(rawAccum); | |
| thinkTokens = thinkTokEst; | |
| genTokens = Math.max(0, data.step - thinkTokEst); | |
| updateRing(promptTokens, thinkTokens, genTokens); | |
| updateBubble(rawAccum, true); | |
| updateStatus('Generating...', data.tps, data.elapsed, data.step, thinkTokens); | |
| scrollBottom(); | |
| } | |
| else if (data.type === 'done') { | |
| updateBubble(rawAccum, false); | |
| updateStatus('Done', data.tps, data.elapsed, data.tokens, thinkTokens); | |
| updateRing(promptTokens, thinkTokens, genTokens); | |
| scrollBottom(); | |
| } | |
| } catch (e) { /* skip malformed */ } | |
| } | |
| } | |
| } catch (e) { | |
| const bubble = currentBubbleEl; | |
| if (bubble) bubble.innerHTML = `<span style="color:var(--danger)">Error: ${e.message}</span>`; | |
| updateStatus('Error', null, null, null, 0); | |
| } | |
| isStreaming = false; | |
| document.getElementById('send-btn').disabled = false; | |
| document.getElementById('stop-btn').classList.add('hidden'); | |
| } | |
| // ββ STOP ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| async function stopGen() { | |
| await fetch('/stop', { method: 'POST' }); | |
| updateStatus('Stopped', null, null, null, thinkTokens); | |
| const bubble = currentBubbleEl; | |
| if (bubble) { | |
| bubble.querySelectorAll('.cursor').forEach(c => c.remove()); | |
| } | |
| isStreaming = false; | |
| document.getElementById('send-btn').disabled = false; | |
| document.getElementById('stop-btn').classList.add('hidden'); | |
| } | |
| // ββ INIT ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| updateRing(0, 0, 0); | |
| document.getElementById('prompt-input').focus(); | |
| </script> | |
| </body> | |
| </html> |