Files
sproutclaw/.pi/extensions/webui/public/settings.js
2026-05-10 14:47:51 +08:00

178 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use strict";
const systemPromptInput = document.getElementById("systemPromptInput");
const systemPromptPath = document.getElementById("systemPromptPath");
const saveSystemPromptBtn = document.getElementById("saveSystemPromptBtn");
const refreshSettingsBtn = document.getElementById("refreshSettingsBtn");
const settingsStatus = document.getElementById("settingsStatus");
const skillsList = document.getElementById("skillsList");
const skillsCount = document.getElementById("skillsCount");
const extensionsList = document.getElementById("extensionsList");
const extensionsMeta = document.getElementById("extensionsMeta");
const settingsNavBtns = document.querySelectorAll(".settings-nav-btn");
const settingsPanes = document.querySelectorAll(".settings-pane");
const SETTINGS_PANE_KEY = "sproutclaw-settings-pane";
function escHtml(s) {
const div = document.createElement("div");
div.textContent = s;
return div.innerHTML;
}
function setSettingsStatus(text, kind = "") {
if (!settingsStatus) return;
settingsStatus.textContent = text || "";
settingsStatus.className = `settings-status${kind ? ` ${kind}` : ""}`;
}
function showSettingsPane(paneId) {
const id = ["prompt", "skills", "extensions"].includes(paneId) ? paneId : "prompt";
settingsNavBtns.forEach((btn) => {
const active = btn.dataset.pane === id;
btn.classList.toggle("is-active", active);
btn.setAttribute("aria-selected", active ? "true" : "false");
});
settingsPanes.forEach((pane) => {
const active = pane.id === `pane-${id}`;
pane.classList.toggle("is-active", active);
pane.toggleAttribute("hidden", !active);
});
try {
sessionStorage.setItem(SETTINGS_PANE_KEY, id);
} catch {
/* ignore */
}
}
function initSettingsNav() {
settingsNavBtns.forEach((btn) => {
btn.addEventListener("click", () => showSettingsPane(btn.dataset.pane));
});
let initial = "prompt";
try {
const saved = sessionStorage.getItem(SETTINGS_PANE_KEY);
if (["prompt", "skills", "extensions"].includes(saved)) initial = saved;
} catch {
/* ignore */
}
showSettingsPane(initial);
}
async function loadSettings() {
if (skillsList) skillsList.innerHTML = '<div class="settings-empty">加载中...</div>';
if (extensionsList) extensionsList.innerHTML = '<div class="settings-empty">加载中...</div>';
setSettingsStatus("");
try {
const res = await fetch("/api/settings");
const data = await res.json().catch(() => ({}));
if (!res.ok || data.error) throw new Error(data.error || res.statusText);
if (systemPromptInput) systemPromptInput.value = data.systemPrompt || "";
if (systemPromptPath) systemPromptPath.textContent = data.systemPromptPath || "";
renderSkills(data.skills || []);
renderExtensions(data.extensions || [], data.extensionsPath || "");
} catch (err) {
if (skillsList) skillsList.innerHTML = `<div class="settings-empty">加载失败: ${escHtml(err.message)}</div>`;
if (extensionsList) extensionsList.innerHTML = `<div class="settings-empty">加载失败: ${escHtml(err.message)}</div>`;
setSettingsStatus(`加载设置失败: ${err.message}`, "error");
}
}
function renderSkills(skills) {
if (!skillsList) return;
if (skillsCount) skillsCount.textContent = `${skills.length} 个已加载`;
if (!skills.length) {
skillsList.innerHTML = '<div class="settings-empty">暂无已加载 skills</div>';
return;
}
skillsList.innerHTML = skills.map((skill) => {
const meta = [skill.scope, skill.source].filter(Boolean).join(" · ");
const path = skill.path || "";
return `<div class="skill-item">
<div class="skill-title-row">
<div class="skill-name">${escHtml(skill.name || skill.command || "未命名")}</div>
${meta ? `<div class="skill-meta">${escHtml(meta)}</div>` : ""}
</div>
${skill.description ? `<div class="skill-desc">${escHtml(skill.description)}</div>` : ""}
${path ? `<div class="skill-path">${escHtml(path)}</div>` : ""}
</div>`;
}).join("");
}
function renderExtensions(extensions, extensionsPath) {
if (!extensionsList) return;
if (extensionsMeta) {
extensionsMeta.textContent = extensionsPath
? `${extensions.length} 个已加载 · ${extensionsPath}`
: `${extensions.length} 个已加载`;
}
if (!extensions.length) {
extensionsList.innerHTML = '<div class="settings-empty">暂无已加载插件扩展</div>';
return;
}
extensionsList.innerHTML = extensions.map((extension) => {
const meta = extension.kind || [extension.scope, extension.source].filter(Boolean).join(" · ");
const counts = [
[`命令`, extension.commands?.length || 0],
[`工具`, extension.tools?.length || 0],
[`事件`, extension.handlers?.length || 0],
[`Flags`, extension.flags?.length || 0],
[`快捷键`, extension.shortcuts?.length || 0],
].filter(([, count]) => count > 0);
const hasDetails = ["commands", "tools", "handlers", "flags", "shortcuts"].some((key) => extension[key]?.length);
return `<div class="extension-item">
<div class="extension-title-row">
<div class="extension-name">${escHtml(extension.name || extension.path || "未命名")}</div>
${meta ? `<div class="extension-meta">${escHtml(meta)}</div>` : ""}
</div>
${counts.length ? `<div class="extension-counts">${counts.map(([label, count]) => `<span>${escHtml(label)} ${count}</span>`).join("")}</div>` : ""}
${hasDetails ? `<details class="extension-details">
<summary>查看注册项</summary>
${renderNameList("命令", extension.commands)}
${renderNameList("工具", extension.tools)}
${renderNameList("事件", extension.handlers)}
${renderNameList("Flags", extension.flags)}
${renderNameList("快捷键", extension.shortcuts)}
</details>` : ""}
${extension.location ? `<div class="extension-path">${escHtml(extension.location)}</div>` : ""}
</div>`;
}).join("");
}
function renderNameList(label, items) {
if (!Array.isArray(items) || !items.length) return "";
return `<div class="extension-name-list">
<span>${escHtml(label)}</span>
<div>${items.map((item) => `<code>${escHtml(item)}</code>`).join("")}</div>
</div>`;
}
async function saveSystemPrompt() {
if (!systemPromptInput || !saveSystemPromptBtn) return;
saveSystemPromptBtn.disabled = true;
setSettingsStatus("保存中...");
try {
const res = await fetch("/api/settings/system-prompt", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ systemPrompt: systemPromptInput.value }),
});
const data = await res.json().catch(() => ({}));
if (!res.ok || data.error) throw new Error(data.error || res.statusText);
if (systemPromptPath && data.systemPromptPath) systemPromptPath.textContent = data.systemPromptPath;
setSettingsStatus("已保存Agent 已重新加载", "ok");
} catch (err) {
setSettingsStatus(`保存失败: ${err.message}`, "error");
} finally {
saveSystemPromptBtn.disabled = false;
}
}
initSettingsNav();
saveSystemPromptBtn?.addEventListener("click", saveSystemPrompt);
refreshSettingsBtn?.addEventListener("click", loadSettings);
document.getElementById("refreshExtensionsBtn")?.addEventListener("click", loadSettings);
loadSettings();