aniruddhakumarpaul's picture
Upload folder using huggingface_hub
f74654d verified
const micBtn = document.getElementById('micBtn');
const statusText = document.getElementById('statusText');
const visualizer = document.getElementById('visualizer');
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
// Result Modal Elements
const resultModal = document.getElementById('resultModal');
const closeModal = document.getElementById('closeModal');
const resultEmoji = document.getElementById('resultEmoji');
const resultLabel = document.getElementById('resultLabel');
const resultConfidence = document.getElementById('resultConfidence');
const btnCorrect = document.getElementById('btnCorrect');
const btnIncorrect = document.getElementById('btnIncorrect');
const correctionArea = document.getElementById('correctionArea');
const submitCorrection = document.getElementById('submitCorrection');
let mediaRecorder;
let audioChunks = [];
let currentTempFilename = null;
let currentPrediction = null;
// Emotion to Emoji Map
const emotionEmojis = {
'neutral': '😐',
'calm': '😌',
'happiness': 'πŸ˜„',
'happy': 'πŸ˜„',
'sadness': '😒',
'sad': '😒',
'anger': '😠',
'angry': '😠',
'fear': '😱',
'disgust': '🀒',
'surprise': '😲'
};
// --- Recording Logic ---
micBtn.addEventListener('mousedown', startRecording);
micBtn.addEventListener('mouseup', stopRecording);
micBtn.addEventListener('mouseleave', () => {
if (mediaRecorder && mediaRecorder.state === 'recording') {
stopRecording();
}
});
// Training Logic
const trainingModal = document.getElementById('trainingModal');
const trainingLog = document.getElementById('trainingLog');
const closeTrainingModal = document.getElementById('closeTrainingModal');
closeTrainingModal.addEventListener('click', () => {
trainingModal.classList.add('hidden');
});
// --- Training & Password Logic ---
const passwordModal = document.getElementById('passwordModal');
const closePasswordModal = document.getElementById('closePasswordModal');
const submitPasswordBtn = document.getElementById('submitPasswordBtn');
const adminPasswordInput = document.getElementById('adminPasswordInput');
// Open Password Modal
document.getElementById('trainBtn').addEventListener('click', () => {
passwordModal.classList.remove('hidden');
adminPasswordInput.value = '';
adminPasswordInput.focus();
});
// Close Password Modal
closePasswordModal.addEventListener('click', () => {
passwordModal.classList.add('hidden');
});
// Handle Password Submission
// Handle Password Submission
function submitPassword() {
const password = adminPasswordInput.value;
if (!password) {
showToast("Please enter a password", "error");
return;
}
passwordModal.classList.add('hidden');
startTraining(password);
}
submitPasswordBtn.addEventListener('click', submitPassword);
// Allow Enter key to submit password and Esc to close modals
document.addEventListener('keydown', (e) => {
// Enter Key in Password Input
if (e.key === 'Enter' && document.activeElement === adminPasswordInput) {
submitPassword();
}
// Escape Key Global
if (e.key === 'Escape') {
passwordModal.classList.add('hidden');
resultModal.classList.add('hidden');
trainingModal.classList.add('hidden');
}
});
async function startTraining(password) {
// Open Terminal
trainingModal.classList.remove('hidden');
trainingLog.innerHTML = '<span class="log-line">Authenticating...</span>';
try {
const response = await fetch('/train', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ password: password })
});
if (response.status === 401) {
trainingLog.innerHTML += '<span class="log-line" style="color:red">Error: Unauthorized. Incorrect Password.</span>';
showToast("Incorrect Admin Password", "error");
return;
}
const data = await response.json();
if (data.status === 'training_started') {
trainingLog.innerHTML += '<span class="log-line">Access Granted. Starting training sequence...</span>';
pollLogs();
}
} catch (e) {
showToast("Failed to start training.", "error");
trainingLog.innerHTML += `<span class="log-line" style="color:red">Error: ${e.message}</span>`;
}
}
async function pollLogs(startIndex = 0) {
try {
const response = await fetch(`/logs?after=${startIndex}`);
const data = await response.json();
if (data.logs && data.logs.length > 0) {
data.logs.forEach(log => {
const line = document.createElement('span');
line.className = 'log-line';
line.innerText = log;
if (log.includes("CRITICAL") || log.includes("Error")) line.style.color = '#ff5555';
if (log.includes("Success") || log.includes("complete")) line.style.color = '#55ff55';
trainingLog.appendChild(line);
});
// Auto scroll
trainingLog.scrollTop = trainingLog.scrollHeight;
}
// Continue polling if not complete (simple check: if logs stop or specific message?)
// Better: The backend just keeps logs coming. We'll poll until we see "Training complete"
const lastLog = data.logs.length > 0 ? data.logs[data.logs.length - 1] : "";
if (lastLog.includes("Training complete")) {
trainingLog.innerHTML += '<span class="log-line">>> Process finished. You may close this window.</span>';
return;
}
setTimeout(() => pollLogs(data.next_index), 500); // Poll every 500ms
} catch (e) {
console.error("Polling error", e);
setTimeout(() => pollLogs(startIndex), 2000); // Retry slower on error
}
}
// Touch support for mobile
micBtn.addEventListener('touchstart', (e) => { e.preventDefault(); startRecording(); });
micBtn.addEventListener('touchend', (e) => { e.preventDefault(); stopRecording(); });
function startRecording() {
statusText.innerText = "Recording...";
micBtn.classList.add('recording');
visualizer.classList.remove('hidden');
audioChunks = [];
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => {
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.start();
mediaRecorder.addEventListener("dataavailable", event => {
audioChunks.push(event.data);
});
mediaRecorder.addEventListener("stop", () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); // Default typically webm, assumes backend handles it or we send as file
// Usually comes as webm/ogg from browser. We'll verify mimetype.
uploadAudio(audioBlob, "recording.wav"); // Naming it .wav but content might be webm, backend pydub handles it.
});
})
.catch(err => {
console.error("Error accessing mic:", err);
statusText.innerText = "Error Accessing Mic";
});
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
statusText.innerText = "Processing...";
micBtn.classList.remove('recording');
visualizer.classList.add('hidden');
}
}
// --- File Upload Logic ---
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover'));
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.classList.remove('dragover');
if (e.dataTransfer.files.length > 0) {
handleFile(e.dataTransfer.files[0]);
}
});
function handleFile(file) {
statusText.innerText = `Uploading ${file.name}...`;
uploadAudio(file, file.name);
}
// --- Toast Notifications ---
function showToast(message, type = 'info') {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
// Icon selection
let icon = 'fa-info-circle';
if (type === 'success') icon = 'fa-check-circle';
if (type === 'error') icon = 'fa-exclamation-circle';
toast.innerHTML = `
<i class="fa-solid ${icon}"></i>
<span>${message}</span>
`;
container.appendChild(toast);
// Auto remove
setTimeout(() => {
toast.classList.add('hide');
toast.addEventListener('animationend', () => toast.remove());
}, 3000);
}
// --- API Calls ---
async function uploadAudio(fileOrBlob, filename) {
const formData = new FormData();
formData.append("file", fileOrBlob, filename); // Append file
try {
const response = await fetch('/predict', {
method: 'POST',
body: formData
});
if (!response.ok) {
const errData = await response.json();
throw new Error(errData.detail || "Prediction failed");
}
const data = await response.json();
showResult(data);
statusText.innerText = "Click & Hold to Record";
showToast("Analysis Complete", "success");
} catch (error) {
console.error(error);
statusText.innerText = "Error: " + error.message;
showToast("Error: " + error.message, "error");
}
}
function showResult(data) {
currentTempFilename = data.temp_filename;
currentPrediction = data.prediction;
resultEmoji.innerText = emotionEmojis[data.prediction.toLowerCase()] || '❓';
resultLabel.innerText = data.prediction.charAt(0).toUpperCase() + data.prediction.slice(1);
resultConfidence.innerText = `Confidence: ${(data.confidence * 100).toFixed(1)}%`;
// Reset feedback UI
correctionArea.classList.add('hidden');
resultModal.classList.remove('hidden');
if (data.is_fallback) {
showToast("Model not trained. Please label this audio to build the dataset.", "info");
correctionArea.classList.remove('hidden');
resultLabel.innerText = "Label Required";
resultEmoji.innerText = "🏷️";
resultConfidence.innerText = "Help the AI learn!";
}
// --- NLP Analysis Display ---
let nlpDiv = document.getElementById('nlp-results');
if (!nlpDiv) {
nlpDiv = document.createElement('div');
nlpDiv.id = 'nlp-results';
nlpDiv.className = 'nlp-container';
// Insert before feedback section
const feedbackSection = resultModal.querySelector('.feedback-section');
resultModal.querySelector('.modal-content').insertBefore(nlpDiv, feedbackSection);
}
// Clear previous
nlpDiv.innerHTML = '';
if (data.nlp_analysis && data.nlp_analysis.transcription) {
const textEmotion = data.nlp_analysis.text_emotion;
const confidencePct = (textEmotion.score * 100).toFixed(1);
// Show Hybrid Breakdown
nlpDiv.innerHTML = `
<div class="divider">Hybrid Analysis</div>
<p class="transcription">"${data.nlp_analysis.transcription}"</p>
<div class="breakdown-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 10px; font-size: 0.9rem;">
<div class="breakdown-item">
<div style="color: #94a3b8;">Audio Tone</div>
<div class="highlight">${data.audio_emotion.label}</div>
<div class="confidence-small">${(data.audio_emotion.confidence * 100).toFixed(1)}%</div>
</div>
<div class="breakdown-item">
<div style="color: #94a3b8;">Text Context</div>
<div class="highlight">${textEmotion.label}</div>
<div class="confidence-small">${confidencePct}%</div>
</div>
</div>
<div style="margin-top: 10px; font-size: 0.8rem; color: #64748b;">
Result fused from acoustic and semantic models.
</div>
`;
} else {
nlpDiv.innerHTML = `
<div class="divider">Context Analysis</div>
<p style="color: #64748b; font-style: italic;">No speech detected or analysis unavailable.</p>
`;
}
}
// --- Modal & Feedback ---
closeModal.addEventListener('click', () => resultModal.classList.add('hidden'));
window.onclick = (event) => {
if (event.target == resultModal) resultModal.classList.add('hidden');
};
btnCorrect.addEventListener('click', () => {
submitFeedback(currentPrediction);
});
btnIncorrect.addEventListener('click', () => {
correctionArea.classList.remove('hidden');
});
submitCorrection.addEventListener('click', () => {
const selected = document.getElementById('emotionSelect').value;
if (selected) {
submitFeedback(selected);
}
});
async function submitFeedback(correctLabel) {
try {
const response = await fetch('/feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filename: currentTempFilename,
original_emotion: currentPrediction,
correct_emotion: correctLabel
})
});
const res = await response.json();
if (res.status === 'success') {
showToast("Feedback saved successfully!", "success");
resultModal.classList.add('hidden');
}
} catch (e) {
showToast("Failed to save feedback.", "error");
}
}