File size: 5,587 Bytes
bb12dec ff7fea2 bb12dec 4ecd433 bb12dec 4ecd433 bb12dec |
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 |
<!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 }
</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>
<!-- Load UMD bundle (no ESM/dynamic import).
If jsDelivr is blocked, try unpkg:
https://unpkg.com/@xenova/transformers@3.0.0/dist/transformers.min.js
-->
<script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@3.0.0/dist/transformers.min.js"></script>
<script>
// Grab DOM elements
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");
// Show UI immediately
btn.disabled = false;
btn.value = "Translate";
// Transformers.js UMD exposes window.transformers
const tf = window.transformers;
if (!tf) {
document.body.insertAdjacentHTML('beforeend',
'<p style="color:#b00">Failed to load Transformers.js. Check CDN/network.</p>');
}
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;
// WASM fallback uses threads; harmless if WebGPU available
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
// If you hit issues with quant hints, you can add:
// dtype: { encoder_model: "q8", decoder_model_merged: "q8" },
});
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 for details.";
} finally {
btn.disabled = false; btn.value = prev;
}
});
</script>
</body>
</html> |