added new plugin
This commit is contained in:
275
scripts/transcribe-call.js
Normal file
275
scripts/transcribe-call.js
Normal file
@@ -0,0 +1,275 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user