Files
work-calls-corpus/scripts/transcribe-call.js
2025-07-21 20:12:48 +02:00

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;