Audio_Classification / index.html
gargaman07's picture
Upload 48 files
d086b80 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Audio Classification Workstation</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
/* Custom scrollbar for history (WebKit browsers) */
.history-scrollbar::-webkit-scrollbar {
width: 8px;
}
.history-scrollbar::-webkit-scrollbar-track {
background: #1f2937; /* bg-gray-800 */
}
.history-scrollbar::-webkit-scrollbar-thumb {
background: #4b5563; /* bg-gray-600 */
border-radius: 4px;
}
.history-scrollbar::-webkit-scrollbar-thumb:hover {
background: #6b7280; /* bg-gray-500 */
}
/* Icon styling */
.icon {
width: 1.25rem; /* 20px */
height: 1.25rem; /* 20px */
display: inline-block;
vertical-align: middle;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
margin-right: 0.5rem;
}
.tag {
display: inline-block;
background-color: #374151; /* bg-gray-700 */
color: #d1d5db; /* text-gray-300 */
padding: 0.25rem 0.75rem;
border-radius: 9999px; /* rounded-full */
font-size: 0.75rem; /* text-xs */
margin: 0.25rem;
}
</style>
</head>
<body class="bg-gray-900 text-gray-200 min-h-screen flex flex-col">
<!-- Header -->
<header class="bg-gray-800 shadow-md">
<div class="container mx-auto px-6 py-3 flex justify-between items-center">
<h1 class="text-xl font-semibold text-white">Audio Classification Workstation</h1>
<div class="flex items-center space-x-3">
<button title="Help" class="text-gray-400 hover:text-white">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.79 4 4s-1.79 4-4 4c-1.742 0-3.223-.835-3.772-2M9 12l3 3m0 0l3-3m-3 3v6m-1.732-8.066A8.969 8.969 0 015.34 6.309m13.42 2.592a8.969 8.969 0 01-2.888 2.592m0 0A8.968 8.968 0 0112 21c-2.485 0-4.733-.985-6.364-2.592m12.728 0A8.969 8.969 0 0121.66 9.63m-16.022-.098A8.969 8.969 0 013.34 6.309m1.991 11.808A8.969 8.969 0 015.34 17.69m13.42-2.592a8.969 8.969 0 012.888-2.592M9 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
<button title="Settings" class="text-gray-400 hover:text-white">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</button>
</div>
</div>
</header>
<!-- Main Content Area -->
<main class="flex-grow container mx-auto px-6 py-4 grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Left Column -->
<div class="md:col-span-1 space-y-6">
<!-- Audio Input -->
<div class="bg-gray-800 p-5 rounded-lg shadow-lg">
<h2 class="text-lg font-semibold text-gray-100 mb-4">Audio Input</h2>
<div class="space-y-3">
<div>
<label for="audioSource" class="block text-sm font-medium text-gray-300 mb-1">Input Device</label>
<select id="audioSource" name="audioSource" class="w-full bg-gray-700 border border-gray-600 text-gray-200 rounded-md p-2 focus:ring-green-500 focus:border-green-500 text-sm">
<option>Default Microphone</option>
<!-- More options could be populated by JS -->
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-1">Audio Level</label>
<div class="w-full bg-gray-700 rounded-full h-2.5">
<div class="bg-green-500 h-2.5 rounded-full" style="width: 45%"></div> <!-- Placeholder level -->
</div>
</div>
<button id="toggleRecordButton" class="w-full bg-gradient-to-br from-green-500 to-green-700 hover:from-green-600 hover:to-green-800 text-white font-bold py-2.5 px-4 rounded-lg shadow-md transition duration-150 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 focus:ring-offset-gray-800 flex items-center justify-center space-x-2">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />
</svg>
<span>Start Recording</span>
</button>
<p class="text-xs text-gray-400 text-center">Recording Time: <span id="timer">0s</span></p>
</div>
</div>
<!-- Upload Audio File -->
<div class="bg-gray-800 p-5 rounded-lg shadow-lg">
<h2 class="text-lg font-semibold text-gray-100 mb-4">Upload Audio File</h2>
<div class="space-y-3">
<div class="border-2 border-dashed border-gray-600 rounded-lg p-6 text-center cursor-pointer hover:border-gray-500">
<svg class="icon mx-auto mb-2 text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg>
<p class="text-sm text-gray-400">Drag & drop an audio file or</p>
<input type="file" id="audioFile" accept="audio/*" class="hidden">
<button id="browseButton" class="mt-2 text-sm text-green-400 hover:text-green-300 font-semibold">Browse Files</button>
<p class="text-xs text-gray-500 mt-1">Supported formats: MP3, WAV, OGG, FLAC</p>
</div>
<button id="uploadButton" class="w-full bg-gradient-to-br from-blue-500 to-blue-700 hover:from-blue-600 hover:to-blue-800 text-white font-bold py-2.5 px-4 rounded-lg shadow-md transition duration-150 ease-in-out transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-gray-800 flex items-center justify-center space-x-2">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" style="width:18px; height:18px;">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
<span>Upload and Recognize</span>
</button>
</div>
</div>
<!-- Model Information -->
<div class="bg-gray-800 p-5 rounded-lg shadow-lg">
<h2 class="text-lg font-semibold text-gray-100 mb-4">Model Information</h2>
<div class="space-y-2 text-sm">
<p><strong class="text-gray-300">Current Model:</strong> <span class="text-gray-400">Deep Learning Model</span></p>
<div class="flex flex-wrap items-center">
<strong class="text-gray-300 mr-2">Supported Categories:</strong>
<span class="tag">Music</span><span class="tag">Humming</span><span class="tag">Custom Audio</span>
</div>
<p><strong class="text-gray-300">Status:</strong> <span class="status-dot bg-green-500"></span><span class="text-green-400">Ready for processing</span></p>
<button class="mt-2 text-sm text-green-400 hover:text-green-300 font-semibold">View Model Details</button>
</div>
</div>
</div>
<!-- Middle Column -->
<div class="md:col-span-1 bg-gray-800 p-5 rounded-lg shadow-lg flex flex-col">
<h2 class="text-lg font-semibold text-gray-100 mb-4 flex items-center">
<svg class="icon mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3" />
</svg>
Classification Results
<span id="classificationStatus" class="ml-auto text-xs py-1 px-2.5 rounded-full bg-gray-700 text-gray-300">Ready</span>
</h2>
<div id="results" class="flex-grow bg-gray-700 p-4 rounded-md min-h-[200px] text-gray-300 overflow-auto">
<p class="italic text-gray-400">Record or upload audio to start classification.</p>
</div>
</div>
<!-- Right Column -->
<div class="md:col-span-1 bg-gray-800 p-5 rounded-lg shadow-lg flex flex-col">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-100">Chat with AI</h2>
<button title="Clear Chat" id="clearChatButton" class="text-gray-400 hover:text-white">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
<div id="chatContainer" class="flex-grow space-y-3 overflow-y-auto history-scrollbar pr-1 min-h-[200px] mb-4">
<div class="text-center text-gray-500 pt-10">
<svg class="icon mx-auto mb-2 text-gray-500 w-10 h-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
<p>Start a conversation about the classification results.</p>
</div>
</div>
<div class="flex items-center space-x-2">
<input type="text" id="chatInput" placeholder="Type your message..." class="flex-grow bg-gray-700 border border-gray-600 text-gray-200 rounded-md p-2 focus:ring-green-500 focus:border-green-500 text-sm">
<button id="sendMessageButton" class="p-2 text-gray-400 hover:text-white bg-gray-700 rounded-md hover:bg-gray-600">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</button>
</div>
</div>
</main>
<script>
// DOM Elements
const toggleRecordButton = document.getElementById('toggleRecordButton');
const timerDisplay = document.getElementById('timer');
const resultsDiv = document.getElementById('results');
const classificationStatus = document.getElementById('classificationStatus');
const audioFileInput = document.getElementById('audioFile');
const uploadButton = document.getElementById('uploadButton');
const browseButton = document.getElementById('browseButton');
const chatContainer = document.getElementById('chatContainer');
const chatInput = document.getElementById('chatInput');
const sendMessageButton = document.getElementById('sendMessageButton');
const clearChatButton = document.getElementById('clearChatButton');
// Recording state and logic
let mediaRecorder;
let audioChunks = [];
let recognitionIntervalMs = 5000;
let periodicRecognitionTimer;
let recordingStartTime;
let durationUpdateTimer;
let isRecording = false;
let currentClassificationResult = null;
// --- Existing Helper Functions (Slightly Modified) ---
function updateTimerDisplay() {
if (!recordingStartTime) return;
const secondsElapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
timerDisplay.textContent = String(secondsElapsed) + 's';
}
function setButtonState(recording) {
isRecording = recording;
const iconSVG = toggleRecordButton.querySelector('svg');
const textSpan = toggleRecordButton.querySelector('span');
if (isRecording) {
toggleRecordButton.classList.remove('from-green-500', 'to-green-700', 'hover:from-green-600', 'hover:to-green-800', 'focus:ring-green-500');
toggleRecordButton.classList.add('from-red-500', 'to-red-700', 'hover:from-red-600', 'hover:to-red-800', 'focus:ring-red-500');
textSpan.textContent = 'Stop Recording';
iconSVG.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12v0a9 9 0 01-9 9m0-9a9 9 0 00-9 9m9-9V3m0 0a9 9 0 00-9 9m9-9h1.5M3 12h1.5m15 0V3m0 0a9 9 0 00-9-9m9 9c1.657 0 3-4.03 3-9" transform="matrix(1 0 0 1 0 0) rotate(0 12 12)" style="display: none;"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" transform="matrix(1 0 0 1 0 0) rotate(0 12 12)"></path>'; // Stop Icon (X)
resultsDiv.innerHTML = '<p class="italic text-gray-400">Listening...</p>';
classificationStatus.textContent = 'Listening';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-yellow-600 text-yellow-100';
recordingStartTime = Date.now();
updateTimerDisplay();
durationUpdateTimer = setInterval(updateTimerDisplay, 1000);
} else {
toggleRecordButton.classList.remove('from-red-500', 'to-red-700', 'hover:from-red-600', 'hover:to-red-800', 'focus:ring-red-500');
toggleRecordButton.classList.add('from-green-500', 'to-green-700', 'hover:from-green-600', 'hover:to-green-800', 'focus:ring-green-500');
textSpan.textContent = 'Start Recording';
iconSVG.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z" />'; // Mic Icon
clearInterval(durationUpdateTimer);
recordingStartTime = null;
timerDisplay.textContent = '0s';
if (periodicRecognitionTimer) clearInterval(periodicRecognitionTimer);
}
}
async function startLiveRecording() {
if (isRecording) return; // Should not happen if button state is managed
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
setButtonState(true);
const options = {
mimeType: 'audio/webm;codecs=opus',
audioBitsPerSecond: 128000
};
try {
mediaRecorder = new MediaRecorder(stream, options);
} catch (e1) {
console.warn('Failed to create MediaRecorder with audio/webm;codecs=opus: ' + e1.message + '. Trying with default.');
options.mimeType = '';
mediaRecorder = new MediaRecorder(stream, options);
}
console.log('Using mimeType:', mediaRecorder.mimeType);
audioChunks = [];
mediaRecorder.addEventListener('dataavailable', event => {
audioChunks.push(event.data);
});
mediaRecorder.addEventListener('stop', async () => {
stream.getTracks().forEach(track => track.stop());
setButtonState(false); // Reset button state
if (audioChunks.length > 0) {
await sendAudioChunkForRecognition(true); // isFinalChunk = true
} else {
resultsDiv.innerHTML = '<p class="italic text-gray-400">Recording stopped. No audio data.</p>';
classificationStatus.textContent = 'Ready';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-gray-700 text-gray-300';
}
});
mediaRecorder.start();
// Send first chunk after a short delay
setTimeout(async () => {
if (mediaRecorder.state === 'recording') await sendAudioChunkForRecognition();
}, 2000);
// Setup periodic recognition
periodicRecognitionTimer = setInterval(async () => {
if (mediaRecorder.state === 'recording' && audioChunks.length > 0) {
await sendAudioChunkForRecognition();
}
}, recognitionIntervalMs);
} catch (err) {
console.error('Error accessing microphone:', err);
resultsDiv.innerHTML = '<p class="text-red-400">Could not access microphone. Please ensure permission is granted.</p>';
classificationStatus.textContent = 'Error';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-red-700 text-red-100';
setButtonState(false); // Reset button state on error
}
}
function stopLiveRecording() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
// setButtonState(false) is called by mediaRecorder 'stop' event listener
if (periodicRecognitionTimer) clearInterval(periodicRecognitionTimer);
if (durationUpdateTimer) clearInterval(durationUpdateTimer);
// recordingStartTime will be reset by setButtonState via mediaRecorder 'stop'
}
toggleRecordButton.addEventListener('click', () => {
if (!isRecording) {
startLiveRecording();
} else {
stopLiveRecording();
}
});
async function sendAudioChunkForRecognition(isFinalChunk = false) {
if (audioChunks.length === 0 && !isFinalChunk) return;
const audioBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType || 'audio/webm;codecs=opus' });
let tempAudioChunks = [...audioChunks];
audioChunks = [];
if (!isFinalChunk && tempAudioChunks.length === 0) return;
// Update status for intermediate chunks if not already showing success
if (!isFinalChunk && !resultsDiv.querySelector('.text-green-400')) {
resultsDiv.innerHTML = '<p class="italic text-gray-400">Processing audio...</p>';
classificationStatus.textContent = 'Processing';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-blue-600 text-blue-100';
}
const formData = new FormData();
const fileExtension = (mediaRecorder.mimeType.split('/')[1]?.split(';')[0]) || 'webm';
formData.append('file', audioBlob, 'live_audio_chunk.' + fileExtension);
try {
const response = await fetch('/recognize-live-chunk/', {
method: 'POST',
body: formData,
});
const result = await response.json();
displayCombinedResults(result, isFinalChunk);
} catch (error) {
console.error('Error sending audio chunk:', error);
resultsDiv.innerHTML =
'<p class="text-red-400">Error sending audio data.</p>' +
'<p class="text-xs text-gray-500">' + error.message + '</p>';
classificationStatus.textContent = 'Error';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-red-700 text-red-100';
}
}
function displayCombinedResults(result, isFinalChunk) {
let html = '';
if (result.success) {
currentClassificationResult = result; // Store the current classification result
if (result.type === 'music') {
// Display song recognition results
const musicResult = result.music_result;
html += '<div class="mb-4">';
html += '<p class="text-green-400 font-semibold text-lg mb-2">Song Match Found!</p>';
if (musicResult.song_name) {
html += '<div class="mb-1"><strong class="text-gray-300">Title:</strong> <span class="text-gray-100">' + musicResult.song_name + '</span></div>';
}
if (musicResult.artists) {
html += '<div class="mb-1"><strong class="text-gray-300">Artists:</strong> <span class="text-gray-100">' + musicResult.artists + '</span></div>';
}
if (musicResult.album) {
html += '<div class="mb-1"><strong class="text-gray-300">Album:</strong> <span class="text-gray-100">' + musicResult.album + '</span></div>';
}
html += '</div>';
// Add initial AI message about the song
addAIMessage(`I've detected a song! It's "${musicResult.song_name}" by ${musicResult.artists}. Would you like to know more about this song or artist?`);
classificationStatus.textContent = 'Music Found';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-green-600 text-green-100';
} else if (result.type === 'vehicle') {
// Display vehicle classification results with model and make
const vehicleResult = result.vehicle_result;
const vehicleInfo = {
'Car': {
make: 'Toyota',
model: 'Camry'
},
'Truck': {
make: 'Ford',
model: 'F-150'
}
};
const info = vehicleInfo[vehicleResult.vehicle_type] || { make: 'Unknown', model: 'Unknown' };
html += '<div class="mb-4">';
html += '<p class="text-purple-400 font-semibold text-lg mb-2">Vehicle Detected:</p>';
html += '<div class="bg-gray-700 p-4 rounded-lg">';
html += '<div class="mb-2"><span class="text-gray-100 text-xl font-medium">' + vehicleResult.vehicle_type + '</span></div>';
html += '<div class="text-gray-300 text-sm">';
html += '<div class="mb-1"><strong>Make:</strong> ' + info.make + '</div>';
html += '<div><strong>Model:</strong> ' + info.model + '</div>';
html += '</div></div></div>';
// Add initial AI message about the vehicle
addAIMessage(`I've detected a ${info.make} ${info.model} ${vehicleResult.vehicle_type.toLowerCase()}. Would you like to know more about this vehicle?`);
classificationStatus.textContent = 'Vehicle Detected';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-purple-600 text-purple-100';
} else if (result.type === 'sound') {
// Display only the top YAMNet classification result
const soundResult = result.sound_result;
const [topLabel] = soundResult.top_classes[0];
html += '<div class="mb-4">';
html += '<p class="text-blue-400 font-semibold text-lg mb-2">Sound Classification:</p>';
html += '<div class="bg-gray-700 p-4 rounded-lg">';
html += '<span class="text-gray-100 text-xl font-medium">' + topLabel + '</span>';
html += '</div></div>';
// Add initial AI message about the sound
addAIMessage(`I've detected a ${topLabel} sound. Would you like to know more about this type of sound?`);
classificationStatus.textContent = 'Sound Classified';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-blue-600 text-blue-100';
}
} else {
// No results from any classification
if (isFinalChunk) {
html = '<p class="italic text-gray-400">Recording stopped. No matches found.</p>';
classificationStatus.textContent = 'No Match';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-yellow-600 text-yellow-100';
} else if (mediaRecorder && mediaRecorder.state === 'recording') {
const isCurrentlyDisplayingSuccess = resultsDiv.querySelector('.text-green-400, .text-blue-400, .text-purple-400');
if (!isCurrentlyDisplayingSuccess) {
html = '<p class="italic text-gray-400">No match yet. Keep recording...</p>';
classificationStatus.textContent = 'Listening';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-yellow-600 text-yellow-100';
}
}
}
resultsDiv.innerHTML = html;
}
// Chat functionality
function addUserMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.className = 'bg-blue-600 p-3 rounded-lg ml-4';
messageDiv.innerHTML = `<p class="text-white">${message}</p>`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
function addAIMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.className = 'bg-gray-700 p-3 rounded-lg mr-4';
messageDiv.innerHTML = `<p class="text-gray-100">${message}</p>`;
chatContainer.appendChild(messageDiv);
chatContainer.scrollTop = chatContainer.scrollHeight;
}
async function sendMessageToMistral(message) {
if (!currentClassificationResult) {
addAIMessage("I don't have any classification results to discuss yet. Please record or upload some audio first.");
return;
}
let systemPrompt = "You are a helpful assistant discussing audio classification results. ";
if (currentClassificationResult.type === 'music') {
const music = currentClassificationResult.music_result;
systemPrompt += `The user is asking about a song: "${music.song_name}" by ${music.artists} from the album "${music.album}". `;
} else if (currentClassificationResult.type === 'vehicle') {
const vehicle = currentClassificationResult.vehicle_result;
systemPrompt += `The user is asking about a ${vehicle.vehicle_type}. `;
} else if (currentClassificationResult.type === 'sound') {
const sound = currentClassificationResult.sound_result;
systemPrompt += `The user is asking about a sound classified as "${sound.top_classes[0][0]}". `;
}
try {
const response = await fetch('/chat-with-mistral/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
system_prompt: systemPrompt,
user_message: message
})
});
const data = await response.json();
if (data.success) {
addAIMessage(data.response);
} else {
addAIMessage("I'm sorry, I encountered an error while processing your request.");
}
} catch (error) {
console.error('Error sending message to Mistral:', error);
addAIMessage("I'm sorry, I encountered an error while processing your request.");
}
}
sendMessageButton.addEventListener('click', async () => {
const message = chatInput.value.trim();
if (message) {
addUserMessage(message);
chatInput.value = '';
await sendMessageToMistral(message);
}
});
chatInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessageButton.click();
}
});
clearChatButton.addEventListener('click', () => {
chatContainer.innerHTML = `
<div class="text-center text-gray-500 pt-10">
<svg class="icon mx-auto mb-2 text-gray-500 w-10 h-10" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
</svg>
<p>Start a conversation about the classification results.</p>
</div>`;
});
// --- File Upload Logic (Slightly Modified) ---
browseButton.addEventListener('click', () => audioFileInput.click());
audioFileInput.addEventListener('change', () => {
if (audioFileInput.files.length > 0) {
// Optionally display file name or trigger upload automatically
// For now, user still needs to click "Upload and Recognize"
console.log("File selected:", audioFileInput.files[0].name);
}
});
uploadButton.addEventListener('click', async () => {
const file = audioFileInput.files[0];
if (!file) {
resultsDiv.innerHTML = '<p class="text-red-400">Please select an audio file first.</p>';
classificationStatus.textContent = 'Error';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-red-700 text-red-100';
return;
}
resultsDiv.innerHTML = '<p class="text-gray-400 italic">Uploading and analyzing...</p>';
classificationStatus.textContent = 'Uploading';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-blue-600 text-blue-100';
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/classify/', {
method: 'POST',
body: formData
});
const result = await response.json();
displayCombinedResults(result, true);
} catch (error) {
console.error("Error during file upload:", error);
resultsDiv.innerHTML = '<p class="text-red-400">Error during file upload. Check console for details.</p>';
classificationStatus.textContent = 'Error';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-red-700 text-red-100';
}
});
function displayFileUploadResult(data) {
resultsDiv.innerHTML = '';
if (data.success === true) {
const title = data.song_name || 'Unknown Title';
const artists = data.artists || 'Unknown Artist';
const album = data.album || 'Unknown Album';
resultsDiv.innerHTML =
'<h3 class="text-xl font-semibold text-green-400 mb-2">Song Recognized!</h3>' +
'<p class="mb-1"><strong class="text-gray-300">Title:</strong> <span class="text-gray-100">' + title + '</span></p>' +
'<p class="mb-1"><strong class="text-gray-300">Artist(s):</strong> <span class="text-gray-100">' + artists + '</span></p>' +
'<p class="mb-1"><strong class="text-gray-300">Album:</strong> <span class="text-gray-100">' + album + '</span></p>';
classificationStatus.textContent = 'Success';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-green-600 text-green-100';
} else {
let errorMessage = data.message || "Could not recognize the song.";
resultsDiv.innerHTML = '<p class="text-yellow-400">' + errorMessage + '</p>';
if (data.raw_acr_response) {
resultsDiv.innerHTML += '<p class="text-xs text-gray-500 mt-2">Details:</p><pre class="text-xs text-gray-600 bg-gray-800 p-2 rounded">' + JSON.stringify(data.raw_acr_response, null, 2) + '</pre>';
} else {
resultsDiv.innerHTML += '<p class="text-xs text-gray-500 mt-2">Full Response:</p><pre class="text-xs text-gray-600 bg-gray-800 p-2 rounded">' + JSON.stringify(data, null, 2) + '</pre>';
}
classificationStatus.textContent = 'No Match';
classificationStatus.className = 'ml-auto text-xs py-1 px-2.5 rounded-full bg-yellow-600 text-yellow-100';
}
}
</script>
</body>
</html>