// 技能 modal:两栏 master-detail。左栏「平台 skill / 我的 skill」两组列表,
// 右栏展示选中 skill 的完整 SKILL.md;删除按钮在右栏正文头部(只对「我的」)。
// 左侧 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";
const PLACEHOLDER = '
← 从左侧选一个 skill 查看完整说明
';
function openSkillsModal() {
$("skills-modal").classList.add("show");
$("sk-detail").innerHTML = PLACEHOLDER;
renderList();
}
export function closeSkillsModal() {
$("skills-modal").classList.remove("show");
}
function itemHtml(s) {
const badge = s.overrides_builtin
? ' 已覆盖平台同名'
: "";
return `
${escapeHtml(s.name)}${badge}
${escapeHtml(s.description || "")}
`;
}
async function renderList() {
const colB = $("sk-builtin");
const colU = $("sk-user");
colB.innerHTML = '加载中…
';
colU.innerHTML = "";
let data;
try {
data = await api("GET", "/v1/skills");
} catch (e) {
colB.innerHTML = `加载失败: ${escapeHtml(e.message)}
`;
return;
}
state.skills = data.skills || []; // 顺手刷新新建任务下拉的缓存
const platform = state.skills.filter((s) => s.source === "builtin");
const mine = state.skills.filter((s) => s.source === "user");
colB.innerHTML =
`平台 skill (${platform.length})
` +
(platform.map(itemHtml).join("") ||
'(无)
');
let userHtml = `我的 skill (${mine.length})
`;
userHtml += mine.length
? mine.map(itemHtml).join("")
: '还没有。让助手「帮我做个 skill」或「把某个平台 skill fork 成我的」即可创建。
';
if (data.load_errors && data.load_errors.length) {
const errs = data.load_errors
.map((e) => `${escapeHtml(e.name)}(${escapeHtml(e.reason)})`)
.join(";");
userHtml += `⚠ ${data.load_errors.length} 个未加载:${errs}
`;
}
colU.innerHTML = userHtml;
}
async function selectSkill(name, itemEl) {
// 跨两个列表栏清高亮
$("sk-cols").querySelectorAll(".sk-item.active").forEach((el) => el.classList.remove("active"));
if (itemEl) itemEl.classList.add("active");
const detail = $("sk-detail");
detail.innerHTML = '加载中…
';
let data;
try {
data = await api("GET", "/v1/skills/" + encodeURIComponent(name));
} catch (e) {
detail.innerHTML = `加载失败: ${escapeHtml(e.message)}
`;
return;
}
const isUser = data.source === "user";
const srcBadge = isUser
? `${data.overrides_builtin ? "我的 · 已覆盖平台" : "我的"}`
: `平台`;
const delBtn = isUser
? ``
: "";
detail.innerHTML =
`${escapeHtml(data.name)}${srcBadge}${delBtn}
` +
`${renderMd(data.content)}
`;
highlightIn(detail);
}
// ───── 顶层绑定 ─────
$("hd-skills").onclick = openSkillsModal;
$("sk-close").onclick = closeSkillsModal;
$("skills-modal").addEventListener("click", (e) => {
if (e.target.id === "skills-modal") closeSkillsModal(); // 点遮罩关闭
});
// 列表栏(平台 + 我的):点 item 选中 → 右栏载正文。委托到 #sk-cols,
// detail 栏的点击不含 .sk-item 故不误触(删除按钮另由下方 #sk-detail 监听处理)。
$("sk-cols").addEventListener("click", (e) => {
const item = e.target.closest(".sk-item");
if (item) selectSkill(item.getAttribute("data-name"), item);
});
// 右栏:删除(冒泡到 [data-del])
$("sk-detail").addEventListener("click", async (e) => {
const del = e.target.closest("[data-del]");
if (!del) return;
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; // 失效缓存,新建任务下拉下次重拉
$("sk-detail").innerHTML = PLACEHOLDER;
renderList();
} catch (err) {
alert("删除失败: " + err.message);
del.disabled = false;
}
});