diff --git a/web/static/dev.html b/web/static/dev.html
index 48983e1..7e9b6ee 100644
--- a/web/static/dev.html
+++ b/web/static/dev.html
@@ -205,12 +205,16 @@
flex-shrink: 0; border-top: 1px solid var(--border);
padding: 8px; display: flex; gap: 6px;
}
- #rail-resources > button { flex: 1; font-size: 13px; }
+ #rail-resources > button {
+ flex: 1; font-size: 13px;
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
+ }
+ #rail-resources > button svg { flex-shrink: 0; opacity: .85; }
- /* ───── 技能查看 modal ───── */
+ /* ───── 技能查看 modal(两栏 master-detail)───── */
#skills-modal { z-index: 112; }
#skills-modal .card {
- width: 720px; max-width: 92vw; max-height: 84vh;
+ width: 880px; max-width: 94vw; height: 80vh; max-height: 80vh;
display: flex; flex-direction: column;
}
#skills-modal h3 {
@@ -219,21 +223,28 @@
display: flex; align-items: center; gap: 8px;
}
#skills-modal h3 .spacer { flex: 1; }
+ #skills-modal h3 svg { opacity: .85; }
#skills-modal .sk-x {
border: none; background: transparent; font-size: 16px;
cursor: pointer; color: var(--muted); padding: 2px 6px;
}
- #skills-modal #sk-back { margin-right: 4px; }
- #skills-modal .sk-detail-name { font-weight: 600; }
- #skills-modal .body { padding: 12px 16px; overflow: auto; }
+ /* 两栏:左列表 / 右正文 */
+ #sk-cols { flex: 1; display: flex; min-height: 0; }
+ #sk-list {
+ width: 290px; flex-shrink: 0; overflow: auto;
+ padding: 12px; border-right: 1px solid var(--border);
+ }
+ #sk-detail { flex: 1; min-width: 0; overflow: auto; padding: 16px 20px; }
+ .sk-empty { color: var(--muted); font-size: 13px; padding: 24px 8px; text-align: center; }
+
.sk-group-title { font-weight: 600; font-size: 12px; color: var(--muted); margin: 0 0 8px; }
+ .sk-group-title.mt { margin-top: 16px; }
.sk-item {
- display: flex; align-items: center; gap: 10px;
- padding: 8px 10px; border: 1px solid var(--border);
+ padding: 7px 10px; border: 1px solid var(--border);
border-radius: var(--r-md); margin-bottom: 6px; cursor: pointer;
}
.sk-item:hover { border-color: var(--accent); background: #fafafa; }
- .sk-item-main { flex: 1; min-width: 0; }
+ .sk-item.active { border-color: var(--accent); background: rgba(120,120,200,0.07); }
.sk-item .sk-name { font-weight: 600; font-size: 13px; }
.sk-item .sk-desc {
font-size: 12px; color: var(--muted); margin-top: 2px;
@@ -242,23 +253,36 @@
.sk-badge {
font-size: 10px; font-weight: 500; color: var(--accent);
border: 1px solid var(--accent); border-radius: 8px; padding: 0 5px;
- margin-left: 4px; white-space: nowrap;
+ white-space: nowrap;
}
- .sk-del { flex-shrink: 0; }
+ .sk-name .sk-badge { margin-left: 4px; }
.sk-loaderr {
margin-top: 14px; padding: 8px 10px; font-size: 12px;
border: 1px solid var(--accent); border-radius: var(--r-md);
color: var(--accent); background: rgba(220,80,80,0.05);
}
- .sk-detail { font-size: 13px; line-height: 1.6; }
- .sk-detail pre {
+ /* 右栏正文头 + markdown */
+ .sk-d-head {
+ display: flex; align-items: center; gap: 8px; margin-bottom: 12px;
+ padding-bottom: 10px; border-bottom: 1px solid var(--border);
+ }
+ .sk-d-head .sk-d-name { font-weight: 600; font-size: 15px; }
+ .sk-d-head .spacer { flex: 1; }
+ .sk-detail-md { font-size: 13px; line-height: 1.6; }
+ .sk-detail-md pre {
white-space: pre-wrap; word-break: break-word;
background: #f5f5f5; padding: 10px; border-radius: var(--r-md); overflow: auto;
}
- .sk-detail code { word-break: break-word; }
- .sk-detail h1, .sk-detail h2, .sk-detail h3 { margin: 14px 0 6px; }
- .sk-detail table { border-collapse: collapse; }
- .sk-detail th, .sk-detail td { border: 1px solid var(--border); padding: 4px 8px; }
+ .sk-detail-md code { word-break: break-word; }
+ .sk-detail-md h1, .sk-detail-md h2, .sk-detail-md h3 { margin: 14px 0 6px; }
+ .sk-detail-md table { border-collapse: collapse; }
+ .sk-detail-md th, .sk-detail-md td { border: 1px solid var(--border); padding: 4px 8px; }
+ /* 窄屏:两栏改上下堆叠 */
+ @media (max-width: 640px) {
+ #skills-modal .card { height: 88vh; max-height: 88vh; }
+ #sk-cols { flex-direction: column; }
+ #sk-list { width: auto; max-height: 38vh; border-right: none; border-bottom: 1px solid var(--border); }
+ }
/* ───── 3-pane layout ───── */
#app { display: none; height: 100vh; }
@@ -1054,11 +1078,15 @@
@@ -1129,7 +1157,10 @@
-
+
diff --git a/web/static/js/skills.js b/web/static/js/skills.js
index 11766d0..422f143 100644
--- a/web/static/js/skills.js
+++ b/web/static/js/skills.js
@@ -1,5 +1,6 @@
-// 技能 modal:查看「平台 skill」/「我的 skill」两组列表,点任一项展开完整 SKILL.md,
-// 「我的」每项可删除(平台 skill 只读)。左侧 rail 底部「技能」按钮触发。
+// 技能 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";
@@ -8,8 +9,11 @@ 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() {
@@ -20,27 +24,20 @@ function itemHtml(s) {
const badge = s.overrides_builtin
? ' 已覆盖平台同名'
: "";
- const del =
- s.source === "user"
- ? ``
- : "";
return `
-
-
${escapeHtml(s.name)}${badge}
-
${escapeHtml(s.description || "")}
-
${del}
+
${escapeHtml(s.name)}${badge}
+
${escapeHtml(s.description || "")}
`;
}
async function renderList() {
- const body = $("sk-body");
- $("sk-title").textContent = "技能";
- body.innerHTML = '加载中…
';
+ const list = $("sk-list");
+ list.innerHTML = '加载中…
';
let data;
try {
data = await api("GET", "/v1/skills");
} catch (e) {
- body.innerHTML = `加载失败: ${escapeHtml(e.message)}
`;
+ list.innerHTML = `加载失败: ${escapeHtml(e.message)}
`;
return;
}
state.skills = data.skills || []; // 顺手刷新新建任务下拉的缓存
@@ -52,10 +49,10 @@ async function renderList() {
html +=
platform.map(itemHtml).join("") ||
'(无)
';
- html += `我的 skill (${mine.length})
`;
+ html += `我的 skill (${mine.length})
`;
html += mine.length
? mine.map(itemHtml).join("")
- : '还没有。让助手「帮我做个 skill」或「把某个平台 skill fork 成我的」即可创建。
';
+ : '还没有。让助手「帮我做个 skill」或「把某个平台 skill fork 成我的」即可创建。
';
if (data.load_errors && data.load_errors.length) {
const errs = data.load_errors
@@ -63,23 +60,34 @@ async function renderList() {
.join(";");
html += `⚠ ${data.load_errors.length} 个 skill 因格式问题未加载:${errs}
`;
}
- body.innerHTML = html;
+ list.innerHTML = html;
}
-async function showDetail(name) {
- const body = $("sk-body");
- $("sk-title").innerHTML = `${escapeHtml(name)}`;
- $("sk-back").onclick = renderList;
- body.innerHTML = '加载中…
';
+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) {
- body.innerHTML = `加载失败: ${escapeHtml(e.message)}
`;
+ detail.innerHTML = `加载失败: ${escapeHtml(e.message)}
`;
return;
}
- body.innerHTML = `${renderMd(data.content)}
`;
- highlightIn(body);
+ 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);
}
// ───── 顶层绑定 ─────
@@ -89,24 +97,26 @@ $("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;
- }
+// 左栏:点 item 选中 → 右栏载正文
+$("sk-list").addEventListener("click", (e) => {
const item = e.target.closest(".sk-item");
- if (item) showDetail(item.getAttribute("data-name"));
+ 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;
+ }
});