Javedalam's picture
Update index.html
01723a1 verified
raw
history blame
6.76 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>NLLB Translation (WebGPU/WASM) + Progress</title>
<style>
body { font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 16px; }
h1 { margin-top:0; }
label { display:block; margin: 8px 0 4px; }
textarea { width: 100%; min-height: 120px; }
select, input[type="submit"] { padding: 8px; font: inherit; }
#row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
#progress-wrap { margin: 10px 0; display:none; }
#footer { opacity:.7; margin-top:12px; font-size:12px; }
.muted { opacity:.8 }
.error { color:#b00 }
.ok { color:#0a0 }
</style>
</head>
<body>
<h1>NLLB translation in browser</h1>
<div id="row">
<div>
<label for="src_lang">Source language</label>
<select id="src_lang">
<option value="eng_Latn" selected>English (eng_Latn)</option>
<option value="spa_Latn">Spanish (spa_Latn)</option>
<option value="fra_Latn">French (fra_Latn)</option>
<option value="hin_Deva">Hindi (hin_Deva)</option>
</select>
</div>
<div>
<label for="tgt_lang">Target language</label>
<select id="tgt_lang">
<option value="spa_Latn" selected>Spanish (spa_Latn)</option>
<option value="eng_Latn">English (eng_Latn)</option>
<option value="fra_Latn">French (fra_Latn)</option>
<option value="hin_Deva">Hindi (hin_Deva)</option>
</select>
</div>
</div>
<label for="from">Input text</label>
<textarea id="from" placeholder="Type text to translate"></textarea>
<!-- Progress UI -->
<div id="progress-wrap">
<progress id="loadProgress" value="0" max="1" style="width:100%;"></progress>
<div id="progressText" class="muted">Initializing…</div>
</div>
<input type="submit" id="submit" value="Loading..." disabled />
<label for="to" style="margin-top:12px">Output</label>
<textarea id="to" readonly>Output will be here...</textarea>
<div id="footer" class="muted">
Tip: first load downloads the model; later loads are much faster thanks to browser cache.
</div>
<div id="libStatus" class="error"></div>
<!-- Loader that tries multiple CDNs, then local file -->
<script>
(function loadTransformersUMD() {
const tries = [
"https://cdn.jsdelivr.net/npm/@xenova/transformers@3.0.0/dist/transformers.min.js",
"https://unpkg.com/@xenova/transformers@3.0.0/dist/transformers.min.js",
"https://huggingface.co/datasets/Xenova/transformers.js/resolve/main/3.0.0/transformers.min.js",
"./transformers.min.js" // <- upload this file as a final fallback
];
const statusEl = document.getElementById("libStatus");
function tryNext(i) {
if (i >= tries.length) {
statusEl.textContent = "❌ Failed to load Transformers.js from all sources.";
return;
}
const src = tries[i];
const s = document.createElement("script");
s.src = src;
s.async = true;
s.onload = () => {
if (window.transformers) {
statusEl.className = "ok";
statusEl.textContent = "✅ Transformers.js loaded from: " + src;
initApp();
} else {
statusEl.textContent = "⚠️ Loaded but window.transformers missing: " + src;
tryNext(i + 1);
}
};
s.onerror = () => {
statusEl.textContent = "⚠️ Failed: " + src + " — trying next…";
tryNext(i + 1);
};
document.head.appendChild(s);
}
tryNext(0);
// initApp runs only after the library is present
function initApp() {
const btn = document.getElementById("submit");
const out = document.getElementById("to");
const fromEl = document.getElementById("from");
const srcEl = document.getElementById("src_lang");
const tgtEl = document.getElementById("tgt_lang");
const wrap = document.getElementById("progress-wrap");
const bar = document.getElementById("loadProgress");
const txt = document.getElementById("progressText");
btn.disabled = false;
btn.value = "Translate";
const tf = window.transformers;
let translator = null;
function showProgressUI(show, message) {
wrap.style.display = show ? "" : "none";
if (message) txt.textContent = message;
bar.value = 0; bar.max = 1;
}
function progressCallback(p) {
if (p.status) txt.textContent = p.status;
if (typeof p.loaded === "number" && typeof p.total === "number" && p.total > 0) {
bar.max = p.total;
bar.value = p.loaded;
const pct = Math.round((p.loaded / p.total) * 100);
txt.textContent = `Downloading ${p.file || "model"}${pct}%`;
}
}
async function getTranslator() {
if (translator) return translator;
showProgressUI(true, "Loading model…");
try {
const { pipeline, env } = tf;
env.useBrowserCache = true;
env.allowLocalModels = false;
// If WASM fallback happens, more threads help a bit
env.backends.onnx.wasm.numThreads = Math.max(4, Math.min(8, navigator.hardwareConcurrency || 4));
translator = await pipeline("translation", "Xenova/nllb-200-distilled-600M", {
device: (navigator.gpu ? "webgpu" : "wasm"),
progress_callback: progressCallback
});
txt.textContent = "✅ Model ready";
bar.max = 1; bar.value = 1;
return translator;
} catch (e) {
console.error(e);
txt.textContent = "❌ Error loading model — see console.";
throw e;
}
}
btn.addEventListener("click", async () => {
const prev = btn.value;
btn.disabled = true; btn.value = "Working…";
try {
const t = await getTranslator();
const text = (fromEl.value || "").trim();
if (!text) { out.value = "Please type some text."; return; }
const res = await t(text, { src_lang: srcEl.value, tgt_lang: tgtEl.value, max_length: 128 });
out.value = res?.[0]?.translation_text || "(no output)";
} catch (e) {
out.value = "❌ Translation failed. See console.";
} finally {
btn.disabled = false; btn.value = prev;
}
});
}
})();
</script>
</body>
</html>