Spaces:
Sleeping
Sleeping
| <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> | |