Static_H / Bsb
MissSqui's picture
Update Bsb
eecc94f verified
// CHANGED/ADDED: Declare global variable for modal
let bootstrapFeedbackModal = null;
const userId = "{{ user_id }}";
const effortId = "{{ effort_id }}";
const notebookId = "{{ notebook_id }}"
const effortName = "{{ effort_name }}";
const notebookName = "{{ notebook_name}}"
const preloadedFiles = {{ files | tojson }};
const preloadedZones = {{ zones | tojson }};
const globalZones = {{ global_ezones | tojson }};
const chatHistory = {{ chat_history | tojson }};
const chatBox = document.getElementById("chat-box");
const zoneDropdown = document.getElementById("zone-dropdown");
const fileList = document.getElementById("file-list");
const modal = document.getElementById("uploadModal");
const fileInput = document.getElementById("fileInput");
const backButton = document.getElementById("backBtn"); // Moved up for clarity
fileInput.addEventListener("change", () => {
const list = document.getElementById("selectedFilesList");
list.innerHTML = "";
const files = fileInput.files;
if (!files.length) {
list.textContent = "No files selected.";
return;
}
for (let file of files) {
const item = document.createElement("div");
item.className = "flex items-center gap-2";
item.innerHTML = `πŸ“„ <span class="truncate">${file.name}</span>`;
list.appendChild(item);
}
});
let currentZoneId = null;
let currentFileUri = "";
let selectedFileElement = null;
function appendMessage(sender, message, color, alignRight = false) {
const wrapper = document.createElement("div");
wrapper.className = alignRight ? "flex justify-end" : "flex justify-start";
const bubble = document.createElement("div");
bubble.className = `max-w-xl px-4 py-2 rounded-lg ${color} text-white`;
bubble.innerHTML = `<strong>${sender}:</strong><br><pre class="whitespace-pre-wrap break-words">${message}</pre>`;
wrapper.appendChild(bubble);
chatBox.appendChild(wrapper);
chatBox.scrollTop = chatBox.scrollHeight;
}
function loadZones() {
zoneDropdown.innerHTML = "";
const defaultOpt = document.createElement("option");
defaultOpt.disabled = true;
defaultOpt.selected = true;
defaultOpt.textContent = "Select a Configuration";
zoneDropdown.appendChild(defaultOpt);
try {
const addedZoneIds = new Set();
preloadedZones.forEach(zone => {
if (!addedZoneIds.has(zone.id)) {
const opt = document.createElement("option");
opt.value = zone.id;
opt.textContent = zone.name;
zoneDropdown.appendChild(opt);
addedZoneIds.add(zone.id);
}
});
globalZones.forEach(zone => {
if (!addedZoneIds.has(zone.id)) {
const opt = document.createElement("option");
opt.value = zone.id;
opt.textContent = zone.name + " (Global)";
zoneDropdown.appendChild(opt);
addedZoneIds.add(zone.id);
}
});
const divider = document.createElement("option");
divider.disabled = true;
divider.textContent = "──────────";
zoneDropdown.appendChild(divider);
} catch (err) {
console.error("Error loading zones:", err);
}
const manage = document.createElement("option");
manage.value = "manage";
manage.textContent = "βž• Manage Configurations";
zoneDropdown.appendChild(manage);
zoneDropdown.addEventListener("change", (e) => {
if (e.target.value === "manage") {
const form = document.createElement("form");
form.method = "POST";
form.action = "/chat_feature/manage_zones";
const userInput = document.createElement("input");
userInput.type = "hidden";
userInput.name = "user_id";
userInput.value = userId;
form.appendChild(userInput);
const effortInput = document.createElement("input");
effortInput.type = "hidden";
effortInput.name = "effort_id";
effortInput.value = effortId;
form.appendChild(effortInput);
const notebookInput = document.createElement("input");
notebookInput.type = "hidden";
notebookInput.name = "notebook_id";
notebookInput.value = notebookId;
form.appendChild(notebookInput);
document.body.appendChild(form);
form.submit();
} else {
currentZoneId = parseInt(e.target.value);
console.log("Zone selected, currentZoneID:", currentZoneId);
}
});
}
// CHANGED/ADDED: Modal initialization moved inside window.onload
window.onload = function () {
loadFiles();
loadZones();
// CHANGED/ADDED: Modal is now initialized after DOM is loaded
const feedbackModalEl = document.getElementById("feedbackModal");
bootstrapFeedbackModal = new bootstrap.Modal(feedbackModalEl);
let historyFilename = ""
if (chatHistory && chatHistory.length > 0) {
chatHistory.forEach(entry => {
if (entry.filename && (entry.filename !== historyFilename || historyFilename === "")) {
historyFilename = entry.filename;
appendMessage("System", `Selected file: ${historyFilename}`, "bg-purple-600");
}
const userMessage = entry.question;
appendMessage("You", userMessage, "bg-blue-500", true);
const personaMessage = entry.persona;
appendMessage("Persona", personaMessage, "bg-blue-500", true);
const botMessage = entry.response;
appendMessage("Assistant", botMessage, "bg-gray-700", false);
});
}
};
// Feedback Modal POP-UP on Back Button
backButton.addEventListener("click", async function(event){
console.log("Back button clicked");
event.preventDefault();
if(!currentZoneId){
console.log("no configuration selected, going back directly")
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
return;
}
console.log("Debug BackBtn:", {userId, notebookId, currentZoneId, effortId});
try {
const response = await fetch(`/chat_feature/user_feedback_eligibility?user_id=${userId}&notebook_id=${notebookId}&zone_id=${currentZoneId}`);
const data = await response.json();
if (data.eligible_for_feedback) {
console.log("User is eligible for feedback:", data);
if(data.has_feedback){
document.getElementById("feedbackLevel").value = data.rating ||"";
document.getElementById("comment").value = data.comment ||"";
document.getElementById("updateNotice").style.display = "block";
}
else{
document.getElementById("feedbackLevel").value = data.rating ||"";
document.getElementById("comment").value = data.comment ||"";
document.getElementById("updateNotice").style.display = "none";
}
// CHANGED/ADDED: Now modal will always be initialized and ready here
if (bootstrapFeedbackModal) {
bootstrapFeedbackModal.show();
} else {
alert("Feedback modal not initialized!");
}
} else {
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
}
} catch (error) {
console.error('Error checking eligibility:', error);
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
}
});
// Feedback Submit
document.getElementById("submitFeedback").addEventListener("click", async function () {
const rating = document.getElementById("feedbackLevel").value;
const comment = document.getElementById("comment").value;
const payload = {
user_id: userId,
notebook_id: parseInt(notebookId),
effort_id: parseInt(effortId),
zone_id: parseInt(currentZoneId),
rating: rating ? parseInt(rating) : null,
comment: comment
};
try {
const response = await fetch("/chat_feature/submit_feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (response.ok) {
bootstrapFeedbackModal.hide();
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
} else {
alert("Error submitting feedback");
}
} catch (error) {
console.error("Error submitting feedback:", error);
alert("Error submitting feedback");
}
});
// Skip feedback
document.getElementById("skipFeedback").addEventListener("click", function () {
bootstrapFeedbackModal.hide();
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
});
function loadFiles() {
const files = preloadedFiles || [];
fileList.innerHTML = "";
files.forEach(file => {
const li = document.createElement("li");
li.className =
"flex items-center gap-3 p-4 rounded-2xl border border-gray-200 bg-white hover:bg-blue-50 hover:shadow-lg transition shadow group";
const fileIcon = document.createElement("div");
fileIcon.className = "flex-shrink-0 text-blue-600 text-lg";
fileIcon.textContent = "πŸ“„";
const fileName = document.createElement("div");
fileName.className = "flex-1 min-w-0 cursor-pointer";
fileName.innerHTML = `
<p class="font-medium truncate group-hover:whitespace-normal group-hover:break-all" title="${file.name}">
${file.name}
</p>
`;
fileName.onclick = () => {
currentFileUri = file.uri;
appendMessage("System", `Selected file: ${file.name}`, "bg-purple-600");
document.getElementById("notebook-path").textContent = notebookName;
document.getElementById("effort-path").textContent = effortName;
document.getElementById("file-path").textContent = file.name;
if (selectedFileElement) {
selectedFileElement.classList.remove("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
}
li.classList.add("border-blue-500", "bg-blue-100", "ring", "ring-blue-300", "shadow-lg", "transform", "scale-105");
selectedFileElement = li;
};
const deleteButton = document.createElement("button");
deleteButton.className = "btn btn-sm btn-outline-danger";
deleteButton.setAttribute("data-bs-toggle", "modal");
deleteButton.setAttribute("data-bs-target", "#deleteFileModal");
deleteButton.innerHTML = `<i class="bi bi-trash"></i>`;
deleteButton.onclick = (e) => {
e.stopPropagation();
setDeleteFile(file.uri, file.name);
};
li.appendChild(fileIcon);
li.appendChild(fileName);
li.appendChild(deleteButton);
fileList.appendChild(li);
});
}
async function sendMessage() {
const message = document.getElementById("message").value;
const persona = document.getElementById("persona").value;
if (!message.trim() || !currentFileUri) {
alert("Please select a file and type a message.");
return;
}
if (!currentZoneId) {
alert("Please select a zone before sending a message.");
return;
}
appendMessage("You", message, "bg-blue-500", true);
const loadingId = "loading-msg";
const loadingWrapper = document.createElement("div");
loadingWrapper.id = loadingId;
loadingWrapper.className = "flex justify-start";
const loadingBubble = document.createElement("div");
loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
loadingWrapper.appendChild(loadingBubble);
chatBox.appendChild(loadingWrapper);
chatBox.scrollTop = chatBox.scrollHeight;
const res = await fetch("/chat_feature/send_message", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
message,
persona,
file_uri: currentFileUri,
experiment_zone_id: currentZoneId,
effortId: effortId,
notebookId: notebookId
})
});
const data = await res.json();
document.getElementById(loadingId)?.remove();
appendMessage("Assistant", data.message || "No response", "bg-gray-700");
document.getElementById("message").value = "";
}
async function summarize() {
const summaryType = document.getElementById("summary-type").value;
if (!currentFileUri) {
alert("Please select a file and summarize.");
return;
}
if (!currentZoneId) {
alert("Please select a zone before summarizing.");
return;
}
appendMessage("You", `Summarize (${summaryType})`, "bg-green-500", true);
const loadingId = "loading-msg";
const loadingWrapper = document.createElement("div");
loadingWrapper.id = loadingId;
loadingWrapper.className = "flex justify-start";
const loadingBubble = document.createElement("div");
loadingBubble.className = "max-w-xl px-4 py-2 rounded-lg bg-gray-300 text-gray-800";
loadingBubble.innerHTML = `<strong>Assistant:</strong> <span class="loading-dots"><span></span><span></span><span></span></span>`;
loadingWrapper.appendChild(loadingBubble);
chatBox.appendChild(loadingWrapper);
chatBox.scrollTop = chatBox.scrollHeight;
const res = await fetch("/chat_feature/summarize", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
summary_type: summaryType,
file_uri: currentFileUri,
experiment_zone_id: currentZoneId,
effortId: effortId,
notebookId: notebookId
})
});
const data = await res.json();
document.getElementById(loadingId)?.remove();
appendMessage("Summary", data.message || "No summary", "bg-gray-700");
}
function openModal() { modal.classList.remove("hidden"); }
function closeModal() { modal.classList.add("hidden"); fileInput.value = ""; }
function triggerFileInput() { fileInput.click(); }
async function submitFile() {
const files = fileInput.files;
if (!files.length) return alert("Choose at least one file.");
const formData = new FormData();
for (let file of files) {
if (file.type !== "application/pdf") {
alert(`Invalid file type: ${file.name}`);
return;
}
formData.append("files", file);
}
formData.append("effort_id", effortId);
try {
const res = await fetch("/chat_feature/upload_file", {
method: "POST",
body: formData
});
const contentType = res.headers.get("content-type") || "";
let responseData = contentType.includes("application/json") ? await res.json() : await res.text();
if (!res.ok) {
const errorMessage = typeof responseData === "string"
? responseData
: (responseData.error || JSON.stringify(responseData));
alert(`Upload failed: ${errorMessage}`);
return;
}
alert("Upload successful!");
location.reload();
} catch (err) {
alert("Error: " + err.message);
}
}
window.onclick = function (event) {
if (event.target == modal) closeModal();
}
let deleteFileUriToConfirm = "";
let deleteFileName = ""
function setDeleteFile(fileUri, fileName) {
deleteFileUriToConfirm = fileUri;
deleteFileName = fileName
document.getElementById("deleteFileName").textContent = fileName;
}
async function confirmDeleteFile() {
try {
const res = await fetch("/chat_feature/delete_file", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
file_uri: deleteFileUriToConfirm,
effort_id: effortId,
filename: deleteFileName
})
});
if (!res.ok) {
const text = await res.text();
alert("Failed to delete: " + text);
} else {
const modal = bootstrap.Modal.getInstance(document.getElementById("deleteFileModal"));
modal.hide();
alert("File deleted.");
location.reload();
}
} catch (err) {
alert("Error deleting file: " + err.message);
}
}
const dropZone = document.querySelector(".drop-zone");
dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
dropZone.classList.add("border-blue-500", "text-blue-500");
});
dropZone.addEventListener("dragleave", () => {
dropZone.classList.remove("border-blue-500", "text-blue-500");
});
dropZone.addEventListener("drop", (e) => {
e.preventDefault();
dropZone.classList.remove("border-blue-500", "text-blue-500");
const files = e.dataTransfer.files;
fileInput.files = files;
const list = document.getElementById("selectedFilesList");
list.innerHTML = "";
for (const file of files) {
const p = document.createElement("p");
p.textContent = file.name;
list.appendChild(p);
}
});
========================================================================
backButton.addEventListener("click", async function(event){
console.log("Back button clicked");
event.preventDefault();
if(!currentZoneId){
console.log("no configuration selected, going back directly")
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
return;
}
console.log("Debug BackBtn:", {userId, notebookId, currentZoneId, effortId});
try {
console.log("Checking user feedback eligibility...");
// CHANGED/ADDED: Fetch eligibility data from the server
const response = await fetch(`/chat_feature/user_feedback_eligibility?user_id=${userId}&notebook_id=${notebookId}&zone_id=${currentZoneId}`);
const data = await response.json();
if (data.eligible_for_feedback) {
console.log("User is eligible for feedback:", data);
// show the modal popup
//document.getElementById("feedbackModal").showModal = true;
// CHANGED/ADDED: Initialize the modal here
if(data.has_feedback){
document.getElementById("feedbackLevel").value = data.rating ||"";
document.getElementById("comment").value = data.comment ||"";
document.getElementById("updateNotice").style.display = "block";
}
else{
document.getElementById("feedbackLevel").value = data.rating ||"";
document.getElementById("comment").value = data.comment ||"";
document.getElementById("updateNotice").style.display = "none";
}
// CHANGED/ADDED: Now modal will always be initialized and ready here
console.log(bootstrapFeedbackModal);
if (bootstrapFeedbackModal) {
console.log("Showing feedback modal");
bootstrapFeedbackModal.show();
} else {
alert("Feedback modal not initialized!");
}
} else {
console.log("User is not eligible for feedback:", data);
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
}
} catch (error) {
console.error('Error checking eligibility:', error);
window.location.href = `/notebook/?effort_id=${effortId}&user_id=${userId}`;
}
}); ---> in this why my 'bootstrapFeedbackModal.show();'
---------------------------
Feedback Modal Feature – Technical Design
---
1. Overview
This feature enables contextual user feedback collection in the β€œDocument Chat & Summarization” UI.
Feedback (rating/comment) is requested only if the user selects a configuration (zone), engages in chat or summarization, and then attempts to leave the page via the back button.
---
2. Technical Architecture
Frontend
Stack: JavaScript (Vanilla), Bootstrap 5 (modal)
Session State:
Tracks currentZoneId (active configuration) and hasChatted (true if user has sent a message or summarized)
Eligibility Check:
On back button click, modal is triggered only if both currentZoneId is set and hasChatted is true.
API Integration:
AJAX/fetch to /user_feedback_eligibility endpoint for final eligibility confirmation and feedback prefill.
POST to /submit_feedback endpoint for feedback persistence.
Backend
Stack: Flask (UI Gateway), FastAPI (microservices), PostgreSQL
Security: All feedback endpoints protected via Flask’s @login_required decorator.
Eligibility Endpoint:
/user_feedback_eligibility returns:
eligible_for_feedback (True if chat exists for notebook/zone)
has_feedback (True if prior feedback exists)
rating, comment (prefill values if present)
Submission Endpoint:
/submit_feedback creates or updates feedback in DB.
Data Model
feedback (
id SERIAL PRIMARY KEY,
user_id VARCHAR,
notebook_id INT,
effort_id INT,
zone_id INT,
rating INT,
comment TEXT,
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, notebook_id, effort_id, zone_id)
)
---
3. Flow Sequence (with Tech Detail)
1. Zone selection:
User selects a configuration from the dropdown; currentZoneId is set.
2. Chat/Summarize interaction:
On any message or summary, hasChatted = true; in JS.
3. Back button event:
On click, JS checks:
If no zone or hasChatted == false, navigates away with no modal.
If both present, calls /user_feedback_eligibility.
4. Eligibility check (backend):
Flask endpoint verifies chat history via FastAPI; returns feedback eligibility and prefill values.
5. Modal Display:
If eligible, Bootstrap modal is shown. Prefilled if feedback exists.
6. Feedback submission:
On modal submit, JS POSTs feedback to /submit_feedback.
FastAPI microservice creates or updates DB row.
---
4. API Reference
Eligibility Request (GET):
/user_feedback_eligibility?user_id=...&notebook_id=...&zone_id=...
Returns:
{
"eligible_for_feedback": true,
"has_feedback": true,
"rating": 4,
"comment": "Nice"
}
Feedback Submission (POST):
/submit_feedback
Payload:
{
"user_id": "PI0001",
"notebook_id": 2,
"effort_id": 1,
"zone_id": 36,
"rating": 4,
"comment": "Nice"
}
---
5. Edge Cases & Error Handling
Session loss:
If the user refreshes, hasChatted resets. Modal will not pop up unless the user interacts again.
API failure:
Eligibility API failure: User is redirected back.
Submission API failure: User sees an error alert.
Feedback updates:
Existing feedback is prefilled and updatable via the modal.
Security:
All endpoints require authentication (@login_required).
---
6. Pseudocode (Frontend Eligibility Logic)
let hasChatted = false;
function sendMessage() { ... hasChatted = true; }
function summarize() { ... hasChatted = true; }
backButton.addEventListener("click", async function(event) {
if (!currentZoneId) { goBack(); return; }
if (!hasChatted) { goBack(); return; }
// Call backend for eligibility check and show modal if eligible
});
---
7. Sequence Diagram (Textual)
User
↓
[Select Zone] --(sets currentZoneId)-->
↓
[Chat/Summarize] --(sets hasChatted)-->
↓
[Click Back]
↓
[JS: If both set, call API]
↓
[Flask: /user_feedback_eligibility]
↓
[FastAPI: Checks chat history]
↓
[Return eligible? Prefill values?]
↓
[If eligible: Show modal]
↓
[User submits feedback]
↓
[JS: POST to /submit_feedback]
↓
[FastAPI: Store/update feedback in DB]
---
8. Security & Robustness Notes
State variables (hasChatted, currentZoneId) are session-only and do not persist on page reload.
Server-side check guarantees modal is shown only after real chat/summarize event.
No feedback collection for passive/idle sessions.
---
Summary
This design guarantees that feedback is solicited only after meaningful user interaction in the correct context, prevents duplicate feedback, and provides seamless update flows. All actions are secured, API-driven, and fail-safe against common errors.
=======================
window.addEventListener('DOMContentLoaded', function () {
loadFiles();
loadZones();
// Modal initialization after DOM is loaded
const feedbackModalEl = document.getElementById("feedbackModal");
bootstrapFeedbackModal = new bootstrap.Modal(feedbackModalEl);
let historyFilename = "";
if (chatHistory && chatHistory.length > 0) {
chatHistory.forEach(entry => {
if (entry.filename && (entry.filename !== historyFilename || historyFilename === "")) {
historyFilename = entry.filename;
appendMessage("System", `Selected file: ${historyFilename}`, "bg-purple-600");
}
const userMessage = entry.question;
appendMessage("You", userMessage, "bg-blue-500", true);
const personaMessage = entry.persona;
appendMessage("Persona", personaMessage, "bg-blue-500", true);
const botMessage = entry.response;
appendMessage("Assistant", botMessage, "bg-gray-700", false);
});
}
// ---- FEEDBACK BUTTON LOGIC ----
const feedbackBtn = document.getElementById("feedbackBtn");
if (feedbackBtn) {
feedbackBtn.addEventListener("click", async function () {
try {
const response = await fetch(`/chat_feature/get_latest_feedback?user_id=${userId}&notebook_id=${notebookId}`);
const data = await response.json();
console.log("Feedback data:", data);
if (data && data.exists) {
const msg = `Your Previous Experience on Last Used Configuration:\nConfiguration: ${data.zone_name}\nRating: ${data.rating}\nComment: ${data.comment}`;
alert(msg);
} else {
alert("No feedback exists for your previous configuration.");
}
} catch (err) {
alert("Error fetching feedback.");
console.error(err);
}
});
}
// ---- AUTO POPUP ALERT LOGIC ----
fetch(`/chat_feature/get_latest_feedback?user_id=${userId}&notebook_id=${notebookId}`)
.then(response => response.json())
.then(data => {
if (data && data.exists) {
setTimeout(function () {
const msg = `Your Previous Experience on Last Used Configuration:\nConfiguration: ${data.zone_name}\nRating: ${data.rating}\nComment: ${data.comment}`;
alert(msg);
}, 5000); // 5 seconds
}
})
.catch(err => {
console.error("Error fetching previous feedback:", err);
});
});
====================================
window.addEventListener('DOMContentLoaded', function () {
loadFiles();
loadZones();
// Modal initialization after DOM is loaded
const feedbackModalEl = document.getElementById("feedbackModal");
bootstrapFeedbackModal = new bootstrap.Modal(feedbackModalEl);
let historyFilename = "";
if (chatHistory && chatHistory.length > 0) {
chatHistory.forEach(entry => {
if (entry.filename && (entry.filename !== historyFilename || historyFilename === "")) {
historyFilename = entry.filename;
appendMessage("System", `Selected file: ${historyFilename}`, "bg-purple-600");
}
const userMessage = entry.question;
appendMessage("You", userMessage, "bg-blue-500", true);
const personaMessage = entry.persona;
appendMessage("Persona", personaMessage, "bg-blue-500", true);
const botMessage = entry.response;
appendMessage("Assistant", botMessage, "bg-gray-700", false);
});
}
// ---- FEEDBACK BUTTON LOGIC ----
const feedbackBtn = document.getElementById("feedbackBtn");
if (feedbackBtn) {
feedbackBtn.addEventListener("click", async function () {
try {
//new change
let zoneParam = "";
if (currentZoneId) {
zoneParam = `&zone_id=${currentZoneId}`;
}
const userId = "{{ user_id }}";
const notebookId = "{{ notebook_id }}";
const response = await fetch(`/chat_feature/get_latest_feedback?user_id=${userId}&notebook_id=${notebookId}${zoneParam}`);
//const response = await fetch(`/chat_feature/get_latest_feedback?user_id=${userId}&notebook_id=${notebookId}`);
const data = await response.json();
console.log("Feedback data:", data);
if (data && data.exists) {
const msg = `Your Previous Experience on Last Used Configuration:\nConfiguration: ${data.zone_name}\nRating: ${data.rating}\nComment: ${data.comment}`;
alert(msg);
} else {
alert("No feedback exists for your previous configuration.");
}
} catch (err) {
alert("Error fetching feedback.");
console.error(err);
}
});
}
}); --> make me understand this in 3-4 lines