zcbot/web/static/js/skills.js

113 lines
4.4 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.

// 技能 modal:查看「平台 skill」/「我的 skill」两组列表,点任一项展开完整 SKILL.md,
// 「我的」每项可删除(平台 skill 只读)。左侧 rail 底部「技能」按钮触发。
// 后端:GET /v1/skills(列表)、GET /v1/skills/{name}(正文)、DELETE /v1/skills/{name}(只删 user 源)。
// 创建 / 改 / fork 仍走对话(save_skill / fork_skill / skill-creator)。
import { $ } from "./dom.js";
import { state } from "./state.js";
import { api } from "./api.js";
import { escapeHtml } from "./format.js";
import { renderMd, highlightIn } from "./markdown.js";
function openSkillsModal() {
$("skills-modal").classList.add("show");
renderList();
}
export function closeSkillsModal() {
$("skills-modal").classList.remove("show");
}
function itemHtml(s) {
const badge = s.overrides_builtin
? ' <span class="sk-badge">已覆盖平台同名</span>'
: "";
const del =
s.source === "user"
? `<button class="sk-del small danger" data-del="${escapeHtml(s.name)}" title="删除我的这个 skill">删除</button>`
: "";
return `<div class="sk-item" data-name="${escapeHtml(s.name)}">
<div class="sk-item-main">
<div class="sk-name">${escapeHtml(s.name)}${badge}</div>
<div class="sk-desc">${escapeHtml(s.description || "")}</div>
</div>${del}
</div>`;
}
async function renderList() {
const body = $("sk-body");
$("sk-title").textContent = "技能";
body.innerHTML = '<div class="muted" style="padding:8px;">加载中…</div>';
let data;
try {
data = await api("GET", "/v1/skills");
} catch (e) {
body.innerHTML = `<div class="err" style="padding:8px;">加载失败: ${escapeHtml(e.message)}</div>`;
return;
}
state.skills = data.skills || []; // 顺手刷新新建任务下拉的缓存
const platform = state.skills.filter((s) => s.source === "builtin");
const mine = state.skills.filter((s) => s.source === "user");
let html = "";
html += `<div class="sk-group-title">平台 skill (${platform.length})</div>`;
html +=
platform.map(itemHtml).join("") ||
'<div class="muted" style="padding:4px 8px;">(无)</div>';
html += `<div class="sk-group-title" style="margin-top:14px;">我的 skill (${mine.length})</div>`;
html += mine.length
? mine.map(itemHtml).join("")
: '<div class="muted" style="padding:4px 8px;">还没有。让助手「帮我做个 skill」或「把某个平台 skill fork 成我的」即可创建。</div>';
if (data.load_errors && data.load_errors.length) {
const errs = data.load_errors
.map((e) => `${escapeHtml(e.name)}(${escapeHtml(e.reason)})`)
.join("");
html += `<div class="sk-loaderr">⚠ ${data.load_errors.length} 个 skill 因格式问题未加载:${errs}</div>`;
}
body.innerHTML = html;
}
async function showDetail(name) {
const body = $("sk-body");
$("sk-title").innerHTML = `<button id="sk-back" class="small"> 返回</button><span class="sk-detail-name">${escapeHtml(name)}</span>`;
$("sk-back").onclick = renderList;
body.innerHTML = '<div class="muted" style="padding:8px;">加载中…</div>';
let data;
try {
data = await api("GET", "/v1/skills/" + encodeURIComponent(name));
} catch (e) {
body.innerHTML = `<div class="err" style="padding:8px;">加载失败: ${escapeHtml(e.message)}</div>`;
return;
}
body.innerHTML = `<div class="sk-detail">${renderMd(data.content)}</div>`;
highlightIn(body);
}
// ───── 顶层绑定 ─────
$("hd-skills").onclick = openSkillsModal;
$("sk-close").onclick = closeSkillsModal;
$("skills-modal").addEventListener("click", (e) => {
if (e.target.id === "skills-modal") closeSkillsModal(); // 点遮罩关闭
});
// 列表区事件委托:删除(冒泡到 [data-del])优先于点开详情(.sk-item)
$("sk-body").addEventListener("click", async (e) => {
const del = e.target.closest("[data-del]");
if (del) {
e.stopPropagation();
const name = del.getAttribute("data-del");
if (!confirm(`删除你的 skill「${name}」?不可撤销(平台同名 skill 不受影响)。`)) return;
del.disabled = true;
try {
await api("DELETE", "/v1/skills/" + encodeURIComponent(name));
state.skills = null; // 失效缓存,新建任务下拉下次重拉
renderList();
} catch (err) {
alert("删除失败: " + err.message);
del.disabled = false;
}
return;
}
const item = e.target.closest(".sk-item");
if (item) showDetail(item.getAttribute("data-name"));
});