From 1dc7ab9727c95fd832cc9b655cc21016b7e0670d Mon Sep 17 00:00:00 2001 From: wangwei Date: Tue, 23 Jun 2026 13:58:43 +0800 Subject: [PATCH] fix: restore LLM profile test connectivity buttons (lost from git) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Frontend test functionality was implemented but never committed to git. Re-adds: - profiles.js: testCard(), testForm(), _showTestResult(), test btn in renderCard - api.js: testProfile(id) and probeConnectivity(body) methods - index.html: 测试连通性 button + result div in profile form - app.css: .btn-test and .profile-test-result styles Backend /probe and /{id}/test endpoints were already present in llm_profiles.py. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- webapp/static/css/app.css | 15 +++++++++ webapp/static/index.html | 2 ++ webapp/static/js/api.js | 12 +++++++ webapp/static/js/profiles.js | 65 ++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) diff --git a/webapp/static/css/app.css b/webapp/static/css/app.css index 645b1dd..1499d0e 100644 --- a/webapp/static/css/app.css +++ b/webapp/static/css/app.css @@ -294,6 +294,21 @@ table.group-table td { border-bottom: 1px solid #f1f5f9; font-variant-numeric: t .btn-sm { padding: 4px 10px; font-size: 12px; } .btn-danger { color: var(--bad); border-color: var(--bad); } .btn-danger:hover { background: #fee2e2; } +.btn-test { color: #0369a1; border-color: #0369a1; } +.btn-test:hover { background: #e0f2fe; } + +/* LLM 连通性测试结果 */ +.profile-test-result { + margin-top: 8px; + padding: 6px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + display: none; +} +.profile-test-result:not([hidden]) { display: block; } +.profile-test-result.ok { background: #dcfce7; color: #166534; border: 1px solid #bbf7d0; } +.profile-test-result.fail { background: #fee2e2; color: #991b1b; border: 1px solid #fecaca; word-break: break-all; } /* 选中态 run 卡片 */ .run-card.selected { diff --git a/webapp/static/index.html b/webapp/static/index.html index d7a2d57..a262c55 100644 --- a/webapp/static/index.html +++ b/webapp/static/index.html @@ -219,9 +219,11 @@
+
+ diff --git a/webapp/static/js/api.js b/webapp/static/js/api.js index caa4f15..26f7e14 100644 --- a/webapp/static/js/api.js +++ b/webapp/static/js/api.js @@ -65,4 +65,16 @@ const API = { }); }, applyProfiles(body) { return API.post("/api/llm-profiles/apply", body); }, + + // 测试已保存 profile 的连通性 + testProfile(id) { + return fetch(`/api/llm-profiles/${encodeURIComponent(id)}/test`, { method: "POST" }) + .then(async r => { + if (!r.ok) { const d = await API._extractError(r); throw new Error(d); } + return r.json(); + }); + }, + + // 测试表单中填写的内联参数(保存前即可测试) + probeConnectivity(body) { return API.post("/api/llm-profiles/probe", body); }, }; diff --git a/webapp/static/js/profiles.js b/webapp/static/js/profiles.js index 83e5a53..4355b09 100644 --- a/webapp/static/js/profiles.js +++ b/webapp/static/js/profiles.js @@ -8,6 +8,7 @@ const Profiles = { document.getElementById("add-profile-btn").addEventListener("click", () => Profiles.showForm()); document.getElementById("save-profile-btn").addEventListener("click", () => Profiles.save()); document.getElementById("cancel-profile-btn").addEventListener("click", () => Profiles.hideForm()); + document.getElementById("test-profile-btn").addEventListener("click", () => Profiles.testForm()); }, // 加载并渲染 Profile 列表 @@ -39,6 +40,7 @@ const Profiles = {
${App.escape(p.name)}
+
@@ -46,12 +48,72 @@ const Profiles = {
模型 ${App.escape(p.model)}
Base URL ${App.escape(p.base_url)}
超时 ${p.timeout_seconds}s
+ `; + card.querySelector("[data-action=test]").addEventListener("click", () => Profiles.testCard(p, card)); card.querySelector("[data-action=edit]").addEventListener("click", () => Profiles.showForm(p)); card.querySelector("[data-action=delete]").addEventListener("click", () => Profiles.remove(p.profile_id, p.name)); return card; }, + // 测试已保存的 profile(卡片上的测试按钮) + async testCard(p, card) { + const btn = card.querySelector("[data-action=test]"); + const resultEl = card.querySelector("[data-result]"); + btn.disabled = true; + btn.textContent = "测试中…"; + resultEl.hidden = true; + resultEl.className = "profile-test-result"; + try { + const res = await API.testProfile(p.profile_id); + Profiles._showTestResult(resultEl, res); + } catch (err) { + Profiles._showTestResult(resultEl, { ok: false, message: err.message }); + } finally { + btn.disabled = false; + btn.textContent = "测试"; + } + }, + + // 测试表单中当前填写的参数(保存前即可测试) + async testForm() { + const body = { + model: document.getElementById("pf-model").value.trim(), + base_url: document.getElementById("pf-base-url").value.trim(), + api_key: document.getElementById("pf-api-key").value.trim(), + timeout_seconds: parseInt(document.getElementById("pf-timeout").value, 10) || 30, + }; + const errEl = document.getElementById("profile-form-error"); + if (!body.model || !body.base_url || !body.api_key) { + errEl.textContent = "请先填写模型名称、Base URL 和 API Key"; + return; + } + errEl.textContent = ""; + const testBtn = document.getElementById("test-profile-btn"); + const resultEl = document.getElementById("profile-form-test-result"); + testBtn.disabled = true; + testBtn.textContent = "测试中…"; + resultEl.hidden = true; + resultEl.className = "profile-test-result"; + try { + const res = await API.probeConnectivity(body); + Profiles._showTestResult(resultEl, res); + } catch (err) { + Profiles._showTestResult(resultEl, { ok: false, message: err.message }); + } finally { + testBtn.disabled = false; + testBtn.textContent = "测试连通性"; + } + }, + + // 渲染测试结果到指定元素 + _showTestResult(el, res) { + el.hidden = false; + el.classList.add(res.ok ? "ok" : "fail"); + const latency = res.latency_ms != null ? ` (${res.latency_ms}ms)` : ""; + el.textContent = res.ok ? `✓ 连接成功${latency}` : `✗ ${res.message}`; + }, + // 显示新建或编辑表单 showForm(profile = null) { const panel = document.getElementById("profile-form-panel"); @@ -65,6 +127,9 @@ const Profiles = { document.getElementById("pf-api-key").value = profile ? profile.api_key : ""; document.getElementById("pf-timeout").value = profile ? profile.timeout_seconds : 30; document.getElementById("profile-form-error").textContent = ""; + const resultEl = document.getElementById("profile-form-test-result"); + resultEl.hidden = true; + resultEl.className = "profile-test-result"; panel.scrollIntoView({ behavior: "smooth", block: "start" }); },