From 682d67939e6542e35842274b920c84d6085f4f39 Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Fri, 17 Oct 2025 17:32:14 +0100 Subject: [PATCH 01/10] initial commit --- frontend/index.html | 142 ++++++++++++++++++++++++++++++++++++++++++++ frontend/script.js | 91 ++++++++++++++++++++++++++++ frontend/style.css | 111 ++++++++++++++++++++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 frontend/index.html create mode 100644 frontend/script.js create mode 100644 frontend/style.css diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..33d8f7e --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,142 @@ + + + + + + IAT Validator + + + + + +
+

IAT Validator

+ +
+
+ +
+
+ + + API Docs + +
+
+ +
+
+ Only .json files are supported. +
+
+ + +
+
+ + +
+
+ + +
+ + +
+ +
+ +
+
+ Errors +
+
+ + +
+
+ +
+ Upload a JSON file and clickSubmitto + begin validation. +
+
+
+
+ + + + + + diff --git a/frontend/script.js b/frontend/script.js new file mode 100644 index 0000000..4bc0c23 --- /dev/null +++ b/frontend/script.js @@ -0,0 +1,91 @@ +document.getElementById("submitButton").addEventListener("click", async () => { + const selectedFile = document.getElementById("fileInput").files[0]; + + if (!selectedFile) { + alert("Please select a file first."); + return; + } + + const reader = new FileReader(); + const imcSwitch = document.getElementById("imcSwitch"); + const advancedSwitch = document.getElementById("advancedSwitch"); + + reader.onload = async (e) => { + const rawJSON = JSON.parse(e.target.result); + + // log to see if the json is being parsed correctly + console.log("Parsed JSON:", rawJSON); + + const response = await fetch("/api/validate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + xaif: rawJSON, + hasIntermapConnections: imcSwitch.checked, + }), + }); + + const data = await response.json(); + console.log("Server response:", data); + + if (data.errors.length === 0) { + let displayMessage = "No errors to display"; + document.getElementById("responseBox").innerText = displayMessage; + return; + } + + let html; + if (data.errors.length < 3) { + html = ``; + } else { + html = `
`; + } + + function applyAdvancedToggle() { + const show = advancedSwitch.checked; + document.querySelectorAll(".advanced-col").forEach((cell) => { + cell.classList.toggle("d-none", !show); + }); + } + + advancedSwitch.addEventListener("change", applyAdvancedToggle); + html += ` + + + + + + + + + `; + for (const [index, error] of data.errors.entries()) { + html += ` + + + `; + + // Add nodes to table + html += + '"; + + // Add edges to table + html += + '"; + + html += ``; + } + html += "
#Error DescriptionNode TextNode IDEdge ID
${index + 1}${error.description}${error.nodes[0].text}' + + (error.nodes?.map((node) => node.nodeID).join(", ") || "N/A") + + "' + + (error.edges?.map((edge) => edge.edgeID).join(", ") || "N/A") + + "
"; + console.log(html); + document.getElementById("responseBox").innerHTML = html; + applyAdvancedToggle(); + }; + + reader.readAsText(selectedFile); +}); diff --git a/frontend/style.css b/frontend/style.css new file mode 100644 index 0000000..6fd4d0c --- /dev/null +++ b/frontend/style.css @@ -0,0 +1,111 @@ +:root { + --bg: #f5f7fa; + --card-bg: #ffffff; + --muted: #6c757d; + --primary: #0b4fc6; /* navy blue */ + --accent: #0d9488; /* teal */ + --accent-2: #475ea8; /* indigo */ + --border: #e6e9ef; +} + +body { + background-color: var(--bg); + font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + color: #222; + padding: 48px 16px; +} + +.container { + max-width: 960px; +} + +h1.app-title { + text-align: center; + color: var(--primary); + margin-bottom: 40px; + font-weight: 600; + letter-spacing: -0.3px; + font-size: 2.9rem; +} + +.app-card { + border: none; + border-radius: 12px; + background: var(--card-bg); + box-shadow: 0 10px 30px rgba(28, 40, 60, 0.08); + overflow: hidden; +} + +.app-card::before { + content: ""; + display: block; + height: 6px; + background: linear-gradient(90deg, var(--primary), var(--accent)); +} + +.app-card .card-body { + padding: 1.5rem; +} + +label.form-label { + font-weight: 600; + color: #24324a; +} + +.file-row { + display: flex; + gap: 0.6rem; + align-items: center; + flex-wrap: wrap; +} + +.form-text.small-muted { + color: var(--muted); +} + +.form-control:focus { + box-shadow: 0 6px 18px rgba(13, 148, 136, 0.08); + border-color: var(--accent); +} + +.form-check-input:checked { + background-color: var(--accent-2); + border-color: var(--accent-2); +} + +.btn-primary { + background-color: var(--primary); + border-color: var(--primary); + border-radius: 999px; + padding: 0.55rem 1rem; + font-weight: 600; + transition: transform 0.12s ease, box-shadow 0.12s ease; +} +.btn-primary:hover { + transform: translateY(-3px); + box-shadow: 0 12px 28px rgba(11, 79, 198, 0.12); +} + +.btn-sm:hover { + transform: none; +} + +.divider { + height: 1px; + background: linear-gradient(90deg, transparent, var(--border), transparent); + margin: 1rem 0; +} + +#responseBox { + background: linear-gradient(to bottom, #f9fbfd, #f6f8fa); + border: 1px solid #dce3eb; + border-radius: 8px; + padding: 1.25rem; + min-height: 150px; + font-size: 0.95rem; + color: #2b3b50; + text-align: center; + display: flex; + align-items: center; + justify-content: center; +} \ No newline at end of file From dd89b0d6c7fd823dd85a1b919dd4214b31490a50 Mon Sep 17 00:00:00 2001 From: Joshua Price Date: Fri, 17 Oct 2025 17:43:43 +0100 Subject: [PATCH 02/10] Serve frontend using fastapi --- src/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/__init__.py b/src/__init__.py index 473f879..17ec246 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,6 +1,7 @@ import traceback from fastapi import FastAPI, HTTPException +from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, ConfigDict from . import papa @@ -29,3 +30,13 @@ def all_analytics(body: RequestBody | dict) -> dict: return papa.all_analytics(body) except Exception: raise HTTPException(status_code=500, detail=traceback.format_exc()) + + +app.mount( + "/", + StaticFiles( + directory="frontend", + html=True, + ), + name="web", +) From 1de5f1ada479ce10b204e01ace3279d15aecabb8 Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Tue, 21 Oct 2025 17:02:37 +0100 Subject: [PATCH 03/10] download response file after clicking the submit button, the response file from the server automatically gets downloaded on the frotnend. also added a bash script to run the project. --- frontend/index.html | 23 +++------- frontend/script.js | 105 ++++++++++++++++++++++---------------------- run.sh | 3 ++ 3 files changed, 62 insertions(+), 69 deletions(-) create mode 100644 run.sh diff --git a/frontend/index.html b/frontend/index.html index 33d8f7e..386d2c8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -3,19 +3,19 @@ - IAT Validator + PAPA - + diff --git a/frontend/script.js b/frontend/script.js index 4bc0c23..c7bfdbb 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -8,7 +8,6 @@ document.getElementById("submitButton").addEventListener("click", async () => { const reader = new FileReader(); const imcSwitch = document.getElementById("imcSwitch"); - const advancedSwitch = document.getElementById("advancedSwitch"); reader.onload = async (e) => { const rawJSON = JSON.parse(e.target.result); @@ -16,75 +15,77 @@ document.getElementById("submitButton").addEventListener("click", async () => { // log to see if the json is being parsed correctly console.log("Parsed JSON:", rawJSON); - const response = await fetch("/api/validate", { + const response = await fetch("/api/all_analytics", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ xaif: rawJSON, - hasIntermapConnections: imcSwitch.checked, }), }); const data = await response.json(); console.log("Server response:", data); - if (data.errors.length === 0) { - let displayMessage = "No errors to display"; - document.getElementById("responseBox").innerText = displayMessage; - return; - } + // creating a downloadable file for the user + const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); - let html; - if (data.errors.length < 3) { - html = ``; - } else { - html = `
`; - } + a.href = url; + a.download = "updated.json"; // name of the file + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(url); - function applyAdvancedToggle() { - const show = advancedSwitch.checked; - document.querySelectorAll(".advanced-col").forEach((cell) => { - cell.classList.toggle("d-none", !show); - }); - } + // if (data.errors.length === 0) { + // let displayMessage = "No errors to display"; + // document.getElementById("responseBox").innerText = displayMessage; + // return; + // } - advancedSwitch.addEventListener("change", applyAdvancedToggle); - html += ` - - - - - - - - - `; - for (const [index, error] of data.errors.entries()) { - html += ` - - - `; + // let html; + // if (data.errors.length < 3) { + // html = `
#Error DescriptionNode TextNode IDEdge ID
${index + 1}${error.description}${error.nodes[0].text}
`; + // } else { + // html = `
`; + // } - // Add nodes to table - html += - '"; + // html += ` + // + // + // + // + // + // + // + // + // `; + // for (const [index, error] of data.errors.entries()) { + // html += ` + // + // + // `; - // Add edges to table - html += - '"; + // // Add nodes to table + // html += + // '"; - html += ``; - } - html += "
' + - (error.nodes?.map((node) => node.nodeID).join(", ") || "N/A") + - "
#Error DescriptionNode TextNode IDEdge ID
${index + 1}${error.description}${error.nodes[0].text}' + - (error.edges?.map((edge) => edge.edgeID).join(", ") || "N/A") + - "' + + // (error.nodes?.map((node) => node.nodeID).join(", ") || "N/A") + + // "
"; - console.log(html); - document.getElementById("responseBox").innerHTML = html; - applyAdvancedToggle(); + // // Add edges to table + // html += + // '' + + // (error.edges?.map((edge) => edge.edgeID).join(", ") || "N/A") + + // ""; + + // html += ``; + // } + // html += ""; + // console.log(html); + // document.getElementById("responseBox").innerHTML = html; }; reader.readAsText(selectedFile); diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..0110998 --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +export PYTHONPATH=src + +uv run fastapi dev src \ No newline at end of file From 97b8a2a1bc5095ee5710c3225a65071235e376e6 Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Tue, 21 Oct 2025 17:29:34 +0100 Subject: [PATCH 04/10] add three optional attributes --- frontend/index.html | 204 ++++++++++++++++++++------------------------ frontend/script.js | 10 ++- 2 files changed, 100 insertions(+), 114 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 386d2c8..a36fbe3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,131 +1,109 @@ - - - - PAPA - - - - - -
-

PAPA: Amazing Python Analytics

-
-
- -
- -
- -
-
- Only .json files are supported. -
+ + + + PAPA + + + + + + +
+

PAPA: Amazing Python Analytics

+ +
+
+ +
+
+ + + API Docs + +
+
+
+
+ Only .json files are supported. +
+
- -
-
- - + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
- -
- - -
+ +
+ +
-
+
-
-
- Analytics -
-
+
+
+ Analytics +
+
-
- Analytics will be displayed here -
+
+ Analytics will be displayed here
+
- - // if (clearBtn) { - // clearBtn.addEventListener("click", () => { - // if (fileInput) fileInput.value = ""; - // responseBox.textContent = "No file selected"; - // }); - // } - })(); - + + - - - + \ No newline at end of file diff --git a/frontend/script.js b/frontend/script.js index c7bfdbb..0cd4286 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -7,13 +7,18 @@ document.getElementById("submitButton").addEventListener("click", async () => { } const reader = new FileReader(); - const imcSwitch = document.getElementById("imcSwitch"); + const nodeLevel = document.getElementById("node_level"); + const speaker = document.getElementById("speaker"); + const forecast = document.getElementById("forecast"); reader.onload = async (e) => { const rawJSON = JSON.parse(e.target.result); // log to see if the json is being parsed correctly console.log("Parsed JSON:", rawJSON); + console.log(nodeLevel.checked); + console.log(speaker.checked); + console.log(forecast.checked); const response = await fetch("/api/all_analytics", { method: "POST", @@ -22,6 +27,9 @@ document.getElementById("submitButton").addEventListener("click", async () => { }, body: JSON.stringify({ xaif: rawJSON, + node_level: nodeLevel.checked, + speaker: speaker.checked, + forecast: forecast.checked, }), }); From c4fa70b4375e2d1e0580a06af40d927d82ddfe1a Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Thu, 30 Oct 2025 14:59:53 +0000 Subject: [PATCH 05/10] displaying json on frontend --- frontend/index.html | 2 ++ frontend/script.js | 49 +++------------------------------------------ 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index a36fbe3..e952185 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -77,6 +77,7 @@
Analytics will be displayed here
+
@@ -101,6 +102,7 @@
})(); + diff --git a/frontend/script.js b/frontend/script.js index 0cd4286..5a2abc9 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -48,52 +48,9 @@ document.getElementById("submitButton").addEventListener("click", async () => { a.remove(); URL.revokeObjectURL(url); - // if (data.errors.length === 0) { - // let displayMessage = "No errors to display"; - // document.getElementById("responseBox").innerText = displayMessage; - // return; - // } - - // let html; - // if (data.errors.length < 3) { - // html = ``; - // } else { - // html = `
`; - // } - - // html += ` - // - // - // - // - // - // - // - // - // `; - // for (const [index, error] of data.errors.entries()) { - // html += ` - // - // - // `; - - // // Add nodes to table - // html += - // '"; - - // // Add edges to table - // html += - // '"; - - // html += ``; - // } - // html += "
#Error DescriptionNode TextNode IDEdge ID
${index + 1}${error.description}${error.nodes[0].text}' + - // (error.nodes?.map((node) => node.nodeID).join(", ") || "N/A") + - // "' + - // (error.edges?.map((edge) => edge.edgeID).join(", ") || "N/A") + - // "
"; - // console.log(html); - // document.getElementById("responseBox").innerHTML = html; + // displaying the json file on frontend + document.querySelector('#json').data = data; + document.getElementById("responseBox").style = "display: none"; }; reader.readAsText(selectedFile); From e86cb948b4e4b74d2cfd4068dd1f0785de953b87 Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Thu, 30 Oct 2025 15:50:26 +0000 Subject: [PATCH 06/10] add a clear button this clears out the analytics + the chosen file and displays the response box --- frontend/index.html | 16 ++++++++++++++-- frontend/script.js | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index e952185..b3e2b86 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -72,6 +72,9 @@

PAPA: Amazing Python Analytics

Analytics
+
@@ -86,19 +89,28 @@
// optional UI helper for showing/clearing file selection (function () { const fileInput = document.getElementById("fileInput"); - // const clearBtn = document.getElementById("clearFile"); + const clearBtn = document.getElementById("clearFile"); const responseBox = document.getElementById("responseBox"); if (fileInput) { fileInput.addEventListener("change", () => { const file = fileInput.files[0]; if (file) { - responseBox.textContent = `Selected file: ${file.name}\n\nClick Submit to validate.`; + responseBox.textContent = `Selected file: ${file.name}\n\nClick Submit to get analytics.`; } else { responseBox.textContent = "No file selected"; } }); } + + if (clearBtn) { + clearBtn.addEventListener("click", () => { + if (fileInput) fileInput.value = ""; + responseBox.style = "display: flex"; + responseBox.textContent = "Please select a file"; + document.getElementById('json').style = "display: none"; + }); + } })(); diff --git a/frontend/script.js b/frontend/script.js index 5a2abc9..c26ca1e 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -49,6 +49,7 @@ document.getElementById("submitButton").addEventListener("click", async () => { URL.revokeObjectURL(url); // displaying the json file on frontend + document.getElementById("json").style = "display: flex"; document.querySelector('#json').data = data; document.getElementById("responseBox").style = "display: none"; }; From 4d1206b28e6a07afc7a31c1514ae5ce63fb901ae Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Thu, 30 Oct 2025 16:42:27 +0000 Subject: [PATCH 07/10] add error handling if papa fails the frontend was previously not displaying anything in the response box if papa failed. it is now displaying an error message in the response box. --- frontend/index.html | 2 +- frontend/script.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index b3e2b86..83c7639 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -96,7 +96,7 @@
fileInput.addEventListener("change", () => { const file = fileInput.files[0]; if (file) { - responseBox.textContent = `Selected file: ${file.name}\n\nClick Submit to get analytics.`; + responseBox.textContent = `Selected file: ${file.name}\n\nClick the submit button to get analytics.`; } else { responseBox.textContent = "No file selected"; } diff --git a/frontend/script.js b/frontend/script.js index c26ca1e..c122f99 100644 --- a/frontend/script.js +++ b/frontend/script.js @@ -48,10 +48,18 @@ document.getElementById("submitButton").addEventListener("click", async () => { a.remove(); URL.revokeObjectURL(url); - // displaying the json file on frontend - document.getElementById("json").style = "display: flex"; - document.querySelector('#json').data = data; - document.getElementById("responseBox").style = "display: none"; + // displaying the json file on frontend if there is a response + if (response.status === 200) { + document.getElementById("json").style = "display: flex"; + document.querySelector('#json').data = data.analytics; + document.getElementById("responseBox").style = "display: none"; + } + // displaying an error message in response box if papa fails + else { + document.getElementById('json').style = "display: none"; + document.getElementById("responseBox").style = "display: flex"; + document.getElementById("responseBox").innerHTML = "There was an error in the analytics code. Open the downloaded file to see more details" + } }; reader.readAsText(selectedFile); From f645dc78e47de9e53ee8f58803aac9eb77b29334 Mon Sep 17 00:00:00 2001 From: Ananya Bhatt Date: Thu, 30 Oct 2025 16:46:10 +0000 Subject: [PATCH 08/10] change title for selecting file it previously said 'select file to validate' but instead of validate it should be analyse. --- frontend/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/index.html b/frontend/index.html index 83c7639..6ce83e4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -21,7 +21,7 @@

PAPA: Amazing Python Analytics