Spaces:
Sleeping
Sleeping
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>OCR Translation - English to Hindi</title> | |
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet"> | |
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
<style> | |
body { | |
font-family: 'Poppins', sans-serif; | |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
} | |
.loader { | |
border: 3px solid #f3f3f3; | |
border-radius: 50%; | |
border-top: 3px solid #3498db; | |
width: 40px; | |
height: 40px; | |
animation: spin 1s linear infinite; | |
display: none; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.drop-zone { | |
border: 2px dashed #cbd5e0; | |
transition: all 0.3s ease; | |
} | |
.drop-zone:hover { | |
border-color: #3498db; | |
background-color: #f8fafc; | |
} | |
.result-box { | |
background: rgba(255, 255, 255, 0.9); | |
backdrop-filter: blur(10px); | |
transition: all 0.3s ease; | |
} | |
.result-box:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1); | |
} | |
.custom-file-input::-webkit-file-upload-button { | |
visibility: hidden; | |
width: 0; | |
} | |
.custom-file-input::before { | |
content: 'Choose File'; | |
display: inline-block; | |
background: #3498db; | |
color: white; | |
padding: 8px 16px; | |
border-radius: 5px; | |
cursor: pointer; | |
} | |
.translate-btn { | |
background: linear-gradient(135deg, #3498db 0%, #2980b9 100%); | |
transition: all 0.3s ease; | |
} | |
.translate-btn:hover { | |
transform: translateY(-1px); | |
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.3); | |
} | |
.error-message { | |
background-color: #fee2e2; | |
border: 1px solid #ef4444; | |
color: #b91c1c; | |
padding: 10px; | |
border-radius: 5px; | |
margin-bottom: 15px; | |
display: none; | |
} | |
</style> | |
</head> | |
<body class="min-h-screen py-12 px-4"> | |
<div class="container mx-auto max-w-4xl"> | |
<!-- Header --> | |
<div class="text-center mb-12"> | |
<h1 class="text-4xl font-bold text-gray-800 mb-3"> | |
<i class="fas fa-language mr-2"></i>OCR Translation | |
</h1> | |
<p class="text-gray-600">English to Hindi Translation with Image Recognition</p> | |
</div> | |
<!-- Error Message Box --> | |
<div id="errorMessage" class="error-message mb-4"> | |
<i class="fas fa-exclamation-circle mr-2"></i> | |
<span id="errorText">Error message will appear here</span> | |
</div> | |
<!-- Main Content --> | |
<div class="bg-white rounded-xl shadow-lg p-8 mb-8"> | |
<!-- Upload Section --> | |
<div class="drop-zone rounded-lg p-8 text-center mb-6"> | |
<div class="mb-4"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
<h3 class="text-lg font-semibold text-gray-700 mb-2">Upload Image</h3> | |
<p class="text-sm text-gray-500 mb-4">Support for PNG, JPG, JPEG, GIF, BMP</p> | |
</div> | |
<input type="file" | |
id="imageInput" | |
accept="image/*" | |
class="custom-file-input w-full mb-4 cursor-pointer"> | |
<button id="translateBtn" onclick="processImage()" | |
class="translate-btn w-full py-3 px-6 text-white rounded-lg font-medium flex items-center justify-center"> | |
<i class="fas fa-sync-alt mr-2"></i> | |
<span>Translate Now</span> | |
</button> | |
<div id="loader" class="loader mx-auto mt-4"></div> | |
</div> | |
<!-- Preview Section --> | |
<div id="previewSection" class="hidden mb-6"> | |
<h3 class="text-lg font-semibold text-gray-700 mb-3">Image Preview</h3> | |
<img id="imagePreview" class="max-w-full h-auto rounded-lg shadow" src="" alt="Preview"> | |
</div> | |
<!-- Results Section --> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
<!-- Original Text --> | |
<div class="result-box rounded-xl p-6"> | |
<div class="flex items-center justify-between mb-4"> | |
<h3 class="text-lg font-semibold text-gray-700"> | |
<i class="fas fa-file-alt mr-2"></i>Extracted Text | |
</h3> | |
<button onclick="copyText('extractedText')" class="text-blue-500 hover:text-blue-600"> | |
<i class="far fa-copy"></i> | |
</button> | |
</div> | |
<div id="extractedText" class="p-4 bg-gray-50 rounded-lg min-h-[150px] text-gray-700"> | |
<!-- Extracted text will appear here --> | |
</div> | |
</div> | |
<!-- Translated Text --> | |
<div class="result-box rounded-xl p-6"> | |
<div class="flex items-center justify-between mb-4"> | |
<h3 class="text-lg font-semibold text-gray-700"> | |
<i class="fas fa-language mr-2"></i>Hindi Translation | |
</h3> | |
<button onclick="copyText('translatedText')" class="text-blue-500 hover:text-blue-600"> | |
<i class="far fa-copy"></i> | |
</button> | |
</div> | |
<div id="translatedText" class="p-4 bg-gray-50 rounded-lg min-h-[150px] text-gray-700" | |
style="font-family: 'Noto Sans Devanagari', sans-serif;"> | |
<!-- Translated text will appear here --> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
function showError(message) { | |
const errorDiv = document.getElementById('errorMessage'); | |
const errorText = document.getElementById('errorText'); | |
errorText.textContent = message; | |
errorDiv.style.display = 'block'; | |
} | |
function hideError() { | |
document.getElementById('errorMessage').style.display = 'none'; | |
} | |
// Image preview functionality | |
document.getElementById('imageInput').addEventListener('change', function(e) { | |
const file = e.target.files[0]; | |
if (file) { | |
const reader = new FileReader(); | |
reader.onload = function(e) { | |
document.getElementById('imagePreview').src = e.target.result; | |
document.getElementById('previewSection').classList.remove('hidden'); | |
} | |
reader.readAsDataURL(file); | |
hideError(); // Hide any previous errors | |
} | |
}); | |
// Copy text functionality | |
function copyText(elementId) { | |
const text = document.getElementById(elementId).textContent; | |
navigator.clipboard.writeText(text).then(() => { | |
// Show a brief notification | |
const element = document.getElementById(elementId); | |
const originalBackground = element.style.backgroundColor; | |
element.style.backgroundColor = '#e8f5e9'; | |
setTimeout(() => { | |
element.style.backgroundColor = originalBackground; | |
}, 500); | |
}); | |
} | |
function processImage() { | |
const fileInput = document.getElementById('imageInput'); | |
const loader = document.getElementById('loader'); | |
const extractedTextDiv = document.getElementById('extractedText'); | |
const translatedTextDiv = document.getElementById('translatedText'); | |
if (!fileInput.files[0]) { | |
showError('Please select an image first'); | |
return; | |
} | |
const formData = new FormData(); | |
formData.append('file', fileInput.files[0]); | |
// Hide previous errors | |
hideError(); | |
// Show loader and clear previous results | |
loader.style.display = 'block'; | |
extractedTextDiv.textContent = ''; | |
translatedTextDiv.textContent = ''; | |
fetch('/upload', { | |
method: 'POST', | |
body: formData | |
}) | |
.then(response => { | |
if (!response.ok) { | |
return response.json().then(data => { | |
throw new Error(data.error || `Server error: ${response.status}`); | |
}); | |
} | |
return response.json(); | |
}) | |
.then(data => { | |
extractedTextDiv.textContent = data.original_text; | |
translatedTextDiv.textContent = data.translated_text; | |
// Check if the response contains error messages | |
if (data.original_text && data.original_text.startsWith('Could not extract text')) { | |
showError(data.original_text); | |
} | |
}) | |
.catch(error => { | |
console.error('Error:', error); | |
showError(error.message || 'An unknown error occurred'); | |
extractedTextDiv.textContent = 'Error: Could not process image.'; | |
translatedTextDiv.textContent = 'Translation unavailable due to processing error.'; | |
}) | |
.finally(() => { | |
loader.style.display = 'none'; | |
}); | |
} | |
</script> | |
</body> | |
</html> |