Spaces:
Running
Running
File size: 5,715 Bytes
2d28d93 1473218 87c1899 846a060 9406201 3630083 1473218 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 87c1899 3630083 ebde5c8 3630083 02afc65 | 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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | import { Tokenizer } from "./tokenizers.mjs";
// IndexedDB helper for caching tokenizer JSON.
// IndexedDB can store large blobs, unlike localStorage's ~5MB cap.
const dbName = "tokenizerCacheDB";
const storeName = "tokenizerCache";
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(dbName, 1);
request.onupgradeneeded = () => {
request.result.createObjectStore(storeName);
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
let dbPromise;
async function getCache(modelId) {
if (!dbPromise) dbPromise = openDB();
const db = await dbPromise;
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readonly");
const get = tx.objectStore(storeName).get(modelId);
get.onsuccess = () => resolve(get.result);
get.onerror = () => reject(get.error);
});
}
async function setCache(modelId, tokenizerJSON) {
if (!dbPromise) dbPromise = openDB();
const db = await dbPromise;
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readwrite");
tx.objectStore(storeName).put(tokenizerJSON, modelId);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
async function clearCache() {
if (!dbPromise) dbPromise = openDB();
const db = await dbPromise;
return new Promise((resolve, reject) => {
const tx = db.transaction(storeName, "readwrite");
tx.objectStore(storeName).clear();
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
}
// Fetch a tokenizer.json while reporting download progress via the
// <progress> element. Uses the response stream so we can count bytes.
async function fetchTokenizer(modelId) {
const response = await fetch(`https://huggingface.co/${modelId}/resolve/main/tokenizer.json`);
if (!response.ok) {
throw new Error(`Download failed (HTTP ${response.status})`);
}
const total = Number(response.headers.get("content-length")) || 0;
const reader = response.body.getReader();
const chunks = [];
let received = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
if (total) showProgress((received / total) * 100);
}
const bytes = new Uint8Array(received);
let offset = 0;
for (const chunk of chunks) {
bytes.set(chunk, offset);
offset += chunk.length;
}
return JSON.parse(new TextDecoder().decode(bytes));
}
const statusEl = document.querySelector("#status");
const progressEl = document.querySelector("#progress");
const progressWrapEl = document.querySelector("#progress-wrap");
function setStatus(text, isError = false) {
statusEl.textContent = text;
statusEl.hidden = !text;
statusEl.classList.toggle("error", isError);
}
function showProgress(percent) {
progressEl.value = percent;
progressWrapEl.hidden = false;
}
function hideProgress() {
progressEl.value = 0;
progressWrapEl.hidden = true;
}
let tokenizeButton = document.querySelector("#tokenize");
tokenizeButton.addEventListener("click", async () => {
tokenizeButton.disabled = true;
setStatus("");
hideProgress();
const modelId = document.querySelector("#model").value;
try {
const text = document.querySelector("#text").value;
let tokenizerJSON;
setStatus(`Loading tokenizer for ${modelId}…`);
try {
tokenizerJSON = await getCache(modelId);
} catch (err) {
console.error("Failed to read from IndexedDB:", err);
}
if (tokenizerJSON === undefined) {
setStatus(`Downloading ${modelId} tokenizer…`);
tokenizerJSON = await fetchTokenizer(modelId);
try {
await setCache(modelId, tokenizerJSON);
} catch (err) {
console.error("Failed to write to IndexedDB:", err);
}
} else {
setStatus(`Loading cached tokenizer…`);
}
hideProgress();
setStatus("Building tokenizer…");
let tokenizer = new Tokenizer(tokenizerJSON, {});
setStatus("Tokenizing…");
// Yield to the event loop so the browser can paint the status message
// before the synchronous encode/decode blocks the main thread. (A rAF
// alone runs *before* paint, so it wouldn't show.)
await new Promise((resolve) => setTimeout(resolve, 50));
let tokenized = tokenizer.encode(text);
document.querySelector("#totalLength").textContent = `Total length: ${tokenized.ids.length}`;
let output = document.querySelector("#output");
output.contentDocument.body.innerHTML = "";
let colors = ["#ff00007f", "#00ff007f", "#ffff007f", "#0000ff7f"].sort(() => Math.random() - 0.5);
tokenized.ids.forEach((token, index) => {
let content = document.createElement("span");
content.textContent = tokenizer.decode([token]);
content.style.backgroundColor = colors[index % colors.length];
content.style.fontFamily = "monospace";
content.style.whiteSpace = "pre-wrap";
content.title = token.toString();
output.contentDocument.body.appendChild(content);
});
// Clear feedback once done (no lingering completion message).
setStatus("");
} catch (err) {
console.error(err);
hideProgress();
setStatus(`Error: ${err.message}. Try clearing the cache.`, true);
} finally {
tokenizeButton.disabled = false;
}
});
document.querySelector("#clear-cache").addEventListener("click", async () => {
try {
await clearCache();
setStatus("Cache cleared.");
} catch (err) {
console.error("Failed to clear cache:", err);
setStatus(`Error clearing cache: ${err.message}`, true);
}
}); |