auto-save 2026-05-11 15:46 (~6)

This commit is contained in:
2026-05-11 15:47:02 +08:00
parent a3cc734902
commit 3bb30ae1d0
6 changed files with 215 additions and 70 deletions

View File

@@ -125,15 +125,29 @@ function saveSettings(options = {}) {
pingBackend();
}
function ensureModelChoice(modelValue, labelValue = "") {
const model = (modelValue || "").trim();
if (!model) return;
const label = (labelValue || model).trim();
const pick = document.getElementById("modelPick");
if (pick && !Array.from(pick.options).some(item => item.value === model)) {
pick.appendChild(new Option(label, model));
}
const list = document.getElementById("modelOptions");
if (list && !Array.from(list.options).some(item => item.value === model)) {
const option = document.createElement("option");
option.value = model;
option.label = label;
list.appendChild(option);
}
}
function syncModelPick(modelValue) {
const model = (modelValue || state.model || "").trim();
const pick = document.getElementById("modelPick");
if (!pick || !model) return;
ensureModelChoice(model);
let option = Array.from(pick.options).find(item => item.value === model);
if (!option) {
option = new Option(model, model);
pick.appendChild(option);
}
pick.value = model;
state.model = model;
const stat = document.getElementById("statModel");
@@ -596,19 +610,30 @@ async function testApiConnection() {
let _hermesConfigLoaded = false;
let _hermesConfigLoading = false;
function setHermesConfigStatus(text, isError = false) {
const el = document.getElementById("hermesConfigStatus");
let _hermesConfigSnapshot = null;
function setSettingsStatus(id, text, isError = false) {
const el = document.getElementById(id);
if (!el) return;
el.textContent = text;
el.style.color = isError ? "var(--err)" : "";
}
function setHermesModelStatus(text, isError = false) {
setSettingsStatus("hermesModelStatus", text, isError);
}
function setHermesMcpStatus(text, isError = false) {
setSettingsStatus("hermesMcpStatus", text, isError);
}
function setHermesConfigStatuses(text, isError = false) {
setHermesModelStatus(text, isError);
setHermesMcpStatus(text, isError);
}
async function refreshHermesConfig(force = false) {
if (_hermesConfigLoading || (_hermesConfigLoaded && !force)) return;
const modelEl = document.getElementById("hermesModelDefault");
if (!modelEl) return;
_hermesConfigLoading = true;
setHermesConfigStatus("正在读取线上配置...");
setHermesConfigStatuses("正在读取线上配置...");
try {
const res = await fetch("/feishu/hermes-config", {
credentials: "same-origin",
@@ -622,63 +647,146 @@ async function refreshHermesConfig(force = false) {
document.getElementById("hermesModelProvider").value = model.provider || "";
document.getElementById("hermesModelBaseUrl").value = model.base_url || "";
document.getElementById("mcpServersYaml").value = config.mcp_servers_yaml || "";
_hermesConfigSnapshot = {
model: {
default: model.default || "",
provider: model.provider || "",
base_url: model.base_url || "",
},
mcp_servers_yaml: config.mcp_servers_yaml || "",
};
if (model.default) syncModelPick(model.default);
_hermesConfigLoaded = true;
setHermesConfigStatus("已读取线上配置" + (config.lxc ? " · " + config.lxc : ""));
const suffix = config.lxc ? " · " + config.lxc : "";
setHermesModelStatus("已读取模型配置" + suffix);
setHermesMcpStatus("已读取 MCP 配置" + suffix);
} catch (e) {
setHermesConfigStatus("读取失败: " + (e.message || e), true);
setHermesConfigStatuses("读取失败: " + (e.message || e), true);
} finally {
_hermesConfigLoading = false;
}
}
async function saveHermesConfig() {
function readModelConfigFields() {
const modelDefault = document.getElementById("hermesModelDefault")?.value.trim() || "";
const provider = document.getElementById("hermesModelProvider")?.value.trim() || "openrouter";
const baseUrl = document.getElementById("hermesModelBaseUrl")?.value.trim() || "";
const mcpServersYaml = document.getElementById("mcpServersYaml")?.value || "";
if (!modelDefault) {
return { default: modelDefault, provider, base_url: baseUrl };
}
function snapshotModelOrFields() {
const model = _hermesConfigSnapshot?.model || {};
if (model.default) return { ...model };
const fields = readModelConfigFields();
if (fields.default) return fields;
return {
default: state.model || "gemini-3-pro-preview",
provider: fields.provider || "openrouter",
base_url: fields.base_url || "",
};
}
async function postHermesRuntimeConfig(payload) {
const res = await fetch("/feishu/hermes-config", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
const data = await res.json().catch(() => ({}));
if (!res.ok || data.code !== 0) throw new Error(data.msg || ("HTTP " + res.status));
return data.config || {};
}
async function saveModelConfig() {
const model = readModelConfigFields();
if (!model.default) {
toast("默认模型不能为空");
return;
}
if (!confirm("保存后会重启线上 Hermes agent当前正在生成的任务可能中断。继续吗")) return;
const btn = document.getElementById("hermesConfigSaveBtn");
if (!confirm("保存 AI 模型接入配置后会重启线上 Hermes agent当前正在生成的任务可能中断。继续吗")) return;
const btn = document.getElementById("hermesModelSaveBtn");
const oldHTML = btn?.innerHTML;
if (btn) {
btn.disabled = true;
btn.textContent = "保存并重启中...";
btn.textContent = "保存模型中...";
}
setHermesConfigStatus("正在写入 config.yaml 并重启 Hermes agent...");
setHermesModelStatus("正在写入 model 配置并重启 Hermes agent...");
try {
const res = await fetch("/feishu/hermes-config", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: {
default: modelDefault,
provider,
base_url: baseUrl,
},
mcp_servers_yaml: mcpServersYaml,
restart: true,
}),
const saved = await postHermesRuntimeConfig({
model,
mcp_servers_yaml: _hermesConfigSnapshot ? _hermesConfigSnapshot.mcp_servers_yaml : (document.getElementById("mcpServersYaml")?.value || ""),
restart: true,
});
const data = await res.json().catch(() => ({}));
if (!res.ok || data.code !== 0) throw new Error(data.msg || ("HTTP " + res.status));
const saved = data.config || {};
const savedModel = saved.model || {};
if (savedModel.default) syncModelPick(savedModel.default);
document.getElementById("mcpServersYaml").value = saved.mcp_servers_yaml || "";
_hermesConfigSnapshot = {
model: {
default: savedModel.default || model.default,
provider: savedModel.provider || model.provider,
base_url: savedModel.base_url || model.base_url,
},
mcp_servers_yaml: _hermesConfigSnapshot ? _hermesConfigSnapshot.mcp_servers_yaml : (saved.mcp_servers_yaml || ""),
};
_hermesConfigLoaded = false;
setHermesConfigStatus("已保存并重启 · 备份 " + (saved.backup || "已创建"));
toast("模型与 MCP 配置已生效");
setHermesModelStatus("模型配置已保存并重启 · 备份 " + (saved.backup || "已创建"));
toast("AI 模型接入配置已生效");
setTimeout(() => {
pingBackend();
refreshDashboard();
}, 1800);
} catch (e) {
setHermesConfigStatus("保存失败: " + (e.message || e), true);
setHermesModelStatus("保存失败: " + (e.message || e), true);
toast("保存失败: " + (e.message || e));
} finally {
if (btn) {
btn.disabled = false;
btn.innerHTML = oldHTML;
}
}
}
async function saveMcpConfig() {
const mcpServersYaml = document.getElementById("mcpServersYaml")?.value || "";
const model = snapshotModelOrFields();
if (!model.default) {
toast("先读取或填写默认模型");
return;
}
if (!confirm("保存 MCP 工具接入配置后会重启线上 Hermes agent当前正在生成的任务可能中断。继续吗")) return;
const btn = document.getElementById("hermesMcpSaveBtn");
const oldHTML = btn?.innerHTML;
if (btn) {
btn.disabled = true;
btn.textContent = "保存 MCP 中...";
}
setHermesMcpStatus("正在写入 mcp_servers 配置并重启 Hermes agent...");
try {
const saved = await postHermesRuntimeConfig({
model,
mcp_servers_yaml: mcpServersYaml,
restart: true,
});
const savedModel = saved.model || model;
if (savedModel.default) syncModelPick(savedModel.default);
document.getElementById("mcpServersYaml").value = saved.mcp_servers_yaml || "";
_hermesConfigSnapshot = {
model: {
default: savedModel.default || model.default,
provider: savedModel.provider || model.provider,
base_url: savedModel.base_url || model.base_url,
},
mcp_servers_yaml: saved.mcp_servers_yaml || "",
};
_hermesConfigLoaded = false;
setHermesMcpStatus("MCP 配置已保存并重启 · 备份 " + (saved.backup || "已创建"));
toast("MCP 工具接入配置已生效");
setTimeout(() => {
pingBackend();
refreshDashboard();
}, 1800);
} catch (e) {
setHermesMcpStatus("保存失败: " + (e.message || e), true);
toast("保存失败: " + (e.message || e));
} finally {
if (btn) {