Download index.html from ogx786/chatbot: direct link, hf CLI and curl.
- Browser
- Download file 9.27 kB
-
https://huggingface.co/ogx786/chatbot/resolve/main/index.html
- Command line
-
hf download hf://ogx786/chatbot/index.html
-
curl -L -o index.html https://huggingface.co/ogx786/chatbot/resolve/main/index.html
9.27 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>HBL Internal Assistant</title> | |
| <style> | |
| :root { | |
| --bg: #14181a; | |
| --panel: #1b2124; | |
| --panel-2: #222a2d; | |
| --line: #2c3538; | |
| --text: #ece9e2; | |
| --text-dim: #8fa998; | |
| --accent: #1f7a4d; | |
| --accent-bright: #2fa869; | |
| --danger: #b2503f; | |
| } | |
| * { box-sizing: border-box; } | |
| html, body { | |
| height: 100%; margin: 0; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: -apple-system, "Segoe UI", Inter, Roboto, sans-serif; | |
| } | |
| #app { display: flex; flex-direction: column; height: 100vh; } | |
| header { | |
| display: flex; align-items: center; justify-content: space-between; | |
| padding: 14px 20px; | |
| border-bottom: 1px solid var(--line); | |
| background: var(--panel); | |
| } | |
| .brand { display: flex; align-items: center; gap: 10px; } | |
| .brand-mark { | |
| width: 26px; height: 26px; border-radius: 7px; | |
| background: linear-gradient(135deg, var(--accent-bright), var(--accent)); | |
| flex-shrink: 0; | |
| } | |
| .brand-text { font-size: 15px; font-weight: 600; letter-spacing: 0.2px; } | |
| .brand-sub { font-size: 11px; color: var(--text-dim); margin-top: 1px; } | |
| .status { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-dim); } | |
| .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-bright); } | |
| .dot.offline { background: var(--danger); } | |
| button.reset { | |
| background: transparent; border: 1px solid var(--line); color: var(--text-dim); | |
| padding: 6px 12px; border-radius: 7px; font-size: 12px; cursor: pointer; | |
| } | |
| button.reset:hover { border-color: var(--accent-bright); color: var(--text); } | |
| main { flex: 1; overflow-y: auto; padding: 24px 0; } | |
| .thread { max-width: 760px; margin: 0 auto; padding: 0 20px; display: flex; flex-direction: column; gap: 18px; } | |
| .empty-state { text-align: center; color: var(--text-dim); margin-top: 18vh; font-size: 14px; line-height: 1.7; } | |
| .empty-state b { color: var(--text); } | |
| .msg { display: flex; gap: 12px; } | |
| .msg .avatar { | |
| width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0; | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 11px; font-weight: 700; | |
| } | |
| .msg.user .avatar { background: var(--panel-2); color: var(--text-dim); } | |
| .msg.assistant .avatar { background: var(--accent); color: #fff; } | |
| .msg .bubble { | |
| padding: 10px 14px; border-radius: 10px; line-height: 1.55; font-size: 14.5px; | |
| white-space: pre-wrap; word-wrap: break-word; max-width: 100%; | |
| } | |
| .msg.user .bubble { background: var(--panel-2); } | |
| .msg.assistant .bubble { background: transparent; padding-left: 0; } | |
| .cursor { display: inline-block; width: 7px; height: 15px; background: var(--accent-bright); margin-left: 2px; animation: blink 1s step-start infinite; vertical-align: text-bottom; } | |
| @keyframes blink { 50% { opacity: 0; } } | |
| form#composer { | |
| border-top: 1px solid var(--line); background: var(--panel); | |
| padding: 14px 20px 18px; | |
| } | |
| .composer-inner { max-width: 760px; margin: 0 auto; display: flex; gap: 10px; align-items: flex-end; } | |
| textarea#input { | |
| flex: 1; resize: none; max-height: 160px; min-height: 44px; | |
| background: var(--panel-2); color: var(--text); border: 1px solid var(--line); | |
| border-radius: 10px; padding: 11px 14px; font-size: 14.5px; font-family: inherit; | |
| outline: none; line-height: 1.4; | |
| } | |
| textarea#input:focus { border-color: var(--accent-bright); } | |
| button#send { | |
| background: var(--accent); color: #fff; border: none; border-radius: 10px; | |
| padding: 0 18px; height: 44px; font-size: 14px; font-weight: 600; cursor: pointer; | |
| } | |
| button#send:disabled { background: var(--panel-2); color: var(--text-dim); cursor: not-allowed; } | |
| .hint { text-align: center; font-size: 11px; color: var(--text-dim); margin-top: 8px; } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="app"> | |
| <header> | |
| <div class="brand"> | |
| <div class="brand-mark"></div> | |
| <div> | |
| <div class="brand-text">HBL Internal Assistant</div> | |
| <div class="brand-sub">Runs locally on the GPU server · not connected to the internet</div> | |
| </div> | |
| </div> | |
| <div style="display:flex; align-items:center; gap:14px;"> | |
| <div class="status"><span id="status-dot" class="dot"></span><span id="status-text">connecting…</span></div> | |
| <button class="reset" id="reset-btn">Reset chat</button> | |
| </div> | |
| </header> | |
| <main> | |
| <div class="thread" id="thread"> | |
| <div class="empty-state" id="empty-state"> | |
| Ask about <b>HBL policies, products, or processes</b> —<br> | |
| or ask it to draft or edit a professional email. | |
| </div> | |
| </div> | |
| </main> | |
| <form id="composer"> | |
| <div class="composer-inner"> | |
| <textarea id="input" placeholder="Message the assistant…" rows="1"></textarea> | |
| <button id="send" type="submit">Send</button> | |
| </div> | |
| <div class="hint">Enter to send · Shift+Enter for a new line</div> | |
| </form> | |
| </div> | |
| <script> | |
| // --------------------------------------------------------------------- | |
| // Config — same origin as this page by default. Change if the API is | |
| // served from a different host/port than the one hosting this file. | |
| // --------------------------------------------------------------------- | |
| const API_BASE = ""; | |
| const threadEl = document.getElementById("thread"); | |
| const emptyStateEl = document.getElementById("empty-state"); | |
| const inputEl = document.getElementById("input"); | |
| const sendBtn = document.getElementById("send"); | |
| const composerEl = document.getElementById("composer"); | |
| const resetBtn = document.getElementById("reset-btn"); | |
| const statusDot = document.getElementById("status-dot"); | |
| const statusText = document.getElementById("status-text"); | |
| let sessionId = localStorage.getItem("hbl_session_id"); | |
| let generating = false; | |
| async function ensureSession() { | |
| if (sessionId) return sessionId; | |
| const res = await fetch(`${API_BASE}/session/new`, { method: "POST" }); | |
| const data = await res.json(); | |
| sessionId = data.session_id; | |
| localStorage.setItem("hbl_session_id", sessionId); | |
| return sessionId; | |
| } | |
| async function checkHealth() { | |
| try { | |
| const res = await fetch(`${API_BASE}/health`); | |
| const data = await res.json(); | |
| statusDot.classList.remove("offline"); | |
| statusText.textContent = `online \u00b7 ${data.device.toUpperCase()}`; | |
| } catch (e) { | |
| statusDot.classList.add("offline"); | |
| statusText.textContent = "server unreachable"; | |
| } | |
| } | |
| function addMessage(role, text) { | |
| emptyStateEl.style.display = "none"; | |
| const wrap = document.createElement("div"); | |
| wrap.className = `msg ${role}`; | |
| const avatar = document.createElement("div"); | |
| avatar.className = "avatar"; | |
| avatar.textContent = role === "user" ? "You" : "AI"; | |
| const bubble = document.createElement("div"); | |
| bubble.className = "bubble"; | |
| bubble.textContent = text; | |
| wrap.appendChild(avatar); | |
| wrap.appendChild(bubble); | |
| threadEl.appendChild(wrap); | |
| wrap.scrollIntoView({ behavior: "smooth", block: "end" }); | |
| return bubble; | |
| } | |
| function autosize() { | |
| inputEl.style.height = "auto"; | |
| inputEl.style.height = Math.min(inputEl.scrollHeight, 160) + "px"; | |
| } | |
| inputEl.addEventListener("input", autosize); | |
| inputEl.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter" && !e.shiftKey) { | |
| e.preventDefault(); | |
| composerEl.requestSubmit(); | |
| } | |
| }); | |
| composerEl.addEventListener("submit", async (e) => { | |
| e.preventDefault(); | |
| const message = inputEl.value.trim(); | |
| if (!message || generating) return; | |
| await ensureSession(); | |
| inputEl.value = ""; | |
| autosize(); | |
| addMessage("user", message); | |
| const assistantBubble = addMessage("assistant", ""); | |
| const cursor = document.createElement("span"); | |
| cursor.className = "cursor"; | |
| assistantBubble.appendChild(cursor); | |
| generating = true; | |
| sendBtn.disabled = true; | |
| sendBtn.textContent = "Generating…"; | |
| try { | |
| const res = await fetch(`${API_BASE}/chat`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ session_id: sessionId, message }), | |
| }); | |
| if (!res.ok || !res.body) throw new Error(`Server returned ${res.status}`); | |
| const reader = res.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| let full = ""; | |
| while (true) { | |
| const { value, done } = await reader.read(); | |
| if (done) break; | |
| full += decoder.decode(value, { stream: true }); | |
| assistantBubble.textContent = full; | |
| assistantBubble.appendChild(cursor); | |
| threadEl.scrollTop = threadEl.scrollHeight; | |
| } | |
| cursor.remove(); | |
| } catch (err) { | |
| cursor.remove(); | |
| assistantBubble.textContent = `\u26a0\ufe0f Couldn't reach the assistant (${err.message}). Check the server is running and reachable.`; | |
| } finally { | |
| generating = false; | |
| sendBtn.disabled = false; | |
| sendBtn.textContent = "Send"; | |
| inputEl.focus(); | |
| } | |
| }); | |
| resetBtn.addEventListener("click", async () => { | |
| if (!sessionId) return; | |
| await fetch(`${API_BASE}/session/${sessionId}/reset`, { method: "POST" }); | |
| threadEl.innerHTML = ""; | |
| threadEl.appendChild(emptyStateEl); | |
| emptyStateEl.style.display = "block"; | |
| }); | |
| checkHealth(); | |
| ensureSession(); | |
| inputEl.focus(); | |
| </script> | |
| </body> | |
| </html> |