feat: add LLM配置 management page (profiles view)
This commit is contained in:
@@ -43,4 +43,26 @@ const API = {
|
||||
return API.post("/api/evaluations", { scenario_path: scenarioPath });
|
||||
},
|
||||
taskStatus(taskId) { return API.get(`/api/evaluations/${encodeURIComponent(taskId)}`); },
|
||||
|
||||
// LLM Profile API
|
||||
profiles() { return API.get("/api/llm-profiles"); },
|
||||
createProfile(body) { return API.post("/api/llm-profiles", body); },
|
||||
updateProfile(id, body) {
|
||||
return fetch(`/api/llm-profiles/${encodeURIComponent(id)}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
}).then(async r => {
|
||||
if (!r.ok) { const d = await API._extractError(r); throw new Error(d); }
|
||||
return r.json();
|
||||
});
|
||||
},
|
||||
deleteProfile(id) {
|
||||
return fetch(`/api/llm-profiles/${encodeURIComponent(id)}`, { method: "DELETE" })
|
||||
.then(async r => {
|
||||
if (!r.ok) { const d = await API._extractError(r); throw new Error(d); }
|
||||
return r.json();
|
||||
});
|
||||
},
|
||||
applyProfiles(body) { return API.post("/api/llm-profiles/apply", body); },
|
||||
};
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
const App = {
|
||||
currentRunId: null,
|
||||
views: ["runs", "new", "report"],
|
||||
titles: { runs: "运行列表", new: "新建评估", report: "报告详情" },
|
||||
views: ["runs", "new", "report", "profiles"],
|
||||
titles: { runs: "运行列表", new: "新建评估", report: "报告详情", profiles: "LLM 配置" },
|
||||
|
||||
// 初始化:绑定导航、加载首屏、启动健康检查。
|
||||
init() {
|
||||
@@ -13,6 +13,7 @@ const App = {
|
||||
document.getElementById("refresh-btn").addEventListener("click", () => App.refreshCurrent());
|
||||
|
||||
Runner.init();
|
||||
Profiles.init();
|
||||
App.switchView("runs");
|
||||
App.checkHealth();
|
||||
setInterval(App.checkHealth, 15000);
|
||||
@@ -36,6 +37,7 @@ const App = {
|
||||
if (view === "runs") App.loadRuns();
|
||||
if (view === "new") Runner.loadScenarios();
|
||||
if (view === "report") Report.render(App.currentRunId);
|
||||
if (view === "profiles") Profiles.load();
|
||||
},
|
||||
|
||||
// 刷新当前视图的数据。
|
||||
|
||||
118
webapp/static/js/profiles.js
Normal file
118
webapp/static/js/profiles.js
Normal file
@@ -0,0 +1,118 @@
|
||||
// profiles.js — LLM 配置管理页面逻辑
|
||||
|
||||
const Profiles = {
|
||||
_data: [],
|
||||
|
||||
// 初始化:绑定按钮事件
|
||||
init() {
|
||||
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());
|
||||
},
|
||||
|
||||
// 加载并渲染 Profile 列表
|
||||
async load() {
|
||||
const grid = document.getElementById("profile-cards");
|
||||
const empty = document.getElementById("profiles-empty");
|
||||
grid.innerHTML = '<p class="muted">加载中…</p>';
|
||||
try {
|
||||
const data = await API.profiles();
|
||||
Profiles._data = data.profiles || [];
|
||||
grid.innerHTML = "";
|
||||
if (Profiles._data.length === 0) {
|
||||
empty.hidden = false;
|
||||
} else {
|
||||
empty.hidden = true;
|
||||
Profiles._data.forEach(p => grid.appendChild(Profiles.renderCard(p)));
|
||||
}
|
||||
} catch (err) {
|
||||
grid.innerHTML = `<p class="muted">加载失败:${App.escape(err.message)}</p>`;
|
||||
}
|
||||
},
|
||||
|
||||
// 渲染单个 Profile 卡片
|
||||
renderCard(p) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "profile-card";
|
||||
card.dataset.id = p.profile_id;
|
||||
card.innerHTML = `
|
||||
<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" data-action="edit">编辑</button>
|
||||
<button class="btn btn-sm btn-danger" data-action="delete">删除</button>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
`;
|
||||
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;
|
||||
},
|
||||
|
||||
// 显示新建或编辑表单
|
||||
showForm(profile = null) {
|
||||
const panel = document.getElementById("profile-form-panel");
|
||||
const title = document.getElementById("profile-form-title");
|
||||
panel.hidden = false;
|
||||
title.textContent = profile ? "编辑 LLM 配置" : "新建 LLM 配置";
|
||||
document.getElementById("edit-profile-id").value = profile ? profile.profile_id : "";
|
||||
document.getElementById("pf-name").value = profile ? profile.name : "";
|
||||
document.getElementById("pf-model").value = profile ? profile.model : "";
|
||||
document.getElementById("pf-base-url").value = profile ? profile.base_url : "";
|
||||
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 = "";
|
||||
panel.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
},
|
||||
|
||||
hideForm() {
|
||||
document.getElementById("profile-form-panel").hidden = true;
|
||||
},
|
||||
|
||||
// 保存(新建 or 更新)
|
||||
async save() {
|
||||
const id = document.getElementById("edit-profile-id").value;
|
||||
const body = {
|
||||
name: document.getElementById("pf-name").value.trim(),
|
||||
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.name || !body.model || !body.base_url || !body.api_key) {
|
||||
errEl.textContent = "请填写所有必填字段(名称、模型、Base URL、API Key)";
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (id) {
|
||||
await API.updateProfile(id, body);
|
||||
} else {
|
||||
await API.createProfile(body);
|
||||
}
|
||||
Profiles.hideForm();
|
||||
await Profiles.load();
|
||||
} catch (err) {
|
||||
errEl.textContent = `保存失败:${err.message}`;
|
||||
}
|
||||
},
|
||||
|
||||
// 删除 Profile
|
||||
async remove(profileId, name) {
|
||||
if (!confirm(`确认删除配置「${name}」?`)) return;
|
||||
try {
|
||||
await API.deleteProfile(profileId);
|
||||
await Profiles.load();
|
||||
} catch (err) {
|
||||
alert(`删除失败:${err.message}`);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取当前已加载的 profiles(供 runner.js 使用)
|
||||
getAll() {
|
||||
return Profiles._data;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user