275 lines
13 KiB
JavaScript
275 lines
13 KiB
JavaScript
import { ParameterType } from 'jspsych';
|
|
import html from '../utils/html.js';
|
|
|
|
const info = {
|
|
name: "transcribe-call",
|
|
parameters: {
|
|
|
|
},
|
|
};
|
|
|
|
class jsPsychTranscribeCall {
|
|
constructor(jsPsych) {
|
|
this.jsPsych = jsPsych;
|
|
}
|
|
static {
|
|
this.info = info;
|
|
}
|
|
|
|
|
|
trial(display_element, trial) {
|
|
// Get the original recording data from the record-call trial
|
|
// Look through recent trials to find one with recording response
|
|
const allData = this.jsPsych.data.get();
|
|
const recentTrials = allData.trials.slice(-10); // Look at last 10 trials
|
|
|
|
let recordingData = null;
|
|
for (let i = recentTrials.length - 1; i >= 0; i--) {
|
|
const trial = recentTrials[i];
|
|
if (trial.response && typeof trial.response === 'string' && trial.response.length > 100) {
|
|
// Found a trial with audio recording data
|
|
recordingData = trial;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!recordingData || !recordingData.response) {
|
|
display_element.innerHTML = `
|
|
<div style="text-align: center; padding: 20px;">
|
|
<p style="color: red;">No recording found from the previous trial.</p>
|
|
<button onclick="this.jsPsych.finishTrial()" style="
|
|
padding: 10px 20px;
|
|
font-size: 14px;
|
|
background-color: #007cba;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
">Continue</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
// Convert base64 back to audio blob for playback
|
|
let audioData;
|
|
let audioBlob;
|
|
try {
|
|
const byteCharacters = atob(recordingData.response);
|
|
const byteNumbers = new Array(byteCharacters.length);
|
|
for (let i = 0; i < byteCharacters.length; i++) {
|
|
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
}
|
|
const byteArray = new Uint8Array(byteNumbers);
|
|
audioBlob = new Blob([byteArray], { type: 'audio/ogg' });
|
|
audioData = URL.createObjectURL(audioBlob);
|
|
} catch (error) {
|
|
console.error('Error creating audio blob:', error);
|
|
display_element.innerHTML = `
|
|
<div style="text-align: center; padding: 20px;">
|
|
<p style="color: red;">Error loading audio data.</p>
|
|
<button onclick="this.jsPsych.finishTrial({})" style="
|
|
padding: 10px 20px;
|
|
font-size: 14px;
|
|
background-color: #007cba;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
">Continue</button>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
display_element.innerHTML = `
|
|
<div style="text-align: center; padding: 20px;">
|
|
<h2 style="margin-bottom: 20px;">Recording Transcription</h2>
|
|
|
|
<div style="margin: 20px 0;">
|
|
<audio id="transcription-playback-audio" controls style="display: block; margin: 10px auto;"></audio>
|
|
</div>
|
|
|
|
<div style="margin: 20px; text-align: left; max-width: 600px; margin-left: auto; margin-right: auto;">
|
|
<h3 style="text-align: center; margin-bottom: 20px;">Please answer the following questions:</h3>
|
|
|
|
<div style="margin-bottom: 15px;">
|
|
<label for="spelling-input" style="display: block; margin-bottom: 5px; font-weight: bold;">How would you spell what you have just recorded?</label>
|
|
<input type="text" id="spelling-input" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;" required>
|
|
</div>
|
|
|
|
<div style="margin-bottom: 15px;">
|
|
<label for="language-select" style="display: block; margin-bottom: 5px; font-weight: bold;">What language is it in?</label>
|
|
<select id="language-select" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;" required>
|
|
<option value="">Select a language...</option>
|
|
<option value="English">English</option>
|
|
<option value="Spanish">Spanish</option>
|
|
<option value="French">French</option>
|
|
<option value="German">German</option>
|
|
<option value="Italian">Italian</option>
|
|
<option value="Portuguese">Portuguese</option>
|
|
<option value="Russian">Russian</option>
|
|
<option value="Chinese">Chinese</option>
|
|
<option value="Japanese">Japanese</option>
|
|
<option value="Korean">Korean</option>
|
|
<option value="Arabic">Arabic</option>
|
|
<option value="Hindi">Hindi</option>
|
|
<option value="Other">Other</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div id="other-language-section" style="margin-bottom: 15px; display: none;">
|
|
<label for="other-language-input" style="display: block; margin-bottom: 5px; font-weight: bold;">Please specify the language:</label>
|
|
<input type="text" id="other-language-input" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
|
|
</div>
|
|
|
|
<div id="translation-section" style="margin-bottom: 15px; display: none;">
|
|
<label for="translation-input" style="display: block; margin-bottom: 5px; font-weight: bold;">How would you translate it to English?</label>
|
|
<input type="text" id="translation-input" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
|
|
</div>
|
|
|
|
<div style="margin-bottom: 15px;">
|
|
<label for="meaning-input" style="display: block; margin-bottom: 5px; font-weight: bold;">Does it have a meaning? If so, write it here in English:</label>
|
|
<input type="text" id="meaning-input" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px;" placeholder="Write the meaning or 'No meaning' if it doesn't have one">
|
|
</div>
|
|
|
|
<button id="submit-answers-button" style="
|
|
padding: 12px 24px;
|
|
font-size: 16px;
|
|
background-color: #007cba;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
display: block;
|
|
margin: 20px auto 0 auto;
|
|
">Submit Answers</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Set the audio source
|
|
const audio = document.getElementById('transcription-playback-audio');
|
|
audio.src = audioData;
|
|
|
|
this.setupTranscriptionEvents(recordingData);
|
|
}
|
|
|
|
setupTranscriptionEvents(recordingData) {
|
|
const audio = document.getElementById('transcription-playback-audio');
|
|
const spellingInput = document.getElementById('spelling-input');
|
|
const languageSelect = document.getElementById('language-select');
|
|
const otherLanguageSection = document.getElementById('other-language-section');
|
|
const otherLanguageInput = document.getElementById('other-language-input');
|
|
const translationSection = document.getElementById('translation-section');
|
|
const translationInput = document.getElementById('translation-input');
|
|
const meaningInput = document.getElementById('meaning-input');
|
|
const submitButton = document.getElementById('submit-answers-button');
|
|
|
|
// Language selection logic
|
|
languageSelect.addEventListener('change', () => {
|
|
const selectedLanguage = languageSelect.value;
|
|
|
|
if (selectedLanguage === 'Other') {
|
|
otherLanguageSection.style.display = 'block';
|
|
otherLanguageInput.required = true;
|
|
} else {
|
|
otherLanguageSection.style.display = 'none';
|
|
otherLanguageInput.required = false;
|
|
otherLanguageInput.value = '';
|
|
}
|
|
|
|
if (selectedLanguage && selectedLanguage !== 'English' && selectedLanguage !== '') {
|
|
translationSection.style.display = 'block';
|
|
translationInput.required = true;
|
|
} else {
|
|
translationSection.style.display = 'none';
|
|
translationInput.required = false;
|
|
translationInput.value = '';
|
|
}
|
|
});
|
|
|
|
// Form validation and submission
|
|
const validateForm = () => {
|
|
const spelling = spellingInput.value.trim();
|
|
const language = languageSelect.value;
|
|
const otherLanguage = otherLanguageInput.value.trim();
|
|
const translation = translationInput.value.trim();
|
|
const meaning = meaningInput.value.trim();
|
|
|
|
let isValid = true;
|
|
|
|
if (!spelling) {
|
|
spellingInput.style.borderColor = '#f44336';
|
|
isValid = false;
|
|
} else {
|
|
spellingInput.style.borderColor = '#ddd';
|
|
}
|
|
|
|
if (!language) {
|
|
languageSelect.style.borderColor = '#f44336';
|
|
isValid = false;
|
|
} else {
|
|
languageSelect.style.borderColor = '#ddd';
|
|
}
|
|
|
|
if (language === 'Other' && !otherLanguage) {
|
|
otherLanguageInput.style.borderColor = '#f44336';
|
|
isValid = false;
|
|
} else {
|
|
otherLanguageInput.style.borderColor = '#ddd';
|
|
}
|
|
|
|
if (language && language !== 'English' && language !== '' && !translation) {
|
|
translationInput.style.borderColor = '#f44336';
|
|
isValid = false;
|
|
} else {
|
|
translationInput.style.borderColor = '#ddd';
|
|
}
|
|
|
|
if (!meaning) {
|
|
meaningInput.style.borderColor = '#f44336';
|
|
isValid = false;
|
|
} else {
|
|
meaningInput.style.borderColor = '#ddd';
|
|
}
|
|
|
|
return isValid;
|
|
};
|
|
|
|
// Submit button event
|
|
submitButton.addEventListener('click', () => {
|
|
if (!validateForm()) {
|
|
alert('Please fill in all required fields.');
|
|
return;
|
|
}
|
|
|
|
const finalLanguage = languageSelect.value === 'Other' ? otherLanguageInput.value.trim() : languageSelect.value;
|
|
|
|
const trialData = {
|
|
spelling: spellingInput.value.trim(),
|
|
language: finalLanguage,
|
|
translation: translationInput.value.trim(),
|
|
meaning: meaningInput.value.trim(),
|
|
original_recording_data: {
|
|
response: recordingData.response,
|
|
rt: recordingData.rt,
|
|
stimulus: recordingData.stimulus,
|
|
audio_duration: recordingData.audio_duration
|
|
}
|
|
};
|
|
|
|
// Clean up object URL to prevent memory leaks
|
|
if (audio.src && audio.src.startsWith('blob:')) {
|
|
URL.revokeObjectURL(audio.src);
|
|
}
|
|
|
|
this.jsPsych.finishTrial(trialData);
|
|
});
|
|
|
|
// Auto-focus on first input
|
|
spellingInput.focus();
|
|
}
|
|
}
|
|
|
|
export default jsPsychTranscribeCall; |