// 技能 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 list = $("sk-list"); list.innerHTML = '
加载中…
'; let data; try { data = await api("GET", "/v1/skills"); } catch (e) { list.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"); let html = ""; html += `
平台 skill (${platform.length})
`; html += platform.map(itemHtml).join("") || '
(无)
'; html += `
我的 skill (${mine.length})
`; html += 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(";"); html += `
⚠ ${data.load_errors.length} 个 skill 因格式问题未加载:${errs}
`; } list.innerHTML = html; } async function selectSkill(name, itemEl) { // 左栏高亮 $("sk-list").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-list").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; } });