/*******************************
* Interview Q&A Frontend JS *
*******************************/
const recordBtn = document.getElementById("record-button");
const screenshotBtn = document.getElementById("screenshot-button");
const fileInput = document.getElementById("file-input");
const questionEl = document.getElementById("question-output");
const answerEl = document.getElementById("answer-output");
const editBtn = document.getElementById("edit-btn");
/* ─────────────────── Typing effect utility ─────────────────── */
function typeEffect(el, text, speed = 30) {
el.textContent = "";
let idx = 0;
const timer = setInterval(() => {
el.textContent += text.charAt(idx);
idx++;
if (idx >= text.length) clearInterval(timer);
}, speed);
}
/* ─────────────────── Abort-controller wrapper ───────────────── */
let currentController = null;
function fetchWithAbort(url, opts = {}) {
if (currentController) currentController.abort(); // cancel previous req
currentController = new AbortController();
return fetch(url, { ...opts, signal: currentController.signal });
}
/* ─────────────────── Audio recording setup ─────────────────── */
let mediaRecorder, chunks = [];
async function initMedia() {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = e => chunks.push(e.data);
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(chunks, { type: "audio/wav" });
chunks = [];
const form = new FormData();
form.append("file", audioBlob, "record.wav");
questionEl.textContent = "⌛ Transcribing…";
answerEl.innerHTML = "";
try {
const res = await fetchWithAbort("/voice-transcribe", { method: "POST", body: form });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
displayQa(data);
} catch (err) {
answerEl.textContent = "❌ " + err.message;
}
};
}
/* ─────────────── Screenshot / image-question upload ─────────── */
fileInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
const form = new FormData();
form.append("file", file);
questionEl.textContent = "⌛ Processing screenshot…";
answerEl.innerHTML = "";
try {
const res = await fetchWithAbort("/image-question", { method: "POST", body: form });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
displayQa(data);
} catch (err) {
answerEl.textContent = "❌ " + err.message;
} finally {
fileInput.value = ""; // reset for next upload
}
});
screenshotBtn.addEventListener("click", () => fileInput.click());
/* ─────────────────── Hold-to-record UX ─────────────────────── */
function bindRecordBtn() {
recordBtn.addEventListener("mousedown", () => mediaRecorder.start());
recordBtn.addEventListener("mouseup", () => mediaRecorder.stop());
recordBtn.addEventListener("touchstart", e => { e.preventDefault(); mediaRecorder.start(); });
recordBtn.addEventListener("touchend", e => { e.preventDefault(); mediaRecorder.stop(); });
}
/* ─────────────────── Editable question block ───────────────── */
function enableEdit() {
questionEl.contentEditable = "true";
questionEl.classList.add("editing");
questionEl.focus();
}
async function sendEditedQuestion(text) {
questionEl.contentEditable = "false";
questionEl.classList.remove("editing");
answerEl.textContent = "⌛ Thinking…";
try {
const res = await fetchWithAbort("/text-question", {
method : "POST",
headers: { "Content-Type": "application/json" },
body : JSON.stringify({ question: text })
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
displayQa(data);
} catch (err) {
answerEl.textContent = "❌ " + err.message;
}
}
editBtn.addEventListener("click", () => enableEdit());
questionEl.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
const text = questionEl.innerText.trim();
if (text) sendEditedQuestion(text);
}
});
/* ─────────────────────── helpers ───────────────────────────── */
function displayQa(data) {
let qHtml = "", aHtml = "";
// Parse and bind JSON now as Q&A can be an array with more than 1 component(s)
const qaList = Array.isArray(data) ? data : [data];
qaList.forEach((item, idx) => {
const q = item.question || "[no question]";
const a = item.answer || "[no answer]";
qHtml += `Q${idx + 1}: ${q}\n`;
aHtml += `Q${idx + 1}: ${DOMPurify.sanitize(marked.parseInline(q))}
`;
aHtml += `A${idx + 1}: ${DOMPurify.sanitize(marked.parse(a))}