Spaces:
Sleeping
Sleeping
| 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(); | |