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>