/*******************************
* 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 = [], isRecording = false;
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 () => {
recordBtn.textContent = "🎤 Start Recording";
isRecording = false;
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;
}
};
}
/* ─────────────── Click-to-record UX ─────────────── */
recordBtn.addEventListener("click", () => {
if (!mediaRecorder) return;
if (isRecording) {
mediaRecorder.stop();
} else {
chunks = [];
mediaRecorder.start();
recordBtn.textContent = "🎤 Stop Recording";
isRecording = true;
}
});
/* ─────────────── Screenshot 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());
/* ─────────────── 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);
}
});
/* ─────────────── render helpers ─────────────── */
function displayQa(data) {
let qHtml = "", aHtml = "";
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))}