Javedalam's picture
Update index.html
26f161e verified
raw
history blame
6.21 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>NLLB translation in browser</title>
<style>
body { font:14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin:16px; }
h1 { margin:0 0 12px }
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 }
.muted { opacity:.8 }
.ok { color:#0a0 } .err{ color:#b00 } .warn{ color:#b66 }
</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>
<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="status" class="muted" style="margin-top:8px">Loading library…</div>
<script>
const statusEl = document.getElementById('status');
async function loadTransformers() {
// Try ESM from CDNs (like julien-c’s page)
const cdns = [
'https://cdn.jsdelivr.net/npm/@xenova/transformers@3.0.0',
'https://unpkg.com/@xenova/transformers@3.0.0'
];
for (const url of cdns) {
try {
statusEl.textContent = `Trying ESM: ${url}`;
const mod = await import(/* @vite-ignore */ url);
if (mod?.pipeline) {
window.transformers = mod; // expose
statusEl.textContent = `✅ Loaded via ESM: ${url}`;
return;
}
} catch (_) {}
}
// Fallback: local UMD (needs real ~3–4 MB bundle in repo root)
statusEl.textContent = 'Trying local UMD ./transformers.min.js…';
await new Promise((resolve) => {
const s = document.createElement('script');
s.src = './transformers.min.js?v=' + Math.random();
s.onload = resolve; s.onerror = resolve;
document.head.appendChild(s);
});
if (window.transformers?.pipeline) {
statusEl.textContent = '✅ Loaded via local UMD (./transformers.min.js)';
return;
}
statusEl.textContent = '❌ Could not load Transformers.js (ESM or UMD).';
}
function showProgressUI(show, message) {
const wrap = document.getElementById("progress-wrap");
const txt = document.getElementById("progressText");
const bar = document.getElementById("loadProgress");
wrap.style.display = show ? "" : "none";
if (message) txt.textContent = message;
bar.value = 0; bar.max = 1;
}
function progressCallback(p) {
const txt = document.getElementById("progressText");
const bar = document.getElementById("loadProgress");
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 () => {
await loadTransformers();
const tf = window.transformers;
if (!(tf && tf.pipeline)) return;
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");
// env setup
const { env } = tf;
env.useBrowserCache = true;
env.allowLocalModels = false;
env.backends.onnx.wasm.numThreads = Math.max(4, Math.min(8, navigator.hardwareConcurrency || 4));
btn.disabled = false;
btn.value = "Translate";
let translator = null;
async function getTranslator() {
if (translator) return translator;
showProgressUI(true, "Loading model…");
translator = await tf.pipeline("translation", "Xenova/nllb-200-distilled-600M", {
device: (navigator.gpu ? "webgpu" : "wasm"),
progress_callback: progressCallback
});
document.getElementById("progressText").textContent = "✅ Model ready";
const bar = document.getElementById("loadProgress"); bar.max = 1; bar.value = 1;
return translator;
}
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) {
console.error(e);
out.value = "❌ Translation failed. See console.";
} finally {
btn.disabled = false; btn.value = prev;
}
});
})();
</script>
</body>
</html>