final_year / frontend /app.js
jayasrees's picture
Initial commit
414e495
const currentHost = window.location.hostname || "127.0.0.1";
const API_BASES = [
`http://${currentHost}:5000/api`,
`http://${currentHost}:5001/api`,
"http://127.0.0.1:5000/api",
"http://localhost:5000/api",
"http://127.0.0.1:5001/api",
"http://localhost:5001/api",
];
const ANALYZE_URLS = [
`http://${currentHost}:5000/api/analyze`,
`http://${currentHost}:5000/analyze`,
`http://${currentHost}:5001/api/analyze`,
`http://${currentHost}:5001/analyze`,
"http://127.0.0.1:5000/api/analyze",
"http://127.0.0.1:5000/analyze",
"http://localhost:5000/api/analyze",
"http://localhost:5000/analyze",
"http://127.0.0.1:5001/api/analyze",
"http://127.0.0.1:5001/analyze",
"http://localhost:5001/api/analyze",
"http://localhost:5001/analyze",
];
const page = (window.location.pathname.split("/").pop() || "index.html").toLowerCase();
function escapeHtml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function setText(el, text, type = null) {
if (!el) return;
el.textContent = text;
el.classList.remove("success", "error");
if (type) el.classList.add(type);
}
function getUser() {
const userRaw = sessionStorage.getItem("lsi_user");
if (!userRaw) return null;
try {
return JSON.parse(userRaw);
} catch {
return null;
}
}
function setUser(user) {
sessionStorage.setItem("lsi_user", JSON.stringify(user));
}
function clearSession() {
sessionStorage.removeItem("lsi_user");
sessionStorage.removeItem("lsi_analysis_payload");
}
function getAnalysisPayload() {
const raw = sessionStorage.getItem("lsi_analysis_payload");
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
return null;
}
}
function setAnalysisPayload(payload) {
sessionStorage.setItem("lsi_analysis_payload", JSON.stringify(payload));
}
function normalizeSpaces(text) {
return String(text || "").replace(/\s+/g, " ").trim();
}
function ensureAuth() {
const user = getUser();
if (!user) {
window.location.href = "index.html#home";
return null;
}
const badge = document.getElementById("userBadge");
if (badge) {
badge.textContent = `${user.fullName || user.email || "User"}`;
}
const logoutBtn = document.getElementById("logoutBtn");
if (logoutBtn) {
logoutBtn.addEventListener("click", () => {
clearSession();
window.location.href = "index.html#home";
});
}
return user;
}
async function postAuth(endpoint, payload) {
let response = null;
let data = null;
let lastNetworkError = null;
for (const base of API_BASES) {
try {
response = await fetch(`${base}${endpoint}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
data = await response.json().catch(() => null);
lastNetworkError = null;
break;
} catch (error) {
lastNetworkError = error;
}
}
if (lastNetworkError) {
throw new Error(`Cannot reach backend at ${API_BASES.join(", ")}.`);
}
return { response, data };
}
async function runDocumentAnalysis(formData) {
let response = null;
let data = null;
let lastNetworkError = null;
let status = null;
for (const url of ANALYZE_URLS) {
try {
response = await fetch(url, { method: "POST", body: formData });
data = await response.json().catch(() => null);
status = response.status;
lastNetworkError = null;
if (response.status !== 404) break;
} catch (error) {
lastNetworkError = error;
}
}
if (lastNetworkError) {
throw new Error("Cannot connect to backend for analysis.");
}
if (!response.ok) {
throw new Error(data?.error || `Analysis request failed with HTTP ${status || response.status}.`);
}
return data;
}
function buildIssueRows(lineIssues, category) {
const rows = lineIssues
.filter((item) => item.category === category)
.slice(0, 80)
.map(
(item) => `
<tr>
<td>${escapeHtml(item.location || `Pg ${item.page}, Ln ${item.line}`)}</td>
<td>${escapeHtml(item.issueType || "-")}</td>
<td>${escapeHtml(item.confidence ?? "-")}</td>
</tr>
`
)
.join("");
if (!rows) {
return `<p class="result-muted">No ${category} lines detected.</p>`;
}
return `
<div class="table-wrap">
<table class="result-table">
<thead>
<tr>
<th>Page/Line</th>
<th>Issue Type</th>
<th>Confidence</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
`;
}
function initIndexPage() {
const loginTab = document.getElementById("loginTab");
const signupTab = document.getElementById("signupTab");
const authForm = document.getElementById("authForm");
const nameField = document.getElementById("nameField");
const fullNameInput = document.getElementById("fullName");
const emailInput = document.getElementById("email");
const passwordInput = document.getElementById("password");
const submitBtn = document.getElementById("submitBtn");
const formSubtitle = document.getElementById("formSubtitle");
const message = document.getElementById("message");
let mode = "login";
function setMode(nextMode) {
mode = nextMode;
const isSignup = mode === "signup";
signupTab.classList.toggle("active", isSignup);
loginTab.classList.toggle("active", !isSignup);
nameField.classList.toggle("hidden", !isSignup);
submitBtn.textContent = isSignup ? "Create Account" : "Login";
formSubtitle.textContent = isSignup
? "Create your account to start securely."
: "Enter your credentials to access your account.";
fullNameInput.required = isSignup;
setText(message, "", null);
}
async function handleAuthSubmit(event) {
event.preventDefault();
setText(message, "", null);
const email = emailInput.value.trim();
const password = passwordInput.value;
const fullName = fullNameInput.value.trim();
if (!email || !password || (mode === "signup" && !fullName)) {
setText(message, "Please fill all required fields.", "error");
return;
}
submitBtn.disabled = true;
try {
const endpoint = mode === "signup" ? "/register" : "/login";
const payload = mode === "signup" ? { fullName, email, password } : { email, password };
const { response, data } = await postAuth(endpoint, payload);
if (!response.ok) {
throw new Error(data?.error || `Request failed with HTTP ${response.status}.`);
}
if (mode === "signup") {
setText(message, "Account created. Please login now.", "success");
authForm.reset();
setMode("login");
return;
}
const user = data?.user || { fullName: fullName || email, email };
setUser(user);
window.location.href = "upload.html";
} catch (error) {
setText(message, error.message || "Something went wrong.", "error");
} finally {
submitBtn.disabled = false;
}
}
loginTab.addEventListener("click", () => setMode("login"));
signupTab.addEventListener("click", () => setMode("signup"));
authForm.addEventListener("submit", handleAuthSubmit);
setMode("login");
if (getUser()) {
window.location.href = "upload.html";
}
}
function initUploadPage() {
if (!ensureAuth()) return;
const uploadForm = document.getElementById("uploadForm");
const legalFile = document.getElementById("legalFile");
const referenceFiles = document.getElementById("referenceFiles");
const scanMode = document.getElementById("scanMode");
const uploadMessage = document.getElementById("uploadMessage");
const loadingState = document.getElementById("loadingState");
const analysisInputSummary = document.getElementById("analysisInputSummary");
function renderUploadSummary() {
if (!legalFile.files || !legalFile.files[0]) return;
const selectedFile = legalFile.files[0];
const refs = Array.from((referenceFiles && referenceFiles.files) ? referenceFiles.files : []).slice(0, 2);
const refNames = refs.length ? refs.map((f) => escapeHtml(f.name)).join(", ") : "None";
analysisInputSummary.classList.remove("hidden");
analysisInputSummary.innerHTML = `
<p><strong>Final File:</strong> ${escapeHtml(selectedFile.name)}</p>
<p><strong>Final Type:</strong> ${escapeHtml(selectedFile.type || "unknown")}</p>
<p><strong>Final Size:</strong> ${escapeHtml((selectedFile.size / 1024).toFixed(2))} KB</p>
<p><strong>Reference Docs:</strong> ${refs.length}</p>
<p><strong>Reference Names:</strong> ${refNames}</p>
<p><strong>Scan Mode:</strong> ${escapeHtml(scanMode.value)}</p>
`;
setText(uploadMessage, `Final document selected: ${selectedFile.name}`, "success");
}
legalFile.addEventListener("change", () => {
renderUploadSummary();
});
if (referenceFiles) {
referenceFiles.addEventListener("change", () => {
const refs = Array.from(referenceFiles.files || []);
if (refs.length > 2) {
setText(uploadMessage, "Please select at most 2 reference documents.", "error");
referenceFiles.value = "";
return;
}
renderUploadSummary();
});
}
scanMode.addEventListener("change", () => {
renderUploadSummary();
});
uploadForm.addEventListener("submit", async (event) => {
event.preventDefault();
setText(uploadMessage, "", null);
if (!legalFile.files || legalFile.files.length === 0) {
setText(uploadMessage, "Please choose a file to continue.", "error");
return;
}
const selectedFile = legalFile.files[0];
const selectedScanMode = scanMode.value;
const refs = Array.from((referenceFiles && referenceFiles.files) ? referenceFiles.files : []);
if (refs.length > 2) {
setText(uploadMessage, "You can upload up to 2 reference documents.", "error");
return;
}
const formData = new FormData();
formData.append("file", selectedFile);
formData.append("scanMode", selectedScanMode);
refs.forEach((file) => formData.append("referenceFiles", file));
uploadForm.classList.add("hidden");
loadingState.classList.remove("hidden");
try {
const payload = await runDocumentAnalysis(formData);
payload._meta = {
fileName: selectedFile.name,
fileType: selectedFile.type || "unknown",
fileSizeKb: Number((selectedFile.size / 1024).toFixed(2)),
referenceFiles: refs.map((f) => f.name),
};
setAnalysisPayload(payload);
window.location.href = "issues.html";
} catch (error) {
loadingState.classList.add("hidden");
uploadForm.classList.remove("hidden");
setText(uploadMessage, error.message || "Analysis failed.", "error");
}
});
}
function initIssuesPage() {
if (!ensureAuth()) return;
const payload = getAnalysisPayload();
if (!payload) {
window.location.href = "upload.html";
return;
}
const summary = payload.summary || {};
const lineIssues = Array.isArray(payload.finalLineIssues)
? payload.finalLineIssues
: Array.isArray(payload.lineIssues)
? payload.lineIssues
: [];
const issueStats = document.getElementById("issueStats");
issueStats.innerHTML = `
<article class="stat-card stat-dup">
<h3>Duplication</h3>
<p>${escapeHtml(summary.duplicationCount ?? 0)}</p>
</article>
<article class="stat-card stat-inc">
<h3>Inconsistency</h3>
<p>${escapeHtml(summary.inconsistencyCount ?? 0)}</p>
</article>
<article class="stat-card stat-con">
<h3>Contradiction</h3>
<p>${escapeHtml(summary.contradictionCount ?? 0)}</p>
</article>
`;
const lineIssueTables = document.getElementById("lineIssueTables");
lineIssueTables.innerHTML = `
<section class="result-card">
<h4>Duplication Lines</h4>
${buildIssueRows(lineIssues, "duplication")}
</section>
<section class="result-card">
<h4>Inconsistency Lines</h4>
${buildIssueRows(lineIssues, "inconsistency")}
</section>
<section class="result-card">
<h4>Contradiction Lines</h4>
${buildIssueRows(lineIssues, "contradiction")}
</section>
`;
}
function initSummaryPage() {
if (!ensureAuth()) return;
const payload = getAnalysisPayload();
if (!payload) {
window.location.href = "upload.html";
return;
}
const summary = payload.summary || {};
const findings = Array.isArray(payload.findings) ? payload.findings : [];
const pageSummaries = Array.isArray(payload.pageSummaries) ? payload.pageSummaries : [];
const lineIssues = Array.isArray(payload.finalLineIssues)
? payload.finalLineIssues
: Array.isArray(payload.lineIssues)
? payload.lineIssues
: [];
const detailedSummary = String(payload.detailedSummary || "").trim();
const meta = payload._meta || {};
const summaryDetails = document.getElementById("summaryDetails");
summaryDetails.innerHTML = `
<article class="summary-item"><span>File</span><strong>${escapeHtml(meta.fileName || "-")}</strong></article>
<article class="summary-item"><span>Scan Mode</span><strong>${escapeHtml(summary.scanMode || "-")}</strong></article>
<article class="summary-item"><span>Threshold</span><strong>${escapeHtml(summary.threshold ?? "-")}</strong></article>
<article class="summary-item"><span>Clauses</span><strong>${escapeHtml(summary.clauses ?? 0)}</strong></article>
<article class="summary-item"><span>Pairs Compared</span><strong>${escapeHtml(summary.pairsCompared ?? 0)}</strong></article>
<article class="summary-item"><span>Total Issues</span><strong>${escapeHtml(summary.issuesFound ?? 0)}</strong></article>
<article class="summary-item"><span>Reference Docs</span><strong>${escapeHtml(summary.referenceDocs ?? 0)}</strong></article>
`;
const findingsBoard = document.getElementById("findingsBoard");
const pageSummaryBoard = document.getElementById("pageSummaryBoard");
const detailedSummaryText = document.getElementById("detailedSummaryText");
if (detailedSummaryText) {
detailedSummaryText.textContent = detailedSummary || "Detailed summary is not available for this document.";
}
if (pageSummaryBoard) {
if (pageSummaries.length === 0) {
pageSummaryBoard.innerHTML =
`<article class="result-card"><p class="result-muted">No page-wise summary available for this document.</p></article>`;
} else {
pageSummaryBoard.innerHTML = pageSummaries
.map((item) => {
const keyLines = Array.isArray(item.keyLines) ? item.keyLines : [];
const keyLineHtml = keyLines.length
? keyLines.map((k) => `<li>${escapeHtml(k)}</li>`).join("")
: "<li>No flagged lines on this page.</li>";
return `
<article class="result-card">
<h4>Page ${escapeHtml(item.page)}</h4>
<p><strong>Clauses:</strong> ${escapeHtml(item.clauseCount ?? 0)}</p>
<p><strong>Issues:</strong> ${escapeHtml(item.issueCount ?? 0)} (Duplication: ${escapeHtml(item.duplicationCount ?? 0)}, Inconsistency: ${escapeHtml(item.inconsistencyCount ?? 0)}, Contradiction: ${escapeHtml(item.contradictionCount ?? 0)})</p>
<p><strong>Page Snippet:</strong> ${escapeHtml(item.pageSnippet || "-")}</p>
<p><strong>Summary:</strong> ${escapeHtml(item.summaryText || "-")}</p>
<p><strong>Key Lines:</strong></p>
<ul>${keyLineHtml}</ul>
</article>
`;
})
.join("");
}
}
if (findings.length === 0) {
findingsBoard.innerHTML = `<article class="result-card"><p class="result-muted">No major findings detected for this document.</p></article>`;
return;
}
const topFindings = findings.slice(0, 20);
findingsBoard.innerHTML = topFindings
.map(
(item) => `
<article class="result-card">
<h4>${escapeHtml(item.category || "issue")} - ${escapeHtml(item.issueType || "-")}</h4>
<p><strong>Confidence:</strong> ${escapeHtml(item.confidence ?? "-")}</p>
<p><strong>Location A:</strong> ${escapeHtml(item.location1 || "-")}</p>
<p><strong>Location B:</strong> ${escapeHtml(item.location2 || "-")}</p>
<p><strong>Reason:</strong> ${escapeHtml(item.reason || "-")}</p>
</article>
`
)
.join("");
}
function initDashboardPage() {
if (!ensureAuth()) return;
const payload = getAnalysisPayload();
if (!payload) {
window.location.href = "upload.html";
return;
}
const findings = Array.isArray(payload.findings) ? payload.findings : [];
const lineIssues = Array.isArray(payload.finalLineIssues)
? payload.finalLineIssues
: Array.isArray(payload.lineIssues)
? payload.lineIssues
: [];
const lineErrorDashboard = document.getElementById("lineErrorDashboard");
const comparisonBoard = document.getElementById("comparisonBoard");
if (lineErrorDashboard) {
if (lineIssues.length === 0) {
lineErrorDashboard.innerHTML = `<p class="result-muted">No line-level errors detected.</p>`;
} else {
const rows = lineIssues
.slice(0, 200)
.map(
(item) => `
<tr>
<td>${escapeHtml(item.location || `Pg ${item.page}, Ln ${item.line}`)}</td>
<td>${escapeHtml(item.category || "-")}</td>
<td>${escapeHtml(item.issueType || "-")}</td>
<td>${escapeHtml(item.confidence ?? "-")}</td>
<td>${escapeHtml(item.reason || "-")}</td>
</tr>
`
)
.join("");
lineErrorDashboard.innerHTML = `
<div class="table-wrap">
<table class="result-table">
<thead>
<tr>
<th>Page/Line</th>
<th>Category</th>
<th>Issue Type</th>
<th>Confidence</th>
<th>Reason</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
`;
}
}
if (comparisonBoard) {
const crossFindings = findings
.filter((f) => String(f.source1 || "").startsWith("reference_") || String(f.source2 || "").startsWith("reference_"))
.slice(0, 80);
if (!crossFindings.length) {
comparisonBoard.innerHTML = `<article class="result-card"><p class="result-muted">Reference vs Final cross-verification mismatches not found.</p></article>`;
return;
}
function suggestionFor(category, issueType, refText, finalText) {
const c = String(category || "").toLowerCase();
const i = String(issueType || "").toLowerCase();
if (c === "duplication" || i.includes("duplication")) {
return "இந்த clause repeated/near-duplicate. ஒரே legal meaning உள்ள line-ஐ மட்டும் வைத்துக்கொண்டு மற்றதை remove செய்யவும்.";
}
if (c === "inconsistency" || i.includes("inconsistency") || i.includes("numeric")) {
return "Number/term mismatch இருக்கு. Reference document value-ஐ verify பண்ணி final document-ல் same value update செய்யவும்.";
}
if (c === "contradiction" || i.includes("conflict") || i.includes("contradiction")) {
return "இரண்டு lines opposite meaning கொடுக்குது. Reference document intent எது சரி என்று confirm செய்து final document line-ஐ அதற்கு align செய்யவும்.";
}
if (String(refText || "").trim() && String(finalText || "").trim()) {
return "Reference line மற்றும் final line legal intent same ஆக இருக்கிறதா verify செய்து, ambiguous words remove செய்து rewrite செய்யவும்.";
}
return "Clause wording-ஐ reference document-ஓடு compare செய்து consistent version-ஆ மாற்றவும்.";
}
comparisonBoard.innerHTML = crossFindings
.map((item) => {
const source1 = String(item.source1 || "");
const source2 = String(item.source2 || "");
const firstIsFinal = source1 === "final";
const finalText = firstIsFinal ? item.clause1 : item.clause2;
const refText = firstIsFinal ? item.clause2 : item.clause1;
const finalLoc = firstIsFinal ? item.location1 : item.location2;
const refLoc = firstIsFinal ? item.location2 : item.location1;
const refLabel = firstIsFinal ? item.sourceLabel2 : item.sourceLabel1;
const fixSuggestion = suggestionFor(item.category, item.issueType, refText, finalText);
return `
<article class="result-card comparison-card">
<h4>Error at ${escapeHtml(finalLoc || "-")}</h4>
<p><strong>Type:</strong> ${escapeHtml(item.category || "issue")} - ${escapeHtml(item.issueType || "-")}</p>
<p><strong>What is wrong:</strong> ${escapeHtml(item.reason || "-")}</p>
<p><strong>Original (${escapeHtml(refLabel || "Reference")} - ${escapeHtml(refLoc || "-")}):</strong></p>
<p class="compare-text">${escapeHtml(refText || "-")}</p>
<p><strong>Your Final Document (${escapeHtml(finalLoc || "-")}):</strong></p>
<p class="compare-text">${escapeHtml(finalText || "-")}</p>
<p><strong>How to rectify:</strong> ${escapeHtml(fixSuggestion)}</p>
<div class="workflow-actions">
<button
type="button"
class="secondary-btn rectify-btn"
data-ref-text="${escapeHtml(normalizeSpaces(refText || ""))}"
data-final-text="${escapeHtml(normalizeSpaces(finalText || ""))}"
>
Rectify this line
</button>
<span class="rectify-hint">Suggested corrected line will be copied.</span>
</div>
</article>
`;
})
.join("");
const rectifyButtons = comparisonBoard.querySelectorAll(".rectify-btn");
rectifyButtons.forEach((btn) => {
btn.addEventListener("click", async () => {
const refLine = normalizeSpaces(btn.getAttribute("data-ref-text") || "");
const finalLine = normalizeSpaces(btn.getAttribute("data-final-text") || "");
const suggestion = refLine || finalLine || "Review this clause with reference document and update wording for consistency.";
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(suggestion);
}
const hint = btn.parentElement && btn.parentElement.querySelector(".rectify-hint");
if (hint) {
hint.textContent = "Corrected line copied. Paste into your final document.";
}
btn.textContent = "Copied";
} catch {
const hint = btn.parentElement && btn.parentElement.querySelector(".rectify-hint");
if (hint) {
hint.textContent = `Suggested line: ${suggestion}`;
}
}
});
});
}
}
if (page === "index.html" || page === "") {
initIndexPage();
} else if (page === "upload.html") {
initUploadPage();
} else if (page === "issues.html") {
initIssuesPage();
} else if (page === "summary.html") {
initSummaryPage();
} else if (page === "dashboard.html") {
initDashboardPage();
} else if (page === "workflow.html") {
window.location.href = "upload.html";
}