| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Text Saliency Pro</title> |
| <style> |
| body { |
| font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 2rem; |
| line-height: 1.5; |
| background-color: #f9fafb; |
| color: #111827; |
| } |
| |
| h1 { |
| font-size: 2.5rem; |
| margin-bottom: 1rem; |
| text-align: center; |
| } |
| |
| p.description { |
| text-align: center; |
| color: #4b5563; |
| margin-bottom: 2rem; |
| } |
| |
| .container { |
| background: white; |
| padding: 2rem; |
| border-radius: 0.5rem; |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); |
| } |
| |
| textarea { |
| width: 100%; |
| height: 150px; |
| padding: 0.75rem; |
| border: 1px solid #d1d5db; |
| border-radius: 0.375rem; |
| font-size: 1rem; |
| resize: vertical; |
| margin-bottom: 1rem; |
| box-sizing: border-box; |
| } |
| |
| .controls { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| margin-bottom: 1.5rem; |
| flex-wrap: wrap; |
| gap: 1rem; |
| } |
| |
| .slider-group { |
| display: flex; |
| align-items: center; |
| gap: 1rem; |
| flex-grow: 1; |
| } |
| |
| input[type="range"] { |
| flex-grow: 1; |
| max-width: 300px; |
| } |
| |
| button { |
| background-color: #3b82f6; |
| color: white; |
| border: none; |
| padding: 0.5rem 1.5rem; |
| font-size: 1rem; |
| border-radius: 0.375rem; |
| cursor: pointer; |
| transition: background-color 0.2s; |
| } |
| |
| button:hover { |
| background-color: #2563eb; |
| } |
| |
| button:disabled { |
| background-color: #9ca3af; |
| cursor: not-allowed; |
| } |
| |
| #result-container { |
| margin-top: 2rem; |
| padding: 1.5rem; |
| background-color: #f3f4f6; |
| border-radius: 0.375rem; |
| min-height: 100px; |
| white-space: pre-wrap; |
| font-size: 1.125rem; |
| } |
| |
| |
| .token { |
| transition: font-weight 0.2s; |
| } |
| |
| .highlighted { |
| font-weight: 800; |
| color: #000; |
| } |
| |
| #loading { |
| display: none; |
| color: #6b7280; |
| text-align: center; |
| margin-top: 1rem; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <h1>Text Saliency Pro</h1> |
| <p class="description">Improve reading comprehension using LLM attention vectors.<br>Words with attention above the threshold will be bolded.</p> |
|
|
| <div class="container"> |
| <textarea id="text-input" placeholder="Enter or paste your text here...">In this project I want to use the attention vectors of a llm to bold the most important words in an input text to improve reading comprehension.</textarea> |
| |
| <div class="controls"> |
| <button id="analyze-btn">Analyze Text</button> |
| <div class="slider-group"> |
| <label for="threshold">Attention Threshold: <span id="threshold-val">0.50</span></label> |
| <input type="range" id="threshold" min="0" max="1" step="0.01" value="0.5"> |
| </div> |
| </div> |
| |
| <div id="loading">Analyzing attention vectors with Gemma 2B... Please wait.</div> |
|
|
| <div id="result-container"> |
| |
| </div> |
| </div> |
|
|
| <script> |
| const inputArea = document.getElementById('text-input'); |
| const analyzeBtn = document.getElementById('analyze-btn'); |
| const thresholdSlider = document.getElementById('threshold'); |
| const thresholdVal = document.getElementById('threshold-val'); |
| const resultContainer = document.getElementById('result-container'); |
| const loading = document.getElementById('loading'); |
| |
| let currentTokens = []; |
| |
| |
| thresholdSlider.addEventListener('input', (e) => { |
| thresholdVal.textContent = parseFloat(e.target.value).toFixed(2); |
| renderTokens(); |
| }); |
| |
| |
| analyzeBtn.addEventListener('click', async () => { |
| const text = inputArea.value.trim(); |
| if (!text) return; |
| |
| |
| analyzeBtn.disabled = true; |
| loading.style.display = 'block'; |
| resultContainer.innerHTML = ''; |
| |
| try { |
| |
| const response = await fetch('/analyze', { |
| method: 'POST', |
| headers: { |
| 'Content-Type': 'application/json' |
| }, |
| body: JSON.stringify({ text }) |
| }); |
| |
| if (!response.ok) { |
| throw new Error('Network response was not ok'); |
| } |
| |
| const data = await response.json(); |
| currentTokens = data.words || []; |
| renderTokens(); |
| |
| } catch (error) { |
| console.error('Error analyzing text:', error); |
| resultContainer.innerHTML = '<span style="color: red;">Error analyzing text. Is the backend running?</span>'; |
| } finally { |
| |
| analyzeBtn.disabled = false; |
| loading.style.display = 'none'; |
| } |
| }); |
| |
| |
| function renderTokens() { |
| if (!currentTokens.length) return; |
| |
| const threshold = parseFloat(thresholdSlider.value); |
| resultContainer.innerHTML = ''; |
| |
| currentTokens.forEach((item, index) => { |
| |
| if (index === 0 && (item.token.includes('<bos>') || item.word.includes('<bos>'))) { |
| return; |
| } |
| |
| const span = document.createElement('span'); |
| span.className = 'token'; |
| |
| |
| if (item.score >= threshold) { |
| span.classList.add('highlighted'); |
| } |
| |
| |
| |
| |
| |
| let displayText = item.token; |
| |
| span.textContent = displayText; |
| resultContainer.appendChild(span); |
| }); |
| } |
| </script> |
| </body> |
| </html> |