| <!DOCTYPE html> |
| <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> |
| |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| |
| :root { |
| |
| --bg: #0a0c0b; |
| --surface: #111512; |
| --surface2: #171d19; |
| --surface3: #1e2621; |
| --border: #253028; |
| --border-hi: #3a4a3f; |
| |
| |
| --sig-prompt: #5fd0d6; |
| --sig-think: #ffb454; |
| --sig-gen: #8b7bff; |
| --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; |
| } |
| |
| |
| 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; |
| } |
| |
| |
| .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 { |
| 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 { |
| 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%; |
| } |
| |
| |
| .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; |
| } |
| |
| |
| .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 { |
| 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; |
| } |
| |
| |
| 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-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 { |
| grid-area: main; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden; |
| } |
| |
| |
| .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 { |
| 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; } |
| |
| |
| .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 { |
| 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 { |
| 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 { |
| white-space: pre-wrap; |
| word-break: break-word; |
| } |
| |
| |
| .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 { |
| 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 { |
| 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; |
| } |
| |
| |
| ::-webkit-scrollbar { width: 5px; height: 5px; } |
| ::-webkit-scrollbar-track { background: transparent; } |
| ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; } |
| |
| |
| button:focus-visible, input:focus-visible, textarea:focus-visible { |
| outline: 2px solid var(--sig-prompt); |
| outline-offset: 2px; |
| } |
| |
| |
| @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 !important; transition-duration: 0.001ms !important; } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="app"> |
|
|
| |
| <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> |
|
|
| |
| <aside class="sidebar"> |
|
|
| |
| <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"> |
| |
| <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)"/> |
| |
| <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"/> |
| |
| <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"/> |
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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> |
| |
| const MAX_CTX = 2048; |
| const CIRC = 2 * Math.PI * 55; |
| let isStreaming = false; |
| let thinkTokens = 0; |
| let genTokens = 0; |
| let promptTokens = 0; |
| let currentBubbleEl = null; |
| |
| |
| (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); |
| } |
| })(); |
| |
| |
| 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); |
| |
| |
| 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(); |
| } |
| } |
| |
| |
| function setPrompt(text) { |
| const ta = document.getElementById('prompt-input'); |
| ta.value = text; |
| autoResize(ta); |
| ta.focus(); |
| } |
| |
| |
| function updateRing(pTok, thinkTok, genTok) { |
| const total = Math.min(pTok + thinkTok + genTok, MAX_CTX); |
| |
| |
| const pArc = (pTok / MAX_CTX) * CIRC; |
| const thinkArc = (thinkTok / MAX_CTX) * CIRC; |
| const genArc = (genTok / MAX_CTX) * CIRC; |
| |
| |
| const rp = document.getElementById('ring-prompt'); |
| rp.style.strokeDashoffset = CIRC - pArc; |
| |
| |
| const rt = document.getElementById('ring-think'); |
| rt.style.strokeDasharray = `${thinkArc} ${CIRC - thinkArc}`; |
| rt.style.strokeDashoffset = CIRC - pArc; |
| |
| |
| const rg = document.getElementById('ring-gen'); |
| rg.style.strokeDasharray = `${genArc} ${CIRC - genArc}`; |
| rg.style.strokeDashoffset = CIRC - pArc - thinkArc; |
| |
| |
| document.getElementById('ring-used').textContent = total; |
| |
| |
| 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'; |
| |
| |
| 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; |
| } |
| |
| |
| |
| function parseResponse(raw) { |
| |
| let normalized = raw |
| .replace(/<\|think\|>/g, '<THINK>') |
| .replace(/<\|\/think\|>/g, '</THINK>') |
| .replace(/<think>/gi, '<THINK>') |
| .replace(/<\/think>/gi, '</THINK>'); |
| |
| |
| 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(); |
| } |
| |
| |
| const thinkTokEst = Math.round(thinkContent.split(/\s+/).length * 1.3); |
| |
| return { thinkContent, answerContent, thinkTokEst }; |
| } |
| |
| |
| function renderAnswer(text, isStreaming) { |
| |
| 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; |
| } |
| |
| |
| 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); |
| }); |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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'); |
| scrollBottom(); |
| return div; |
| } |
| |
| |
| function updateBubble(raw, streaming) { |
| const bubble = currentBubbleEl; |
| if (!bubble) return; |
| |
| const { thinkContent, answerContent, thinkTokEst } = parseResponse(raw); |
| let html = ''; |
| |
| |
| 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>`; |
| } |
| |
| |
| html += renderAnswer(answerContent, streaming); |
| bubble.innerHTML = html; |
| } |
| |
| function toggleThink(header) { |
| header.classList.toggle('collapsed'); |
| header.nextElementSibling.classList.toggle('collapsed'); |
| } |
| |
| |
| function scrollBottom() { |
| const ca = document.getElementById('chat-area'); |
| ca.scrollTop = ca.scrollHeight; |
| } |
| |
| |
| 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...'); |
| } |
| |
| |
| 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; |
| |
| |
| 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) { } |
| } |
| } |
| } 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'); |
| } |
| |
| |
| 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'); |
| } |
| |
| |
| updateRing(0, 0, 0); |
| document.getElementById('prompt-input').focus(); |
| </script> |
| </body> |
| </html> |