v2.5.0 模型实时获取:接口配置区「📋 查看模型」按钮,Base URL+API Key 下实时拉取模型列表(GET /models),搜索/点击填入,仍支持手动输入

This commit is contained in:
2026-09-06 01:52:06 +08:00
parent 35589b1e9b
commit 3fe82328b6
6 changed files with 193 additions and 7 deletions
+82 -2
View File
@@ -191,6 +191,7 @@ function loadSelectedConfig() {
$("#cfg-temp").value = c.temperature ?? 0.7;
$("#tmp-val").textContent = (c.temperature ?? 0.7).toFixed(1);
updateDefaultUrlHint();
hideModelPick();
flash("已加载配置:" + c.name);
}).catch(() => toast("加载失败"));
}
@@ -223,6 +224,81 @@ function showConn(ok, text) {
box.textContent = text;
}
/* ───────────────────────── 查看模型(实时获取接口下所有模型) ───────────────────────── */
function hideModelPick() {
$("#model-pick").hidden = true;
$("#model-result").hidden = true;
}
async function listModels() {
const cfg = currentConfig();
hideModelPick();
if (!cfg.base_url) { toast("请先填写 Base URL"); return; }
if (!cfg.api_key) { toast("请先填写 API Key"); return; }
const panel = $("#model-pick");
panel.hidden = false;
const listEl = $("#model-pick-list");
listEl.innerHTML = '<div class="model-pick-empty">⏳ 正在获取模型列表(GET /models...</div>';
try {
const r = await api("/api/models", "POST", cfg);
if (!r.ok) throw new Error(r.error || "获取失败");
const models = r.models || [];
if (!models.length) {
listEl.innerHTML = '<div class="model-pick-empty">⚠️ ' + esc(r.note || "接口可用,但未返回任何模型") + '</div>';
return;
}
renderModelList(models);
} catch (e) {
panel.hidden = true;
const box = $("#model-result");
box.hidden = false;
box.className = "conn-result fail";
box.textContent = "❌ 获取模型失败:" + e.message;
}
}
function renderModelList(models) {
const listEl = $("#model-pick-list");
const search = $("#model-pick-search");
search.value = "";
listEl.innerHTML = "";
const fill = (m) => {
$("#cfg-model").value = m;
hideModelPick();
toast("已填入模型:" + m);
};
const render = () => {
const q = search.value.trim().toLowerCase();
const cur = $("#cfg-model").value.trim();
const filtered = q ? models.filter((m) => m.toLowerCase().includes(q)) : models;
if (!filtered.length) {
listEl.innerHTML = '<div class="model-pick-empty">无匹配模型,可直接在上方输入模型名</div>';
return;
}
listEl.innerHTML = "";
for (const m of filtered) {
const item = document.createElement("div");
item.className = "model-pick-item" + (m === cur ? " active" : "");
item.textContent = m;
item.title = "点击填入模型名称";
item.addEventListener("click", () => fill(m));
listEl.appendChild(item);
}
};
search.addEventListener("input", render);
search.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const v = search.value.trim();
if (v) fill(v); // 手动输入回车直接确认填入
} else if (e.key === "Escape") {
hideModelPick();
}
});
render();
search.focus();
}
/* ───────────────────────── 测试运行 ───────────────────────── */
function setStatus(state, label) {
@@ -951,13 +1027,17 @@ function flash(msg) {
/* ───────────────────────── 事件绑定 ───────────────────────── */
function bind() {
$("#cfg-provider").addEventListener("change", updateDefaultUrlHint);
$("#cfg-provider").addEventListener("change", () => { updateDefaultUrlHint(); hideModelPick(); });
$("#cfg-baseurl").addEventListener("input", hideModelPick);
$("#cfg-apikey").addEventListener("input", hideModelPick);
$("#cfg-temp").addEventListener("input", () => $("#tmp-val").textContent = parseFloat($("#cfg-temp").value).toFixed(1));
$("#btn-toggle-key").addEventListener("click", () => {
const inp = $("#cfg-apikey");
inp.type = inp.type === "password" ? "text" : "password";
});
$("#btn-list-models").addEventListener("click", listModels);
$("#model-pick-close").addEventListener("click", hideModelPick);
$("#btn-save-config").addEventListener("click", async () => {
const r = await api("/api/configs", "POST", currentConfig());
if (r.ok) { toast("配置已保存"); loadConfigs(); }
@@ -1027,7 +1107,7 @@ function bind() {
$("#dt-export-xlsx").addEventListener("click", () => { if (window.__detail) exportXlsx(window.__detail.id); });
$("#dt-send-eval").addEventListener("click", () => { if (window.__detail) sendToEval(window.__detail.id); });
document.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeDetail(); closeCompare(); } });
document.addEventListener("keydown", (e) => { if (e.key === "Escape") { hideModelPick(); closeDetail(); closeCompare(); } });
}
/* ───────────────────────── 初始化 ───────────────────────── */