fix: restore LLM profile test connectivity buttons (lost from git)

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>
This commit is contained in:
2026-06-23 13:58:43 +08:00
parent 7cc3aff95a
commit 1dc7ab9727
4 changed files with 94 additions and 0 deletions

View File

@@ -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 {

View File

@@ -219,9 +219,11 @@
</div>
<div class="form-actions">
<button class="btn btn-primary" id="save-profile-btn">保存</button>
<button class="btn btn-test" id="test-profile-btn">测试连通性</button>
<button class="btn" id="cancel-profile-btn">取消</button>
<span class="form-error muted" id="profile-form-error"></span>
</div>
<div class="profile-test-result" id="profile-form-test-result" hidden></div>
</div>
</div>

View File

@@ -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); },
};

View File

@@ -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 = {
<div class="profile-card-head">
<div class="profile-card-name">${App.escape(p.name)}</div>
<div class="profile-card-actions">
<button class="btn btn-sm btn-test" data-action="test">测试</button>
<button class="btn btn-sm" data-action="edit">编辑</button>
<button class="btn btn-sm btn-danger" data-action="delete">删除</button>
</div>
@@ -46,12 +48,72 @@ const Profiles = {
<div class="profile-card-field"><span class="field-label">模型</span> <code>${App.escape(p.model)}</code></div>
<div class="profile-card-field"><span class="field-label">Base URL</span> <code>${App.escape(p.base_url)}</code></div>
<div class="profile-card-field"><span class="field-label">超时</span> ${p.timeout_seconds}s</div>
<div class="profile-test-result" data-result hidden></div>
`;
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" });
},