| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Kimi K2.7-Code — Terminal</title> |
| <meta name="description" content="Kimi K2.7-Code terminal — Moonshot AI multimodal code model."> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,700;1,300&display=swap" rel="stylesheet"> |
| <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> |
| <style> |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } |
| |
| :root { |
| --bg: #030303; |
| --surface: #0a0a0a; |
| --surface2: #111; |
| --border: rgba(255,255,255,0.07); |
| --border-hi: rgba(0, 230, 118, 0.35); |
| --text: #dcdcdc; |
| --text-dim: #555; |
| --text-muted:#333; |
| --green: #00e676; |
| --green-dim: rgba(0, 230, 118, 0.12); |
| --green-glow:rgba(0, 230, 118, 0.08); |
| --error: #ff5252; |
| --radius: 12px; |
| --mono: 'JetBrains Mono', 'Fira Code', monospace; |
| } |
| |
| html, body { |
| height: 100%; |
| background: var(--bg); |
| color: var(--text); |
| font-family: var(--mono); |
| font-size: 13.5px; |
| line-height: 1.65; |
| overflow: hidden; |
| -webkit-font-smoothing: antialiased; |
| } |
| |
| |
| body::before { |
| content: ''; |
| position: fixed; |
| inset: 0; |
| background: repeating-linear-gradient( |
| 0deg, |
| transparent, |
| transparent 2px, |
| rgba(0,0,0,0.03) 2px, |
| rgba(0,0,0,0.03) 4px |
| ); |
| pointer-events: none; |
| z-index: 9999; |
| } |
| |
| |
| .shell { |
| display: flex; |
| flex-direction: column; |
| height: 100vh; |
| max-width: 880px; |
| margin: 0 auto; |
| padding: 0 28px; |
| } |
| |
| |
| .header { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 18px 0 14px; |
| border-bottom: 1px solid var(--border); |
| flex-shrink: 0; |
| } |
| |
| .logo { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| |
| |
| .moon-wrap { |
| position: relative; |
| width: 30px; |
| height: 30px; |
| flex-shrink: 0; |
| } |
| |
| .logo-info { display: flex; flex-direction: column; gap: 1px; } |
| |
| .logo-name { |
| font-size: 13px; |
| font-weight: 500; |
| color: var(--text); |
| letter-spacing: 0.02em; |
| } |
| |
| .logo-sub { |
| font-size: 10px; |
| color: var(--text-dim); |
| font-weight: 300; |
| letter-spacing: 0.06em; |
| text-transform: uppercase; |
| } |
| |
| .header-right { |
| display: flex; |
| align-items: center; |
| gap: 16px; |
| } |
| |
| .status-pill { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| font-size: 11px; |
| color: var(--text-dim); |
| letter-spacing: 0.04em; |
| } |
| |
| .status-dot { |
| width: 5px; |
| height: 5px; |
| border-radius: 50%; |
| background: var(--text-muted); |
| transition: all 0.3s; |
| } |
| .status-dot.on { |
| background: var(--green); |
| box-shadow: 0 0 6px var(--green); |
| } |
| |
| |
| .toolbar { |
| display: flex; |
| align-items: center; |
| gap: 0; |
| padding: 6px 0; |
| border-bottom: 1px solid var(--border); |
| flex-shrink: 0; |
| } |
| |
| .tb-item { |
| font-size: 11px; |
| color: var(--text-muted); |
| padding: 4px 12px; |
| cursor: pointer; |
| border-right: 1px solid var(--border); |
| letter-spacing: 0.04em; |
| transition: color 0.15s; |
| user-select: none; |
| } |
| .tb-item:first-child { padding-left: 0; } |
| .tb-item:hover { color: var(--green); } |
| |
| .tb-sep { flex: 1; } |
| |
| .tb-model { |
| font-size: 10px; |
| color: var(--text-muted); |
| letter-spacing: 0.06em; |
| text-transform: uppercase; |
| padding-right: 0; |
| } |
| |
| |
| .chat-area { |
| flex: 1; |
| overflow-y: auto; |
| padding: 24px 0 8px; |
| display: flex; |
| flex-direction: column; |
| scrollbar-width: thin; |
| scrollbar-color: #1a1a1a transparent; |
| } |
| .chat-area::-webkit-scrollbar { width: 3px; } |
| .chat-area::-webkit-scrollbar-thumb { background: #1a1a1a; } |
| |
| |
| .hero { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| flex: 1; |
| gap: 24px; |
| padding-bottom: 80px; |
| text-align: center; |
| } |
| |
| .hero-moon { |
| position: relative; |
| width: 72px; |
| height: 72px; |
| animation: moonFloat 6s ease-in-out infinite; |
| } |
| |
| @keyframes moonFloat { |
| 0%, 100% { transform: translateY(0px); } |
| 50% { transform: translateY(-6px); } |
| } |
| |
| .hero-title { |
| font-size: 15px; |
| font-weight: 300; |
| color: var(--text-dim); |
| letter-spacing: 0.04em; |
| max-width: 400px; |
| } |
| |
| .hero-title span { |
| color: var(--green); |
| font-weight: 400; |
| } |
| |
| .hero-chips { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| justify-content: center; |
| margin-top: 4px; |
| } |
| |
| .chip { |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: 100px; |
| padding: 5px 14px; |
| font-size: 11px; |
| color: var(--text-dim); |
| cursor: pointer; |
| transition: all 0.2s; |
| font-family: var(--mono); |
| letter-spacing: 0.02em; |
| } |
| .chip:hover { |
| border-color: var(--border-hi); |
| color: var(--green); |
| background: var(--green-dim); |
| } |
| |
| |
| .msg-row { |
| padding: 18px 0; |
| border-bottom: 1px solid var(--border); |
| animation: msgIn 0.2s ease; |
| } |
| .msg-row:last-child { border-bottom: none; } |
| |
| @keyframes msgIn { |
| from { opacity: 0; transform: translateY(6px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| |
| .msg-prefix { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| margin-bottom: 8px; |
| font-size: 11px; |
| letter-spacing: 0.06em; |
| text-transform: uppercase; |
| } |
| |
| .prefix-sigil { |
| color: var(--green); |
| font-size: 13px; |
| font-weight: 700; |
| } |
| |
| .prefix-label { color: var(--text-muted); } |
| |
| |
| .msg-body { |
| font-size: 13.5px; |
| line-height: 1.75; |
| font-weight: 300; |
| color: var(--text); |
| } |
| |
| .msg-body.user { |
| color: rgba(220,220,220,0.9); |
| padding-left: 18px; |
| border-left: 1px solid var(--border); |
| } |
| |
| .msg-body.kimi { |
| padding-left: 18px; |
| border-left: 1px solid var(--border-hi); |
| } |
| |
| |
| .msg-body p { margin-bottom: 10px; } |
| .msg-body p:last-child { margin-bottom: 0; } |
| |
| .msg-body h1,.msg-body h2,.msg-body h3 { |
| font-weight: 500; |
| margin: 18px 0 8px; |
| color: #eee; |
| } |
| .msg-body h1 { font-size: 16px; } |
| .msg-body h2 { font-size: 14px; } |
| .msg-body h3 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--green); } |
| |
| .msg-body ul, .msg-body ol { |
| padding-left: 18px; |
| margin-bottom: 10px; |
| } |
| .msg-body li { margin-bottom: 3px; } |
| |
| .msg-body code { |
| font-family: var(--mono); |
| font-size: 12.5px; |
| background: var(--surface2); |
| border: 1px solid var(--border); |
| border-radius: 4px; |
| padding: 1px 5px; |
| color: var(--green); |
| } |
| |
| .msg-body pre { |
| background: #050505 !important; |
| border: 1px solid var(--border) !important; |
| border-left: 2px solid var(--green) !important; |
| border-radius: 6px 6px 0 0 !important; |
| padding: 14px 16px !important; |
| overflow-x: auto; |
| margin: 12px 0 0 !important; |
| position: relative; |
| } |
| |
| .msg-body pre code { |
| background: none !important; |
| border: none !important; |
| padding: 0 !important; |
| color: #b0b0b0 !important; |
| font-size: 12.5px !important; |
| line-height: 1.7 !important; |
| } |
| |
| |
| .code-bar { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| background: #080808; |
| border: 1px solid var(--border); |
| border-top: none; |
| border-radius: 0 0 6px 6px; |
| padding: 5px 12px; |
| margin: 0 0 12px; |
| gap: 8px; |
| } |
| |
| .code-lang { |
| font-size: 10px; |
| color: var(--text-muted); |
| letter-spacing: 0.06em; |
| text-transform: uppercase; |
| font-family: var(--mono); |
| } |
| |
| .code-actions { display: flex; gap: 6px; } |
| |
| .code-btn { |
| background: transparent; |
| border: 1px solid var(--border); |
| border-radius: 4px; |
| color: var(--text-dim); |
| font-family: var(--mono); |
| font-size: 10px; |
| padding: 3px 9px; |
| cursor: pointer; |
| transition: all 0.15s; |
| letter-spacing: 0.04em; |
| } |
| .code-btn:hover { |
| border-color: var(--border-hi); |
| color: var(--green); |
| background: var(--green-dim); |
| } |
| .code-btn.active { |
| border-color: var(--green); |
| color: var(--green); |
| background: var(--green-dim); |
| } |
| |
| |
| .preview-panel { |
| display: none; |
| border: 1px solid var(--border-hi); |
| border-top: 2px solid var(--green); |
| border-radius: 0 0 8px 8px; |
| margin: -12px 0 12px; |
| overflow: hidden; |
| background: #fff; |
| position: relative; |
| } |
| .preview-panel.open { display: block; } |
| |
| .preview-topbar { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| padding: 5px 12px; |
| background: #0d0d0d; |
| border-bottom: 1px solid var(--border); |
| } |
| |
| .preview-url { |
| font-size: 10px; |
| color: var(--text-muted); |
| font-family: var(--mono); |
| letter-spacing: 0.03em; |
| } |
| |
| .preview-resize { |
| display: flex; |
| gap: 4px; |
| } |
| |
| .resize-btn { |
| background: transparent; |
| border: 1px solid var(--border); |
| border-radius: 3px; |
| color: var(--text-muted); |
| font-family: var(--mono); |
| font-size: 9px; |
| padding: 2px 7px; |
| cursor: pointer; |
| transition: all 0.15s; |
| } |
| .resize-btn:hover { color: var(--green); border-color: var(--border-hi); } |
| .resize-btn.active { color: var(--green); border-color: var(--green); background: var(--green-dim); } |
| |
| .preview-frame-wrap { |
| width: 100%; |
| overflow: hidden; |
| transition: height 0.3s ease; |
| } |
| |
| .preview-iframe { |
| width: 100%; |
| height: 100%; |
| border: none; |
| display: block; |
| background: #fff; |
| } |
| |
| .msg-body blockquote { |
| border-left: 2px solid var(--green); |
| padding-left: 14px; |
| margin: 10px 0; |
| color: var(--text-dim); |
| font-style: italic; |
| } |
| |
| .msg-body table { |
| width: 100%; |
| border-collapse: collapse; |
| margin: 10px 0; |
| font-size: 12.5px; |
| } |
| .msg-body th, .msg-body td { |
| padding: 7px 12px; |
| border: 1px solid var(--border); |
| text-align: left; |
| } |
| .msg-body th { |
| background: var(--surface2); |
| font-weight: 500; |
| color: var(--green); |
| letter-spacing: 0.04em; |
| } |
| |
| .msg-body a { color: var(--green); text-decoration: none; } |
| .msg-body a:hover { text-decoration: underline; } |
| |
| |
| .img-pill { |
| display: inline-flex; |
| align-items: center; |
| gap: 8px; |
| background: var(--surface2); |
| border: 1px solid var(--border); |
| border-radius: 6px; |
| padding: 5px 10px; |
| margin-bottom: 8px; |
| font-size: 11px; |
| color: var(--text-dim); |
| } |
| .img-pill img { |
| width: 32px; height: 32px; |
| object-fit: cover; |
| border-radius: 3px; |
| } |
| |
| |
| .cursor { |
| display: inline-block; |
| width: 7px; |
| height: 13px; |
| background: var(--green); |
| margin-left: 2px; |
| vertical-align: middle; |
| animation: blink 0.9s step-end infinite; |
| } |
| @keyframes blink { |
| 0%,100% { opacity: 1; } |
| 50% { opacity: 0; } |
| } |
| |
| |
| .sys-line { |
| font-size: 11px; |
| color: var(--text-muted); |
| padding: 6px 0; |
| font-style: italic; |
| } |
| .sys-line.err { color: var(--error); font-style: normal; } |
| |
| |
| .input-zone { |
| flex-shrink: 0; |
| padding: 12px 0 24px; |
| } |
| |
| |
| .staged-bar { |
| display: none; |
| align-items: center; |
| gap: 10px; |
| padding: 8px 14px; |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-bottom: none; |
| border-radius: var(--radius) var(--radius) 0 0; |
| } |
| .staged-bar.show { display: flex; } |
| |
| .staged-bar img { |
| width: 32px; height: 32px; |
| object-fit: cover; |
| border-radius: 4px; |
| border: 1px solid var(--border); |
| } |
| |
| .staged-bar-name { |
| flex: 1; |
| font-size: 11px; |
| color: var(--text-dim); |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .staged-rm { |
| width: 18px; height: 18px; |
| border-radius: 50%; |
| background: transparent; |
| border: 1px solid var(--border); |
| color: var(--text-muted); |
| font-size: 10px; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: all 0.15s; |
| } |
| .staged-rm:hover { background: var(--error); border-color: var(--error); color: #fff; } |
| |
| |
| .input-box { |
| display: flex; |
| align-items: flex-end; |
| gap: 0; |
| background: var(--surface); |
| border: 1px solid var(--border); |
| border-radius: var(--radius); |
| padding: 12px 14px; |
| transition: border-color 0.2s, box-shadow 0.2s; |
| } |
| .input-box.has-img { |
| border-top-left-radius: 0; |
| border-top-right-radius: 0; |
| } |
| .input-box:focus-within { |
| border-color: var(--border-hi); |
| box-shadow: 0 0 0 1px var(--green-glow), 0 0 20px var(--green-glow); |
| } |
| |
| |
| .input-prompt { |
| font-size: 13px; |
| color: var(--green); |
| font-weight: 500; |
| user-select: none; |
| flex-shrink: 0; |
| padding-right: 8px; |
| padding-bottom: 2px; |
| letter-spacing: 0.02em; |
| } |
| |
| .input-ta { |
| flex: 1; |
| background: transparent; |
| border: none; |
| outline: none; |
| color: var(--text); |
| font-family: var(--mono); |
| font-size: 13.5px; |
| font-weight: 300; |
| line-height: 1.5; |
| resize: none; |
| min-height: 22px; |
| max-height: 180px; |
| overflow-y: auto; |
| scrollbar-width: none; |
| } |
| .input-ta::placeholder { color: var(--text-muted); } |
| .input-ta::-webkit-scrollbar { display: none; } |
| |
| .input-btns { |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| padding-left: 10px; |
| flex-shrink: 0; |
| } |
| |
| .icon-btn { |
| width: 30px; height: 30px; |
| border-radius: 6px; |
| background: transparent; |
| border: 1px solid transparent; |
| color: var(--text-dim); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.15s; |
| } |
| .icon-btn:hover { |
| background: var(--surface2); |
| border-color: var(--border); |
| color: var(--text); |
| } |
| .icon-btn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } |
| |
| .send-btn { |
| width: 30px; height: 30px; |
| border-radius: 6px; |
| background: var(--green); |
| border: none; |
| color: #000; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.15s; |
| flex-shrink: 0; |
| } |
| .send-btn:hover { background: #33eb91; } |
| .send-btn:disabled { |
| background: var(--surface2); |
| border: 1px solid var(--border); |
| color: var(--text-muted); |
| cursor: not-allowed; |
| } |
| .send-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; } |
| |
| .input-foot { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-top: 8px; |
| font-size: 10px; |
| color: var(--text-muted); |
| letter-spacing: 0.04em; |
| } |
| |
| |
| .drag-overlay { |
| display: none; |
| position: fixed; |
| inset: 0; |
| background: rgba(0,0,0,0.92); |
| z-index: 9998; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| gap: 14px; |
| border: 1px dashed rgba(0,230,118,0.25); |
| } |
| .drag-overlay.on { display: flex; } |
| .drag-overlay svg { width: 40px; height: 40px; opacity: 0.35; } |
| .drag-overlay p { font-size: 13px; color: var(--text-muted); letter-spacing: 0.04em; } |
| |
| #file-in { display: none; } |
| |
| @media (max-width: 600px) { |
| .shell { padding: 0 14px; } |
| .toolbar { display: none; } |
| .hero-chips { display: none; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| |
| <div class="drag-overlay" id="drag-overlay"> |
| <svg viewBox="0 0 24 24" fill="none" stroke="rgba(0,230,118,0.5)" stroke-width="1.5"> |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> |
| <polyline points="17 8 12 3 7 8"/> |
| <line x1="12" y1="3" x2="12" y2="15"/> |
| </svg> |
| <p>drop image to attach</p> |
| </div> |
|
|
| <div class="shell"> |
|
|
| |
| <header class="header"> |
| <div class="logo"> |
| |
| <svg class="moon-wrap" viewBox="0 0 30 30" fill="none"> |
| <circle cx="15" cy="15" r="14" fill="#080808" stroke="rgba(255,255,255,0.06)" stroke-width="1"/> |
| <circle cx="15" cy="15" r="9" fill="#0f0f0f" stroke="rgba(255,255,255,0.08)" stroke-width="0.5"/> |
| <path d="M11 11 Q15 6 19 11 Q23 15 19 19 Q15 24 11 19 Q7 15 11 11Z" |
| fill="url(#hg)" opacity="0.95"/> |
| <defs> |
| <radialGradient id="hg" cx="62%" cy="28%" r="70%"> |
| <stop offset="0%" stop-color="#fff" stop-opacity="0.95"/> |
| <stop offset="40%" stop-color="#888" stop-opacity="0.3"/> |
| <stop offset="100%" stop-color="#000" stop-opacity="0"/> |
| </radialGradient> |
| </defs> |
| </svg> |
| <div class="logo-info"> |
| <div class="logo-name">Kimi K2.7-Code</div> |
| <div class="logo-sub">Moonshot AI · Novita</div> |
| </div> |
| </div> |
| <div class="header-right"> |
| <div class="status-pill"> |
| <div class="status-dot" id="sdot"></div> |
| <span id="stxt">connecting…</span> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="toolbar"> |
| <span class="tb-item" onclick="cmd('/clear')">clear</span> |
| <span class="tb-item" onclick="cmd('/history')">history</span> |
| <span class="tb-item" onclick="cmd('/model')">model info</span> |
| <div class="tb-sep"></div> |
| <span class="tb-model">moonshotai/Kimi-K2.7-Code:novita</span> |
| </div> |
|
|
| |
| <div class="chat-area" id="chat-area"> |
| <div class="hero" id="hero"> |
| |
| <svg class="hero-moon" viewBox="0 0 72 72" fill="none"> |
| <circle cx="36" cy="36" r="35" fill="#070707" stroke="rgba(255,255,255,0.05)" stroke-width="1"/> |
| <circle cx="36" cy="36" r="22" fill="#0e0e0e" stroke="rgba(255,255,255,0.07)" stroke-width="0.5"/> |
| <path d="M26 26 Q36 14 46 26 Q56 36 46 46 Q36 58 26 46 Q16 36 26 26Z" |
| fill="url(#hm)" opacity="0.95"/> |
| <defs> |
| <radialGradient id="hm" cx="60%" cy="26%" r="72%"> |
| <stop offset="0%" stop-color="#fff" stop-opacity="1"/> |
| <stop offset="38%" stop-color="#777" stop-opacity="0.45"/> |
| <stop offset="100%" stop-color="#000" stop-opacity="0"/> |
| </radialGradient> |
| </defs> |
| </svg> |
| <p class="hero-title">Seeking the optimal conversion from<br><span>energy to intelligence</span></p> |
| <div class="hero-chips"> |
| <div class="chip" onclick="fillPrompt('Write a Python async web scraper with aiohttp')">python async scraper</div> |
| <div class="chip" onclick="fillPrompt('Explain this code and find bugs')">code review</div> |
| <div class="chip" onclick="fillPrompt('Write a Rust CLI tool for file compression')">rust cli tool</div> |
| <div class="chip" onclick="fillPrompt('What do you see in this image?')">vision analysis</div> |
| <div class="chip" onclick="fillPrompt('Design a REST API with FastAPI and SQLAlchemy')">fastapi design</div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="input-zone"> |
| <div class="staged-bar" id="staged-bar"> |
| <img id="staged-thumb" src="" alt=""> |
| <span class="staged-bar-name" id="staged-name">—</span> |
| <button class="staged-rm" onclick="clearImg()">✕</button> |
| </div> |
| <div class="input-box" id="input-box"> |
| <span class="input-prompt">>_</span> |
| <textarea |
| class="input-ta" |
| id="pta" |
| placeholder="Throw me a hard one, I'm ready." |
| rows="1" |
| autofocus |
| ></textarea> |
| <div class="input-btns"> |
| <button class="icon-btn" onclick="triggerFile()" title="Attach image"> |
| <svg viewBox="0 0 24 24"> |
| <path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/> |
| </svg> |
| </button> |
| <button class="send-btn" id="send-btn" onclick="handleSend()" title="Send (Enter)"> |
| <svg viewBox="0 0 24 24"> |
| <line x1="12" y1="19" x2="12" y2="5"/> |
| <polyline points="5 12 12 5 19 12"/> |
| </svg> |
| </button> |
| </div> |
| </div> |
| <div class="input-foot"> |
| <span>enter to send · shift+enter for newline · /clear /history /model</span> |
| <span id="foot-time">—</span> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| <input type="file" id="file-in" accept="image/*" onchange="onFileChange(event)"> |
|
|
| |
| <script type="module"> |
| import { client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
| |
| |
| let app = null; |
| let chatHistory = []; |
| let stagedFile = null; |
| let stagedB64 = ""; |
| let isGenerating = false; |
| |
| |
| setInterval(() => { |
| const el = document.getElementById("foot-time"); |
| if (el) el.textContent = new Date().toTimeString().slice(0,8); |
| }, 1000); |
| |
| |
| marked.setOptions({ breaks: true }); |
| |
| |
| async function connect() { |
| try { |
| |
| app = await client(window.location.origin); |
| setStatus(true); |
| appendSys("session established · model ready"); |
| } catch(e) { |
| setStatus(false); |
| appendSys("connection failed: " + e.message, true); |
| } |
| } |
| |
| function setStatus(on) { |
| document.getElementById("sdot").className = "status-dot" + (on ? " on" : ""); |
| document.getElementById("stxt").textContent = on ? "online" : "offline"; |
| } |
| |
| |
| window.cmd = function(c) { |
| if (c === "/clear") { |
| const ca = document.getElementById("chat-area"); |
| ca.innerHTML = ""; |
| chatHistory = []; |
| appendSys("terminal cleared"); |
| return; |
| } |
| if (c === "/history") { |
| appendSys(`history buffer: ${chatHistory.length} messages`); |
| return; |
| } |
| if (c === "/model") { |
| appendSys("model: moonshotai/Kimi-K2.7-Code:novita"); |
| appendSys("host: https://router.huggingface.co/v1"); |
| appendSys("context: 128k tokens · multimodal: yes"); |
| return; |
| } |
| }; |
| |
| |
| window.fillPrompt = function(t) { |
| const ta = document.getElementById("pta"); |
| ta.value = t; |
| ta.dispatchEvent(new Event("input")); |
| ta.focus(); |
| }; |
| |
| |
| window.triggerFile = function() { document.getElementById("file-in").click(); }; |
| window.onFileChange = function(e) { const f = e.target.files[0]; if(f) stageFile(f); }; |
| |
| function stageFile(file) { |
| if (!file.type.startsWith("image/")) return; |
| stagedFile = file; |
| const reader = new FileReader(); |
| reader.onload = ev => { |
| stagedB64 = ev.target.result; |
| document.getElementById("staged-thumb").src = stagedB64; |
| document.getElementById("staged-name").textContent = |
| file.name + " (" + (file.size/1024).toFixed(1) + " KB)"; |
| document.getElementById("staged-bar").classList.add("show"); |
| document.getElementById("input-box").classList.add("has-img"); |
| }; |
| reader.readAsDataURL(file); |
| } |
| |
| window.clearImg = function() { |
| stagedFile = null; stagedB64 = ""; |
| document.getElementById("file-in").value = ""; |
| document.getElementById("staged-bar").classList.remove("show"); |
| document.getElementById("input-box").classList.remove("has-img"); |
| }; |
| |
| |
| const overlay = document.getElementById("drag-overlay"); |
| window.addEventListener("dragenter", e => { e.preventDefault(); overlay.classList.add("on"); }); |
| overlay.addEventListener("dragover", e => e.preventDefault()); |
| overlay.addEventListener("dragleave", e => { if (!overlay.contains(e.relatedTarget)) overlay.classList.remove("on"); }); |
| overlay.addEventListener("drop", e => { |
| e.preventDefault(); overlay.classList.remove("on"); |
| const f = e.dataTransfer.files[0]; if (f) stageFile(f); |
| }); |
| |
| |
| const pta = document.getElementById("pta"); |
| pta.addEventListener("input", function() { |
| this.style.height = "auto"; |
| this.style.height = Math.min(this.scrollHeight, 180) + "px"; |
| }); |
| pta.addEventListener("keydown", e => { |
| if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSend(); } |
| }); |
| |
| |
| const ca = () => document.getElementById("chat-area"); |
| |
| function hideHero() { |
| const h = document.getElementById("hero"); |
| if (h) h.remove(); |
| } |
| |
| function scrollBot() { |
| const el = ca(); el.scrollTop = el.scrollHeight; |
| } |
| |
| function appendSys(text, isErr = false) { |
| const d = document.createElement("div"); |
| d.className = "sys-line" + (isErr ? " err" : ""); |
| d.textContent = "// " + text; |
| ca().appendChild(d); |
| scrollBot(); |
| } |
| |
| function addUserRow(prompt, imgSrc, imgName) { |
| hideHero(); |
| const row = document.createElement("div"); |
| row.className = "msg-row"; |
| let imgHtml = ""; |
| if (imgSrc) { |
| imgHtml = `<div class="img-pill"><img src="${imgSrc}" alt=""><span>${escHtml(imgName)}</span></div><br>`; |
| } |
| row.innerHTML = ` |
| <div class="msg-prefix"> |
| <span class="prefix-sigil">›</span> |
| <span class="prefix-label">you</span> |
| </div> |
| <div class="msg-body user">${imgHtml}${escHtml(prompt)}</div> |
| `; |
| ca().appendChild(row); |
| scrollBot(); |
| } |
| |
| function addKimiRow() { |
| const row = document.createElement("div"); |
| row.className = "msg-row"; |
| row.innerHTML = ` |
| <div class="msg-prefix"> |
| <span class="prefix-sigil" style="color:#888;">◎</span> |
| <span class="prefix-label">kimi</span> |
| </div> |
| <div class="msg-body kimi" id="kimi-active"><span class="cursor"></span></div> |
| `; |
| ca().appendChild(row); |
| scrollBot(); |
| return document.getElementById("kimi-active"); |
| } |
| |
| function escHtml(t) { |
| return t.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); |
| } |
| |
| |
| function makeRenderer(el) { |
| let buf = ""; |
| let pending = false; |
| |
| function render() { |
| pending = false; |
| el.innerHTML = marked.parse(buf) + '<span class="cursor"></span>'; |
| scrollBot(); |
| } |
| |
| return { |
| push(token) { |
| buf += token; |
| if (!pending) { pending = true; requestAnimationFrame(render); } |
| }, |
| finish() { |
| el.id = ""; |
| el.innerHTML = marked.parse(buf); |
| injectCodeBars(el); |
| scrollBot(); |
| return buf; |
| } |
| }; |
| } |
| |
| |
| function injectCodeBars(container) { |
| const pres = container.querySelectorAll("pre"); |
| pres.forEach(pre => { |
| |
| if (pre.nextElementSibling && pre.nextElementSibling.classList.contains("code-bar")) return; |
| |
| const codeEl = pre.querySelector("code"); |
| const rawCode = codeEl ? codeEl.textContent : pre.textContent; |
| |
| |
| const cls = codeEl ? codeEl.className : ""; |
| const langMatch = cls.match(/language-(\w+)/); |
| const lang = langMatch ? langMatch[1].toLowerCase() : ""; |
| |
| |
| const isHtml = lang === "html" || lang === "svg"; |
| const isPreviewable = isHtml || |
| (!lang && rawCode.trim().startsWith("<") && rawCode.includes("</")); |
| |
| |
| const bar = document.createElement("div"); |
| bar.className = "code-bar"; |
| |
| let previewBtn = ""; |
| if (isPreviewable || lang === "css" || lang === "js" || lang === "javascript") { |
| previewBtn = `<button class="code-btn preview-toggle" title="Render preview">▶ Preview</button>`; |
| } |
| |
| bar.innerHTML = ` |
| <span class="code-lang">${lang || "code"}</span> |
| <div class="code-actions"> |
| ${previewBtn} |
| <button class="code-btn copy-btn" title="Copy">Copy</button> |
| </div> |
| `; |
| |
| pre.after(bar); |
| |
| |
| const copyBtn = bar.querySelector(".copy-btn"); |
| copyBtn.addEventListener("click", () => { |
| navigator.clipboard.writeText(rawCode).then(() => { |
| copyBtn.textContent = "Copied!"; |
| copyBtn.classList.add("active"); |
| setTimeout(() => { copyBtn.textContent = "Copy"; copyBtn.classList.remove("active"); }, 1800); |
| }); |
| }); |
| |
| |
| const pvBtn = bar.querySelector(".preview-toggle"); |
| if (!pvBtn) return; |
| |
| |
| const panel = document.createElement("div"); |
| panel.className = "preview-panel"; |
| |
| |
| let renderSrc = rawCode; |
| if (lang === "css") { |
| renderSrc = `<!DOCTYPE html><html><head><style>${rawCode}</style></head><body><p style='font-family:sans-serif;padding:20px;color:#333'>CSS preview — add HTML to see layout</p></body></html>`; |
| } else if (lang === "js" || lang === "javascript") { |
| renderSrc = `<!DOCTYPE html><html><body><script>${rawCode}<\/script></body></html>`; |
| } |
| |
| panel.innerHTML = ` |
| <div class="preview-topbar"> |
| <span class="preview-url">▣ live preview — sandbox</span> |
| <div class="preview-resize"> |
| <button class="resize-btn" data-h="260">S</button> |
| <button class="resize-btn active" data-h="420">M</button> |
| <button class="resize-btn" data-h="620">L</button> |
| <button class="resize-btn" data-h="900">XL</button> |
| </div> |
| </div> |
| <div class="preview-frame-wrap" style="height:420px"> |
| <iframe class="preview-iframe" |
| sandbox="allow-scripts allow-forms allow-modals" |
| srcdoc="" |
| ></iframe> |
| </div> |
| `; |
| |
| bar.after(panel); |
| |
| |
| panel.querySelectorAll(".resize-btn").forEach(rb => { |
| rb.addEventListener("click", () => { |
| panel.querySelectorAll(".resize-btn").forEach(b => b.classList.remove("active")); |
| rb.classList.add("active"); |
| panel.querySelector(".preview-frame-wrap").style.height = rb.dataset.h + "px"; |
| }); |
| }); |
| |
| let loaded = false; |
| pvBtn.addEventListener("click", () => { |
| const isOpen = panel.classList.toggle("open"); |
| pvBtn.textContent = isOpen ? "▼ Hide" : "▶ Preview"; |
| pvBtn.classList.toggle("active", isOpen); |
| |
| if (isOpen && !loaded) { |
| loaded = true; |
| const iframe = panel.querySelector(".preview-iframe"); |
| iframe.srcdoc = renderSrc; |
| } |
| scrollBot(); |
| }); |
| }); |
| } |
| |
| |
| window.handleSend = async function() { |
| if (isGenerating) return; |
| const prompt = pta.value.trim(); |
| if (!prompt && !stagedFile) return; |
| |
| const imgSrc = stagedB64; |
| const imgName = stagedFile ? stagedFile.name : ""; |
| const imgB64 = stagedB64; |
| |
| pta.value = ""; pta.style.height = "auto"; |
| clearImg(); |
| |
| addUserRow(prompt, imgSrc, imgName); |
| |
| if (!app) { |
| appendSys("not connected — retrying…", true); |
| await connect(); |
| if (!app) return; |
| } |
| |
| isGenerating = true; |
| document.getElementById("send-btn").disabled = true; |
| |
| const kimiEl = addKimiRow(); |
| const renderer = makeRenderer(kimiEl); |
| let fullReply = ""; |
| |
| try { |
| |
| const job = app.submit("/chat", { |
| prompt, |
| history_json: JSON.stringify(chatHistory), |
| image_b64: imgB64 |
| }); |
| |
| for await (const msg of job) { |
| if (msg.type === "data") { |
| const token = msg.data[0]; |
| if (token) { renderer.push(token); fullReply += token; } |
| } else if (msg.type === "status" && msg.stage === "error") { |
| renderer.push("\n\n[endpoint error — check logs]"); |
| } |
| } |
| |
| fullReply = renderer.finish(); |
| |
| if (fullReply) { |
| chatHistory.push({ role: "user", content: prompt }); |
| chatHistory.push({ role: "assistant", content: fullReply }); |
| } |
| |
| } catch(err) { |
| renderer.finish(); |
| appendSys("error: " + err.message, true); |
| } finally { |
| isGenerating = false; |
| document.getElementById("send-btn").disabled = false; |
| pta.focus(); |
| } |
| }; |
| |
| connect(); |
| </script> |
| </body> |
| </html> |
|
|