// 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()); document.getElementById("test-profile-btn").addEventListener("click", () => Profiles.testForm()); }, // 加载并渲染 Profile 列表 async load() { const grid = document.getElementById("profile-cards"); const empty = document.getElementById("profiles-empty"); grid.innerHTML = '

加载中…

'; 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 = `

加载失败:${App.escape(err.message)}

`; } }, // 渲染单个 Profile 卡片 renderCard(p) { const card = document.createElement("div"); card.className = "profile-card"; card.dataset.id = p.profile_id; card.innerHTML = `
${App.escape(p.name)}
模型 ${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"); 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 = ""; const resultEl = document.getElementById("profile-form-test-result"); resultEl.hidden = true; resultEl.className = "profile-test-result"; 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; }, };