Spaces:
Sleeping
Sleeping
Masfiq Mahmud
Add sentiment analysis feature using Gemini API and update UI for sentiment display
ea78f87
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>BanglaFeel Translator</title> | |
| <link rel="stylesheet" href="/static/style.css" /> | |
| <link href="https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500&family=Roboto:wght@400;500&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> | |
| </head> | |
| <body> | |
| <div class="page-container"> | |
| <header> | |
| <h1>BanglaFeel Translator</h1> | |
| <p class="subtitle">Translate Romanized Bangla to Bengali with ease</p> | |
| </header> | |
| <div class="translation-box"> | |
| <div class="input-section"> | |
| <div class="input-header"> | |
| <label> | |
| <span class="material-icons">language</span> | |
| English | |
| </label> | |
| </div> | |
| <textarea id="userInput" placeholder="Type or paste your text here..." maxlength="500"></textarea> | |
| </div> | |
| <div class="controls"> | |
| <div class="left-controls"> | |
| <button id="clearBtn" class="icon-btn" title="Clear text"> | |
| <span class="material-icons">clear</span> | |
| </button> | |
| </div> | |
| <div class="right-controls"> | |
| <span class="char-count">0/500</span> | |
| <button id="translateBtn" class="primary-btn"> | |
| <span class="material-icons">translate</span> | |
| Translate | |
| </button> | |
| </div> | |
| </div> | |
| <div class="output-section"> | |
| <div class="output-header"> | |
| <label> | |
| <span class="material-icons">translate</span> | |
| Bengali | |
| </label> | |
| <button id="copyBtn" class="icon-btn" title="Copy translation"> | |
| <span class="material-icons">content_copy</span> | |
| </button> | |
| </div> | |
| <div class="translation-result"> | |
| <p id="translationResult"></p> | |
| <div id="sentimentContainer" style="display: none; margin-top: 15px;"> | |
| <strong>Sentiment:</strong> <span id="sentimentResult" class="sentiment-badge"></span> | |
| </div> | |
| <div class="loading-spinner" style="display: none;"> | |
| <div class="spinner"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const userInput = document.getElementById('userInput'); | |
| const charCount = document.querySelector('.char-count'); | |
| const translateBtn = document.getElementById('translateBtn'); | |
| const clearBtn = document.getElementById('clearBtn'); | |
| const copyBtn = document.getElementById('copyBtn'); | |
| const translationResult = document.getElementById('translationResult'); | |
| const loadingSpinner = document.querySelector('.loading-spinner'); | |
| const sentimentContainer = document.getElementById('sentimentContainer'); | |
| const sentimentResult = document.getElementById('sentimentResult'); | |
| // Character counter | |
| userInput.addEventListener('input', function() { | |
| charCount.textContent = `${this.value.length}/500`; | |
| }); | |
| // Clear button | |
| clearBtn.addEventListener('click', function() { | |
| userInput.value = ''; | |
| translationResult.textContent = ''; | |
| sentimentContainer.style.display = 'none'; | |
| sentimentResult.textContent = ''; | |
| sentimentResult.className = 'sentiment-badge'; | |
| charCount.textContent = '0/500'; | |
| }); | |
| // Copy button | |
| copyBtn.addEventListener('click', function() { | |
| if (translationResult.textContent) { | |
| navigator.clipboard.writeText(translationResult.textContent) | |
| .then(() => { | |
| copyBtn.innerHTML = '<span class="material-icons">check</span>'; | |
| setTimeout(() => { | |
| copyBtn.innerHTML = '<span class="material-icons">content_copy</span>'; | |
| }, 2000); | |
| }); | |
| } | |
| }); | |
| // Translation | |
| translateBtn.addEventListener('click', async function() { | |
| const text = userInput.value.trim(); | |
| if (!text) { | |
| showToast('Please enter some text first.'); | |
| return; | |
| } | |
| // Show loading state | |
| loadingSpinner.style.display = 'flex'; | |
| translateBtn.disabled = true; | |
| translationResult.style.opacity = '0.5'; | |
| translationResult.textContent = ''; | |
| sentimentContainer.style.display = 'none'; | |
| try { | |
| const response = await fetch('/translate', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ text: text }) | |
| }); | |
| if (!response.ok) throw new Error(`Server error: ${response.status}`); | |
| const data = await response.json(); | |
| translationResult.textContent = data.translation; | |
| if (data.sentiment && data.sentiment !== 'Error' && data.sentiment !== 'Unknown') { | |
| sentimentResult.textContent = data.sentiment; | |
| sentimentResult.className = 'sentiment-badge'; // Reset classes | |
| sentimentResult.classList.add(`sentiment-${data.sentiment.toLowerCase()}`); | |
| sentimentContainer.style.display = 'flex'; | |
| } | |
| translationResult.style.opacity = '1'; | |
| } catch (error) { | |
| console.error('Error:', error); | |
| translationResult.textContent = 'An error occurred during translation.'; | |
| showToast('Translation failed. Please try again.'); | |
| } finally { | |
| loadingSpinner.style.display = 'none'; | |
| translateBtn.disabled = false; | |
| } | |
| }); | |
| function showToast(message) { | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast'; | |
| toast.textContent = message; | |
| document.body.appendChild(toast); | |
| setTimeout(() => toast.remove(), 3000); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |