240 lines
10 KiB
Plaintext
240 lines
10 KiB
Plaintext
@{
|
|
Layout = "_ConfigurePlugin";
|
|
}
|
|
|
|
@await Component.InvokeAsync("StoreScopeConfiguration")
|
|
|
|
<form asp-action="ReceiveVoiceRecording" asp-controller="FruitBankAdmin" method="post">
|
|
@Html.AntiForgeryToken()
|
|
</form>
|
|
|
|
<div class="card card-default">
|
|
<div class="card-body">
|
|
<h4>Voice Recorder</h4>
|
|
<p>Click the button below to start recording your voice message.</p>
|
|
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle"></i> <strong>Important:</strong> When you click "Start Recording", your browser will ask for permission to use your microphone. Please click "Allow" to enable voice recording.
|
|
</div>
|
|
|
|
<div id="permissionInstructions" class="alert alert-warning" style="display:none;">
|
|
<strong><i class="fas fa-exclamation-triangle"></i> Microphone Permission Required</strong>
|
|
<p class="mb-2">Your browser blocked microphone access. To enable it:</p>
|
|
<ul class="mb-2">
|
|
<li><strong>Chrome/Edge:</strong> Click the camera/microphone icon in the address bar (or the lock icon) → Site settings → Allow Microphone</li>
|
|
<li><strong>Firefox:</strong> Click the microphone icon in the address bar → Click the X to remove the block → Refresh the page</li>
|
|
<li><strong>Safari:</strong> Safari menu → Settings for This Website → Microphone → Allow</li>
|
|
</ul>
|
|
<p class="mb-0">After allowing permission, <strong>refresh this page</strong> and try again.</p>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-9">
|
|
<button type="button" id="recordButton" class="btn btn-primary">
|
|
<i class="fas fa-microphone"></i> Start Recording
|
|
</button>
|
|
<button type="button" id="stopButton" class="btn btn-danger" style="display:none;">
|
|
<i class="fas fa-stop"></i> Stop Recording
|
|
</button>
|
|
<span id="recordingStatus" style="margin-left: 15px; font-weight: bold;"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row" id="audioPlaybackSection" style="display:none;">
|
|
<div class="col-md-9">
|
|
<audio id="audioPlayback" controls style="width: 100%;"></audio>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row" id="sendSection" style="display:none;">
|
|
<div class="col-md-9">
|
|
<button type="button" id="sendButton" class="btn btn-success">
|
|
<i class="fas fa-paper-plane"></i> Send to API
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group row">
|
|
<div class="col-md-9">
|
|
<div id="responseMessage" class="alert" style="display:none;"></div>
|
|
<div id="transcriptionResult" style="display:none; margin-top: 15px;">
|
|
<h5>Transcription:</h5>
|
|
<div class="card">
|
|
<div class="card-body">
|
|
<p id="transcriptionText" style="white-space: pre-wrap;"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let mediaRecorder;
|
|
let audioChunks = [];
|
|
let audioBlob;
|
|
|
|
const recordButton = document.getElementById('recordButton');
|
|
const stopButton = document.getElementById('stopButton');
|
|
const sendButton = document.getElementById('sendButton');
|
|
const recordingStatus = document.getElementById('recordingStatus');
|
|
const audioPlayback = document.getElementById('audioPlayback');
|
|
const audioPlaybackSection = document.getElementById('audioPlaybackSection');
|
|
const sendSection = document.getElementById('sendSection');
|
|
const responseMessage = document.getElementById('responseMessage');
|
|
const transcriptionResult = document.getElementById('transcriptionResult');
|
|
const transcriptionText = document.getElementById('transcriptionText');
|
|
const permissionInstructions = document.getElementById('permissionInstructions');
|
|
|
|
recordButton.addEventListener('click', async () => {
|
|
try {
|
|
// Hide permission instructions if shown
|
|
permissionInstructions.style.display = 'none';
|
|
|
|
// Check if we're on HTTPS or localhost
|
|
const isSecure = window.location.protocol === 'https:' ||
|
|
window.location.hostname === 'localhost' ||
|
|
window.location.hostname === '127.0.0.1';
|
|
|
|
if (!isSecure) {
|
|
showMessage('Error: Microphone access requires HTTPS connection. Please access this page via HTTPS.', 'danger');
|
|
return;
|
|
}
|
|
|
|
// Check if getUserMedia is supported
|
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
showMessage('Error: Your browser does not support audio recording. Please use a modern browser like Chrome, Firefox, or Edge.', 'danger');
|
|
return;
|
|
}
|
|
|
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
|
|
mediaRecorder = new MediaRecorder(stream);
|
|
audioChunks = [];
|
|
|
|
mediaRecorder.ondataavailable = (event) => {
|
|
audioChunks.push(event.data);
|
|
};
|
|
|
|
mediaRecorder.onstop = () => {
|
|
audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
|
|
const audioUrl = URL.createObjectURL(audioBlob);
|
|
audioPlayback.src = audioUrl;
|
|
audioPlaybackSection.style.display = 'block';
|
|
sendSection.style.display = 'block';
|
|
|
|
// Stop all tracks to release the microphone
|
|
stream.getTracks().forEach(track => track.stop());
|
|
};
|
|
|
|
mediaRecorder.start();
|
|
recordButton.style.display = 'none';
|
|
stopButton.style.display = 'inline-block';
|
|
recordingStatus.textContent = 'Recording...';
|
|
recordingStatus.style.color = 'red';
|
|
audioPlaybackSection.style.display = 'none';
|
|
sendSection.style.display = 'none';
|
|
|
|
} catch (error) {
|
|
console.error('Error accessing microphone:', error);
|
|
|
|
let errorMessage = 'Error: Could not access microphone. ';
|
|
let showInstructions = false;
|
|
|
|
if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
|
|
errorMessage = 'Microphone access was denied. Please see the instructions below to enable microphone access.';
|
|
showInstructions = true;
|
|
} else if (error.name === 'NotFoundError' || error.name === 'DevicesNotFoundError') {
|
|
errorMessage += 'No microphone found. Please connect a microphone and try again.';
|
|
} else if (error.name === 'NotReadableError' || error.name === 'TrackStartError') {
|
|
errorMessage += 'Microphone is already in use by another application. Please close other applications using the microphone.';
|
|
} else if (error.name === 'OverconstrainedError' || error.name === 'ConstraintNotSatisfiedError') {
|
|
errorMessage += 'Microphone does not meet the required constraints.';
|
|
} else if (error.name === 'NotSupportedError') {
|
|
errorMessage += 'This page must be accessed via HTTPS to use the microphone.';
|
|
} else if (error.name === 'TypeError') {
|
|
errorMessage += 'This page must be accessed via HTTPS to use the microphone.';
|
|
} else {
|
|
errorMessage += error.message || 'Unknown error occurred.';
|
|
}
|
|
|
|
showMessage(errorMessage, 'danger');
|
|
|
|
// Show permission instructions if needed
|
|
if (showInstructions) {
|
|
permissionInstructions.style.display = 'block';
|
|
// Scroll to instructions
|
|
permissionInstructions.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
}
|
|
}
|
|
});
|
|
|
|
stopButton.addEventListener('click', () => {
|
|
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
|
|
mediaRecorder.stop();
|
|
recordButton.style.display = 'inline-block';
|
|
stopButton.style.display = 'none';
|
|
recordingStatus.textContent = 'Recording stopped';
|
|
recordingStatus.style.color = 'green';
|
|
}
|
|
});
|
|
|
|
sendButton.addEventListener('click', async () => {
|
|
if (!audioBlob) {
|
|
showMessage('No audio recorded yet!', 'warning');
|
|
return;
|
|
}
|
|
|
|
const formData = new FormData();
|
|
formData.append('audioFile', audioBlob, 'recording.webm');
|
|
|
|
try {
|
|
sendButton.disabled = true;
|
|
sendButton.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Sending...';
|
|
|
|
// Get the antiforgery token
|
|
const token = document.querySelector('input[name="__RequestVerificationToken"]').value;
|
|
|
|
const response = await fetch('@Url.Action("ReceiveVoiceRecording", "FruitBankAdmin")', {
|
|
method: 'POST',
|
|
headers: {
|
|
'RequestVerificationToken': token
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (response.ok && result.success) {
|
|
showMessage('Audio transcribed successfully!', 'success');
|
|
|
|
// Display transcription
|
|
if (result.transcription) {
|
|
transcriptionText.textContent = result.transcription;
|
|
transcriptionResult.style.display = 'block';
|
|
}
|
|
} else {
|
|
showMessage('Error: ' + (result.message || 'Failed to transcribe audio'), 'danger');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error sending audio:', error);
|
|
showMessage('Error: Failed to send audio to server', 'danger');
|
|
} finally {
|
|
sendButton.disabled = false;
|
|
sendButton.innerHTML = '<i class="fas fa-paper-plane"></i> Send to API';
|
|
}
|
|
});
|
|
|
|
function showMessage(message, type) {
|
|
responseMessage.textContent = message;
|
|
responseMessage.className = 'alert alert-' + type;
|
|
responseMessage.style.display = 'block';
|
|
|
|
setTimeout(() => {
|
|
responseMessage.style.display = 'none';
|
|
}, 5000);
|
|
}
|
|
</script>
|