146 lines
4.3 KiB
HTML
146 lines
4.3 KiB
HTML
<!DOCTYPE html>
|
||||
|
|
<html lang="zh-CN">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>DeepSeek 流式对话</title>
|
|||
|
|
<style>
|
|||
|
|
body { font-family: system-ui, sans-serif; max-width: 760px; margin: 30px auto; padding: 0 16px; }
|
|||
|
|
h1 { font-size: 1.3rem; }
|
|||
|
|
#output {
|
|||
|
|
border: 1px solid #ddd; border-radius: 10px;
|
|||
|
|
padding: 16px; min-height: 300px; white-space: pre-wrap;
|
|||
|
|
line-height: 1.6; background: #fafafa;
|
|||
|
|
}
|
|||
|
|
.reasoning {
|
|||
|
|
background: #f1f5f9; border-left: 4px solid #94a3b8;
|
|||
|
|
padding: 8px 12px; border-radius: 6px; margin-bottom: 12px;
|
|||
|
|
color: #475569; font-size: 0.9rem; white-space: pre-wrap;
|
|||
|
|
}
|
|||
|
|
.content { color: #0f172a; }
|
|||
|
|
.label { font-size: 0.75rem; font-weight: bold; color: #64748b; margin-bottom: 4px; }
|
|||
|
|
.row { display: flex; gap: 8px; margin-top: 12px; }
|
|||
|
|
input {
|
|||
|
|
flex: 1; padding: 12px 16px; border: 1px solid #ccc;
|
|||
|
|
border-radius: 8px; font-size: 1rem; outline: none;
|
|||
|
|
}
|
|||
|
|
input:focus { border-color: #4f46e5; }
|
|||
|
|
button {
|
|||
|
|
padding: 12px 24px; background: #4f46e5; color: white;
|
|||
|
|
border: none; border-radius: 8px; font-size: 1rem;
|
|||
|
|
cursor: pointer;
|
|||
|
|
}
|
|||
|
|
button:disabled { background: #cbd5e1; cursor: not-allowed; }
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<h1>💬 DeepSeek 流式对话</h1>
|
|||
|
|
|
|||
|
|
<div id="output">等待输入…</div>
|
|||
|
|
|
|||
|
|
<div class="row">
|
|||
|
|
<input id="input" placeholder="输入问题,回车发送" autocomplete="off">
|
|||
|
|
<button id="send">发送</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
const inputEl = document.getElementById('input');
|
|||
|
|
const sendBtn = document.getElementById('send');
|
|||
|
|
const outputEl = document.getElementById('output');
|
|||
|
|
|
|||
|
|
let es = null; // EventSource
|
|||
|
|
let reasoningEl = null; // 推理区块
|
|||
|
|
let contentEl = null; // 回答区块
|
|||
|
|
|
|||
|
|
function resetOutput() {
|
|||
|
|
outputEl.innerHTML = '';
|
|||
|
|
reasoningEl = null;
|
|||
|
|
contentEl = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getReasoningEl() {
|
|||
|
|
if (!reasoningEl) {
|
|||
|
|
const wrap = document.createElement('div');
|
|||
|
|
wrap.className = 'reasoning';
|
|||
|
|
const label = document.createElement('div');
|
|||
|
|
label.className = 'label';
|
|||
|
|
label.textContent = '🧠 推理过程';
|
|||
|
|
const text = document.createElement('div');
|
|||
|
|
wrap.appendChild(label);
|
|||
|
|
wrap.appendChild(text);
|
|||
|
|
outputEl.appendChild(wrap);
|
|||
|
|
reasoningEl = text;
|
|||
|
|
}
|
|||
|
|
return reasoningEl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getContentEl() {
|
|||
|
|
if (!contentEl) {
|
|||
|
|
const wrap = document.createElement('div');
|
|||
|
|
wrap.className = 'content';
|
|||
|
|
outputEl.appendChild(wrap);
|
|||
|
|
contentEl = wrap;
|
|||
|
|
}
|
|||
|
|
return contentEl;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function send() {
|
|||
|
|
const text = inputEl.value.trim();
|
|||
|
|
if (!text) return;
|
|||
|
|
|
|||
|
|
// 关闭上一个连接
|
|||
|
|
if (es) { es.close(); es = null; }
|
|||
|
|
resetOutput();
|
|||
|
|
inputEl.value = '';
|
|||
|
|
sendBtn.disabled = true;
|
|||
|
|
|
|||
|
|
const url = `http://localhost:8001/chat?user_input=${encodeURIComponent(text)}&model=deepseek-chat`;
|
|||
|
|
es = new EventSource(url);
|
|||
|
|
|
|||
|
|
// 推理过程(命名事件 reasoning)
|
|||
|
|
es.addEventListener('reasoning', (e) => {
|
|||
|
|
const chunk = JSON.parse(e.data);
|
|||
|
|
getReasoningEl().textContent += chunk;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 正式回答(命名事件 content ★ 关键)
|
|||
|
|
es.addEventListener('content', (e) => {
|
|||
|
|
const chunk = JSON.parse(e.data);
|
|||
|
|
getContentEl().textContent += chunk;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 服务端错误(命名事件 server_error)
|
|||
|
|
es.addEventListener('server_error', (e) => {
|
|||
|
|
const msg = JSON.parse(e.data);
|
|||
|
|
outputEl.innerHTML += `<div style="color:red;">⚠️ ${msg}</div>`;
|
|||
|
|
finish();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 结束(命名事件 done)
|
|||
|
|
es.addEventListener('done', () => {
|
|||
|
|
finish();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 网络层错误
|
|||
|
|
es.onerror = () => {
|
|||
|
|
if (es && es.readyState === EventSource.CLOSED) {
|
|||
|
|
finish();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function finish() {
|
|||
|
|
if (es) { es.close(); es = null; }
|
|||
|
|
sendBtn.disabled = false;
|
|||
|
|
if (!outputEl.textContent.trim()) {
|
|||
|
|
outputEl.textContent = '(无返回内容)';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sendBtn.addEventListener('click', send);
|
|||
|
|
inputEl.addEventListener('keydown', (e) => {
|
|||
|
|
if (e.key === 'Enter') send();
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|