Spaces:
Running
Running
| <html lang="zh"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NLP Demo</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: -apple-system, sans-serif; max-width: 720px; margin: 0 auto; padding: 24px; background: #f5f5f5; } | |
| h1 { text-align: center; margin-bottom: 8px; } | |
| .subtitle { text-align: center; color: #666; margin-bottom: 24px; font-size: 14px; } | |
| .tabs { display: flex; gap: 4px; margin-bottom: 16px; } | |
| .tab { flex: 1; padding: 10px; border: none; cursor: pointer; border-radius: 8px 8px 0 0; font-size: 14px; font-weight: 600; } | |
| .tab.active { background: #fff; color: #f97316; } | |
| .tab:not(.active) { background: #e5e5e5; color: #666; } | |
| .panel { background: #fff; border-radius: 0 0 12px 12px; padding: 20px; display: none; } | |
| .panel.active { display: block; } | |
| label { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; color: #333; } | |
| textarea, input[type="text"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; font-family: inherit; margin-bottom: 12px; } | |
| textarea { resize: vertical; } | |
| button { background: #f97316; color: #fff; border: none; padding: 10px 24px; border-radius: 6px; font-size: 14px; cursor: pointer; font-weight: 600; } | |
| button:hover { background: #ea580c; } | |
| button:disabled { opacity: .5; cursor: not-allowed; } | |
| .result { margin-top: 16px; padding: 16px; border-radius: 8px; background: #f0f9ff; border: 1px solid #bae6fd; font-size: 15px; white-space: pre-wrap; } | |
| .result.error { background: #fef2f2; border-color: #fecaca; color: #dc2626; } | |
| .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid #ccc; border-top-color: #333; border-radius: 50%; animation: spin .6s linear infinite; margin-right: 6px; vertical-align: middle; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| footer { text-align: center; margin-top: 32px; font-size: 12px; color: #aaa; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>🤗 NLP Demo</h1> | |
| <p class="subtitle">Hugging Face Inference API 驱动,无需 GPU</p> | |
| <div class="tabs"> | |
| <button class="tab active" onclick="switchTab('classify')">📝 情绪分类</button> | |
| <button class="tab" onclick="switchTab('qa')">❓ 问答</button> | |
| </div> | |
| <div id="classify" class="panel active"> | |
| <label>输入中文句子</label> | |
| <input id="clsInput" type="text" placeholder="例如:曾经最好的朋友,现在连点赞都不会了。" /> | |
| <button id="clsBtn" onclick="doClassify()">开始分类</button> | |
| <div id="clsResult" class="result" style="display:none"></div> | |
| </div> | |
| <div id="qa" class="panel"> | |
| <label>上下文</label> | |
| <textarea id="qaCtx" rows="4" placeholder="粘贴一段文本作为上下文..."></textarea> | |
| <label>问题</label> | |
| <input id="qaQ" type="text" placeholder="根据上下文提问..." /> | |
| <button id="qaBtn" onclick="doQA()">提 问</button> | |
| <div id="qaResult" class="result" style="display:none"></div> | |
| </div> | |
| <footer>模型 <a href="https://huggingface.co/Kate-lf">Kate-lf</a> · 通过 Inference API 推理 · 首次调用需冷启动约 20 秒</footer> | |
| <script> | |
| const CLS_API = "https://api-inference.huggingface.co/models/Kate-lf/emotion-classification"; | |
| const QA_API = "https://api-inference.huggingface.co/models/Kate-lf/rag-qa-base-bert"; | |
| function switchTab(id) { | |
| document.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); | |
| document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); | |
| document.getElementById(id).classList.add('active'); | |
| event.target.classList.add('active'); | |
| } | |
| async function doClassify() { | |
| const input = document.getElementById('clsInput').value.trim(); | |
| const btn = document.getElementById('clsBtn'); | |
| const box = document.getElementById('clsResult'); | |
| if (!input) return; | |
| btn.disabled = true; btn.innerHTML = '<span class="spinner"></span>推理中...'; | |
| box.style.display = 'none'; | |
| try { | |
| const res = await fetch(CLS_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputs: input }) }); | |
| const data = await res.json(); | |
| if (data.error) throw new Error(data.error); | |
| const top = data[0][0]; | |
| box.className = 'result'; | |
| box.innerText = top.label + '(置信度 ' + (top.score * 100).toFixed(1) + '%)'; | |
| } catch (e) { | |
| box.className = 'result error'; | |
| box.innerText = '模型正在冷启动(约 20 秒),请稍后重试。'; | |
| } | |
| box.style.display = 'block'; | |
| btn.disabled = false; btn.innerHTML = '开始分类'; | |
| } | |
| async function doQA() { | |
| const ctx = document.getElementById('qaCtx').value.trim(); | |
| const q = document.getElementById('qaQ').value.trim(); | |
| const btn = document.getElementById('qaBtn'); | |
| const box = document.getElementById('qaResult'); | |
| if (!ctx || !q) return; | |
| btn.disabled = true; btn.innerHTML = '<span class="spinner"></span>推理中...'; | |
| box.style.display = 'none'; | |
| try { | |
| const res = await fetch(QA_API, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputs: { question: q, context: ctx } }) }); | |
| const data = await res.json(); | |
| if (data.error) throw new Error(data.error); | |
| box.className = 'result'; | |
| box.innerText = data.score < 0.1 || !data.answer.trim() ? '上下文不包含答案,无法回答。' : '答案:' + data.answer + '\n置信度:' + (data.score * 100).toFixed(1) + '%'; | |
| } catch (e) { | |
| box.className = 'result error'; | |
| box.innerText = '模型正在冷启动(约 20 秒),请稍后重试。'; | |
| } | |
| box.style.display = 'block'; | |
| btn.disabled = false; btn.innerHTML = '提 问'; | |
| } | |
| </script> | |
| </body> | |
| </html> | |