simpleverso commited on
Commit
8a0d0e6
·
verified ·
1 Parent(s): ae0d392

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. README.md +18 -5
  2. index.html +208 -18
README.md CHANGED
@@ -1,10 +1,23 @@
1
  ---
2
- title: Espzap Translator
3
- emoji: 🌍
4
- colorFrom: indigo
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Traductor Español a Zapoteco
3
+ emoji: 🌽
4
+ colorFrom: green
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ license: cc-by-nc-4.0
9
+ models:
10
+ - simpleverso/espzap-nllb-600m
11
  ---
12
 
13
+ # Traductor Español → Zapoteco del Istmo (diidxazá)
14
+
15
+ Client-side translation demo. Runs entirely in the browser via
16
+ [Transformers.js](https://github.com/huggingface/transformers.js), no server
17
+ required. The quantized (int8) ONNX model is downloaded once (~1.1 GB) and cached
18
+ by the browser.
19
+
20
+ - Model: [`simpleverso/espzap-nllb-600m`](https://huggingface.co/simpleverso/espzap-nllb-600m)
21
+ - Base: `facebook/nllb-200-distilled-600M` fine-tuned with LoRA
22
+ - Source language: Spanish (`spa_Latn`); target: Isthmus Zapotec (using the
23
+ `ayr_Latn` slot, repurposed during training).
index.html CHANGED
@@ -1,19 +1,209 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="es">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Traductor Español → Zapoteco del Istmo</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1419;
10
+ --panel: #1a2029;
11
+ --border: #2a3441;
12
+ --accent: #4ea1ff;
13
+ --accent-2: #6ee7b7;
14
+ --text: #e6edf3;
15
+ --muted: #8b98a9;
16
+ }
17
+ * { box-sizing: border-box; }
18
+ body {
19
+ margin: 0;
20
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
21
+ background: var(--bg);
22
+ color: var(--text);
23
+ line-height: 1.5;
24
+ min-height: 100vh;
25
+ }
26
+ .wrap { max-width: 760px; margin: 0 auto; padding: 32px 20px 64px; }
27
+ header { text-align: center; margin-bottom: 8px; }
28
+ h1 { font-size: 1.6rem; margin: 0 0 6px; }
29
+ .sub { color: var(--muted); font-size: 0.95rem; margin: 0 0 24px; text-align: center; }
30
+ .card {
31
+ background: var(--panel);
32
+ border: 1px solid var(--border);
33
+ border-radius: 14px;
34
+ padding: 18px;
35
+ margin-bottom: 16px;
36
+ }
37
+ label { display: block; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 8px; }
38
+ textarea {
39
+ width: 100%;
40
+ min-height: 110px;
41
+ resize: vertical;
42
+ background: #10151c;
43
+ color: var(--text);
44
+ border: 1px solid var(--border);
45
+ border-radius: 10px;
46
+ padding: 12px 14px;
47
+ font-size: 1.05rem;
48
+ font-family: inherit;
49
+ }
50
+ textarea:focus { outline: none; border-color: var(--accent); }
51
+ .out {
52
+ min-height: 110px;
53
+ background: #10151c;
54
+ border: 1px solid var(--border);
55
+ border-radius: 10px;
56
+ padding: 12px 14px;
57
+ font-size: 1.15rem;
58
+ white-space: pre-wrap;
59
+ color: var(--accent-2);
60
+ }
61
+ .row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
62
+ button {
63
+ background: var(--accent);
64
+ color: #05233f;
65
+ border: none;
66
+ border-radius: 10px;
67
+ padding: 12px 22px;
68
+ font-size: 1rem;
69
+ font-weight: 600;
70
+ cursor: pointer;
71
+ transition: opacity 0.15s;
72
+ }
73
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
74
+ button:not(:disabled):hover { opacity: 0.9; }
75
+ .hint { color: var(--muted); font-size: 0.85rem; }
76
+ #status { font-size: 0.9rem; color: var(--muted); margin-top: 10px; min-height: 1.2em; }
77
+ .bar { height: 8px; background: #10151c; border-radius: 6px; overflow: hidden; margin-top: 10px; border: 1px solid var(--border); }
78
+ .bar > div { height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); transition: width 0.2s; }
79
+ .examples { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
80
+ .chip {
81
+ background: #10151c; border: 1px solid var(--border); color: var(--muted);
82
+ border-radius: 20px; padding: 6px 12px; font-size: 0.85rem; cursor: pointer;
83
+ }
84
+ .chip:hover { border-color: var(--accent); color: var(--text); }
85
+ footer { text-align: center; color: var(--muted); font-size: 0.82rem; margin-top: 28px; }
86
+ a { color: var(--accent); }
87
+ .warn { background: #2a2410; border-color: #524717; color: #e8d48b; }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <div class="wrap">
92
+ <header>
93
+ <h1>Traductor Español → Zapoteco del Istmo</h1>
94
+ </header>
95
+ <p class="sub">diidxazá · modelo NLLB-200 afinado · corre 100% en tu navegador</p>
96
+
97
+ <div class="card warn" id="notice">
98
+ La primera vez, el modelo (~1.1 GB) se descarga y se guarda en caché en tu
99
+ navegador. Puede tardar según tu conexión. Después carga al instante.
100
+ </div>
101
+
102
+ <div class="card">
103
+ <label for="src">Español</label>
104
+ <textarea id="src" placeholder="Escribe una frase en español...">Buenos días, ¿cómo estás?</textarea>
105
+ <div class="examples" id="examples"></div>
106
+ <div class="row">
107
+ <button id="go" disabled>Cargando modelo…</button>
108
+ <span class="hint">o presiona Ctrl/Cmd + Enter</span>
109
+ </div>
110
+ <div id="status">Inicializando…</div>
111
+ <div class="bar" id="barwrap" style="display:none;"><div id="bar"></div></div>
112
+ </div>
113
+
114
+ <div class="card">
115
+ <label for="out">Diidxazá (Zapoteco del Istmo)</label>
116
+ <div class="out" id="out"></div>
117
+ </div>
118
+
119
+ <footer>
120
+ Modelo:
121
+ <a href="https://huggingface.co/simpleverso/espzap-nllb-600m" target="_blank" rel="noopener">simpleverso/espzap-nllb-600m</a>
122
+ · Inferencia en el navegador con
123
+ <a href="https://github.com/huggingface/transformers.js" target="_blank" rel="noopener">Transformers.js</a>
124
+ </footer>
125
+ </div>
126
+
127
+ <script type="module">
128
+ import { pipeline, env } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1";
129
+
130
+ // Load the model from the Hugging Face Hub (browser cache handles re-loads).
131
+ env.allowLocalModels = false;
132
+
133
+ const MODEL_ID = "simpleverso/espzap-nllb-600m";
134
+ const SRC_LANG = "spa_Latn";
135
+ const TGT_LANG = "ayr_Latn"; // NLLB slot repurposed as Isthmus Zapotec
136
+
137
+ const $ = (id) => document.getElementById(id);
138
+ const srcEl = $("src"), outEl = $("out"), goEl = $("go");
139
+ const statusEl = $("status"), barWrap = $("barwrap"), bar = $("bar");
140
+
141
+ const EXAMPLES = ["Te quiero mucho", "Vamos al río", "El sol brilla", "¿Cómo te llamas?"];
142
+ const exWrap = $("examples");
143
+ for (const ex of EXAMPLES) {
144
+ const c = document.createElement("span");
145
+ c.className = "chip"; c.textContent = ex;
146
+ c.onclick = () => { srcEl.value = ex; };
147
+ exWrap.appendChild(c);
148
+ }
149
+
150
+ const fileProgress = {};
151
+ function onProgress(p) {
152
+ if (p.status === "progress" && p.file) {
153
+ fileProgress[p.file] = p.progress || 0;
154
+ const vals = Object.values(fileProgress);
155
+ const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
156
+ barWrap.style.display = "block";
157
+ bar.style.width = avg.toFixed(1) + "%";
158
+ statusEl.textContent = `Descargando modelo… ${avg.toFixed(0)}%`;
159
+ } else if (p.status === "ready") {
160
+ statusEl.textContent = "";
161
+ }
162
+ }
163
+
164
+ let translator = null;
165
+ (async () => {
166
+ try {
167
+ translator = await pipeline("translation", MODEL_ID, {
168
+ dtype: "q8",
169
+ progress_callback: onProgress,
170
+ });
171
+ barWrap.style.display = "none";
172
+ statusEl.textContent = "Modelo listo.";
173
+ goEl.disabled = false;
174
+ goEl.textContent = "Traducir";
175
+ } catch (e) {
176
+ statusEl.textContent = "Error al cargar el modelo: " + e.message;
177
+ console.error(e);
178
+ }
179
+ })();
180
+
181
+ async function translate() {
182
+ const text = srcEl.value.trim();
183
+ if (!text || !translator) return;
184
+ goEl.disabled = true;
185
+ goEl.textContent = "Traduciendo…";
186
+ outEl.textContent = "";
187
+ try {
188
+ const res = await translator(text, {
189
+ src_lang: SRC_LANG,
190
+ tgt_lang: TGT_LANG,
191
+ max_new_tokens: 96,
192
+ });
193
+ outEl.textContent = res[0].translation_text;
194
+ } catch (e) {
195
+ outEl.textContent = "Error: " + e.message;
196
+ console.error(e);
197
+ } finally {
198
+ goEl.disabled = false;
199
+ goEl.textContent = "Traducir";
200
+ }
201
+ }
202
+
203
+ goEl.addEventListener("click", translate);
204
+ srcEl.addEventListener("keydown", (e) => {
205
+ if ((e.metaKey || e.ctrlKey) && e.key === "Enter") translate();
206
+ });
207
+ </script>
208
+ </body>
209
  </html>