eino-closedloop-cn / web /index.html
3v324v23's picture
init: eino siliconflow demo
32455d6
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Eino + 硅基流动 Qwen2.5-7B-Instruct 展示</title>
<style>
:root {
color-scheme: light;
}
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
"Apple Color Emoji", "Segoe UI Emoji";
background: #ffffff;
color: #111827;
}
.wrap {
max-width: 980px;
margin: 0 auto;
padding: 28px 16px 48px;
}
.title {
display: flex;
align-items: baseline;
gap: 12px;
margin: 0 0 12px;
}
.title h1 {
font-size: 20px;
margin: 0;
font-weight: 700;
letter-spacing: 0.2px;
}
.badge {
font-size: 12px;
padding: 3px 10px;
border-radius: 999px;
border: 1px solid #e5e7eb;
background: #f9fafb;
color: #111827;
}
.sub {
margin: 0 0 18px;
color: #374151;
font-size: 13px;
line-height: 1.55;
}
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 14px;
}
.card {
border: 1px solid #e5e7eb;
border-radius: 12px;
overflow: hidden;
background: #ffffff;
}
.cardHead {
padding: 12px 14px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
border-bottom: 1px solid #e5e7eb;
background: #fafafa;
}
.cardHead strong {
font-size: 13px;
}
.right {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.kv {
display: inline-flex;
gap: 6px;
align-items: baseline;
font-size: 12px;
color: #374151;
}
.kv code {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
background: #f3f4f6;
border: 1px solid #e5e7eb;
padding: 1px 6px;
border-radius: 8px;
color: #111827;
}
.cardBody {
padding: 14px;
}
textarea {
width: 100%;
box-sizing: border-box;
resize: vertical;
min-height: 92px;
border-radius: 10px;
border: 1px solid #d1d5db;
padding: 10px 12px;
font-size: 14px;
line-height: 1.5;
outline: none;
}
textarea:focus {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
}
.actions {
display: flex;
gap: 10px;
margin-top: 10px;
flex-wrap: wrap;
}
button {
appearance: none;
border: 1px solid #d1d5db;
background: #ffffff;
color: #111827;
border-radius: 10px;
padding: 9px 12px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
button.primary {
border-color: #2563eb;
background: #2563eb;
color: #ffffff;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.out {
white-space: pre-wrap;
word-break: break-word;
font-size: 14px;
line-height: 1.65;
color: #111827;
}
.muted {
color: #6b7280;
font-size: 12px;
}
.err {
color: #b91c1c;
}
@media (min-width: 920px) {
.grid {
grid-template-columns: 1fr 1fr;
}
}
</style>
</head>
<body>
<div class="wrap">
<div class="title">
<h1>Eino + 硅基流动(OpenAI 兼容)展示</h1>
<span class="badge" id="statusBadge">未连接</span>
</div>
<p class="sub">
这个页面只负责“看效果”。API Key 不会出现在浏览器里,浏览器只请求本地的 Go 服务 `/chat`。
</p>
<div class="grid">
<div class="card">
<div class="cardHead">
<strong>输入</strong>
<div class="right">
<span class="kv">接口 <code>POST /chat</code></span>
</div>
</div>
<div class="cardBody">
<textarea id="input" placeholder="输入你要问的问题…"></textarea>
<div class="actions">
<button class="primary" id="sendBtn">发送</button>
<button id="fillBtn">填入示例</button>
<button id="clearBtn">清空输出</button>
<span class="muted" id="hint"></span>
</div>
</div>
</div>
<div class="card">
<div class="cardHead">
<strong>输出</strong>
<div class="right">
<span class="kv">模式 <code id="mode">-</code></span>
<span class="kv">模型 <code id="model">-</code></span>
<span class="kv">耗时 <code id="ms">-</code></span>
</div>
</div>
<div class="cardBody">
<div class="out" id="output"></div>
<div class="muted" id="error"></div>
</div>
</div>
</div>
</div>
<script>
const $ = (id) => document.getElementById(id)
const inputEl = $("input")
const outputEl = $("output")
const errorEl = $("error")
const sendBtn = $("sendBtn")
const fillBtn = $("fillBtn")
const clearBtn = $("clearBtn")
const hintEl = $("hint")
const statusBadge = $("statusBadge")
const modeEl = $("mode")
const modelEl = $("model")
const msEl = $("ms")
function setBusy(busy) {
sendBtn.disabled = busy
fillBtn.disabled = busy
clearBtn.disabled = busy
hintEl.textContent = busy ? "请求中…" : ""
}
function setStatus(ok) {
statusBadge.textContent = ok ? "已连接" : "未连接"
statusBadge.style.background = ok ? "#ecfeff" : "#f9fafb"
statusBadge.style.borderColor = ok ? "#a5f3fc" : "#e5e7eb"
statusBadge.style.color = ok ? "#0e7490" : "#111827"
}
async function ping() {
try {
const r = await fetch("/healthz")
setStatus(r.ok)
} catch {
setStatus(false)
}
}
fillBtn.addEventListener("click", () => {
inputEl.value = "请用 3 条要点解释:Qwen2.5-7B-Instruct 适合做什么?"
inputEl.focus()
})
clearBtn.addEventListener("click", () => {
outputEl.textContent = ""
errorEl.textContent = ""
modeEl.textContent = "-"
modelEl.textContent = "-"
msEl.textContent = "-"
})
sendBtn.addEventListener("click", async () => {
const text = (inputEl.value || "").trim()
if (!text) return
setBusy(true)
errorEl.textContent = ""
errorEl.className = "muted"
const t0 = performance.now()
try {
const r = await fetch("/chat", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ input: text }),
})
const data = await r.json().catch(() => ({}))
const t1 = performance.now()
msEl.textContent = `${Math.round(t1 - t0)}ms`
if (!r.ok || !data.ok) {
const msg = data.error || `请求失败(HTTP ${r.status})`
errorEl.textContent = msg
errorEl.className = "muted err"
return
}
modeEl.textContent = data.mock ? "mock" : "real"
modelEl.textContent = data.model || "-"
outputEl.textContent = data.output || ""
} catch (e) {
errorEl.textContent = e?.message || "请求异常"
errorEl.className = "muted err"
} finally {
setBusy(false)
ping()
}
})
ping()
</script>
</body>
</html>