let currentSession = {
topic: null,
currentQuestion: null,
questionIndex: null,
totalScore: 0,
questionCount: 0
};
// Voice recognition setup
let recognition = null;
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = false;
recognition.lang = 'en-US';
recognition.onresult = (event) => {
const transcript = event.results[0][0].transcript;
document.getElementById('studentAnswer').value = transcript;
};
recognition.onerror = (event) => {
console.error('Speech recognition error:', event.error);
stopVoiceInput();
};
}
async function startSession(topic) {
try {
const response = await fetch('/api/start_session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ topic: topic })
});
const data = await response.json();
if (data.status === 'started') {
currentSession.topic = topic;
currentSession.totalScore = 0;
currentSession.questionCount = 0;
displayQuestion(data.first_question);
document.getElementById('sessionTopic').textContent = `Topic: ${getTopicName(topic)}`;
document.getElementById('topicSelection').classList.add('hidden');
document.getElementById('vivaSession').classList.remove('hidden');
updateProgress();
}
} catch (error) {
console.error('Error starting session:', error);
alert('Failed to start session. Please try again.');
}
}
function getTopicName(topicKey) {
const topicNames = {
'upper_limb': 'Upper Limb Anatomy',
'lower_limb': 'Lower Limb Anatomy',
'cardiology': 'Cardiac Anatomy',
'neuroanatomy': 'Neuroanatomy'
};
return topicNames[topicKey] || topicKey;
}
function displayQuestion(questionData) {
if (questionData.completed) {
endSession();
return;
}
currentSession.currentQuestion = questionData.question;
currentSession.questionIndex = questionData.question_index;
document.getElementById('questionText').textContent = questionData.question;
document.getElementById('studentAnswer').value = '';
document.getElementById('feedbackArea').classList.add('hidden');
updateProgress();
}
async function speakCurrentQuestion() {
const questionText = currentSession.currentQuestion;
if (!questionText) return;
const listeningIndicator = document.getElementById('listeningIndicator');
listeningIndicator.classList.remove('hidden');
try {
const response = await fetch('/api/text_to_speech', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text: questionText })
});
const data = await response.json();
if (data.audio_data) {
await playAudioData(data.audio_data);
}
} catch (error) {
console.error('TTS failed:', error);
// Fallback to browser TTS
speakWithBrowserTTS(questionText);
} finally {
listeningIndicator.classList.add('hidden');
}
}
async function playAudioData(base64Audio) {
return new Promise((resolve) => {
const audioBlob = base64ToBlob(base64Audio, 'audio/wav');
const audioUrl = URL.createObjectURL(audioBlob);
const audio = new Audio(audioUrl);
audio.onended = () => {
URL.revokeObjectURL(audioUrl);
resolve();
};
audio.onerror = () => resolve();
audio.play().catch(error => {
console.error('Audio play failed:', error);
resolve();
});
});
}
function speakWithBrowserTTS(text) {
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.rate = 0.8;
utterance.pitch = 1.0;
const voices = speechSynthesis.getVoices();
const englishVoice = voices.find(voice => voice.lang.startsWith('en-'));
if (englishVoice) {
utterance.voice = englishVoice;
}
speechSynthesis.speak(utterance);
}
}
function toggleVoiceInput() {
const voiceBtn = document.getElementById('voiceBtn');
if (!recognition) {
alert('Speech recognition not supported in this browser. Please use Chrome or Edge.');
return;
}
if (voiceBtn.textContent.includes('Start')) {
startVoiceInput();
} else {
stopVoiceInput();
}
}
function startVoiceInput() {
const voiceBtn = document.getElementById('voiceBtn');
voiceBtn.textContent = '🛑 Stop Listening';
voiceBtn.style.background = '#e74c3c';
recognition.start();
}
function stopVoiceInput() {
const voiceBtn = document.getElementById('voiceBtn');
voiceBtn.textContent = '🎤 Voice Input';
voiceBtn.style.background = '#9b59b6';
if (recognition) {
recognition.stop();
}
}
async function submitAnswer() {
const answer = document.getElementById('studentAnswer').value.trim();
if (!answer) {
alert('Please enter your answer');
return;
}
try {
const response = await fetch('/api/evaluate_answer', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
question_index: currentSession.questionIndex,
answer: answer
})
});
const data = await response.json();
displayFeedback(data);
} catch (error) {
console.error('Error submitting answer:', error);
alert('Failed to submit answer. Please try again.');
}
}
function displayFeedback(data) {
currentSession.totalScore += data.score;
currentSession.questionCount++;
document.getElementById('feedbackText').textContent = data.feedback;
document.getElementById('scoreValue').textContent = data.score.toFixed(1);
document.getElementById('totalScore').textContent = currentSession.totalScore.toFixed(1);
document.getElementById('feedbackArea').classList.remove('hidden');
updateProgress();
addToHistory(data);
}
function addToHistory(data) {
const historyContainer = document.getElementById('conversationHistory');
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
historyItem.innerHTML = `
Q${currentSession.questionCount}: ${currentSession.currentQuestion}
Your Answer: ${data.transcribed_answer || document.getElementById('studentAnswer').value.substring(0, 100)}...
Score: ${data.score.toFixed(1)}/10
`;
historyContainer.appendChild(historyItem);
historyContainer.scrollTop = historyContainer.scrollHeight;
}
function updateProgress() {
const progressFill = document.querySelector('.progress-fill');
if (currentSession.questionCount > 0) {
const progress = (currentSession.questionCount / 5) * 100; // Assuming 5 questions per topic
progressFill.style.width = `${Math.min(progress, 100)}%`;
} else {
progressFill.style.width = '0%';
}
}
function nextQuestion() {
// In a real implementation, this would get the next question from the server
// For now, we'll simulate by ending the session after a few questions
if (currentSession.questionCount >= 3) {
endSession();
} else {
// Simulate getting next question
const nextQuestion = {
question: `Next question about ${currentSession.topic}...`,
question_index: currentSession.questionIndex + 1
};
displayQuestion(nextQuestion);
}
}
function endSession() {
const averageScore = currentSession.totalScore / currentSession.questionCount;
alert(`Session completed! Your average score: ${averageScore.toFixed(1)}/10\nWell done!`);
resetSession();
}
function resetSession() {
currentSession = {
topic: null,
currentQuestion: null,
questionIndex: null,
totalScore: 0,
questionCount: 0
};
document.getElementById('vivaSession').classList.add('hidden');
document.getElementById('topicSelection').classList.remove('hidden');
document.getElementById('conversationHistory').innerHTML = '';
document.getElementById('totalScore').textContent = '0';
}
// Utility function
function base64ToBlob(base64, mimeType) {
const byteCharacters = atob(base64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
return new Blob([byteArray], { type: mimeType });
}
// Initialize speech synthesis voices
if ('speechSynthesis' in window) {
speechSynthesis.getVoices();
}