| <!DOCTYPE html> |
| <html lang="en"> |
|
|
| <head> |
| <meta charset="UTF-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| <title>NanoMind · 152M</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link |
| href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&family=DM+Sans:wght@300;500;700;900&display=swap" |
| rel="stylesheet"> |
| <style> |
| *, |
| *::before, |
| *::after { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0 |
| } |
| |
| :root { |
| --bg: rgba(6, 8, 15, 0.65); |
| --bg1: rgba(12, 15, 26, 0.65); |
| --bg2: rgba(18, 22, 39, 0.65); |
| --bg3: rgba(26, 32, 53, 0.65); |
| --bg4: rgba(34, 40, 64, 0.65); |
| --acc: #7c6bff; |
| --acc2: #9d8fff; |
| --acc3: #5a48e0; |
| --dim: rgba(124, 107, 255, .08); |
| --dimb: rgba(124, 107, 255, .18); |
| --cyan: #3ddcff; |
| --green: #3dffb4; |
| --red: #ff5c6e; |
| --warn: #ffc260; |
| --t0: #f2f0ff; |
| --t1: #c0bde8; |
| --t2: #7a78a0; |
| --t3: #3a384d; |
| --mono: 'IBM Plex Mono', monospace; |
| --sans: 'DM Sans', sans-serif; |
| --r: 8px; |
| --sb: 280px; |
| } |
| |
| html, |
| body { |
| height: 100%; |
| background: #06080f; |
| color: var(--t0); |
| font-family: var(--mono); |
| font-size: 13px; |
| overflow: hidden |
| } |
| |
| .app { |
| display: flex; |
| flex-direction: column; |
| height: 100vh |
| } |
| |
| .top-bar { |
| height: 48px; |
| background: var(--bg1); |
| border-bottom: 1px solid var(--bg3); |
| display: flex; |
| align-items: center; |
| padding: 0 18px; |
| gap: 14px; |
| flex-shrink: 0; |
| z-index: 10; |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| } |
| |
| .top-logo { |
| display: flex; |
| align-items: center; |
| gap: 10px |
| } |
| |
| .top-logo-icon { |
| width: 30px; |
| height: 30px; |
| border-radius: 8px; |
| background: linear-gradient(135deg, var(--acc3), var(--acc2)); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-family: var(--sans); |
| font-weight: 900; |
| font-size: 12px; |
| color: #fff; |
| box-shadow: 0 0 16px rgba(124, 107, 255, .35) |
| } |
| |
| .top-logo-text { |
| font-family: var(--sans); |
| font-size: 16px; |
| font-weight: 800; |
| color: var(--t0); |
| letter-spacing: -.03em |
| } |
| |
| .top-nav { |
| display: flex; |
| align-items: center; |
| gap: 4px; |
| margin-left: auto |
| } |
| |
| .nav-item { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| padding: 6px 12px; |
| border-radius: 6px; |
| font-size: 12px; |
| font-family: var(--sans); |
| color: var(--t2); |
| cursor: pointer; |
| transition: .15s; |
| text-decoration: none |
| } |
| |
| .nav-item:hover { |
| color: var(--t0); |
| background: var(--bg3) |
| } |
| |
| .nav-item.active { |
| color: var(--acc2); |
| background: var(--dim) |
| } |
| |
| .nav-item svg { |
| width: 14px; |
| height: 14px; |
| fill: currentColor; |
| opacity: .7 |
| } |
| |
| .user-avatar { |
| width: 28px; |
| height: 28px; |
| border-radius: 50%; |
| background: linear-gradient(135deg, var(--acc3), var(--cyan)); |
| margin-left: 8px; |
| cursor: pointer |
| } |
| |
| .main-area { |
| display: flex; |
| flex: 1; |
| overflow: hidden |
| } |
| |
| .app::before { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E"); |
| pointer-events: none; |
| z-index: 1; |
| opacity: .4 |
| } |
| |
| .app::after { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| |
| |
| background-image: url('https://huggingface.co/spaces/NOT-OMEGA/NanoMind/resolve/main/a_much_more_aggressive_menacing_version_of_the_nano_mind_logo._the_white_tech.png'); |
| |
| background-size: contain; |
| background-position: center; |
| background-repeat: no-repeat; |
| opacity: 0.75; |
| pointer-events: none; |
| z-index: 0; |
| } |
| |
| .app>* { |
| position: relative; |
| z-index: 1 |
| } |
| |
| .sb { |
| width: var(--sb); |
| min-width: var(--sb); |
| background: var(--bg1); |
| border-right: 1px solid var(--bg3); |
| display: flex; |
| flex-direction: column; |
| overflow: hidden |
| } |
| |
| .sb-logo { |
| padding: 16px; |
| background: var(--bg); |
| border-bottom: 1px solid var(--bg3); |
| flex-shrink: 0 |
| } |
| |
| .logo-row { |
| display: flex; |
| align-items: center; |
| gap: 10px |
| } |
| |
| .logo-icon { |
| width: 34px; |
| height: 34px; |
| border-radius: 9px; |
| background: linear-gradient(135deg, var(--acc3), var(--acc2)); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| box-shadow: 0 0 20px rgba(124, 107, 255, .35) |
| } |
| |
| .logo-icon svg { |
| fill: #fff |
| } |
| |
| .logo-text h2 { |
| font-family: var(--sans); |
| font-size: 18px; |
| font-weight: 900; |
| letter-spacing: -.04em; |
| color: var(--t0) |
| } |
| |
| .logo-text h2 em { |
| color: var(--acc2); |
| font-style: normal |
| } |
| |
| .logo-text p { |
| font-size: 9px; |
| color: var(--t2); |
| margin-top: 2px; |
| letter-spacing: .1em; |
| text-transform: uppercase |
| } |
| |
| .sb-tabs { |
| display: flex; |
| border-bottom: 1px solid var(--bg3); |
| flex-shrink: 0; |
| background: var(--bg1) |
| } |
| |
| .sb-tab { |
| flex: 1; |
| padding: 8px 4px; |
| text-align: center; |
| font-size: 9px; |
| font-weight: 700; |
| color: var(--t2); |
| text-transform: uppercase; |
| letter-spacing: .08em; |
| cursor: pointer; |
| border: none; |
| background: none; |
| font-family: var(--mono); |
| border-bottom: 2px solid transparent; |
| transition: .15s |
| } |
| |
| .sb-tab.on { |
| color: var(--acc2); |
| border-bottom-color: var(--acc); |
| background: var(--dim) |
| } |
| |
| .sb-body { |
| flex: 1; |
| overflow-y: auto; |
| padding: 10px; |
| scrollbar-width: thin; |
| scrollbar-color: var(--bg4) transparent |
| } |
| |
| .card { |
| background: var(--bg2); |
| border: 1px solid var(--bg3); |
| border-radius: var(--r); |
| padding: 11px 13px; |
| margin-bottom: 8px |
| } |
| |
| .ct { |
| font-size: 9px; |
| font-weight: 700; |
| color: var(--t2); |
| text-transform: uppercase; |
| letter-spacing: .1em; |
| margin-bottom: 8px; |
| display: flex; |
| align-items: center; |
| gap: 6px |
| } |
| |
| .sr { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 3px 0 |
| } |
| |
| .sl { |
| color: var(--t1); |
| font-size: 11px |
| } |
| |
| .sv { |
| font-size: 12px; |
| font-weight: 700; |
| font-variant-numeric: tabular-nums; |
| color: var(--t0) |
| } |
| |
| .g { |
| color: var(--green) |
| } |
| |
| .c { |
| color: var(--cyan) |
| } |
| |
| .y { |
| color: var(--warn) |
| } |
| |
| .r { |
| color: var(--red) |
| } |
| |
| .p { |
| color: var(--acc2) |
| } |
| |
| .dot { |
| display: inline-block; |
| width: 6px; |
| height: 6px; |
| border-radius: 50%; |
| flex-shrink: 0 |
| } |
| |
| .dot.off { |
| background: var(--t3) |
| } |
| |
| .dot.on { |
| background: var(--green); |
| animation: pulse 1.4s infinite |
| } |
| |
| @keyframes pulse { |
| |
| 0%, |
| 100% { |
| opacity: 1; |
| transform: scale(1) |
| } |
| |
| 50% { |
| opacity: .3; |
| transform: scale(.7) |
| } |
| } |
| |
| canvas#spark { |
| width: 100%; |
| height: 44px; |
| display: block; |
| border-radius: var(--r); |
| background: var(--bg) |
| } |
| |
| .ta { |
| width: 100%; |
| background: var(--bg); |
| border: 1px solid var(--bg3); |
| border-radius: var(--r); |
| color: var(--t0); |
| font-size: 11px; |
| padding: 8px 10px; |
| resize: vertical; |
| min-height: 56px; |
| font-family: var(--mono); |
| line-height: 1.6 |
| } |
| |
| .ta:focus { |
| outline: none; |
| border-color: var(--acc) |
| } |
| |
| .pg { |
| display: flex; |
| flex-direction: column; |
| gap: 4px |
| } |
| |
| .pg label { |
| font-size: 9px; |
| color: var(--t2); |
| text-transform: uppercase; |
| letter-spacing: .08em; |
| display: flex; |
| justify-content: space-between |
| } |
| |
| .pg label span { |
| color: var(--acc2); |
| font-weight: 700 |
| } |
| |
| .pg input[type=range] { |
| width: 100%; |
| -webkit-appearance: none; |
| height: 2px; |
| background: var(--bg4); |
| border-radius: 1px; |
| outline: none |
| } |
| |
| .pg input[type=range]::-webkit-slider-thumb { |
| -webkit-appearance: none; |
| width: 11px; |
| height: 11px; |
| border-radius: 50%; |
| background: var(--acc); |
| cursor: pointer; |
| border: 2px solid var(--bg1) |
| } |
| |
| .btn { |
| display: inline-flex; |
| align-items: center; |
| gap: 5px; |
| padding: 6px 12px; |
| border-radius: var(--r); |
| border: 1px solid var(--bg4); |
| cursor: pointer; |
| font-size: 10px; |
| font-weight: 700; |
| font-family: var(--mono); |
| text-transform: uppercase; |
| letter-spacing: .05em; |
| transition: .13s |
| } |
| |
| .bp { |
| background: var(--acc); |
| color: #fff; |
| border-color: var(--acc) |
| } |
| |
| .bp:hover { |
| background: var(--acc2) |
| } |
| |
| .bs { |
| background: var(--bg3); |
| color: var(--t1) |
| } |
| |
| .bs:hover { |
| background: var(--bg4); |
| color: var(--t0) |
| } |
| |
| .btn.full { |
| width: 100%; |
| justify-content: center |
| } |
| |
| .btn:disabled { |
| opacity: .3; |
| cursor: not-allowed |
| } |
| |
| |
| .fi { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| padding: 5px 7px; |
| border-radius: 6px; |
| cursor: pointer; |
| border: 1px solid transparent; |
| transition: .12s; |
| margin-bottom: 3px; |
| user-select: none |
| } |
| |
| .fi:hover { |
| background: var(--bg3) |
| } |
| |
| .fi.on { |
| background: rgba(124, 107, 255, .1); |
| border-color: rgba(124, 107, 255, .25) |
| } |
| |
| .fi.stories-on { |
| background: rgba(61, 220, 255, .08); |
| border-color: rgba(61, 220, 255, .25) |
| } |
| |
| .fcb { |
| width: 13px; |
| height: 13px; |
| border-radius: 3px; |
| border: 1px solid var(--bg4); |
| background: var(--bg); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| transition: .12s |
| } |
| |
| .fi.on .fcb { |
| background: var(--acc); |
| border-color: var(--acc) |
| } |
| |
| .fi.stories-on .fcb { |
| background: var(--cyan); |
| border-color: var(--cyan) |
| } |
| |
| .fcb svg { |
| opacity: 0; |
| transition: .1s |
| } |
| |
| .fi.on .fcb svg, |
| .fi.stories-on .fcb svg { |
| opacity: 1 |
| } |
| |
| .fdot { |
| width: 6px; |
| height: 6px; |
| border-radius: 50%; |
| flex-shrink: 0 |
| } |
| |
| .flabel { |
| font-size: 11px; |
| color: var(--t1) |
| } |
| |
| .fi.on .flabel, |
| .fi.stories-on .flabel { |
| color: var(--t0) |
| } |
| |
| #bench-panel { |
| display: none; |
| flex-direction: column; |
| height: 100%; |
| overflow: hidden |
| } |
| |
| #bench-panel.visible { |
| display: flex |
| } |
| |
| #stats-panel, |
| #params-panel { |
| display: flex; |
| flex-direction: column; |
| flex: 1; |
| overflow: hidden |
| } |
| |
| .bench-actions { |
| padding: 10px; |
| flex-shrink: 0; |
| display: flex; |
| flex-direction: column; |
| gap: 6px |
| } |
| |
| .bench-scroll { |
| flex: 1; |
| overflow-y: auto; |
| padding: 0 10px 10px; |
| scrollbar-width: thin; |
| scrollbar-color: var(--bg4) transparent |
| } |
| |
| .b-grid { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 5px; |
| margin-bottom: 10px |
| } |
| |
| .b-stat { |
| background: var(--bg2); |
| border: 1px solid var(--bg3); |
| border-radius: var(--r); |
| padding: 10px; |
| text-align: center |
| } |
| |
| .b-stat .bv { |
| font-family: var(--sans); |
| font-size: 22px; |
| font-weight: 900; |
| color: var(--acc2); |
| letter-spacing: -.04em |
| } |
| |
| .b-stat .bk { |
| font-size: 9px; |
| color: var(--t2); |
| text-transform: uppercase; |
| letter-spacing: .1em; |
| margin-top: 2px |
| } |
| |
| .ph-card { |
| background: var(--bg2); |
| border: 1px solid var(--bg3); |
| border-radius: var(--r); |
| padding: 9px 11px; |
| margin-bottom: 6px; |
| animation: fadeup .2s ease |
| } |
| |
| .ph-title { |
| font-size: 9px; |
| font-weight: 700; |
| color: var(--acc2); |
| text-transform: uppercase; |
| letter-spacing: .08em; |
| margin-bottom: 6px |
| } |
| |
| .ph-label { |
| font-size: 11px; |
| color: var(--t2); |
| margin-bottom: 4px |
| } |
| |
| .ph-stats { |
| display: flex; |
| gap: 8px; |
| flex-wrap: wrap; |
| margin-top: 5px |
| } |
| |
| .ph-stat { |
| display: flex; |
| flex-direction: column; |
| gap: 1px; |
| min-width: 50px |
| } |
| |
| .ph-stat .pv { |
| font-size: 13px; |
| font-weight: 700; |
| color: var(--green); |
| font-variant-numeric: tabular-nums |
| } |
| |
| .ph-stat .pk { |
| font-size: 9px; |
| color: var(--t2); |
| text-transform: uppercase; |
| letter-spacing: .06em |
| } |
| |
| .ph-bar { |
| height: 2px; |
| background: var(--bg4); |
| border-radius: 1px; |
| margin-top: 5px; |
| overflow: hidden |
| } |
| |
| .ph-fill { |
| height: 100%; |
| background: linear-gradient(90deg, var(--acc3), var(--acc2)); |
| border-radius: 1px; |
| transition: width .5s ease; |
| width: 0% |
| } |
| |
| .ph-running { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| font-size: 10px; |
| color: var(--warn) |
| } |
| |
| .spin { |
| width: 8px; |
| height: 8px; |
| border: 1.5px solid var(--warn); |
| border-top-color: transparent; |
| border-radius: 50%; |
| animation: spin .7s linear infinite; |
| flex-shrink: 0 |
| } |
| |
| @keyframes spin { |
| to { |
| transform: rotate(360deg) |
| } |
| } |
| |
| .bench-empty { |
| text-align: center; |
| padding: 30px 16px; |
| color: var(--t2); |
| font-size: 12px; |
| line-height: 1.9 |
| } |
| |
| .verdict { |
| padding: 10px 12px; |
| border-radius: var(--r); |
| font-size: 11px; |
| margin-bottom: 8px; |
| border: 1px solid var(--bg3) |
| } |
| |
| .verdict.good { |
| border-color: rgba(61, 255, 180, .3); |
| background: rgba(61, 255, 180, .06); |
| color: var(--green) |
| } |
| |
| .verdict.mid { |
| border-color: rgba(255, 194, 96, .3); |
| background: rgba(255, 194, 96, .06); |
| color: var(--warn) |
| } |
| |
| .chat { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| overflow: hidden |
| } |
| |
| .chat-hdr { |
| height: 50px; |
| display: flex; |
| align-items: center; |
| padding: 0 20px; |
| border-bottom: 1px solid var(--bg3); |
| background: var(--bg1); |
| gap: 8px; |
| flex-shrink: 0 |
| } |
| |
| .badge { |
| background: var(--dim); |
| border: 1px solid rgba(124, 107, 255, .3); |
| border-radius: 20px; |
| padding: 3px 10px; |
| font-size: 10px; |
| font-weight: 700; |
| color: var(--acc2); |
| font-family: var(--sans) |
| } |
| |
| .hdr-title { |
| font-family: var(--sans); |
| font-size: 13px; |
| font-weight: 600; |
| color: var(--t1) |
| } |
| |
| .spc { |
| flex: 1 |
| } |
| |
| .hpill { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| font-size: 10px; |
| color: var(--t2); |
| text-transform: uppercase; |
| letter-spacing: .06em |
| } |
| |
| .msgs { |
| flex: 1; |
| overflow-y: auto; |
| display: flex; |
| flex-direction: column; |
| scrollbar-width: thin; |
| scrollbar-color: var(--bg4) transparent |
| } |
| |
| .welcome { |
| flex: 1; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 14px; |
| text-align: center; |
| padding: 40px; |
| animation: fadeup .4s ease |
| } |
| |
| .wlogo { |
| font-family: var(--sans); |
| font-size: 72px; |
| font-weight: 900; |
| background: linear-gradient(135deg, var(--acc), var(--cyan)); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| background-clip: text; |
| letter-spacing: -.05em; |
| line-height: 1 |
| } |
| |
| .welcome h2 { |
| font-family: var(--sans); |
| font-size: 22px; |
| font-weight: 700; |
| color: var(--t0); |
| letter-spacing: -.02em |
| } |
| |
| .welcome p { |
| max-width: 400px; |
| line-height: 1.8; |
| font-size: 12px; |
| color: var(--t1) |
| } |
| |
| .chips { |
| display: flex; |
| gap: 6px; |
| flex-wrap: wrap; |
| justify-content: center; |
| margin-top: 2px |
| } |
| |
| .chip { |
| font-size: 9px; |
| padding: 4px 10px; |
| border: 1px solid var(--bg4); |
| border-radius: 20px; |
| color: var(--t2); |
| letter-spacing: .07em; |
| text-transform: uppercase; |
| background: var(--bg2) |
| } |
| |
| .mg { |
| display: flex; |
| gap: 12px; |
| padding: 16px 28px; |
| animation: fadeup .18s ease; |
| align-items: flex-start |
| } |
| |
| .mg.user { |
| flex-direction: row-reverse |
| } |
| |
| @keyframes fadeup { |
| from { |
| opacity: 0; |
| transform: translateY(6px) |
| } |
| |
| to { |
| opacity: 1; |
| transform: none |
| } |
| } |
| |
| .mg-avatar { |
| width: 36px; |
| height: 36px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| font-family: var(--sans); |
| font-weight: 900; |
| font-size: 13px |
| } |
| |
| .mg.asst .mg-avatar { |
| background: linear-gradient(135deg, var(--acc3), var(--acc2)); |
| color: #fff; |
| box-shadow: 0 0 14px rgba(124, 107, 255, .3) |
| } |
| |
| .mg.user .mg-avatar { |
| display: none |
| } |
| |
| .mg-body { |
| flex: 1; |
| min-width: 0; |
| max-width: 620px |
| } |
| |
| .mg.user .mg-body { |
| display: flex; |
| flex-direction: column; |
| align-items: flex-end |
| } |
| |
| .bubble { |
| font-size: 13px; |
| line-height: 1.75; |
| color: var(--t1); |
| white-space: pre-wrap; |
| word-break: break-word; |
| padding: 12px 18px; |
| border-radius: 16px; |
| max-width: 560px |
| } |
| |
| .mg.asst .bubble { |
| background: rgba(15, 18, 35, 0.85); |
| |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| |
| border: 1px solid rgba(124, 107, 255, 0.3); |
| border-top-left-radius: 4px; |
| |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); |
| } |
| |
| .mg.user .bubble { |
| background: var(--bg2); |
| border: 1px solid var(--bg3); |
| color: var(--t1); |
| border-top-right-radius: 4px |
| } |
| |
| .msg-actions { |
| display: flex; |
| gap: 4px; |
| margin-top: 4px; |
| opacity: 0; |
| transition: .15s |
| } |
| |
| .mg:hover .msg-actions { |
| opacity: 1 |
| } |
| |
| .msg-actions button { |
| width: 28px; |
| height: 28px; |
| border-radius: 6px; |
| border: 1px solid var(--bg4); |
| background: var(--bg2); |
| color: var(--t2); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: .12s |
| } |
| |
| .msg-actions button:hover { |
| background: var(--bg3); |
| color: var(--t0) |
| } |
| |
| .msg-actions button svg { |
| width: 14px; |
| height: 14px; |
| fill: currentColor |
| } |
| |
| .cur { |
| display: inline-block; |
| width: 7px; |
| height: 2px; |
| background: var(--acc2); |
| margin-left: 3px; |
| vertical-align: middle; |
| animation: blink .6s infinite |
| } |
| |
| @keyframes blink { |
| |
| 0%, |
| 49% { |
| opacity: 1 |
| } |
| |
| 50%, |
| 100% { |
| opacity: 0 |
| } |
| } |
| |
| .bmeta { |
| font-size: 10px; |
| color: var(--t3); |
| margin-top: 6px; |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| padding-left: 18px |
| } |
| |
| .bmeta b { |
| color: var(--acc2) |
| } |
| |
| .inputbar { |
| padding: 16px 24px; |
| background: var(--bg); |
| flex-shrink: 0 |
| } |
| |
| .inputwrap { |
| background: var(--bg2); |
| border: 1px solid rgba(124, 107, 255, 0.55); |
| border-radius: 24px; |
| display: flex; |
| align-items: flex-end; |
| padding: 4px 6px 4px 16px; |
| gap: 4px; |
| transition: .13s; |
| max-width: 760px; |
| margin: 0 auto; |
| box-shadow: 0 0 0 3px rgba(124, 107, 255, 0.12), |
| 0 0 24px rgba(124, 107, 255, 0.25), |
| 0 2px 20px rgba(0, 0, 0, .3); |
| } |
| |
| .inputwrap:focus-within { |
| border-color: var(--acc); |
| box-shadow: 0 2px 20px rgba(124, 107, 255, .15) |
| } |
| |
| #inp { |
| flex: 1; |
| background: none; |
| border: none; |
| outline: none; |
| color: var(--t0); |
| font-size: 13px; |
| font-family: var(--mono); |
| resize: none; |
| line-height: 1.6; |
| max-height: 110px; |
| padding: 8px 0 |
| } |
| |
| #inp::placeholder { |
| color: var(--t3) |
| } |
| |
| .input-icons { |
| display: flex; |
| gap: 2px; |
| align-items: center; |
| padding-bottom: 4px |
| } |
| |
| .ibtn { |
| width: 32px; |
| height: 32px; |
| border-radius: 50%; |
| border: none; |
| background: none; |
| color: var(--t2); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: .12s |
| } |
| |
| .ibtn:hover { |
| color: var(--t0); |
| background: var(--bg3) |
| } |
| |
| .ibtn svg { |
| width: 16px; |
| height: 16px; |
| fill: currentColor |
| } |
| |
| .sbtn { |
| background: linear-gradient(135deg, var(--acc3), var(--acc2)); |
| border: none; |
| cursor: pointer; |
| width: 32px; |
| height: 32px; |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| flex-shrink: 0; |
| transition: .13s; |
| box-shadow: 0 0 12px rgba(124, 107, 255, .4) |
| } |
| |
| .sbtn:hover { |
| filter: brightness(1.15) |
| } |
| |
| .sbtn:disabled { |
| opacity: .3; |
| cursor: not-allowed |
| } |
| |
| .sbtn svg { |
| fill: #fff |
| } |
| |
| .hint { |
| margin-top: 5px; |
| font-size: 10px; |
| color: var(--t3); |
| text-align: center; |
| letter-spacing: .04em |
| } |
| |
| |
| .toggle { |
| position: relative; |
| display: inline-block; |
| width: 36px; |
| height: 18px |
| } |
| |
| .toggle input { |
| opacity: 0; |
| width: 0; |
| height: 0 |
| } |
| |
| .toggle-slider { |
| position: absolute; |
| inset: 0; |
| background: var(--bg4); |
| border-radius: 18px; |
| cursor: pointer; |
| transition: .2s |
| } |
| |
| .toggle-slider::before { |
| content: ''; |
| position: absolute; |
| width: 14px; |
| height: 14px; |
| border-radius: 50%; |
| left: 2px; |
| bottom: 2px; |
| background: var(--t2); |
| transition: .2s |
| } |
| |
| .toggle input:checked+.toggle-slider { |
| background: var(--green) |
| } |
| |
| .toggle input:checked+.toggle-slider::before { |
| transform: translateX(18px); |
| background: #fff |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <div class="app"> |
|
|
|
|
|
|
| <div class="main-area"> |
|
|
| <aside class="sb"> |
| <div class="sb-tabs"> |
| <button class="sb-tab on" id="tab-stats" onclick="switchTab('stats')">Stats</button> |
| <button class="sb-tab" id="tab-bench" onclick="switchTab('bench')">Bench</button> |
| <button class="sb-tab" id="tab-params" onclick="switchTab('params')">Params</button> |
| </div> |
|
|
| |
| <div id="stats-panel"> |
| <div class="sb-body"> |
| <div class="card"> |
| <div class="ct"><span class="dot off" id="dot-s"></span>Live |
| <div style="margin-left:auto"><label class="toggle"><input type="checkbox" id="live-toggle" |
| checked><span class="toggle-slider"></span></label></div> |
| </div> |
| <div style="display:flex;gap:16px;margin:8px 0 4px"> |
| <div style="flex:1"> |
| <div style="font-size:9px;color:var(--t2);text-transform:uppercase;letter-spacing:.08em">Throughput |
| </div> |
| <div style="font-family:var(--sans);font-size:20px;font-weight:900;color:var(--green);margin-top:2px" |
| id="s-tps">—</div> |
| </div> |
| <div style="flex:1"> |
| <div style="font-size:9px;color:var(--t2);text-transform:uppercase;letter-spacing:.08em">Latency</div> |
| <div style="font-family:var(--sans);font-size:20px;font-weight:900;color:var(--warn);margin-top:2px" |
| id="s-lat">—</div> |
| </div> |
| </div> |
| <div class="sr"><span class="sl">TTFT</span> <span class="sv c" id="s-ttft">—</span></div> |
| <div class="sr"><span class="sl">Tokens out</span><span class="sv" id="s-toks">—</span></div> |
| </div> |
| <div class="card"> |
| <div class="ct">Throughput History</div> |
| <canvas id="spark"></canvas> |
| <div style="margin-top:6px"> |
| <div class="sr"><span class="sl">Session avg</span><span class="sv g" id="s-avg">—</span></div> |
| <div class="sr"><span class="sl">Peak</span> <span class="sv g" id="s-peak">—</span></div> |
| </div> |
| </div> |
| <div class="card"> |
| <div class="ct">Session</div> |
| <div class="sr"><span class="sl">Turns</span> <span class="sv" id="s-turns">0</span></div> |
| <div class="sr"><span class="sl">Total tokens</span><span class="sv" id="s-totok">0</span></div> |
| <div class="sr"><span class="sl">Server RAM</span> <span class="sv" id="s-ram">0</span></div> |
| <div style="margin-top:10px"> |
| <button class="btn bp full" onclick="clearChat()" |
| style="border-radius:20px;padding:8px 16px;font-size:11px">RESET</button> |
| </div> |
| </div> |
| <div class="card"> |
| <div class="ct">Engine</div> |
| <div class="sr"><span class="sl">Server RAM</span> <span class="sv" id="s-ram2">—</span></div> |
| <div class="ph-bar" style="margin-top:6px;height:4px;border-radius:2px"> |
| <div class="ph-fill" id="ram-bar" |
| style="width:0%;background:linear-gradient(90deg,var(--acc3),var(--acc2))"></div> |
| </div> |
| <div class="sr" style="margin-top:6px"><span class="sl">Status</span> <span class="sv g" |
| id="s-eng">—</span></div> |
| <div class="sr"><span class="sl">Sessions</span><span class="sv" id="s-sess">—</span></div> |
| <div class="sr"><span class="sl">Uptime</span> <span class="sv" id="s-up">—</span></div> |
| </div> |
|
|
|
|
|
|
| </div> |
| </div> |
|
|
| |
| <div id="bench-panel"> |
| <div class="bench-actions"> |
| <button class="btn bp full" id="btnbench" onclick="runBench()">▶ Run Benchmark</button> |
| <div style="font-size:9px;color:var(--t2);text-align:center;line-height:1.6"> |
| 5 phases · cold/warm TTFT · throughput · concurrency |
| </div> |
| </div> |
| <div class="bench-scroll" id="bench-scroll"> |
| <div class="bench-empty" id="bench-empty"> |
| Run benchmark to measure<br>real system performance |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="params-panel" style="display:none"> |
| <div class="sb-body"> |
| <div class="card"> |
| <div class="ct">System Prompt</div> |
| <textarea class="ta" id="sysprompt" |
| rows="4">You are a helpful, thoughtful, and articulate AI assistant.</textarea> |
| </div> |
| <div class="card"> |
| <div class="ct">Generation</div> |
| <div style="display:flex;flex-direction:column;gap:10px;margin-top:2px"> |
| <div class="pg"> |
| <label>Temperature <span id="v-temp">0.70</span></label> |
| <input type="range" id="p-temp" min="0.1" max="2.0" step="0.05" value="0.7" |
| oninput="document.getElementById('v-temp').textContent=parseFloat(this.value).toFixed(2)"> |
| </div> |
| <div class="pg"> |
| <label>Top-K <span id="v-topk">40</span></label> |
| <input type="range" id="p-topk" min="1" max="200" step="1" value="40" |
| oninput="document.getElementById('v-topk').textContent=this.value"> |
| </div> |
| <div class="pg"> |
| <label>Max tokens <span id="v-maxt">200</span></label> |
| <input type="range" id="p-maxt" min="20" max="256" step="10" value="200" |
| oninput="document.getElementById('v-maxt').textContent=this.value"> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </aside> |
|
|
| |
| <main class="chat"> |
| <header class="chat-hdr"> |
| <div class="badge">NanoMind · 152M</div> |
| <span class="hdr-title">Chat</span> |
| <div class="spc"></div> |
| <div class="hpill"> |
| <span class="dot off" id="dot-h"></span> |
| <span id="hstatus">Idle</span> |
| </div> |
| </header> |
|
|
| <div class="msgs" id="msgs"> |
| <div class="welcome" id="welcome"> |
| </div> |
| </div> |
|
|
| <div class="inputbar"> |
| <div class="inputwrap"> |
| <textarea id="inp" rows="1" placeholder="Send a message..." onkeydown="handleKey(event)"></textarea> |
| <div class="input-icons"> |
| <button class="ibtn" title="Attach"> |
| <svg viewBox="0 0 24 24"> |
| <path |
| d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" /> |
| </svg> |
| </button> |
| <button class="sbtn" id="sbtn" onclick="send()"> |
| <svg width="14" height="14" viewBox="0 0 24 24"> |
| <path d="M2 21l21-9L2 3v7l15 2-15 2v7z" /> |
| </svg> |
| </button> |
| <button class="ibtn" id="fi-stories" title="Stories ↗" onclick="goStories()" style="color:var(--cyan)"> |
| <svg viewBox="0 0 24 24"> |
| <path d="M19 19H5V5h7V3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" /> |
| </svg> |
| </button> |
| <button class="ibtn" title="Scroll to top" onclick="document.getElementById('msgs').scrollTop=0"> |
| <svg viewBox="0 0 24 24"> |
| <path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" /> |
| </svg> |
| </button> |
| </div> |
| </div> |
| <div class="hint">Enter ↵ to send · Shift+Enter for newline</div> |
| </div> |
| </main> |
|
|
| </div> |
| </div> |
|
|
| <script> |
| const API = ''; |
| let sessId = crypto.randomUUID(); |
| let busy = false, turns = 0, totalToks = 0; |
| let tpsHist = [], peakTps = 0; |
| |
| |
| function toggleFilter(el, key) { |
| el.classList.toggle('on'); |
| } |
| |
| function goStories() { |
| const el = document.getElementById('fi-stories'); |
| el.classList.add('stories-on'); |
| setTimeout(() => { |
| window.open('https://not-omega-inference.hf.space/', '_blank'); |
| setTimeout(() => el.classList.remove('stories-on'), 400); |
| }, 160); |
| } |
| |
| |
| function switchTab(name) { |
| ['stats', 'bench', 'params'].forEach(t => { |
| const panelId = t === 'stats' ? 'stats-panel' : t + '-panel'; |
| const panel = document.getElementById(panelId); |
| const tab = document.getElementById('tab-' + t); |
| const show = t === name; |
| panel.style.display = show ? 'flex' : 'none'; |
| tab.classList.toggle('on', show); |
| }); |
| } |
| |
| |
| const inp = document.getElementById('inp'); |
| inp.addEventListener('input', () => { |
| inp.style.height = 'auto'; |
| inp.style.height = Math.min(inp.scrollHeight, 110) + 'px'; |
| }); |
| function handleKey(e) { |
| if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } |
| } |
| |
| |
| function setBusy(v) { |
| busy = v; |
| document.getElementById('sbtn').disabled = v; |
| ['dot-s', 'dot-h'].forEach(id => { |
| document.getElementById(id).className = 'dot ' + (v ? 'on' : 'off'); |
| }); |
| document.getElementById('hstatus').textContent = v ? 'Generating…' : 'Idle'; |
| } |
| function scrollBot() { const el = document.getElementById('msgs'); el.scrollTop = el.scrollHeight; } |
| function hideWelcome() { const w = document.getElementById('welcome'); if (w) w.remove(); } |
| function esc(s) { return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, '''); } |
| |
| |
| function addUserMsg(text) { |
| hideWelcome(); |
| const g = document.createElement('div'); g.className = 'mg user'; |
| g.innerHTML = `<div class="mg-body"><div class="bubble">${esc(text)}</div> |
| <div class="msg-actions"><button onclick="navigator.clipboard.writeText(this.closest('.mg').querySelector('.bubble').textContent)" title="Copy"><svg viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg></button></div></div>`; |
| document.getElementById('msgs').appendChild(g); scrollBot(); |
| } |
| function createAsstSlot() { |
| const g = document.createElement('div'); g.className = 'mg asst'; |
| const bid = 'b' + Date.now(), mid = 'm' + Date.now(); |
| g.innerHTML = `<div class="mg-avatar">NM</div> |
| <div class="mg-body"> |
| <div class="bubble" id="${bid}"><span class="cur"></span></div> |
| <div class="bmeta" id="${mid}"></div> |
| </div>`; |
| document.getElementById('msgs').appendChild(g); scrollBot(); |
| return { bubble: document.getElementById(bid), meta: document.getElementById(mid) }; |
| } |
| |
| |
| async function send() { |
| if (busy) return; |
| const text = inp.value.trim(); if (!text) return; |
| inp.value = ''; inp.style.height = 'auto'; |
| addUserMsg(text); setBusy(true); |
| const { bubble, meta } = createAsstSlot(); |
| let content = '', t0 = Date.now(), firstTok = null, tokCount = 0; |
| const payload = { |
| message: text, |
| session_id: sessId, |
| system_prompt: document.getElementById('sysprompt').value, |
| max_new_tokens: parseInt(document.getElementById('p-maxt').value), |
| temperature: parseFloat(document.getElementById('p-temp').value), |
| top_k: parseInt(document.getElementById('p-topk').value), |
| }; |
| try { |
| const resp = await fetch(`${API}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); |
| if (!resp.ok) throw new Error(`HTTP ${resp.status}`); |
| const reader = resp.body.getReader(), decoder = new TextDecoder(); |
| let buf = ''; |
| while (true) { |
| const { done, value } = await reader.read(); if (done) break; |
| buf += decoder.decode(value, { stream: true }); |
| let nl; |
| while ((nl = buf.indexOf('\n')) !== -1) { |
| const line = buf.slice(0, nl).trim(); buf = buf.slice(nl + 1); |
| if (!line.startsWith('data:')) continue; |
| const raw = line.slice(5).trim(); if (raw === '[DONE]') break; |
| let chunk; try { chunk = JSON.parse(raw); } catch { continue; } |
| if (chunk.type === 'token') { |
| if (firstTok === null) firstTok = Date.now(); |
| content += chunk.text; tokCount++; totalToks++; |
| document.getElementById('s-totok').textContent = totalToks; |
| document.getElementById('s-toks').textContent = tokCount + ' tok'; |
| document.getElementById('s-ttft').textContent = (firstTok - t0) + ' ms'; |
| bubble.innerHTML = esc(content) + '<span class="cur"></span>'; |
| scrollBot(); |
| } else if (chunk.type === 'done') { |
| bubble.innerHTML = esc(content); |
| const ttft = firstTok ? (firstTok - t0) : 0, tps = chunk.tps, ms = chunk.total_ms; |
| meta.innerHTML = `<b>${tps}</b> tok/s · TTFT <b>${ttft}ms</b> · <b>${tokCount}</b> tokens · <b>${ms.toFixed(0)}ms</b>`; |
| document.getElementById('s-tps').textContent = tps + ' tok/s'; |
| document.getElementById('s-lat').textContent = ms.toFixed(0) + ' ms'; |
| tpsHist.push(tps); if (tpsHist.length > 30) tpsHist.shift(); |
| if (tps > peakTps) peakTps = tps; |
| const avg = (tpsHist.reduce((a, b) => a + b, 0) / tpsHist.length).toFixed(1); |
| document.getElementById('s-avg').textContent = avg + ' tok/s'; |
| document.getElementById('s-peak').textContent = peakTps.toFixed(1) + ' tok/s'; |
| if (chunk.session_id) { |
| fetch(`${API}/chat/history?session_id=${chunk.session_id}`) |
| .then(r => r.json()) |
| .then(d => { const kv = document.getElementById('s-kv'); if (kv) kv.textContent = (d.tokens_in_engine || 0) + ' tok'; }) |
| .catch(() => { }); |
| } |
| turns++; document.getElementById('s-turns').textContent = turns; |
| drawSpark(); |
| } else if (chunk.type === 'error') { |
| bubble.innerHTML += `<br><span style="color:var(--red)">⚠ ${esc(chunk.message)}</span>`; |
| } |
| } |
| } |
| } catch (err) { |
| if (tokCount === 0) bubble.innerHTML = `<span style="color:var(--red)">Connection error: ${esc(err.message)}</span>`; |
| else meta.innerHTML += ` <span style="color:var(--warn)">[interrupted]</span>`; |
| } finally { |
| const c = bubble.querySelector('.cur'); if (c) c.remove(); |
| setBusy(false); scrollBot(); |
| } |
| } |
| |
| |
| function drawSpark() { |
| const el = document.getElementById('spark'), ctx = el.getContext('2d'); |
| const dpr = window.devicePixelRatio || 1; |
| el.width = el.clientWidth * dpr; el.height = el.clientHeight * dpr; |
| ctx.scale(dpr, dpr); |
| const W = el.clientWidth, H = el.clientHeight; |
| ctx.clearRect(0, 0, W, H); |
| const d = tpsHist; if (d.length < 2) return; |
| const mx = Math.max(...d) * 1.15 || 1, step = W / (d.length - 1); |
| const grad = ctx.createLinearGradient(0, 0, 0, H); |
| grad.addColorStop(0, 'rgba(124,107,255,.3)'); grad.addColorStop(1, 'rgba(124,107,255,.02)'); |
| ctx.beginPath(); |
| d.forEach((v, i) => { const x = i * step, y = H - (v / mx) * (H - 4) - 2; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }); |
| ctx.strokeStyle = '#9d8fff'; ctx.lineWidth = 1.5; ctx.stroke(); |
| ctx.lineTo((d.length - 1) * step, H); ctx.lineTo(0, H); ctx.closePath(); |
| ctx.fillStyle = grad; ctx.fill(); |
| } |
| |
| |
| async function clearChat() { |
| if (busy) return; |
| await fetch(`${API}/chat/reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: sessId }) }).catch(() => { }); |
| sessId = crypto.randomUUID(); |
| turns = 0; totalToks = 0; tpsHist = []; peakTps = 0; |
| document.getElementById('msgs').innerHTML = ` |
| <div class="welcome" id="welcome"> |
| </div>`; |
| ['s-turns', 's-totok'].forEach(id => document.getElementById(id).textContent = '0'); |
| ['s-tps', 's-ttft', 's-lat', 's-avg', 's-peak', 's-toks'].forEach(id => { const el = document.getElementById(id); if (el) el.textContent = '—'; }); |
| drawSpark(); |
| } |
| |
| |
| async function pollMetrics() { |
| try { |
| const r = await fetch(`${API}/metrics`); if (!r.ok) return; |
| const d = await r.json(); |
| const ramText = d.system_ram_used_pct.toFixed(0) + '% (' + d.process_ram_mb + 'MB)'; |
| document.getElementById('s-ram').textContent = d.process_ram_mb + 'MB'; |
| const ram2 = document.getElementById('s-ram2'); if (ram2) ram2.textContent = ramText; |
| const ramBar = document.getElementById('ram-bar'); if (ramBar) ramBar.style.width = d.system_ram_used_pct.toFixed(0) + '%'; |
| document.getElementById('s-eng').textContent = (d.engines_ready || 0) + ' / ' + (d.n_engines || '?') + ' ready'; |
| document.getElementById('s-sess').textContent = d.active_sessions || 0; |
| document.getElementById('s-up').textContent = fmtUptime(d.uptime_s || 0); |
| } catch { } |
| } |
| function fmtUptime(s) { |
| if (s < 60) return s + 's'; |
| if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60 | 0) + 's'; |
| return Math.floor(s / 3600) + 'h ' + Math.floor((s % 3600) / 60) + 'm'; |
| } |
| pollMetrics(); setInterval(pollMetrics, 4000); |
| |
| |
| const PHASES = [ |
| { id: 'cold_ttft', title: 'Phase 1 — Cold TTFT', desc: 'First token latency on a fresh session', prompts: ["What is the capital of Japan?"], maxNew: 60, temp: 0.1, metric: 'ttft' }, |
| { id: 'warm_ttft', title: 'Phase 2 — Warm TTFT (KV Cache)', desc: 'TTFT on same session — measures KV-cache speedup', prompts: ["Now tell me about Tokyo's population."], maxNew: 60, temp: 0.1, reuseSession: true, metric: 'ttft' }, |
| { id: 'throughput', title: 'Phase 3 — Throughput (tok/s)', desc: '5 prompts, 80 tokens each — avg TPS', prompts: ["Explain machine learning in simple terms.", "Write a Python function to reverse a string.", "What are 3 benefits of regular exercise?", "How does a neural network learn from data?", "Describe the water cycle briefly."], maxNew: 80, temp: 0.7, metric: 'tps' }, |
| { id: 'long_context', title: 'Phase 4 — Long Context', desc: 'Longer prompt input — tests attention scaling', prompts: ["Please provide a detailed explanation of how transformers work in natural language processing, covering the key components including attention mechanisms, positional encoding, and the encoder-decoder architecture used in models like BERT and GPT."], maxNew: 100, temp: 0.5, metric: 'tps' }, |
| { id: 'consistency', title: 'Phase 5 — Consistency', desc: '3 identical prompts — checks TPS variance', prompts: ["What is machine learning?", "What is machine learning?", "What is machine learning?"], maxNew: 60, temp: 0.1, metric: 'variance' }, |
| ]; |
| let benchRunning = false, coldSessId = null; |
| |
| |
| async function runSinglePrompt(prompt, maxNew, temp, sessionId) { |
| let tokCount = 0, finalTps = 0, finalMs = 0, serverTtft = 0; |
| const resp = await fetch(`${API}/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: prompt, session_id: sessionId, system_prompt: 'You are a helpful assistant.', max_new_tokens: maxNew, temperature: temp, top_k: 40 }) }); |
| if (!resp.ok) throw new Error(`HTTP ${resp.status}`); |
| const reader = resp.body.getReader(), decoder = new TextDecoder(); let buf = ''; |
| while (true) { |
| const { done, value } = await reader.read(); if (done) break; |
| buf += decoder.decode(value, { stream: true }); let nl; |
| while ((nl = buf.indexOf('\n')) !== -1) { |
| const line = buf.slice(0, nl).trim(); buf = buf.slice(nl + 1); |
| if (!line.startsWith('data:')) continue; |
| const raw = line.slice(5).trim(); if (raw === '[DONE]') break; |
| let chunk; try { chunk = JSON.parse(raw); } catch { continue; } |
| if (chunk.type === 'token') { tokCount++; } |
| else if (chunk.type === 'done') { |
| finalTps = chunk.tps; |
| finalMs = chunk.total_ms; |
| serverTtft = chunk.ttft_ms || 0; |
| } |
| } |
| } |
| return { ttft: serverTtft, tps: finalTps, totalMs: finalMs, tokens: tokCount }; |
| } |
| |
| function mkPhaseCard(phase) { |
| const div = document.createElement('div'); div.className = 'ph-card'; div.id = 'ph-' + phase.id; |
| div.innerHTML = `<div class="ph-title">${phase.title}</div><div class="ph-label">${phase.desc}</div><div class="ph-running" id="ph-st-${phase.id}"><span class="spin"></span> Running…</div><div class="ph-bar"><div class="ph-fill" id="ph-bar-${phase.id}"></div></div>`; |
| return div; |
| } |
| function setPhaseResult(phase, results) { |
| const st = document.getElementById('ph-st-' + phase.id), bar = document.getElementById('ph-bar-' + phase.id); |
| bar.style.width = '100%'; |
| if (phase.metric === 'ttft') { |
| const avg = results.reduce((s, r) => s + r.ttft, 0) / results.length, cls = avg < 1000 ? 'g' : avg < 2000 ? 'y' : 'r'; |
| st.innerHTML = `<div class="ph-stats"><div class="ph-stat"><div class="pv" style="color:var(--${cls})">${avg.toFixed(0)}ms</div><div class="pk">TTFT</div></div><div class="ph-stat"><div class="pv">${results[0].tokens}</div><div class="pk">tokens</div></div><div class="ph-stat"><div class="pv">${results[0].tps}</div><div class="pk">tok/s</div></div></div>`; |
| return { label: 'TTFT', value: avg, unit: 'ms' }; |
| } else if (phase.metric === 'tps') { |
| const avgTps = results.reduce((s, r) => s + r.tps, 0) / results.length, avgTtft = results.reduce((s, r) => s + r.ttft, 0) / results.length, cls = avgTps > 35 ? 'g' : avgTps > 20 ? 'y' : 'r'; |
| st.innerHTML = `<div class="ph-stats">${results.map((r, i) => `<div class="ph-stat"><div class="pv" style="color:var(--${cls})">${r.tps}</div><div class="pk">P${i + 1} tok/s</div></div>`).join('')}<div class="ph-stat"><div class="pv" style="color:var(--c)">${avgTtft.toFixed(0)}ms</div><div class="pk">avg TTFT</div></div></div>`; |
| return { label: 'Avg TPS', value: avgTps, unit: 'tok/s' }; |
| } else { |
| const tpsList = results.map(r => r.tps), avg = tpsList.reduce((a, b) => a + b, 0) / tpsList.length, stddev = Math.sqrt(tpsList.reduce((s, v) => s + Math.pow(v - avg, 2), 0) / tpsList.length).toFixed(1); |
| st.innerHTML = `<div class="ph-stats">${tpsList.map((t, i) => `<div class="ph-stat"><div class="pv">${t}</div><div class="pk">run ${i + 1}</div></div>`).join('')}<div class="ph-stat"><div class="pv" style="color:var(--c)">±${stddev}</div><div class="pk">std dev</div></div></div>`; |
| return { label: 'Std Dev', value: parseFloat(stddev), unit: '±' }; |
| } |
| } |
| async function runBench() { |
| if (benchRunning || busy) return; benchRunning = true; switchTab('bench'); |
| const btn = document.getElementById('btnbench'), scroll = document.getElementById('bench-scroll'); |
| btn.disabled = true; btn.textContent = 'Running…'; |
| const empty = document.getElementById('bench-empty'); if (empty) empty.remove(); |
| scroll.innerHTML = ''; |
| |
| |
| if (coldSessId) { |
| await fetch(`${API}/chat/reset`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ session_id: coldSessId }) }).catch(() => {}); |
| } |
| |
| |
| coldSessId = crypto.randomUUID(); |
| let summaryData = {}; |
| |
| for (const phase of PHASES) { |
| const card = mkPhaseCard(phase); scroll.appendChild(card); scroll.scrollTop = scroll.scrollHeight; |
| const bar = document.getElementById('ph-bar-' + phase.id), results = []; |
| try { |
| for (let i = 0; i < phase.prompts.length; i++) { |
| |
| const sid = (phase.id === 'cold_ttft' || phase.reuseSession) ? coldSessId : crypto.randomUUID(); |
| bar.style.width = ((i / phase.prompts.length) * 80) + '%'; |
| const r = await runSinglePrompt(phase.prompts[i], phase.maxNew, phase.temp, sid); |
| results.push(r); |
| } |
| summaryData[phase.id] = setPhaseResult(phase, results); |
| } catch (e) { |
| document.getElementById('ph-st-' + phase.id).innerHTML = `<span style="color:var(--red)">✗ ${e.message}</span>`; |
| bar.style.background = 'var(--red)'; bar.style.width = '100%'; |
| } |
| } |
| const p3 = summaryData['throughput'], p1 = summaryData['cold_ttft'], p2 = summaryData['warm_ttft']; |
| const avgTps = p3 ? p3.value.toFixed(1) : '—', coldTtft = p1 ? p1.value.toFixed(0) : '—', warmTtft = p2 ? p2.value.toFixed(0) : '—'; |
| const cacheGain = (p1 && p2 && p1.value > 0) ? ((p1.value - p2.value) / p1.value * 100).toFixed(0) : '—'; |
| const verdict = p3 && p3.value > 35 ? `<div class="verdict good">✓ Throughput target met — ${avgTps} tok/s exceeds 35 tok/s baseline</div>` : `<div class="verdict mid">△ Throughput at ${avgTps} tok/s — within expected range</div>`; |
| const summary = document.createElement('div'); |
| summary.innerHTML = `${verdict}<div class="b-grid"><div class="b-stat"><div class="bv">${avgTps}</div><div class="bk">Avg tok/s</div></div><div class="b-stat"><div class="bv">${coldTtft}ms</div><div class="bk">Cold TTFT</div></div><div class="b-stat"><div class="bv">${warmTtft}ms</div><div class="bk">Warm TTFT</div></div><div class="b-stat"><div class="bv">${cacheGain}%</div><div class="bk">Cache Speedup</div></div></div>`; |
| scroll.insertBefore(summary, scroll.firstChild); |
| btn.textContent = '↺ Run Again'; btn.disabled = false; benchRunning = false; |
| } |
| </script> |
| </body> |
|
|
| </html> |