denela-api / web /app.js
DenizGogus's picture
Upload folder using huggingface_hub
0cd630c verified
Raw
History Blame Contribute Delete
6 kB
const sourceEl = document.querySelector("#source");
const targetEl = document.querySelector("#target");
const inputEl = document.querySelector("#input");
const translateBtn = document.querySelector("#translate");
const clearBtn = document.querySelector("#clearInput");
const statusEl = document.querySelector("#status");
const denelaOut = document.querySelector("#denelaOut");
const englishOut = document.querySelector("#englishOut");
const turkishOut = document.querySelector("#turkishOut");
const checkline = document.querySelector("#checkline");
const breakdownBody = document.querySelector("#breakdown");
const analysisEl = document.querySelector("#analysis");
const warningsEl = document.querySelector("#warnings");
const tokenCountEl = document.querySelector("#tokenCount");
const breakdownColumns = [
["form", "Form"],
["pos", "Part"],
["meaning_en", "English"],
["meaning_tr", "Turkish"],
["status", "Status"]
];
function setStatus(text, state = "") {
statusEl.className = `status ${state}`.trim();
statusEl.innerHTML = "";
const dot = document.createElement("span");
dot.className = "status-dot";
const label = document.createElement("span");
label.textContent = text;
statusEl.append(dot, label);
}
async function loadStatus() {
const response = await fetch("/api/status");
const status = await response.json();
const backend = status.local_model_available ? "local model" : status.translation_backend;
const model = status.local_model_available
? "specialized translators"
: [status.llm_provider, status.llm_model].filter(Boolean).join(" · ");
const pieces = [backend, model, `${status.lexicon_entries} entries`].filter(Boolean);
setStatus(pieces.join(" · "), "ready");
}
function renderRows(rows) {
breakdownBody.innerHTML = "";
tokenCountEl.textContent = `${rows?.length || 0} tokens`;
if (!rows || !rows.length) {
const tr = document.createElement("tr");
tr.className = "empty-row";
const td = document.createElement("td");
td.colSpan = 5;
td.textContent = "No Denela tokens";
tr.appendChild(td);
breakdownBody.appendChild(tr);
return;
}
for (const row of rows) {
const tr = document.createElement("tr");
for (const [key, label] of breakdownColumns) {
const td = document.createElement("td");
td.dataset.label = label;
td.textContent = row[key] || "";
tr.appendChild(td);
}
breakdownBody.appendChild(tr);
}
}
function renderWarnings(result) {
warningsEl.innerHTML = "";
const warnings = [
...(result.warnings || []),
...((result.validation && result.validation.warnings) || [])
];
for (const warning of warnings) {
const li = document.createElement("li");
li.textContent = warning;
warningsEl.appendChild(li);
}
}
function renderCheckline(result) {
checkline.className = "checkline";
const confidence = Number(result.confidence);
const hasConfidence = Number.isFinite(confidence) && confidence > 0;
if (result.validation && result.validation.ok === false) {
checkline.classList.add("review");
checkline.textContent = "Review needed";
return;
}
if (result.meaning_preserved === true) {
checkline.textContent = hasConfidence
? `Meaning preserved · ${confidence.toFixed(2)}`
: "Meaning preserved";
return;
}
if (result.meaning_preserved === false) {
checkline.classList.add("review");
checkline.textContent = hasConfidence
? `Meaning needs review · ${confidence.toFixed(2)}`
: "Meaning needs review";
return;
}
checkline.textContent = result.backend === "local_model" ? "Local model" : "Translated";
}
function renderResult(result) {
denelaOut.textContent = result.denela || "";
englishOut.textContent = result.english || "";
turkishOut.textContent = result.turkish || "";
analysisEl.textContent = result.analysis || "";
renderCheckline(result);
renderRows(result.tokens || []);
renderWarnings(result);
}
async function translate() {
const text = inputEl.value.trim();
if (!text) return;
translateBtn.disabled = true;
translateBtn.textContent = "Translating";
checkline.className = "checkline";
checkline.textContent = "Working";
try {
const response = await fetch("/api/translate", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({
text,
source: sourceEl.value,
target: targetEl.value
})
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText);
}
renderResult(await response.json());
} catch (error) {
checkline.className = "checkline error";
checkline.textContent = "Translation failed";
analysisEl.textContent = error.message;
renderRows([]);
warningsEl.innerHTML = "";
} finally {
translateBtn.disabled = false;
translateBtn.textContent = "Translate";
}
}
async function copyTarget(targetId, button) {
const target = document.querySelector(`#${targetId}`);
const text = target?.textContent?.trim();
if (!text) return;
try {
await navigator.clipboard.writeText(text);
const original = button.textContent;
button.textContent = "Copied";
setTimeout(() => {
button.textContent = original;
}, 1100);
} catch {
button.textContent = "Copy failed";
setTimeout(() => {
button.textContent = "Copy";
}, 1100);
}
}
translateBtn.addEventListener("click", translate);
clearBtn.addEventListener("click", () => {
inputEl.value = "";
inputEl.focus();
});
document.querySelectorAll("[data-copy-target]").forEach((button) => {
button.addEventListener("click", () => copyTarget(button.dataset.copyTarget, button));
});
inputEl.addEventListener("keydown", (event) => {
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
translate();
}
});
loadStatus().catch(() => {
setStatus("Status unavailable", "error");
});
renderRows([]);
translate();