File size: 3,655 Bytes
9d498c2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | const QA_FILE = "questions_answers.json";
// Point this at your running backend (server.py). Examples:
// local: "http://localhost:5000"
// hosted: "https://your-app-name.onrender.com"
const API_URL = "http://localhost:5000";
const listEl = document.getElementById("question-list");
const logEl = document.getElementById("log");
const customInput = document.getElementById("custom-input");
const customSubmit = document.getElementById("custom-submit");
let faq = [];
let hintCleared = false;
async function loadFaq() {
try {
const res = await fetch(QA_FILE, { cache: "no-store" });
if (!res.ok) throw new Error("fetch failed: " + res.status);
faq = await res.json();
} catch (err) {
listEl.innerHTML = `<div class="log-hint">
// could not load ${QA_FILE} (${err.message}). Make sure it's uploaded
alongside index.html in this Space.
</div>`;
return;
}
renderQuestions();
}
function renderQuestions() {
listEl.innerHTML = "";
faq.forEach((item, i) => {
const btn = document.createElement("button");
btn.className = "q-btn";
btn.type = "button";
btn.textContent = item.question;
btn.addEventListener("click", () => runQuestion(item, btn));
listEl.appendChild(btn);
});
}
function runQuestion(item, btnEl) {
document.querySelectorAll(".q-btn").forEach(b => b.classList.remove("active"));
btnEl.classList.add("active");
const aLine = addLogEntry(item.question);
typeAnswer(aLine, item.answer);
}
function typeAnswer(el, text) {
const cursor = document.createElement("span");
cursor.className = "cursor";
let i = 0;
const speed = 14; // ms per character - tune for taste
function step() {
if (i <= text.length) {
el.textContent = text.slice(0, i);
el.appendChild(cursor);
i++;
setTimeout(step, speed);
} else {
cursor.remove();
}
}
step();
}
function addLogEntry(questionText) {
if (!hintCleared) {
logEl.innerHTML = "";
hintCleared = true;
}
document.querySelectorAll(".q-btn").forEach(b => b.classList.remove("active"));
const entry = document.createElement("div");
entry.className = "log-entry";
const qLine = document.createElement("div");
qLine.className = "log-q";
qLine.textContent = questionText;
const aLine = document.createElement("div");
aLine.className = "log-a";
entry.appendChild(qLine);
entry.appendChild(aLine);
logEl.appendChild(entry);
entry.scrollIntoView({ behavior: "smooth", block: "end" });
return aLine;
}
async function askCustom() {
const question = customInput.value.trim();
if (!question) return;
customSubmit.disabled = true;
customInput.disabled = true;
const aLine = addLogEntry(question);
aLine.textContent = "generating...";
try {
const res = await fetch(`${API_URL}/generate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question }),
});
const data = await res.json();
aLine.textContent = "";
if (!res.ok) {
aLine.classList.add("error");
typeAnswer(aLine, `error: ${data.error || res.statusText}`);
} else {
typeAnswer(aLine, data.answer);
}
} catch (err) {
aLine.textContent = "";
aLine.classList.add("error");
typeAnswer(aLine, `could not reach backend at ${API_URL} (${err.message})`);
} finally {
customSubmit.disabled = false;
customInput.disabled = false;
customInput.value = "";
customInput.focus();
}
}
customSubmit.addEventListener("click", askCustom);
customInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") askCustom();
});
loadFaq();
|