sanskrit-parser-api / frontend /html /text_parser.html
psugam's picture
Upload 4 files
e33019a verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sanskrit Text Parser</title>
<style>
:root {
--primary: #1e4d6b;
--secondary: #8b6914;
--accent: #8b3a1a;
--bg: #faf8f3;
--card-bg: #ffffff;
--text: #1a1a1a;
--muted: #5a5a5a;
--border-soft: #d4d4d4;
--border-medium: #b8b8b8;
--entry-border: #e8e5df;
}
body {
font-family: "Crimson Pro", "Georgia", "Times New Roman", serif;
background: linear-gradient(to bottom, #f5f2eb 0%, #faf8f3 100%);
color: var(--text);
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
}
.container {
max-width: 960px;
margin: auto;
padding: 32px 24px;
}
/* ===== NAVBAR ===== */
nav {
background: var(--card-bg);
border-bottom: 1px solid var(--border-medium);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
position: sticky;
top: 0;
z-index: 100;
}
.nav-container {
max-width: 960px;
margin: 0 auto;
padding: 0 24px;
display: flex;
justify-content: space-between;
align-items: center;
height: 64px;
}
.nav-brand {
font-family: "Crimson Pro", Georgia, serif;
font-size: 1.35rem;
font-weight: 700;
color: var(--primary);
text-decoration: none;
}
.nav-links {
display: flex;
gap: 8px;
align-items: center;
}
.nav-links a {
font-family: "Inter", system-ui, sans-serif;
font-size: 0.95rem;
font-weight: 600;
color: var(--muted);
text-decoration: none;
padding: 8px 20px;
border-radius: 8px;
transition: all 0.2s ease;
}
.nav-links a:hover {
color: var(--primary);
background: #f5f2eb;
}
.nav-links a.active {
color: white;
background: var(--primary);
}
/* ===== INPUT SECTION ===== */
.input-section {
background: var(--card-bg);
padding: 22px 26px;
border-radius: 12px;
border: 1px solid var(--border-medium);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
margin-bottom: 32px;
}
textarea {
width: 100%;
height: 120px;
padding: 16px;
font-size: 1.1rem;
border: 1.5px solid var(--border-soft);
border-radius: 8px;
margin-bottom: 16px;
box-sizing: border-box;
font-family: "Crimson Pro", serif;
background: #fafafa;
transition: all 0.2s ease;
}
textarea:focus {
outline: none;
border-color: var(--primary);
background: white;
}
button {
padding: 12px 28px;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-family: "Inter", sans-serif;
font-size: 0.95rem;
}
/* ===== PARSED TEXT ===== */
#parsed-text {
background: var(--card-bg);
padding: 42px 48px;
border-radius: 12px;
border: 1px solid var(--border-medium);
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.05);
font-size: 1.65em;
line-height: 2.1em;
margin-bottom: 32px;
min-height: 100px;
}
.word-token {
cursor: pointer;
color: var(--primary);
border-bottom: 1.5px dotted var(--secondary);
transition: 0.2s;
display: inline-block;
padding: 0 4px;
}
.word-token:hover {
color: var(--accent);
background-color: #f5f2eb;
}
/* ===== MODAL ===== */
.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
}
.modal-content {
background: var(--bg);
margin: 5% auto;
padding: 30px;
width: 85%;
max-width: 800px;
border-radius: 16px;
position: relative;
max-height: 85vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.close-btn {
position: absolute;
right: 20px;
top: 15px;
font-size: 35px;
cursor: pointer;
color: var(--accent);
line-height: 1;
}
/* ===== COMPOUND UI ===== */
.split-container {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin: 22px 0;
}
.part-btn {
background: white;
border: 1.5px solid var(--primary);
color: var(--primary);
padding: 8px 18px;
border-radius: 999px;
cursor: pointer;
font-weight: 600;
font-size: 0.95em;
}
.part-btn.active {
background: var(--primary);
color: white;
}
/* ===== RESULTS ===== */
.meaning-card {
background: white;
border-radius: 10px;
padding: 28px 32px;
margin-top: 20px;
border: 1px solid var(--border-medium);
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.04);
}
.label-pill {
display: inline-block;
background: var(--primary);
color: white;
padding: 6px 14px;
border-radius: 6px;
font-size: 0.7em;
font-weight: 700;
text-transform: uppercase;
font-family: "Inter", sans-serif;
margin-bottom: 16px;
}
.dict-title {
display: block;
margin-top: 1.6em;
margin-bottom: 0.5em;
padding-top: 1.2em;
border-top: 2px solid var(--entry-border);
font-weight: 700;
font-size: 1.1em;
color: #2d3748;
font-family: "Inter", system-ui, sans-serif;
}
.dict-title:first-of-type {
margin-top: 0.8em;
padding-top: 0;
border-top: none;
}
.def-content {
margin-top: 12px;
color: #2d3748;
line-height: 1.75;
font-family: "Crimson Pro", Georgia, serif;
}
.def-content .skt {
font-weight: 600;
color: #1a1a1a;
}
.def-content .abbr {
font-size: 0.9em;
font-weight: 600;
color: #4a5568;
}
@media (max-width: 640px) {
.nav-container {
flex-direction: column;
height: auto;
padding: 16px 24px;
gap: 12px;
}
.nav-links {
width: 100%;
justify-content: center;
}
}
</style>
</head>
<body>
<nav>
<div class="nav-container">
<a href="../../../index.html" class="nav-brand"
>Sanskrit Smart Reader</a
>
<div class="nav-links">
<a href="../../../index.html">Home</a>
<a href="about.html">About</a>
<a href="text_parser.html" class="active">Parser</a>
</div>
</div>
</nav>
<div class="container">
<h2 style="color: var(--primary); letter-spacing: -0.02em">
Sanskrit Text Parser
</h2>
<div class="input-section">
<p style="margin-top: 0; color: var(--muted); font-weight: 600">
Enter Sanskrit text below and click Analyze.
</p>
<textarea id="input-text" placeholder="Enter text here..."></textarea>
<button onclick="transformText()">Analyze</button>
</div>
<div id="parsed-text"></div>
</div>
<div id="wordModal" class="modal">
<div class="modal-content">
<span class="close-btn" onclick="closeModal()">&times;</span>
<div id="modal-body"></div>
</div>
</div>
<script>
const base_api = "https://psugam-sanskrit-parser-api.hf.space/";
console.log("Using API base:", base_api);
const parsedTextContainer = document.getElementById("parsed-text");
const modal = document.getElementById("wordModal");
const modalBody = document.getElementById("modal-body");
// Close modal logic
window.onclick = (e) => {
if (e.target == modal) closeModal();
};
function closeModal() {
modal.style.display = "none";
modalBody.innerHTML = "";
}
function sanitizeHTML(str) {
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
}
function sanitizeAllowHTML(input) {
const decoder = document.createElement("textarea");
decoder.innerHTML = input;
let html = decoder.value;
html = html
.replace(/<div[^>]*\/>/gi, "<br>")
.replace(/<lbinfo[^>]*\/>/gi, "")
.replace(/<\/?meta[^>]*>/gi, "");
const replacements = {
s: 'span class="skt"',
ab: 'span class="abbr"',
lex: 'span class="pos"',
ls: 'span class="source"',
};
for (const [tag, repl] of Object.entries(replacements)) {
html = html.replace(new RegExp(`<${tag}[^>]*>`, "gi"), `<${repl}>`);
html = html.replace(
new RegExp(`</${tag}>`, "gi"),
`</${repl.split(" ")[0]}>`
);
}
html = html.replace(
/<(?!\/?(br|b|i|em|strong|sup|sub|span)\b)[^>]+>/gi,
""
);
return html;
}
function transformText() {
const input = document.getElementById("input-text").value;
parsedTextContainer.innerHTML = "";
const tokens = input.split(/(\s+|[।.।])/);
tokens.forEach((token) => {
if (token.trim()) {
const clean = token.replace(/[।.।\s]/g, "");
if (clean) {
const span = document.createElement("span");
span.className = "word-token";
span.textContent = sanitizeHTML(token);
span.onclick = () => initiateProcess(clean);
parsedTextContainer.appendChild(span);
} else {
parsedTextContainer.appendChild(
document.createTextNode(sanitizeHTML(token))
);
}
}
});
}
async function initiateProcess(word) {
modalBody.innerHTML = `<p style="color: var(--muted)">Analyzing <b>${sanitizeHTML(
word
)}</b>…</p>`;
modal.style.display = "block";
const res = await fetch(
`${base_api}/split?word=${encodeURIComponent(word)}`
);
const data = await res.json();
if (data.is_compound)
renderCompoundUI(word, data.components, modalBody);
else renderDirectMeaning(word, modalBody);
}
function renderCompoundUI(word, parts, target) {
target.innerHTML = `
<h3 style="color: var(--primary)">${sanitizeHTML(word)}</h3>
<div class="split-container">
${parts
.map(
(p) =>
`<button class="part-btn" onclick="fetchPartMeaning('${sanitizeHTML(
p
)}','compound-res',this)">${sanitizeHTML(p)}</button>`
)
.join("")}
</div>
<div id="compound-res"></div>`;
}
async function renderDirectMeaning(word, target) {
target.innerHTML = `<h3 style="color: var(--primary)">${sanitizeHTML(
word
)}</h3><div id="direct-res"></div>`;
fetchPartMeaning(word, "direct-res");
}
async function fetchPartMeaning(word, id, btn = null) {
if (btn) {
btn.parentElement
.querySelectorAll(".part-btn")
.forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
}
const display = document.getElementById(id);
display.innerHTML = "Fetching dictionary…";
const res = await fetch(
`${base_api}/meaning?word=${encodeURIComponent(word)}`
);
const data = await res.json();
const sourceMap = {
mw: "Monier-Williams",
ap90: "Apte",
cae: "Cappeller",
bhs: "Buddhist",
};
display.innerHTML = data
.map((e) => {
// Format the grammar tags: e.g., "Nom, Sg | Acc, Sg"
const tags = e.detected_tags.map((t) => t.join(", ")).join(" | ");
return `
<div class="meaning-card">
<span class="label-pill">${sanitizeHTML(e.type)}</span>
<div class="grammar-tags" style="color: var(--accent); font-weight: bold; margin-bottom: 10px; font-size: 0.85em; text-transform: uppercase; letter-spacing: 0.5px;">
${sanitizeHTML(tags)}
</div>
<div style="margin-bottom: 10px;"><b>Stem:</b> ${sanitizeHTML(
e.stem
)}</div>
${Object.entries(e.definitions)
.map(
([src, defs]) => `
<span class="dict-title">${sourceMap[src] || src}</span>
<div class="def-content">
${defs.map((d) => `• ${sanitizeAllowHTML(d)}`).join("<br>")}
</div>`
)
.join("")}
</div>`;
})
.join("");
}
</script>
</body>
</html>