| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>mdeberta-ner-ontonotes5</title> |
| <meta name="description" content="Named Entity Recognition Demo"> |
| <style> |
| :root { |
| --c-misc: #f3e5f5; |
| --t-misc: #4a148c; |
| } |
| |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 40px 20px; |
| background-color: #fafafa; |
| color: #111; |
| line-height: 1.6; |
| } |
| |
| .container { |
| background: white; |
| padding: 40px; |
| border-radius: 12px; |
| box-shadow: 0 2px 8px rgba(0,0,0,0.04); |
| border: 1px solid #eaeaea; |
| } |
| |
| header { |
| margin-bottom: 30px; |
| } |
| |
| h1 { |
| font-size: 24px; |
| font-weight: 600; |
| margin: 0 0 8px 0; |
| letter-spacing: -0.02em; |
| } |
| |
| .subtitle { |
| color: #666; |
| font-size: 14px; |
| } |
| |
| |
| .input-group { |
| margin-bottom: 20px; |
| position: relative; |
| } |
| |
| label { |
| display: block; |
| font-size: 12px; |
| font-weight: 600; |
| text-transform: uppercase; |
| color: #888; |
| margin-bottom: 8px; |
| letter-spacing: 0.05em; |
| } |
| |
| textarea { |
| width: 100%; |
| min-height: 120px; |
| padding: 16px; |
| border: 1px solid #ddd; |
| border-radius: 8px; |
| font-size: 16px; |
| line-height: 1.6; |
| resize: vertical; |
| background-color: #fff; |
| box-sizing: border-box; |
| font-family: inherit; |
| outline: none; |
| transition: border-color 0.2s, box-shadow 0.2s; |
| color: #333; |
| } |
| |
| textarea:focus { |
| border-color: #000; |
| box-shadow: 0 0 0 2px rgba(0,0,0,0.05); |
| } |
| |
| |
| button.main-btn { |
| background-color: #1a1a1a; |
| color: white; |
| border: none; |
| padding: 14px 32px; |
| border-radius: 8px; |
| font-family: inherit; |
| font-size: 15px; |
| font-weight: 500; |
| cursor: pointer; |
| display: block; |
| width: 100%; |
| transition: background-color 0.2s, transform 0.1s; |
| } |
| |
| button.main-btn:hover { |
| background-color: #333; |
| } |
| |
| button.main-btn:active { |
| transform: scale(0.99); |
| } |
| |
| button.main-btn:disabled { |
| background-color: #ccc; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| |
| #output-wrapper { |
| margin-top: 30px; |
| display: none; |
| animation: fadeIn 0.3s ease-out; |
| } |
| |
| .result-box { |
| padding: 20px; |
| border: 1px solid #eee; |
| background-color: #fcfcfc; |
| border-radius: 8px; |
| font-size: 16px; |
| line-height: 1.8; |
| white-space: pre-wrap; |
| } |
| |
| |
| .entity { |
| padding: 2px 6px; |
| border-radius: 4px; |
| font-weight: 500; |
| cursor: help; |
| position: relative; |
| transition: background-color 0.2s; |
| box-decoration-break: clone; |
| -webkit-box-decoration-break: clone; |
| } |
| |
| |
| .entity::after { |
| content: attr(data-label) " " attr(data-score); |
| position: absolute; |
| bottom: 100%; |
| left: 50%; |
| transform: translateX(-50%) translateY(-4px); |
| background: #1a1a1a; |
| color: #fff; |
| padding: 4px 8px; |
| border-radius: 4px; |
| font-size: 11px; |
| white-space: nowrap; |
| opacity: 0; |
| pointer-events: none; |
| transition: opacity 0.2s, transform 0.2s; |
| z-index: 10; |
| font-weight: 400; |
| } |
| |
| .entity:hover::after { |
| opacity: 1; |
| transform: translateX(-50%) translateY(-8px); |
| } |
| |
| .type-DEFAULT { background: var(--c-misc); color: var(--t-misc); } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .error-msg { |
| color: #d32f2f; |
| background: #ffebee; |
| padding: 12px; |
| border-radius: 6px; |
| margin-top: 20px; |
| font-size: 14px; |
| display: none; |
| } |
| |
| @media (max-width: 600px) { |
| body { padding: 20px 10px; } |
| .container { padding: 24px; } |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="container"> |
| <header> |
| <h1>mdeberta-ner-ontonotes5</h1> |
| <div class="subtitle">Named Entity Recognition Demo</div> |
| </header> |
|
|
| <div class="input-group"> |
| <label for="inputText">Input Text</label> |
| <textarea id="inputText" placeholder="Enter text to analyze...">Apple Inc. is looking at buying a U.K. startup for $1 billion in London next week.</textarea> |
| </div> |
|
|
| <button id="analyzeBtn" class="main-btn" onclick="analyze()">Analyze Text</button> |
| |
| <div id="errorBox" class="error-msg"></div> |
|
|
| <div id="output-wrapper"> |
| <label>Result</label> |
| <div id="resultBox" class="result-box"></div> |
| </div> |
| </div> |
|
|
| <script> |
| async function analyze() { |
| const input = document.getElementById('inputText'); |
| const btn = document.getElementById('analyzeBtn'); |
| const outputWrapper = document.getElementById('output-wrapper'); |
| const resultBox = document.getElementById('resultBox'); |
| const errorBox = document.getElementById('errorBox'); |
| |
| const text = input.value.trim(); |
| if (!text) return; |
| |
| |
| btn.disabled = true; |
| btn.textContent = "Processing..."; |
| errorBox.style.display = 'none'; |
| outputWrapper.style.display = 'none'; |
| |
| try { |
| const response = await fetch('/predict', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ text: text }) |
| }); |
| |
| if (!response.ok) { |
| throw new Error(`Server Error: ${response.statusText}`); |
| } |
| |
| const data = await response.json(); |
| renderResult(text, data.entities); |
| |
| |
| outputWrapper.style.display = 'block'; |
| |
| } catch (err) { |
| errorBox.textContent = err.message; |
| errorBox.style.display = 'block'; |
| } finally { |
| btn.disabled = false; |
| btn.textContent = "Analyze Text"; |
| } |
| } |
| |
| function renderResult(originalText, entities) { |
| const resultBox = document.getElementById('resultBox'); |
| resultBox.innerHTML = ''; |
| |
| if (!entities || entities.length === 0) { |
| resultBox.textContent = originalText; |
| return; |
| } |
| |
| let lastIndex = 0; |
| |
| entities.forEach(entity => { |
| |
| const plainText = originalText.slice(lastIndex, entity.start); |
| resultBox.appendChild(document.createTextNode(plainText)); |
| |
| |
| const span = document.createElement('span'); |
| |
| |
| const type = entity.entity_group || 'DEFAULT'; |
| |
| |
| span.className = `entity type-${type} type-DEFAULT`; |
| |
| span.textContent = originalText.slice(entity.start, entity.end); |
| |
| |
| span.setAttribute('data-label', type); |
| span.setAttribute('data-score', Math.round(entity.score * 100) + '%'); |
| |
| resultBox.appendChild(span); |
| |
| lastIndex = entity.end; |
| }); |
| |
| |
| resultBox.appendChild(document.createTextNode(originalText.slice(lastIndex))); |
| } |
| </script> |
|
|
| </body> |
| </html> |