File size: 5,023 Bytes
b8ce00d 29265d4 b8ce00d 29265d4 0eacb73 b8ce00d 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 0eacb73 29265d4 b8ce00d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | <!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Chatbot Browser — Transformers.js</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="app">
<header class="topbar">
<div class="title">🐠 Chatbot Browser</div>
<div class="controls">
<select id="model">
<option value="onnx-community/Qwen2.5-0.5B-Instruct">Qwen2.5 0.5B (cepat)</option>
<option value="HuggingFaceTB/SmolLM2-360M-Instruct">SmolLM2 360M (ringan)</option>
<option value="onnx-community/Llama-3.2-1B-Instruct-q4f16">Llama 3.2 1B (pintar)</option>
</select>
<button id="load">Muat</button>
<button id="clear" title="Bersihkan chat">🗑</button>
</div>
</header>
<div id="chat" class="chat">
<div class="msg bot">
<div class="bubble">Halo! Pilih model lalu klik <b>Muat</b>, terus mulai ngobrol. Semua jalan di browser kamu — tanpa server. 🚀</div>
</div>
</div>
<div class="status" id="status">Belum ada model dimuat.</div>
<form id="form" class="composer">
<input id="input" type="text" placeholder="Tulis pesan…" autocomplete="off" disabled />
<button id="send" type="submit" disabled>Kirim</button>
</form>
</main>
<script type="module">
import { pipeline, TextStreamer } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.2";
const chat = document.getElementById("chat");
const form = document.getElementById("form");
const input = document.getElementById("input");
const sendBtn = document.getElementById("send");
const loadBtn = document.getElementById("load");
const clearBtn = document.getElementById("clear");
const modelSel = document.getElementById("model");
const statusEl = document.getElementById("status");
const hasWebGPU = !!navigator.gpu;
let generator = null;
let busy = false;
let messages = [
{ role: "system", content: "You are a helpful, friendly assistant. Reply concisely." },
];
function addMsg(role, text) {
const wrap = document.createElement("div");
wrap.className = "msg " + (role === "user" ? "user" : "bot");
const bubble = document.createElement("div");
bubble.className = "bubble";
bubble.textContent = text;
wrap.appendChild(bubble);
chat.appendChild(wrap);
chat.scrollTop = chat.scrollHeight;
return bubble;
}
async function loadModel() {
const id = modelSel.value;
busy = true;
loadBtn.disabled = true;
input.disabled = true;
sendBtn.disabled = true;
generator = null;
statusEl.textContent = `Mengunduh ${id}… (sekali saja, bisa beberapa menit)`;
try {
generator = await pipeline("text-generation", id, {
dtype: hasWebGPU ? "q4f16" : "q4",
device: hasWebGPU ? "webgpu" : "wasm",
progress_callback: (p) => {
if (p.status === "progress" && p.file) {
const pct = Math.round(p.progress || 0);
statusEl.textContent = `Mengunduh ${p.file}: ${pct}%`;
}
},
});
statusEl.textContent = `Siap ✔ (${id} · ${hasWebGPU ? "WebGPU" : "WASM/CPU"})`;
input.disabled = false;
sendBtn.disabled = false;
input.focus();
} catch (e) {
statusEl.textContent = "Gagal memuat model: " + e.message;
}
busy = false;
loadBtn.disabled = false;
}
async function send(text) {
if (!generator || busy) return;
busy = true;
input.disabled = true;
sendBtn.disabled = true;
addMsg("user", text);
messages.push({ role: "user", content: text });
const bubble = addMsg("bot", "");
bubble.classList.add("typing");
let acc = "";
const streamer = new TextStreamer(generator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function: (t) => {
acc += t;
bubble.textContent = acc;
chat.scrollTop = chat.scrollHeight;
},
});
try {
await generator(messages, {
max_new_tokens: 512,
do_sample: true,
temperature: 0.7,
top_p: 0.9,
streamer,
});
messages.push({ role: "assistant", content: acc.trim() });
} catch (e) {
bubble.textContent = "⚠ Error: " + e.message;
}
bubble.classList.remove("typing");
busy = false;
input.disabled = false;
sendBtn.disabled = false;
input.focus();
}
form.addEventListener("submit", (e) => {
e.preventDefault();
const text = input.value.trim();
if (!text) return;
input.value = "";
send(text);
});
loadBtn.addEventListener("click", loadModel);
clearBtn.addEventListener("click", () => {
chat.innerHTML = "";
messages = messages.slice(0, 1);
addMsg("bot", "Chat dibersihkan. Lanjut ngobrol! 🙂");
});
statusEl.textContent = hasWebGPU
? "WebGPU terdeteksi. Pilih model & klik Muat."
: "WebGPU tidak ada — pakai CPU (lebih lambat). Pilih model & klik Muat.";
</script>
</body>
</html>
|