File size: 5,729 Bytes
8031e9c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bd958a4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<!DOCTYPE html>
<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>