ai-voice-interview / src /counter.js
jonathanjordan21's picture
init
e135dc0
// import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
import { RealtimeAgent, RealtimeSession } from "https://esm.sh/@openai/agents/realtime"
// import dotenv from "dotenv";
// dotenv.config();
let mediaRecorder = null
let recordedChunks = []
let stream = null
let chat_history = []
let interviewToken = null;
const API_BASE_URL = 'https://jonathanjordan21-joss-interview-backend-demo.hf.space'
const parts = window.location.pathname.split('/').filter(p => p.trim() !== '');
const interviewId = parts[parts.length - 1];
function formatTime(totalSeconds) {
const mins = Math.floor(totalSeconds / 60);
const secs = totalSeconds % 60;
return `${String(mins).padStart(2, "0")}:${String(secs).padStart(2, "0")}`;
}
const timerElement = document.querySelector(".meeting-time");
let seconds = 0;
if (parts.length != 2) {
console.error('400 Interview not found');
const appDiv = document.getElementById('app');
if (appDiv) appDiv.textContent = '400 Interview not found';
} else {
async function loadData() {
try {
const response = await fetch(`${API_BASE_URL}/interview/${interviewId}`, {
// headers: {"Authorization": "Bearer " + HF_API_KEY}
});
if (!response.ok) throw new Error(`${response.status}`);
const data = await response.json();
} catch (err) {
console.error(err);
console.error(err.message)
const appDiv = document.getElementById('app');
if (appDiv) appDiv.textContent = `Error fetching interview: ${err.message}`;
}
}
loadData();
}
export async function setupCounter(button) {
let started = false
button.addEventListener('click', async () => {
const apiKeyInput = document.querySelector('#apiKeyInput')
if (started) {
button.textContent = 'Loading...'
await stopInterview()
// button.textContent = "Start Interview"
// apiKeyInput.style.display = 'block';
setTimeout(() => {
window.location.href = "/finished.html"
}, 1000)
return
}
const apiKey = apiKeyInput.value.trim()
if (!apiKey) {
alert('Please input your token')
apiKeyInput.focus()
return
}
button.textContent = 'Loading...'
interviewToken = apiKey;
try {
console.log("start interviewing....")
const resp = await fetch(`${API_BASE_URL}/interview/${interviewId}/start_interview`,{
method: "POST",
headers: {"Authorization": "Bearer " + interviewToken},
});
if (!resp.ok){
button.textContent = 'Start Interview'
const detail = (await resp.json())['detail']
alert(detail)
console.log(detail)
return
}
if (resp.status == 202) {
console.log("Redirecting now...");
setTimeout(() => {
window.location.href = "/finished.html"
}, 1500)
return;
}
const interviewData = await resp.json()
started = true
button.textContent = 'Stop Interview';
apiKeyInput.style.display = 'none';
setInterval(() => {
seconds++;
timerElement.textContent = formatTime(seconds);
}, 1000);
await startUserCamera()
startAIBackground()
startAIVoiceInterview(interviewData.data.ephemeral_key, interviewData.data.prompt)
await startInterview()
} catch (err) {
alert(
err instanceof Error
? err.message
: "Request failed. Please try again."
)
console.log(err.message)
return
}
})
}
async function startInterview() {
const video = document.getElementById('userVideo')
stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
video.srcObject = stream
await startRecording(stream)
}
async function startRecording(stream) {
recordedChunks = []
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'video/webm'
})
mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
recordedChunks.push(event.data)
}
}
// mediaRecorder.onstop = saveRecording
mediaRecorder.start()
}
async function stopInterview(session) {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop()
console.log(`STOPPING INTERVIEW...`)
const blob = new Blob(recordedChunks, { type: "audio/webm" });
recordedChunks = [];
const formData = new FormData();
formData.append("file", blob, "recording.webm");
const upload_recording = await fetch(`${API_BASE_URL}/interview/${interviewId}/file`, {
method: "POST",
headers: {"Authorization": "Bearer " + interviewToken},
body: formData
})
const recording_data = await upload_recording.json()
const transcriptData = {
transcriptId: recording_data["data"]["datetime"],
date: new Date().toISOString(),
messages: chat_history
}
const res = await fetch(`${API_BASE_URL}/interview/${interviewId}`, {
method: "PATCH",
headers: {
"Authorization": "Bearer " + interviewToken,
"Content-Type": "application/json"
},
body: JSON.stringify({
transcript:chat_history,
recording_url:recording_data["data"]["url"],
status:"FINISHED",
duration:seconds
}),
})
if (!res.ok) {
throw new Error(`Request failed: ${res.status}`)
}
// const data = await res.json()
}
if (stream) {
stream.getTracks().forEach(track => track.stop())
}
}
// function saveRecording() {
// const timestamp = Date.now()
// const blob = new Blob(recordedChunks, {
// type: 'video/webm'
// })
// const url = URL.createObjectURL(blob)
// const a = document.createElement('a')
// a.href = url
// a.download = `interview_${timestamp}.webm`
// a.click()
// saveInterviewData()
// URL.revokeObjectURL(url)
// sessionStorage.setItem(
// "interviewTranscript",
// JSON.stringify(chat_history)
// )
// }
// function saveInterviewData(timestamp) {
// const videoBlob = new Blob(recordedChunks, {
// type: 'video/webm'
// })
// const videoUrl = URL.createObjectURL(videoBlob)
// const videoLink = document.createElement('a')
// videoLink.href = videoUrl
// videoLink.download = `interview_recording_${timestamp}.webm`
// videoLink.click()
// URL.revokeObjectURL(videoUrl)
// const transcriptData = {
// interviewId: timestamp,
// date: new Date().toISOString(),
// messages: chat_history
// }
// const jsonBlob = new Blob(
// [JSON.stringify(transcriptData, null, 2)],
// { type: "application/json" }
// )
// const jsonUrl = URL.createObjectURL(jsonBlob)
// const jsonLink = document.createElement('a')
// jsonLink.href = jsonUrl
// jsonLink.download = `interview_transcript_${timestamp}.json`
// jsonLink.click()
// URL.revokeObjectURL(jsonUrl)
// setTimeout(() => {
// window.location.href = "chat_history.html"
// }, 1500)
// }
async function startAIBackground() {
const canvas = document.querySelector('#ai-bg')
if (!canvas) {
throw new Error('canvas (#ai-bg) not found')
}
// const canvas = document.querySelector<HTMLCanvasElement>('#ai-bg')!
const ctx = canvas.getContext('2d')
canvas.width = canvas.offsetWidth
canvas.height = canvas.offsetHeight
let t = 0
function animate() {
t += 0.01
const gradient = ctx.createLinearGradient(
0,
0,
canvas.width,
canvas.height
)
gradient.addColorStop(0, `hsl(${t * 50 % 360}, 70%, 45%)`)
gradient.addColorStop(1, `hsl(${(t * 50 + 120) % 360}, 70%, 45%)`)
ctx.fillStyle = gradient
ctx.fillRect(0, 0, canvas.width, canvas.height)
requestAnimationFrame(animate)
}
animate()
}
async function startUserCamera() {
const video = document.querySelector('#userVideo')
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
})
video.srcObject = stream
}
async function startAIVoiceInterview(key, prompt) {
const agent = new RealtimeAgent({
name: 'Interviewer',
instructions: prompt ? prompt
: 'You are AI Agent currently interviewing candidates in Bahasa Indonesia. Ask everything related to their skill',
});
const session = new RealtimeSession(agent, {"model":"gpt-realtime"});
try {
await session.connect({
// To get this ephemeral key string, you can run the following command or implement the equivalent on the server side:
// curl -s -X POST https://api.openai.com/v1/realtime/client_secrets -H "Authorization: Bearer $OPENAI_API_KEY" -H "Content-Type: application/json" -d '{"session": {"type": "realtime", "model": "gpt-realtime"}}' | jq .value
apiKey: key
});
// console.log('You are connected!');
session.on('history_updated', (history) => {
// returns the full history of the session
// console.log(`HISTORY UPDATED`);
// console.log(history);
chat_history = history
});
// session.on('history_added', (history) => {
// console.log(`HISTORY ADDED`)
// console.log(history)
// })
} catch (e) {
console.error(e);
}
}