This commit is contained in:
2026-04-22 16:49:18 +08:00
parent a1ac67058d
commit d929a15dec
2 changed files with 109 additions and 62 deletions

137
test.html
View File

@@ -230,8 +230,8 @@
<div class="card"> <div class="card">
<div class="header"> <div class="header">
<h1>Dify 中转服务 — 测试面板</h1> <h1>数字人中转服务 — 测试面板</h1>
<p>快速验证 digital-human-relay 与 Dify 的连通性</p> <p>快速验证 digital-human 与 Dify 的连通性</p>
</div> </div>
<!-- Status --> <!-- Status -->
@@ -244,20 +244,41 @@
<div class="field"> <div class="field">
<label>中转服务地址</label> <label>中转服务地址</label>
<input type="text" id="relayUrl" placeholder="http://你的服务器IP:8000" /> <input type="text" id="relayUrl" placeholder="http://你的服务器IP:8000" value="http://你的服务器IP:8000" />
<small>填写 digital-human 中转服务的根地址,末尾不要加斜杠</small> <small>填写 digital-human 中转服务的根地址,末尾不要加斜杠</small>
</div> </div>
<div class="field">
<label>Session ID</label>
<input type="text" id="sessionId" placeholder="留空则自动生成" />
<small>用于多轮对话上下文,不填会自动生成</small>
</div>
<div class="row"> <div class="row">
<div class="field"> <div class="field">
<label>Dify API 密钥</label> <label>App ID</label>
<input type="password" id="apiKey" placeholder="app-xxxxxxxx" /> <input type="text" id="appId" placeholder="app-xxxxxxxx" />
</div>
<div class="field">
<label>User</label>
<input type="text" id="user" placeholder="user-xxx" />
</div> </div>
</div> </div>
<div class="field">
<label>测试消息</label>
<textarea id="message" placeholder="请输入你想问的问题...">你好,请用一句话介绍自己。</textarea>
</div>
<div class="field">
<label>
<input type="checkbox" id="streamMode" checked style="width:auto;margin-right:6px;" />
流式传输(Streaming)
</label>
</div>
<div class="btn-row"> <div class="btn-row">
<button class="btn btn-ghost" onclick="testHealth()">健康检查</button> <button class="btn btn-primary" id="sendBtn" onclick="sendMessage()">发送请求</button>
<button class="btn btn-primary" id="sendBtn" onclick="sendMessage()">发送测试请求</button>
</div> </div>
</div> </div>
@@ -278,7 +299,11 @@
// ── Helpers ──────────────────────────────────────────────── // ── Helpers ────────────────────────────────────────────────
const relayUrl = () => document.getElementById('relayUrl').value.replace(/\/$/, ''); const relayUrl = () => document.getElementById('relayUrl').value.replace(/\/$/, '');
const apiKey = () => document.getElementById('apiKey').value.trim(); const sessionId = () => document.getElementById('sessionId').value.trim() || crypto.randomUUID();
const appId = () => document.getElementById('appId').value.trim();
const user = () => document.getElementById('user').value.trim() || 'test-user';
const message = () => document.getElementById('message').value.trim();
const isStream = () => document.getElementById('streamMode').checked;
function setStatus(type, text) { function setStatus(type, text) {
const dot = document.getElementById('statusDot'); const dot = document.getElementById('statusDot');
@@ -310,32 +335,13 @@
document.getElementById('streamingIndicator').style.display = loading ? 'flex' : 'none'; document.getElementById('streamingIndicator').style.display = loading ? 'flex' : 'none';
} }
// ── Health Check ───────────────────────────────────────────
async function testHealth() {
const url = relayUrl();
if (!url) { alert('请先填写"中转服务地址"'); return; }
clearOutput();
setStatus('idle', '检测中…');
try {
const res = await fetch(`${url}/health`);
const data = await res.json().catch(() => ({}));
clearOutput();
append(`HTTP 状态码: ${res.status}\n`, res.ok ? 'success-text' : 'error-text');
append(JSON.stringify(data, null, 2), res.ok ? 'success-text' : 'error-text');
setStatus(res.ok ? 'ok' : 'err', res.ok ? `服务正常 — ${data.status || 'ok'}` : `HTTP ${res.status}`);
} catch (e) {
clearOutput();
append(`连接失败:${e.message}`, 'error-text');
setStatus('err', '连接失败');
}
}
// ── Send Message ─────────────────────────────────────────── // ── Send Message ───────────────────────────────────────────
async function sendMessage() { async function sendMessage() {
const url = relayUrl(); const url = relayUrl();
const key = apiKey();
if (!url) { alert('请填写"中转服务地址"'); return; } if (!url) { alert('请填写"中转服务地址"'); return; }
if (!key) { alert('请填写"Dify API 密钥"'); return; }
const msg = message();
if (!msg) { alert('请填写"测试消息"'); return; }
// abort previous // abort previous
if (abortController) abortController.abort(); if (abortController) abortController.abort();
@@ -346,26 +352,69 @@
setStatus('idle', '请求中…'); setStatus('idle', '请求中…');
const payload = { const payload = {
stream: false, messages: [{ content: msg }],
messages: [ app_id: appId() || 'test-app',
{ role: 'user', content: '你好,请用一句话介绍自己。' } user: user(),
] session_id: sessionId(),
is_stream: isStream(),
}; };
append(`POST ${url}/v1/chat/completions\n`); append(`POST ${url}/digital-human\n\n`);
append(`Authorization: Bearer ${key.substring(0, 8)}…\n\n`);
append(`Request:\n${JSON.stringify(payload, null, 2)}\n\n`); append(`Request:\n${JSON.stringify(payload, null, 2)}\n\n`);
append(`Response:\n`); append(`Response:\n`);
try { try {
// Direct to Dify (bypass relay — useful if relay itself has issues) if (isStream()) {
const endpoint = `${url}/v1/chat/completions`; // ── Streaming ───────────────────────────────────────
const endpoint = `${url}/digital-human`;
const res = await fetch(endpoint, { const res = await fetch(endpoint, {
method: 'POST', method: 'POST',
headers: { headers: { 'Content-Type': 'application/json' },
'Content-Type': 'application/json', body: JSON.stringify(payload),
'Authorization': `Bearer ${key}`, signal: abortController.signal,
}, });
if (!res.ok) {
const errText = await res.text();
append(`[${res.status}] ERROR\n${errText}`, 'error-text');
setStatus('err', `HTTP ${res.status}`);
return;
}
const reader = res.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed === 'data: [DONE]' || trimmed === 'data:') continue;
if (trimmed.startsWith('data: ')) {
try {
const raw = trimmed.slice(6);
const chunk = JSON.parse(raw);
const content = chunk?.choices?.[0]?.message?.content || '';
if (content) append(content, 'success-text');
} catch { /* skip malformed */ }
}
}
}
append('\n\n[DONE]', 'warn-text');
setStatus('ok', '流式响应完成');
} else {
// ── Non-Streaming ────────────────────────────────────
const endpoint = `${url}/digital-human`;
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload), body: JSON.stringify(payload),
signal: abortController.signal, signal: abortController.signal,
}); });
@@ -381,13 +430,11 @@
const reply = data.choices[0]?.message?.content || ''; const reply = data.choices[0]?.message?.content || '';
append(`回复内容:\n${reply}`, 'success-text'); append(`回复内容:\n${reply}`, 'success-text');
setStatus('ok', '请求成功'); setStatus('ok', '请求成功');
} else if (res.ok && data.error) {
append(`Dify 错误:${JSON.stringify(data.error, null, 2)}`, 'error-text');
setStatus('err', 'Dify 返回错误');
} else { } else {
append(`原始响应:\n${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}`, 'error-text'); append(`原始响应:\n${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}`, 'error-text');
setStatus('err', `HTTP ${res.status}`); setStatus('err', `HTTP ${res.status}`);
} }
}
} catch (e) { } catch (e) {
if (e.name === 'AbortError') { if (e.name === 'AbortError') {
append('请求已取消', 'warn-text'); append('请求已取消', 'warn-text');