Mayank14's picture
Setup for Hugging Face Spaces deployment
0a148ee
Raw
History Blame Contribute Delete
18.4 kB
// T5 Text Simplifier - JavaScript Functionality
class TextSimplifier {
constructor() {
// Since we're running the Flask server locally, we'll use the local URL
this.apiUrl = 'http://localhost:5000';
this.isDyslexiaMode = false;
this.isProcessing = false;
this.speechSynthesis = window.speechSynthesis;
this.currentUtterance = null;
this.isManuallyStopping = false;
this.setupElements();
this.setupEventListeners();
this.initializeTooltip();
this.checkSpeechSupport();
}
setupElements() {
// Input elements
this.inputText = document.getElementById('inputText');
this.clearInput = document.getElementById('clearInput');
this.copyInput = document.getElementById('copyInput');
// Output elements
this.outputText = document.getElementById('outputText');
this.clearOutput = document.getElementById('clearOutput');
this.copyOutput = document.getElementById('copyOutput');
this.downloadOutput = document.getElementById('downloadOutput');
// Control elements
this.simplifyBtn = document.getElementById('simplifyBtn');
this.dyslexiaToggle = document.getElementById('dyslexiaToggle');
this.ttsInput = document.getElementById('ttsInput');
this.ttsOutput = document.getElementById('ttsOutput');
// Info elements
this.infoBtn = document.querySelector('.info-btn');
this.infoTooltip = document.getElementById('infoTooltip');
this.closeTooltip = document.querySelector('.close-tooltip');
// Status message
this.statusMessage = document.getElementById('statusMessage');
}
setupEventListeners() {
// Main functionality
this.simplifyBtn.addEventListener('click', () => this.simplifyText());
// Temporarily disable keydown listener to test
// this.inputText.addEventListener('keydown', (e) => this.handleInputKeydown(e));
// Remove the input event listeners that might be interfering
// this.inputText.addEventListener('input', (e) => {
// // Allow normal text input without any interference
// return true;
// });
// this.outputText.addEventListener('input', (e) => {
// // Allow normal text input without any interference
// return true;
// });
// Dyslexia toggle
this.dyslexiaToggle.addEventListener('click', () => this.toggleDyslexiaMode());
// Text-to-Speech
this.ttsInput.addEventListener('click', () => this.handleTTSButtonClick('input'));
this.ttsOutput.addEventListener('click', () => this.handleTTSButtonClick('output'));
// Action buttons
this.clearInput.addEventListener('click', () => this.clearText(this.inputText));
this.clearOutput.addEventListener('click', () => this.clearText(this.outputText));
this.copyInput.addEventListener('click', () => this.copyToClipboard(this.inputText.value, 'Input text'));
this.copyOutput.addEventListener('click', () => this.copyToClipboard(this.outputText.value, 'Simplified text'));
this.downloadOutput.addEventListener('click', () => this.downloadText());
// Info tooltip
this.infoBtn.addEventListener('click', () => this.showTooltip());
this.closeTooltip.addEventListener('click', () => this.hideTooltip());
// Close tooltip on outside click - temporarily disabled to test
// document.addEventListener('click', (e) => {
// if (!this.infoTooltip.contains(e.target) && !this.infoBtn.contains(e.target)) {
// this.hideTooltip();
// }
// });
// Keyboard shortcuts - only enable speech stop shortcut
document.addEventListener('keydown', (e) => this.handleGlobalKeydown(e));
// Stop speech when page is hidden
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
this.stopSpeech();
}
});
}
initializeTooltip() {
// Add backdrop for tooltip
const backdrop = document.createElement('div');
backdrop.className = 'tooltip-backdrop';
backdrop.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
`;
document.body.appendChild(backdrop);
this.tooltipBackdrop = backdrop;
}
checkSpeechSupport() {
if (!this.speechSynthesis) {
this.ttsInput.disabled = true;
this.ttsOutput.disabled = true;
this.ttsInput.title = 'Text-to-speech not supported in this browser';
this.ttsOutput.title = 'Text-to-speech not supported in this browser';
} else {
// Log available voices for debugging
this.logAvailableVoices();
}
}
logAvailableVoices() {
// Wait for voices to load (they might not be available immediately)
const loadVoices = () => {
const voices = this.speechSynthesis.getVoices();
if (voices.length > 0) {
console.log('Available TTS voices:');
voices.forEach((voice, index) => {
console.log(`${index + 1}. ${voice.name} (${voice.lang}) - ${voice.default ? 'Default' : 'Available'}`);
});
} else {
// Try again after a short delay
setTimeout(loadVoices, 100);
}
};
loadVoices();
}
async simplifyText() {
const text = this.inputText.value.trim();
if (!text) {
this.showStatus('Please enter some text to simplify.', 'warning');
return;
}
if (this.isProcessing) {
return;
}
this.setProcessingState(true);
this.showStatus('Simplifying your text...', 'info');
try {
const response = await fetch(`${this.apiUrl}/simplify`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
if (data.simplified_text) {
this.outputText.value = data.simplified_text;
this.showStatus('Text simplified successfully!', 'success');
this.outputText.scrollIntoView({ behavior: 'smooth', block: 'center' });
} else {
throw new Error('No simplified text received from server');
}
} catch (error) {
console.error('Error simplifying text:', error);
this.showStatus(`Failed to simplify text: ${error.message}`, 'error');
} finally {
this.setProcessingState(false);
}
}
setProcessingState(isProcessing) {
this.isProcessing = isProcessing;
this.simplifyBtn.disabled = isProcessing;
if (isProcessing) {
this.simplifyBtn.classList.add('loading');
} else {
this.simplifyBtn.classList.remove('loading');
}
}
toggleDyslexiaMode() {
this.isDyslexiaMode = !this.isDyslexiaMode;
document.body.classList.toggle('dyslexia-friendly', this.isDyslexiaMode);
this.dyslexiaToggle.setAttribute('aria-pressed', this.isDyslexiaMode);
const status = this.isDyslexiaMode ? 'enabled' : 'disabled';
this.showStatus(`Dyslexia-friendly formatting ${status}`, 'info');
// Update button text
const buttonText = this.dyslexiaToggle.querySelector('span');
buttonText.textContent = this.isDyslexiaMode ? 'Dyslexia-Friendly ✓' : 'Dyslexia-Friendly';
}
handleTTSButtonClick(type) {
// Check if speech is currently playing
if (this.speechSynthesis.speaking) {
this.stopSpeech();
} else {
const text = type === 'input' ? this.inputText.value : this.outputText.value;
this.speakText(text, type);
}
}
speakText(text, type) {
if (!this.speechSynthesis) {
this.showStatus('Text-to-speech is not supported in this browser', 'warning');
return;
}
if (!text.trim()) {
this.showStatus(`No ${type} text to read`, 'warning');
return;
}
// Stop any current speech
this.stopSpeech();
this.currentUtterance = new SpeechSynthesisUtterance(text);
// Configure speech settings for more natural sound
this.currentUtterance.rate = 0.85; // Slightly slower for better comprehension
this.currentUtterance.pitch = 0.95; // Slightly lower pitch for warmth
this.currentUtterance.volume = 0.9; // Higher volume for clarity
// Try to use the most natural voice available
const voices = this.speechSynthesis.getVoices();
let selectedVoice = null;
// Priority order for voice selection (most natural first)
const voicePreferences = [
// Google Cloud voices (most natural)
voice => voice.name.includes('Google') && voice.name.includes('Neural'),
voice => voice.name.includes('Google') && voice.name.includes('Wavenet'),
voice => voice.name.includes('Google'),
// Microsoft voices
voice => voice.name.includes('Microsoft') && voice.name.includes('Neural'),
voice => voice.name.includes('Microsoft'),
// Amazon Polly voices
voice => voice.name.includes('Amazon') && voice.name.includes('Neural'),
voice => voice.name.includes('Amazon'),
// Apple voices
voice => voice.name.includes('Samantha') || voice.name.includes('Alex'),
voice => voice.name.includes('Apple'),
// Other natural voices
voice => voice.name.includes('Natural') || voice.name.includes('Enhanced'),
voice => voice.name.includes('Premium'),
voice => voice.name.includes('Neural'),
// Fallback to any English voice
voice => voice.lang.startsWith('en')
];
// Find the best available voice
for (const preference of voicePreferences) {
selectedVoice = voices.find(voice =>
voice.lang.startsWith('en') && preference(voice)
);
if (selectedVoice) break;
}
if (selectedVoice) {
this.currentUtterance.voice = selectedVoice;
console.log('Using voice:', selectedVoice.name);
} else {
console.log('Using default voice');
}
// Event handlers
this.currentUtterance.onstart = () => {
this.showStatus(`Reading ${type} text...`, 'info');
this.updateTTSButton(type, true);
};
this.currentUtterance.onend = () => {
this.showStatus(`Finished reading ${type} text`, 'success');
this.updateTTSButton(type, false);
this.currentUtterance = null;
this.isManuallyStopping = false; // Reset the flag
};
this.currentUtterance.onerror = (event) => {
console.error('Speech synthesis error:', event);
// Only show error message if we're not manually stopping
if (!this.isManuallyStopping) {
this.showStatus('Error reading text', 'error');
}
this.updateTTSButton(type, false);
this.currentUtterance = null;
this.isManuallyStopping = false; // Reset the flag
};
this.speechSynthesis.speak(this.currentUtterance);
}
stopSpeech() {
if (this.speechSynthesis.speaking) {
this.isManuallyStopping = true;
this.speechSynthesis.cancel();
this.showStatus('Stopped reading aloud', 'info');
}
this.currentUtterance = null;
this.updateTTSButton('input', false);
this.updateTTSButton('output', false);
}
updateTTSButton(type, isSpeaking) {
const button = type === 'input' ? this.ttsInput : this.ttsOutput;
const icon = button.querySelector('i');
const text = button.querySelector('span');
if (isSpeaking) {
icon.className = 'fas fa-stop';
text.textContent = 'Stop';
button.style.backgroundColor = 'var(--error-color)';
button.style.color = 'white';
} else {
icon.className = 'fas fa-volume-up';
text.textContent = `Read ${type === 'input' ? 'Input' : 'Output'}`;
button.style.backgroundColor = '';
button.style.color = '';
}
}
clearText(textElement) {
textElement.value = '';
// Don't automatically focus - let user decide where to focus
this.showStatus('Text cleared', 'info');
}
async copyToClipboard(text, type) {
if (!text.trim()) {
this.showStatus(`No ${type.toLowerCase()} to copy`, 'warning');
return;
}
try {
await navigator.clipboard.writeText(text);
this.showStatus(`${type} copied to clipboard!`, 'success');
} catch (error) {
console.error('Failed to copy text:', error);
// Fallback for older browsers
this.fallbackCopyToClipboard(text, type);
}
}
fallbackCopyToClipboard(text, type) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
textArea.style.top = '-999999px';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
this.showStatus(`${type} copied to clipboard!`, 'success');
} catch (error) {
console.error('Fallback copy failed:', error);
this.showStatus('Failed to copy text', 'error');
}
document.body.removeChild(textArea);
}
downloadText() {
const text = this.outputText.value.trim();
if (!text) {
this.showStatus('No simplified text to download', 'warning');
return;
}
const blob = new Blob([text], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `simplified-text-${new Date().toISOString().split('T')[0]}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.showStatus('Text downloaded successfully!', 'success');
}
showTooltip() {
this.infoTooltip.classList.add('show');
this.tooltipBackdrop.style.pointerEvents = 'auto';
this.tooltipBackdrop.style.opacity = '1';
this.infoTooltip.setAttribute('aria-hidden', 'false');
// Focus management
this.closeTooltip.focus();
}
hideTooltip() {
this.infoTooltip.classList.remove('show');
this.tooltipBackdrop.style.pointerEvents = 'none';
this.tooltipBackdrop.style.opacity = '0';
this.infoTooltip.setAttribute('aria-hidden', 'true');
// Return focus to info button
this.infoBtn.focus();
}
showStatus(message, type = 'info') {
this.statusMessage.textContent = message;
this.statusMessage.className = `status-message ${type} show`;
// Auto-hide after 4 seconds
setTimeout(() => {
this.statusMessage.classList.remove('show');
}, 4000);
}
handleInputKeydown(e) {
// Ctrl/Cmd + Enter to simplify
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
this.simplifyText();
}
// Don't interfere with normal typing
return true;
}
handleGlobalKeydown(e) {
// Only handle speech stop shortcut to avoid interfering with text input
// Ctrl/Cmd + Shift + S to stop speech
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'S') {
e.preventDefault();
this.stopSpeech();
}
// Escape to close tooltip (only if tooltip is open)
if (e.key === 'Escape' && this.infoTooltip.classList.contains('show')) {
this.hideTooltip();
}
}
}
// Initialize the application when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new TextSimplifier();
// Show welcome message
setTimeout(() => {
const statusMessage = document.getElementById('statusMessage');
statusMessage.textContent = 'Welcome! Enter your text and click "Simplify Text" to get started.';
statusMessage.className = 'status-message success show';
setTimeout(() => {
statusMessage.classList.remove('show');
}, 5000);
}, 1000);
});
// Service Worker registration for offline functionality (optional)
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/js/sw.js')
.then(registration => {
console.log('SW registered: ', registration);
})
.catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}