espzap-translator / index.html
simpleverso's picture
Update index.html
a16589f verified
Raw
History Blame Contribute Delete
8.04 kB
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Traductor Experimental Español → Zapoteco del Istmo</title>
<style>
:root {
--bg: #0f1419;
--panel: #1a2029;
--border: #2a3441;
--accent: #4ea1ff;
--accent-2: #6ee7b7;
--text: #e6edf3;
--muted: #8b98a9;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
min-height: 100vh;
}
.wrap { max-width: 760px; margin: 0 auto; padding: 32px 20px 64px; }
header { text-align: center; margin-bottom: 8px; }
h1 { font-size: 1.6rem; margin: 0 0 6px; }
.sub { color: var(--muted); font-size: 0.95rem; margin: 0 0 24px; text-align: center; }
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 14px;
padding: 18px;
margin-bottom: 16px;
}
label { display: block; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-bottom: 8px; }
textarea {
width: 100%;
min-height: 110px;
resize: vertical;
background: #10151c;
color: var(--text);
border: 1px solid var(--border);
border-radius: 10px;
padding: 12px 14px;
font-size: 1.05rem;
font-family: inherit;
}
textarea:focus { outline: none; border-color: var(--accent); }
.out {
min-height: 110px;
background: #10151c;
border: 1px solid var(--border);
border-radius: 10px;
padding: 12px 14px;
font-size: 1.15rem;
white-space: pre-wrap;
color: var(--accent-2);
}
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
button {
background: var(--accent);
color: #05233f;
border: none;
border-radius: 10px;
padding: 12px 22px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.15s;
}
button:disabled { opacity: 0.5; cursor: not-allowed; }
button:not(:disabled):hover { opacity: 0.9; }
.hint { color: var(--muted); font-size: 0.85rem; }
#status { font-size: 0.9rem; color: var(--muted); margin-top: 10px; min-height: 1.2em; }
.bar { height: 8px; background: #10151c; border-radius: 6px; overflow: hidden; margin-top: 10px; border: 1px solid var(--border); }
.bar > div { height: 100%; width: 0%; background: linear-gradient(90deg, var(--accent), var(--accent-2)); transition: width 0.2s; }
.examples { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
.chip {
background: #10151c; border: 1px solid var(--border); color: var(--muted);
border-radius: 20px; padding: 6px 12px; font-size: 0.85rem; cursor: pointer;
}
.chip:hover { border-color: var(--accent); color: var(--text); }
footer { text-align: center; color: var(--muted); font-size: 0.82rem; margin-top: 28px; }
a { color: var(--accent); }
.warn { background: #2a2410; border-color: #524717; color: #e8d48b; }
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>Traductor Experimental Español → Zapoteco del Istmo</h1>
</header>
<p class="sub">Diidxazá · basado en modelo NLLB-200 · corre 100% en tu navegador</p>
<div class="card warn" id="notice">
La primera vez, el modelo (~1.1 GB) se descarga y se guarda en caché en tu
navegador. Puede tardar según tu conexión. Después carga al instante.
</div>
<div class="card">
<label for="src">Español</label>
<textarea id="src" placeholder="Escribe una frase en español...">Quiero verte</textarea>
<div class="examples" id="examples"></div>
<div class="row">
<button id="go" disabled>Cargando modelo…</button>
<span class="hint">o presiona Ctrl/Cmd + Enter</span>
</div>
<div id="status">Inicializando…</div>
<div class="bar" id="barwrap" style="display:none;"><div id="bar"></div></div>
</div>
<div class="card">
<label for="out">Diidxazá (Zapoteco del Istmo)</label>
<div class="out" id="out"></div>
</div>
<footer>
Modelo:
<a href="https://huggingface.co/simpleverso/espzap-nllb-600m" target="_blank" rel="noopener">simpleverso/espzap-nllb-600m</a>
· Inferencia en el navegador con
<a href="https://github.com/huggingface/transformers.js" target="_blank" rel="noopener">Transformers.js. </a>
El traductor de español a zapoteco del istmo es un proyecto experimental con la finalidad de generar herramientas capaces de realizar traducciones al idioma Zapoteco desde el español. Sin embargo estas traducciones no siempre son exactas, por lo que se debe evitar su uso en información médica, documentación oficial, información técnica, tareas, libros, ensayos, tatuajes, etc. y se debe preferir siempre la traducción humana.
Cualquier traducción se proporciona únicamente para su conveniencia. El autor no otorga ninguna garantía de ningún tipo, expresa o implícita, en cuanto a la precisión, confiabilidad o corrección de cualquier traducción. Cualquier uso o confianza en el texto traducido es bajo el propio riesgo del usuario. El autor no se hace responsable de ningún daño, pérdida o perjuicio que resulte del uso de esta herramienta de traducción o de la confianza depositada en ella.
</footer>
</div>
<script type="module">
import { pipeline, env } from "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1";
// Load the model from the Hugging Face Hub (browser cache handles re-loads).
env.allowLocalModels = false;
const MODEL_ID = "simpleverso/espzap-nllb-600m";
const SRC_LANG = "spa_Latn";
const TGT_LANG = "ayr_Latn"; // NLLB slot repurposed as Isthmus Zapotec
const $ = (id) => document.getElementById(id);
const srcEl = $("src"), outEl = $("out"), goEl = $("go");
const statusEl = $("status"), barWrap = $("barwrap"), bar = $("bar");
const EXAMPLES = ["Te quiero mucho", "quiero verte", "un gato negro en la noche", "quiero ver tu risa todo el día"];
const exWrap = $("examples");
for (const ex of EXAMPLES) {
const c = document.createElement("span");
c.className = "chip"; c.textContent = ex;
c.onclick = () => { srcEl.value = ex; };
exWrap.appendChild(c);
}
const fileProgress = {};
function onProgress(p) {
if (p.status === "progress" && p.file) {
fileProgress[p.file] = p.progress || 0;
const vals = Object.values(fileProgress);
const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
barWrap.style.display = "block";
bar.style.width = avg.toFixed(1) + "%";
statusEl.textContent = `Descargando modelo… ${avg.toFixed(0)}%`;
} else if (p.status === "ready") {
statusEl.textContent = "";
}
}
let translator = null;
(async () => {
try {
translator = await pipeline("translation", MODEL_ID, {
dtype: "q8",
progress_callback: onProgress,
});
barWrap.style.display = "none";
statusEl.textContent = "Modelo listo.";
goEl.disabled = false;
goEl.textContent = "Traducir";
} catch (e) {
statusEl.textContent = "Error al cargar el modelo: " + e.message;
console.error(e);
}
})();
async function translate() {
const text = srcEl.value.trim();
if (!text || !translator) return;
goEl.disabled = true;
goEl.textContent = "Traduciendo…";
outEl.textContent = "";
try {
const res = await translator(text, {
src_lang: SRC_LANG,
tgt_lang: TGT_LANG,
max_new_tokens: 96,
});
outEl.textContent = res[0].translation_text;
} catch (e) {
outEl.textContent = "Error: " + e.message;
console.error(e);
} finally {
goEl.disabled = false;
goEl.textContent = "Traducir";
}
}
goEl.addEventListener("click", translate);
srcEl.addEventListener("keydown", (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") translate();
});
</script>
</body>
</html>