File size: 2,527 Bytes
e7b1e22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243b6fb
 
e7b1e22
 
 
 
243b6fb
e7b1e22
243b6fb
e7b1e22
 
 
 
 
 
 
 
 
 
33e4e84
e7b1e22
 
243b6fb
e7b1e22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243b6fb
 
e7b1e22
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
/*******************************
 * Interview Q&A Frontend JS   *
 *******************************/

// Elements
const recordBtn   = document.getElementById("record-button");
const questionEl  = document.getElementById("question-output");
const answerEl    = document.getElementById("answer-output");

// Typing animation util
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);
}

// Audio recording setup
let mediaRecorder, chunks = [];
// Initialise media data
async function initMedia() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    mediaRecorder = new MediaRecorder(stream);
    // Continuously push available data
    mediaRecorder.ondataavailable = e => chunks.push(e.data);
    // Stop btn start write and send audio chunk
    mediaRecorder.onstop = async () => {
      const audioBlob = new Blob(chunks, { type: "audio/wav" });
      chunks = [];
      // Build form data
      const form = new FormData();
      form.append("file", audioBlob, "record.wav");
      // UX feedback
      typeEffect(questionEl, "⌛ Transcribing…");
      answerEl.textContent = "";
      try {
        const res = await fetch("/voice-transcribe", { method: "POST", body: form });
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        const data = await res.json();
        // render markdown after a small delay for dramatic effect
        typeEffect(questionEl, data.question || "[no speech detected]");
        setTimeout(() => typeEffect(answerEl, data.answer || "[no answer]"), 500);
      } catch (err) {
        typeEffect(answerEl, "❌ " + err.message);
      }
    };
  } catch (err) {
    alert("Microphone access denied – please allow permissions.");
  }
}

// Hold‑to‑record UX
function bindRecordBtn() {
  if (!mediaRecorder) return;
  recordBtn.addEventListener("mousedown", () => mediaRecorder.start());
  recordBtn.addEventListener("mouseup",   () => mediaRecorder.stop());
  // Touch devices
  recordBtn.addEventListener("touchstart", e => { e.preventDefault(); mediaRecorder.start(); });
  recordBtn.addEventListener("touchend",   e => { e.preventDefault(); mediaRecorder.stop(); });
}

// Init on page load
window.addEventListener("DOMContentLoaded", async () => {
  try { await initMedia(); bindRecordBtn(); }
  catch (e) { alert("Mic permission required"); }
});